style: auto-fix import/export sorting and formatting

Ran pnpm lint:fix to resolve 220 auto-fixable lint issues.
All changes are import/export reordering — no logic changes.
This commit is contained in:
iliya 2026-03-16 20:48:42 +02:00
parent f5d2b7e14f
commit 51f8f3545c
124 changed files with 672 additions and 552 deletions

View file

@ -16,17 +16,17 @@
// On Windows this saturates all threads, blocking the event loop.
process.env.UV_THREADPOOL_SIZE ??= '16';
import { CrossTeamService } from '@main/services/team/CrossTeamService';
import { TeamConfigReader } from '@main/services/team/TeamConfigReader';
import { TeamInboxWriter } from '@main/services/team/TeamInboxWriter';
import { JsonScheduleRepository } from '@main/services/schedule/JsonScheduleRepository';
import { ScheduledTaskExecutor } from '@main/services/schedule/ScheduledTaskExecutor';
import { SchedulerService } from '@main/services/schedule/SchedulerService';
import { ChangeExtractorService } from '@main/services/team/ChangeExtractorService';
import { CrossTeamService } from '@main/services/team/CrossTeamService';
import { FileContentResolver } from '@main/services/team/FileContentResolver';
import { GitDiffFallback } from '@main/services/team/GitDiffFallback';
import { ReviewApplierService } from '@main/services/team/ReviewApplierService';
import { TeamBackupService } from '@main/services/team/TeamBackupService';
import { JsonScheduleRepository } from '@main/services/schedule/JsonScheduleRepository';
import { ScheduledTaskExecutor } from '@main/services/schedule/ScheduledTaskExecutor';
import { SchedulerService } from '@main/services/schedule/SchedulerService';
import { TeamConfigReader } from '@main/services/team/TeamConfigReader';
import { TeamInboxWriter } from '@main/services/team/TeamInboxWriter';
import {
CONTEXT_CHANGED,
SCHEDULE_CHANGE,
@ -51,16 +51,32 @@ import { existsSync } from 'fs';
import { join } from 'path';
import { cleanupEditorState, setEditorMainWindow } from './ipc/editor';
import { setReviewMainWindow } from './ipc/review';
import { initializeIpcHandlers, removeIpcHandlers } from './ipc/handlers';
import { setReviewMainWindow } from './ipc/review';
import {
ApiKeyService,
ExtensionFacadeService,
GlamaMcpEnrichmentService,
McpCatalogAggregator,
McpHealthDiagnosticsService,
McpInstallationStateService,
McpInstallService,
OfficialMcpRegistryService,
PluginCatalogService,
PluginInstallationStateService,
PluginInstallService,
SkillsCatalogService,
SkillsMutationService,
SkillsWatcherService,
} from './services/extensions';
import { startEventLoopLagMonitor } from './services/infrastructure/EventLoopLagMonitor';
import { HttpServer } from './services/infrastructure/HttpServer';
import { TeamInboxReader } from './services/team/TeamInboxReader';
import {
buildTeamControlApiBaseUrl,
clearTeamControlApiState,
writeTeamControlApiState,
} from './services/team/TeamControlApiState';
import { TeamInboxReader } from './services/team/TeamInboxReader';
import { TeamSentMessagesStore } from './services/team/TeamSentMessagesStore';
import { getAppIconPath } from './utils/appIcon';
import { getProjectsBasePath, getTeamsBasePath, getTodosBasePath } from './utils/pathDecoder';
@ -80,22 +96,6 @@ import {
TeamProvisioningService,
UpdaterService,
} from './services';
import {
ApiKeyService,
ExtensionFacadeService,
GlamaMcpEnrichmentService,
McpCatalogAggregator,
McpHealthDiagnosticsService,
McpInstallationStateService,
McpInstallService,
OfficialMcpRegistryService,
PluginCatalogService,
PluginInstallationStateService,
PluginInstallService,
SkillsCatalogService,
SkillsMutationService,
SkillsWatcherService,
} from './services/extensions';
import type { FileChangeEvent } from '@main/types';
import type { TeamChangeEvent } from '@shared/types';

View file

@ -7,10 +7,12 @@ import {
import { createLogger } from '@shared/utils/logger';
import { isAgentActionMode } from '../services/team/actionModeInstructions';
import { validateTaskId, validateTeamName } from './guards';
import type { CrossTeamService } from '../services/team/CrossTeamService';
import type { IpcMain, IpcMainInvokeEvent } from 'electron';
import type { IpcResult, TaskRef } from '@shared/types';
import type { IpcMain, IpcMainInvokeEvent } from 'electron';
const logger = createLogger('IPC:crossTeam');

View file

@ -6,30 +6,6 @@
* Phase 5: install/uninstall mutations.
*/
import { createLogger } from '@shared/utils/logger';
import type {
ApiKeyEntry,
ApiKeyLookupResult,
ApiKeySaveRequest,
ApiKeyStorageStatus,
EnrichedPlugin,
InstalledMcpEntry,
McpCatalogItem,
McpCustomInstallRequest,
McpInstallRequest,
McpServerDiagnostic,
McpSearchResult,
OperationResult,
PluginInstallRequest,
} from '@shared/types/extensions';
import type { IpcMain, IpcMainInvokeEvent } from 'electron';
import type { ExtensionFacadeService } from '../services/extensions/ExtensionFacadeService';
import type { PluginInstallService } from '../services/extensions/install/PluginInstallService';
import type { McpInstallService } from '../services/extensions/install/McpInstallService';
import type { ApiKeyService } from '../services/extensions/apikeys/ApiKeyService';
import type { McpHealthDiagnosticsService } from '../services/extensions/state/McpHealthDiagnosticsService';
import {
API_KEYS_DELETE,
API_KEYS_LIST,
@ -50,9 +26,32 @@ import {
PLUGIN_INSTALL,
PLUGIN_UNINSTALL,
} from '@preload/constants/ipcChannels';
import { createLogger } from '@shared/utils/logger';
import { GitHubStarsService } from '../services/extensions/catalog/GitHubStarsService';
import type { ApiKeyService } from '../services/extensions/apikeys/ApiKeyService';
import type { ExtensionFacadeService } from '../services/extensions/ExtensionFacadeService';
import type { McpInstallService } from '../services/extensions/install/McpInstallService';
import type { PluginInstallService } from '../services/extensions/install/PluginInstallService';
import type { McpHealthDiagnosticsService } from '../services/extensions/state/McpHealthDiagnosticsService';
import type {
ApiKeyEntry,
ApiKeyLookupResult,
ApiKeySaveRequest,
ApiKeyStorageStatus,
EnrichedPlugin,
InstalledMcpEntry,
McpCatalogItem,
McpCustomInstallRequest,
McpInstallRequest,
McpSearchResult,
McpServerDiagnostic,
OperationResult,
PluginInstallRequest,
} from '@shared/types/extensions';
import type { IpcMain, IpcMainInvokeEvent } from 'electron';
const logger = createLogger('IPC:extensions');
/** Allowed scope values */

View file

@ -65,8 +65,8 @@ import {
registerSessionHandlers,
removeSessionHandlers,
} from './sessions';
import { initializeSshHandlers, registerSshHandlers, removeSshHandlers } from './ssh';
import { initializeSkillsHandlers, registerSkillsHandlers, removeSkillsHandlers } from './skills';
import { initializeSshHandlers, registerSshHandlers, removeSshHandlers } from './ssh';
import {
initializeSubagentHandlers,
registerSubagentHandlers,
@ -103,18 +103,18 @@ import type {
TeamProvisioningService,
UpdaterService,
} from '../services';
import type { HttpServer } from '../services/infrastructure/HttpServer';
import type { CrossTeamService } from '../services/team/CrossTeamService';
import type { TeamBackupService } from '../services/team/TeamBackupService';
import type { ApiKeyService } from '../services/extensions/apikeys/ApiKeyService';
import type { ExtensionFacadeService } from '../services/extensions/ExtensionFacadeService';
import type { McpInstallService } from '../services/extensions/install/McpInstallService';
import type { PluginInstallService } from '../services/extensions/install/PluginInstallService';
import type { ApiKeyService } from '../services/extensions/apikeys/ApiKeyService';
import type { McpHealthDiagnosticsService } from '../services/extensions/state/McpHealthDiagnosticsService';
import type { SkillsCatalogService } from '../services/extensions/skills/SkillsCatalogService';
import type { SkillsMutationService } from '../services/extensions/skills/SkillsMutationService';
import type { SkillsWatcherService } from '../services/extensions/skills/SkillsWatcherService';
import type { McpHealthDiagnosticsService } from '../services/extensions/state/McpHealthDiagnosticsService';
import type { HttpServer } from '../services/infrastructure/HttpServer';
import type { SchedulerService } from '../services/schedule/SchedulerService';
import type { CrossTeamService } from '../services/team/CrossTeamService';
import type { TeamBackupService } from '../services/team/TeamBackupService';
/**
* Initializes IPC handlers with service registry.

View file

@ -1,18 +1,3 @@
import { createLogger } from '@shared/utils/logger';
import type {
SkillCatalogItem,
SkillDeleteRequest,
SkillDetail,
SkillImportRequest,
SkillReviewPreview,
SkillUpsertRequest,
} from '@shared/types/extensions';
import type { IpcMain, IpcMainInvokeEvent } from 'electron';
import type { SkillsCatalogService } from '../services/extensions/skills/SkillsCatalogService';
import type { SkillsMutationService } from '../services/extensions/skills/SkillsMutationService';
import type { SkillsWatcherService } from '../services/extensions/skills/SkillsWatcherService';
import {
SKILLS_APPLY_IMPORT,
SKILLS_APPLY_UPSERT,
@ -24,6 +9,20 @@ import {
SKILLS_START_WATCHING,
SKILLS_STOP_WATCHING,
} from '@preload/constants/ipcChannels';
import { createLogger } from '@shared/utils/logger';
import type { SkillsCatalogService } from '../services/extensions/skills/SkillsCatalogService';
import type { SkillsMutationService } from '../services/extensions/skills/SkillsMutationService';
import type { SkillsWatcherService } from '../services/extensions/skills/SkillsWatcherService';
import type {
SkillCatalogItem,
SkillDeleteRequest,
SkillDetail,
SkillImportRequest,
SkillReviewPreview,
SkillUpsertRequest,
} from '@shared/types/extensions';
import type { IpcMain, IpcMainInvokeEvent } from 'electron';
const logger = createLogger('IPC:skills');

View file

@ -1,5 +1,3 @@
import crypto from 'crypto';
import { setCurrentMainOp } from '@main/services/infrastructure/EventLoopLagMonitor';
import { getAppIconPath } from '@main/utils/appIcon';
import { getAppDataPath } from '@main/utils/pathDecoder';
@ -29,8 +27,8 @@ import {
TEAM_LAUNCH,
TEAM_LEAD_ACTIVITY,
TEAM_LEAD_CONTEXT,
TEAM_MEMBER_SPAWN_STATUSES,
TEAM_LIST,
TEAM_MEMBER_SPAWN_STATUSES,
TEAM_PERMANENTLY_DELETE,
TEAM_PREPARE_PROVISIONING,
TEAM_PROCESS_ALIVE,
@ -72,17 +70,18 @@ import {
} from '@shared/utils/cliArgsParser';
import { createLogger } from '@shared/utils/logger';
import { isRateLimitMessage } from '@shared/utils/rateLimitDetector';
import crypto from 'crypto';
import { BrowserWindow, type IpcMain, type IpcMainInvokeEvent, Notification } from 'electron';
import * as fs from 'fs';
import * as path from 'path';
import { ConfigManager } from '../services/infrastructure/ConfigManager';
import { NotificationManager } from '../services/infrastructure/NotificationManager';
import { gitIdentityResolver } from '../services/parsing/GitIdentityResolver';
import {
buildActionModeAgentBlock,
isAgentActionMode,
} from '../services/team/actionModeInstructions';
import { gitIdentityResolver } from '../services/parsing/GitIdentityResolver';
import { TeamAttachmentStore } from '../services/team/TeamAttachmentStore';
import { buildAddMemberSpawnMessage } from '../services/team/TeamProvisioningService';
import { TeamTaskAttachmentStore } from '../services/team/TeamTaskAttachmentStore';
@ -113,13 +112,13 @@ import type {
GlobalTask,
IpcResult,
KanbanColumnId,
LeadContextUsage,
LeadActivitySnapshot,
LeadContextUsage,
LeadContextUsageSnapshot,
MemberFullStats,
MemberSpawnStatusesSnapshot,
MemberLogSummary,
MemberSpawnStatusEntry,
MemberSpawnStatusesSnapshot,
SendMessageRequest,
SendMessageResult,
TaskAttachmentMeta,
@ -548,7 +547,10 @@ async function handlePermanentlyDeleteTeam(
.rm(path.join(appData, 'attachments', validated.value!), { recursive: true, force: true })
.catch(() => undefined);
await fs.promises
.rm(path.join(appData, 'task-attachments', validated.value!), { recursive: true, force: true })
.rm(path.join(appData, 'task-attachments', validated.value!), {
recursive: true,
force: true,
})
.catch(() => undefined);
// Mark in backup registry AFTER successful deletion
if (teamBackupService) {

View file

@ -7,6 +7,12 @@
*/
import { createLogger } from '@shared/utils/logger';
import { type McpCatalogAggregator } from './catalog/McpCatalogAggregator';
import { type PluginCatalogService } from './catalog/PluginCatalogService';
import { type McpInstallationStateService } from './state/McpInstallationStateService';
import { type PluginInstallationStateService } from './state/PluginInstallationStateService';
import type {
EnrichedPlugin,
InstalledMcpEntry,
@ -15,11 +21,6 @@ import type {
PluginCatalogItem,
} from '@shared/types/extensions';
import { PluginCatalogService } from './catalog/PluginCatalogService';
import { McpCatalogAggregator } from './catalog/McpCatalogAggregator';
import { PluginInstallationStateService } from './state/PluginInstallationStateService';
import { McpInstallationStateService } from './state/McpInstallationStateService';
const logger = createLogger('Extensions:Facade');
export class ExtensionFacadeService {

View file

@ -9,12 +9,14 @@
* Storage file: ~/.claude/api-keys.json
*/
import { safeStorage } from 'electron';
import fs from 'node:fs/promises';
import path from 'node:path';
import crypto from 'node:crypto';
import fs from 'node:fs/promises';
import os from 'node:os';
import path from 'node:path';
import { createLogger } from '@shared/utils/logger';
import { safeStorage } from 'electron';
import type {
ApiKeyEntry,
ApiKeyLookupResult,

View file

@ -9,8 +9,8 @@
import https from 'node:https';
import { createLogger } from '@shared/utils/logger';
import { parseGitHubOwnerRepo } from '@shared/utils/extensionNormalizers';
import { createLogger } from '@shared/utils/logger';
const logger = createLogger('Extensions:GitHubStars');
@ -39,7 +39,7 @@ export class GitHubStarsService {
*/
async fetchStars(repositoryUrls: string[]): Promise<Record<string, number>> {
const result: Record<string, number> = {};
const tasks: Array<{ url: string; owner: string; repo: string }> = [];
const tasks: { url: string; owner: string; repo: string }[] = [];
for (const url of repositoryUrls) {
const parsed = parseGitHubOwnerRepo(url);
@ -107,10 +107,10 @@ export class GitHubStarsService {
* Run async tasks with a concurrency limit.
*/
private async withConcurrencyLimit(
tasks: Array<() => Promise<void>>,
tasks: (() => Promise<void>)[],
limit: number
): Promise<Array<'ok' | 'error'>> {
const results: Array<'ok' | 'error'> = [];
): Promise<('ok' | 'error')[]> {
const results: ('ok' | 'error')[] = [];
let index = 0;
const run = async (): Promise<void> => {

View file

@ -9,10 +9,11 @@
* Cursor-based pagination (after), no auth required.
*/
import https from 'node:https';
import http from 'node:http';
import https from 'node:https';
import { createLogger } from '@shared/utils/logger';
import type { McpCatalogItem, McpHostingType, McpToolDef } from '@shared/types/extensions';
const logger = createLogger('Extensions:GlamaMcp');

View file

@ -7,12 +7,13 @@
* - Provides getById() for secure install flow
*/
import { createLogger } from '@shared/utils/logger';
import type { McpCatalogItem, McpSearchResult } from '@shared/types/extensions';
import { normalizeRepoUrl } from '@shared/utils/extensionNormalizers';
import { createLogger } from '@shared/utils/logger';
import { OfficialMcpRegistryService } from './OfficialMcpRegistryService';
import { GlamaMcpEnrichmentService } from './GlamaMcpEnrichmentService';
import { type GlamaMcpEnrichmentService } from './GlamaMcpEnrichmentService';
import { type OfficialMcpRegistryService } from './OfficialMcpRegistryService';
import type { McpCatalogItem, McpSearchResult } from '@shared/types/extensions';
const logger = createLogger('Extensions:McpAggregator');

View file

@ -6,10 +6,11 @@
* Filters for _meta.isLatest to pick only latest versions.
*/
import https from 'node:https';
import http from 'node:http';
import https from 'node:https';
import { createLogger } from '@shared/utils/logger';
import type {
McpAuthHeaderDef,
McpCatalogItem,

View file

@ -8,12 +8,13 @@
* - Deduplicates concurrent requests
*/
import https from 'node:https';
import http from 'node:http';
import https from 'node:https';
import { createLogger } from '@shared/utils/logger';
import type { PluginCatalogItem } from '@shared/types/extensions';
import { buildPluginId } from '@shared/utils/extensionNormalizers';
import { createLogger } from '@shared/utils/logger';
import type { PluginCatalogItem } from '@shared/types/extensions';
const logger = createLogger('Extensions:PluginCatalog');
@ -260,7 +261,7 @@ export class PluginCatalogService {
const json = JSON.parse(response.body) as MarketplaceJson;
const items = this.parseMarketplace(json);
const etag = (response.headers['etag'] as string) ?? null;
const etag = (response.headers.etag as string) ?? null;
this.cache = { items, etag, fetchedAt: Date.now() };
logger.info(`Fetched ${items.length} plugins from marketplace "${json.name}"`);
@ -311,7 +312,7 @@ export class PluginCatalogService {
* e.g. https://github.com/org/repo → https://raw.githubusercontent.com/org/repo/main/README.md
*/
private buildReadmeUrl(repoUrl: string): string | null {
const match = repoUrl.match(/github\.com\/([^/]+)\/([^/]+)/);
const match = /github\.com\/([^/]+)\/([^/]+)/.exec(repoUrl);
if (!match) return null;
const [, owner, repo] = match;
return `https://raw.githubusercontent.com/${owner}/${repo}/main/README.md`;

View file

@ -2,26 +2,26 @@
* Extension services barrel export.
*/
export { PluginCatalogService } from './catalog/PluginCatalogService';
export { OfficialMcpRegistryService } from './catalog/OfficialMcpRegistryService';
export { GlamaMcpEnrichmentService } from './catalog/GlamaMcpEnrichmentService';
export { McpCatalogAggregator } from './catalog/McpCatalogAggregator';
export { PluginInstallationStateService } from './state/PluginInstallationStateService';
export { McpInstallationStateService } from './state/McpInstallationStateService';
export { McpHealthDiagnosticsService } from './state/McpHealthDiagnosticsService';
export { ExtensionFacadeService } from './ExtensionFacadeService';
export { PluginInstallService } from './install/PluginInstallService';
export { McpInstallService } from './install/McpInstallService';
export { ApiKeyService } from './apikeys/ApiKeyService';
export { GitHubStarsService } from './catalog/GitHubStarsService';
export { SkillRootsResolver } from './skills/SkillRootsResolver';
export { SkillScanner } from './skills/SkillScanner';
export { SkillMetadataParser } from './skills/SkillMetadataParser';
export { SkillValidator } from './skills/SkillValidator';
export { SkillsCatalogService } from './skills/SkillsCatalogService';
export { SkillScaffoldService } from './skills/SkillScaffoldService';
export { GlamaMcpEnrichmentService } from './catalog/GlamaMcpEnrichmentService';
export { McpCatalogAggregator } from './catalog/McpCatalogAggregator';
export { OfficialMcpRegistryService } from './catalog/OfficialMcpRegistryService';
export { PluginCatalogService } from './catalog/PluginCatalogService';
export { ExtensionFacadeService } from './ExtensionFacadeService';
export { McpInstallService } from './install/McpInstallService';
export { PluginInstallService } from './install/PluginInstallService';
export { SkillImportService } from './skills/SkillImportService';
export { SkillMetadataParser } from './skills/SkillMetadataParser';
export { SkillPlanService } from './skills/SkillPlanService';
export { SkillReviewService } from './skills/SkillReviewService';
export { SkillRootsResolver } from './skills/SkillRootsResolver';
export { SkillScaffoldService } from './skills/SkillScaffoldService';
export { SkillScanner } from './skills/SkillScanner';
export { SkillsCatalogService } from './skills/SkillsCatalogService';
export { SkillsMutationService } from './skills/SkillsMutationService';
export { SkillsWatcherService } from './skills/SkillsWatcherService';
export { SkillValidator } from './skills/SkillValidator';
export { McpHealthDiagnosticsService } from './state/McpHealthDiagnosticsService';
export { McpInstallationStateService } from './state/McpInstallationStateService';
export { PluginInstallationStateService } from './state/PluginInstallationStateService';

View file

@ -10,12 +10,12 @@
import { execCli } from '@main/utils/childProcess';
import { createLogger } from '@shared/utils/logger';
import type { McpCatalogAggregator } from '../catalog/McpCatalogAggregator';
import type {
McpCustomInstallRequest,
McpInstallRequest,
OperationResult,
} from '@shared/types/extensions';
import type { McpCatalogAggregator } from '../catalog/McpCatalogAggregator';
const logger = createLogger('Extensions:McpInstall');

View file

@ -8,8 +8,8 @@
import { execCli } from '@main/utils/childProcess';
import { createLogger } from '@shared/utils/logger';
import type { OperationResult, PluginInstallRequest } from '@shared/types/extensions';
import type { PluginCatalogService } from '../catalog/PluginCatalogService';
import type { OperationResult, PluginInstallRequest } from '@shared/types/extensions';
const logger = createLogger('Extensions:PluginInstall');

View file

@ -101,13 +101,11 @@ export class SkillImportService {
}
}
private async walkDirectory(
rootDir: string
): Promise<{
files: Array<{ absolutePath: string; relativePath: string }>;
private async walkDirectory(rootDir: string): Promise<{
files: { absolutePath: string; relativePath: string }[];
hiddenEntriesSkipped: number;
}> {
const allFiles: Array<{ absolutePath: string; relativePath: string }> = [];
const allFiles: { absolutePath: string; relativePath: string }[] = [];
let hiddenEntriesSkipped = 0;
let totalBytes = 0;

View file

@ -1,6 +1,9 @@
import * as path from 'node:path';
import { createLogger } from '@shared/utils/logger';
import YAML from 'yaml';
import type { ResolvedSkillRoot } from './SkillRootsResolver';
import type {
SkillCatalogItem,
SkillDetail,
@ -8,9 +11,6 @@ import type {
SkillInvocationMode,
SkillValidationIssue,
} from '@shared/types/extensions';
import YAML from 'yaml';
import type { ResolvedSkillRoot } from './SkillRootsResolver';
const logger = createLogger('Extensions:SkillParser');
@ -203,7 +203,7 @@ export class SkillMetadataParser {
};
}
const match = content.match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n?([\s\S]*)$/u);
const match = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?([\s\S]*)$/u.exec(content);
if (!match) {
return {
rawFrontmatter: null,

View file

@ -1,8 +1,11 @@
import { createHash } from 'node:crypto';
import * as fs from 'node:fs/promises';
import * as os from 'node:os';
import * as path from 'node:path';
import { createHash } from 'node:crypto';
import { SkillScanner } from './SkillScanner';
import type { ImportedSkillSourceFile } from './SkillImportService';
import type {
SkillDraftFile,
SkillReviewFileChange,
@ -10,10 +13,6 @@ import type {
SkillReviewSummary,
} from '@shared/types/extensions';
import type { ImportedSkillSourceFile } from './SkillImportService';
import { SkillScanner } from './SkillScanner';
type SkillPlanInputFile =
| { relativePath: string; isBinary: false; content: string }
| { relativePath: string; isBinary: true; sourceAbsolutePath: string };
@ -73,7 +72,7 @@ export class SkillPlanService {
async applyPlan(plan: SkillExecutionPlan): Promise<void> {
const backupRoot = await fs.mkdtemp(path.join(os.tmpdir(), 'skill-plan-backup-'));
const createdPaths: string[] = [];
const backups: Array<{ absolutePath: string; backupPath: string }> = [];
const backups: { absolutePath: string; backupPath: string }[] = [];
try {
for (const [index, change] of plan.changes.entries()) {
@ -200,7 +199,7 @@ export class SkillPlanService {
const summary = changes.reduce<SkillReviewSummary>(
(acc, change) => {
acc[`${change.action}d` as 'created' | 'updated' | 'deleted'] += 1;
acc[`${change.action}d`] += 1;
if (change.isBinary) {
acc.binary += 1;
}

View file

@ -2,9 +2,9 @@ import * as fs from 'node:fs/promises';
import * as path from 'node:path';
import { createLogger } from '@shared/utils/logger';
import type { SkillDraftFile, SkillReviewFileChange } from '@shared/types/extensions';
import type { ImportedSkillSourceFile } from './SkillImportService';
import type { SkillDraftFile, SkillReviewFileChange } from '@shared/types/extensions';
const logger = createLogger('Extensions:SkillReview');

View file

@ -1,6 +1,7 @@
import * as path from 'node:path';
import { getHomeDir } from '@main/utils/pathDecoder';
import type { SkillRootKind, SkillScope } from '@shared/types/extensions';
export interface ResolvedSkillRoot {
@ -10,7 +11,7 @@ export interface ResolvedSkillRoot {
rootPath: string;
}
const USER_ROOTS: Array<{ rootKind: SkillRootKind; segments: string[] }> = [
const USER_ROOTS: { rootKind: SkillRootKind; segments: string[] }[] = [
{ rootKind: 'claude', segments: ['.claude', 'skills'] },
{ rootKind: 'cursor', segments: ['.cursor', 'skills'] },
{ rootKind: 'agents', segments: ['.agents', 'skills'] },

View file

@ -2,10 +2,11 @@ import * as fs from 'node:fs/promises';
import * as path from 'node:path';
import { isPathWithinRoot, validateFileName } from '@main/utils/pathValidation';
import type { SkillDraftFile, SkillRootKind, SkillScope } from '@shared/types/extensions';
import { SkillRootsResolver } from './SkillRootsResolver';
import type { SkillDraftFile, SkillRootKind, SkillScope } from '@shared/types/extensions';
export class SkillScaffoldService {
constructor(private readonly rootsResolver = new SkillRootsResolver()) {}

View file

@ -1,10 +1,10 @@
import * as fs from 'node:fs/promises';
import * as path from 'node:path';
import type { SkillCatalogItem, SkillDirectoryFlags } from '@shared/types/extensions';
import { SkillMetadataParser, type SkillRelatedFiles } from './SkillMetadataParser';
import type { ResolvedSkillRoot } from './SkillRootsResolver';
import type { SkillCatalogItem, SkillDirectoryFlags } from '@shared/types/extensions';
const SKILL_FILE_CANDIDATES = ['SKILL.md', 'Skill.md', 'skill.md'] as const;

View file

@ -2,13 +2,14 @@ import * as fs from 'node:fs/promises';
import * as path from 'node:path';
import { createLogger } from '@shared/utils/logger';
import type { SkillCatalogItem, SkillDetail } from '@shared/types/extensions';
import { SkillMetadataParser } from './SkillMetadataParser';
import { SkillRootsResolver, type ResolvedSkillRoot } from './SkillRootsResolver';
import { type ResolvedSkillRoot, SkillRootsResolver } from './SkillRootsResolver';
import { SkillScanner } from './SkillScanner';
import { SkillValidator } from './SkillValidator';
import type { SkillCatalogItem, SkillDetail } from '@shared/types/extensions';
const logger = createLogger('Extensions:SkillsCatalog');
export class SkillsCatalogService {

View file

@ -1,6 +1,15 @@
import * as fs from 'node:fs/promises';
import * as path from 'node:path';
import { isPathWithinRoot, validateFileName } from '@main/utils/pathValidation';
import { shell } from 'electron';
import { SkillImportService } from './SkillImportService';
import { SkillPlanService } from './SkillPlanService';
import { SkillRootsResolver } from './SkillRootsResolver';
import { SkillScaffoldService } from './SkillScaffoldService';
import { SkillsCatalogService } from './SkillsCatalogService';
import type {
SkillDeleteRequest,
SkillDetail,
@ -8,15 +17,6 @@ import type {
SkillReviewPreview,
SkillUpsertRequest,
} from '@shared/types/extensions';
import { shell } from 'electron';
import { isPathWithinRoot, validateFileName } from '@main/utils/pathValidation';
import { SkillImportService } from './SkillImportService';
import { SkillPlanService } from './SkillPlanService';
import { SkillScaffoldService } from './SkillScaffoldService';
import { SkillRootsResolver } from './SkillRootsResolver';
import { SkillsCatalogService } from './SkillsCatalogService';
export class SkillsMutationService {
constructor(

View file

@ -1,10 +1,10 @@
import { createLogger } from '@shared/utils/logger';
import type { SkillWatcherEvent } from '@shared/types/extensions';
import { isPathWithinRoot } from '@main/utils/pathValidation';
import { createLogger } from '@shared/utils/logger';
import { watch } from 'chokidar';
import { SkillRootsResolver } from './SkillRootsResolver';
import type { SkillWatcherEvent } from '@shared/types/extensions';
import type { FSWatcher } from 'chokidar';
const logger = createLogger('Extensions:SkillsWatcher');

View file

@ -12,9 +12,10 @@
import * as fs from 'node:fs/promises';
import * as path from 'node:path';
import { createLogger } from '@shared/utils/logger';
import type { InstalledMcpEntry } from '@shared/types/extensions';
import { getHomeDir } from '@main/utils/pathDecoder';
import { createLogger } from '@shared/utils/logger';
import type { InstalledMcpEntry } from '@shared/types/extensions';
const logger = createLogger('Extensions:McpState');

View file

@ -11,10 +11,11 @@
import * as fs from 'node:fs/promises';
import * as path from 'node:path';
import { getClaudeBasePath } from '@main/utils/pathDecoder';
import { createLogger } from '@shared/utils/logger';
import type { InstalledPluginEntry } from '@shared/types/extensions';
import type { InstallScope } from '@shared/types/extensions';
import { getClaudeBasePath } from '@main/utils/pathDecoder';
const logger = createLogger('Extensions:PluginState');
@ -29,24 +30,24 @@ interface InstalledPluginsJson {
version: number;
plugins: Record<
string, // qualifiedName
Array<{
{
scope: string;
installPath?: string;
version?: string;
installedAt?: string;
lastUpdated?: string;
gitCommitSha?: string;
}>
}[]
>;
}
interface InstallCountsJson {
version: number;
fetchedAt: string;
counts: Array<{
counts: {
plugin: string; // qualifiedName format
unique_installs: number;
}>;
}[];
}
// ── Cache ──────────────────────────────────────────────────────────────────

View file

@ -12,8 +12,8 @@
export * from './analysis';
export * from './discovery';
export * from './error';
export * from './extensions';
export * from './infrastructure';
export * from './parsing';
export * from './team';
export * from './schedule';
export * from './extensions';
export * from './team';

View file

@ -15,8 +15,8 @@ import { createLogger } from '@shared/utils/logger';
import * as fs from 'fs';
import * as path from 'path';
import type { Schedule, ScheduleRun } from '@shared/types';
import type { ScheduleRepository } from './ScheduleRepository';
import type { Schedule, ScheduleRun } from '@shared/types';
const logger = createLogger('Service:JsonScheduleRepo');

View file

@ -41,7 +41,7 @@ function extractSummaryFromStreamJson(stdout: string): string {
const content = (parsed.content ??
(parsed.message as Record<string, unknown> | undefined)?.content) as
| Array<{ type?: string; text?: string }>
| { type?: string; text?: string }[]
| undefined;
if (!Array.isArray(content)) continue;

View file

@ -10,6 +10,8 @@ import { createLogger } from '@shared/utils/logger';
import { Cron } from 'croner';
import { randomUUID } from 'crypto';
import type { ScheduledTaskExecutor } from './ScheduledTaskExecutor';
import type { ScheduleRepository } from './ScheduleRepository';
import type {
CreateScheduleInput,
Schedule,
@ -18,8 +20,6 @@ import type {
ScheduleRunStatus,
UpdateSchedulePatch,
} from '@shared/types';
import type { ScheduleRepository } from './ScheduleRepository';
import type { ScheduledTaskExecutor } from './ScheduledTaskExecutor';
const logger = createLogger('Service:Scheduler');
@ -511,7 +511,7 @@ export class SchedulerService {
private async onCronTick(scheduleId: string): Promise<void> {
const schedule = await this.repository.getSchedule(scheduleId);
if (!schedule || schedule.status !== 'active') {
if (schedule?.status !== 'active') {
logger.debug(`Cron tick for ${scheduleId} skipped (not active)`);
return;
}
@ -659,7 +659,7 @@ export class SchedulerService {
}
const freshSchedule = await this.repository.getSchedule(schedule.id);
if (!freshSchedule || freshSchedule.status !== 'active') {
if (freshSchedule?.status !== 'active') {
await this.completeRun(retryRun, 'failed', exitCode, undefined, error);
return false;
}

View file

@ -3,12 +3,12 @@
*/
export { JsonScheduleRepository } from './JsonScheduleRepository';
export type { ScheduleRepository } from './ScheduleRepository';
export { ScheduledTaskExecutor } from './ScheduledTaskExecutor';
export type {
ExecutionRequest,
InternalScheduleRun,
ScheduledTaskResult,
} from './ScheduledTaskExecutor';
export { SchedulerService } from './SchedulerService';
export { ScheduledTaskExecutor } from './ScheduledTaskExecutor';
export type { ScheduleRepository } from './ScheduleRepository';
export type { WarmUpFn } from './SchedulerService';
export { SchedulerService } from './SchedulerService';

View file

@ -577,7 +577,7 @@ export class ChangeExtractorService {
private async parseJSONLFilesWithConcurrency(
paths: string[]
): Promise<Array<{ snippets: SnippetDiff[]; mtime: number }>> {
): Promise<{ snippets: SnippetDiff[]; mtime: number }[]> {
if (paths.length === 0) return [];
const results = new Array<{ snippets: SnippetDiff[]; mtime: number }>(paths.length);

View file

@ -1,5 +1,5 @@
import { CROSS_TEAM_SENT_SOURCE, CROSS_TEAM_SOURCE, formatCrossTeamText } from '@shared/constants';
import { getClaudeBasePath, getTeamsBasePath } from '@main/utils/pathDecoder';
import { CROSS_TEAM_SENT_SOURCE, CROSS_TEAM_SOURCE, formatCrossTeamText } from '@shared/constants';
import { isLeadMember } from '@shared/utils/leadDetection';
import { createLogger } from '@shared/utils/logger';
import * as agentTeamsControllerModule from 'agent-teams-controller';

View file

@ -300,7 +300,8 @@ export class TeamBackupService {
if (raw && isValidConfig(raw)) {
const parsed = JSON.parse(raw) as Record<string, unknown>;
manifest.displayName = typeof parsed.name === 'string' ? parsed.name : undefined;
manifest.projectPath = typeof parsed.projectPath === 'string' ? parsed.projectPath : undefined;
manifest.projectPath =
typeof parsed.projectPath === 'string' ? parsed.projectPath : undefined;
}
} catch {
// best-effort
@ -392,7 +393,7 @@ export class TeamBackupService {
}
const cached = manifest.fileStats[descriptor.relPath];
if (cached && cached.mtime === stat.mtimeMs && cached.size === stat.size) {
if (cached?.mtime === stat.mtimeMs && cached.size === stat.size) {
return false; // not dirty
}
@ -431,7 +432,7 @@ export class TeamBackupService {
if (stat.size > MAX_FILE_SIZE_BYTES) return; // skip oversized silently during shutdown
const cached = manifest.fileStats[descriptor.relPath];
if (cached && cached.mtime === stat.mtimeMs && cached.size === stat.size) return;
if (cached?.mtime === stat.mtimeMs && cached.size === stat.size) return;
const destPath = path.join(backupDir, descriptor.relPath);
@ -572,10 +573,7 @@ export class TeamBackupService {
return count > 0;
}
private async restoreGenericPartial(
teamName: string,
manifest: BackupManifest
): Promise<number> {
private async restoreGenericPartial(teamName: string, manifest: BackupManifest): Promise<number> {
const backupDir = this.getBackupDir(teamName);
const backupFiles = await this.enumerateBackupFiles(teamName);
let count = 0;
@ -892,10 +890,20 @@ export class TeamBackupService {
return path.join(getTasksBasePath(), teamName, relPath.slice('tasks/'.length));
}
if (relPath.startsWith('attachments/')) {
return path.join(getAppDataPath(), 'attachments', teamName, relPath.slice('attachments/'.length));
return path.join(
getAppDataPath(),
'attachments',
teamName,
relPath.slice('attachments/'.length)
);
}
if (relPath.startsWith('task-attachments/')) {
return path.join(getAppDataPath(), 'task-attachments', teamName, relPath.slice('task-attachments/'.length));
return path.join(
getAppDataPath(),
'task-attachments',
teamName,
relPath.slice('task-attachments/'.length)
);
}
return path.join(getTeamsBasePath(), teamName, relPath);
}

View file

@ -1,12 +1,11 @@
import { getHomeDir } from '@main/utils/pathDecoder';
import { createLogger } from '@shared/utils/logger';
import { execFile } from 'child_process';
import { randomUUID } from 'crypto';
import * as fs from 'fs';
import * as os from 'os';
import * as path from 'path';
import { getHomeDir } from '@main/utils/pathDecoder';
import { createLogger } from '@shared/utils/logger';
import { atomicWriteAsync } from './atomicWrite';
interface McpLaunchSpec {

View file

@ -1,9 +1,7 @@
/* eslint-disable no-param-reassign -- ProvisioningRun object is intentionally mutated as a state tracker throughout the provisioning lifecycle */
import { ConfigManager } from '@main/services/infrastructure/ConfigManager';
import { killProcessTree, spawnCli } from '@main/utils/childProcess';
import { shouldAutoAllow } from '@main/utils/toolApprovalRules';
import { FileReadTimeoutError, readFileUtf8WithTimeout } from '@main/utils/fsRead';
import { resolveInteractiveShellEnv } from '@main/utils/shellEnv';
import {
encodePath,
extractBaseDir,
@ -14,6 +12,8 @@ import {
getTasksBasePath,
getTeamsBasePath,
} from '@main/utils/pathDecoder';
import { resolveInteractiveShellEnv } from '@main/utils/shellEnv';
import { shouldAutoAllow } from '@main/utils/toolApprovalRules';
import {
AGENT_BLOCK_CLOSE,
AGENT_BLOCK_OPEN,
@ -21,8 +21,8 @@ import {
} from '@shared/constants/agentBlocks';
import {
CROSS_TEAM_PREFIX_TAG,
CROSS_TEAM_SOURCE,
CROSS_TEAM_SENT_SOURCE,
CROSS_TEAM_SOURCE,
parseCrossTeamPrefix,
stripCrossTeamPrefix,
} from '@shared/constants/crossTeam';
@ -41,14 +41,14 @@ import {
import { createCliAutoSuffixNameGuard, parseNumericSuffixName } from '@shared/utils/teamMemberName';
import { extractToolPreview, formatToolSummaryFromCalls } from '@shared/utils/toolSummary';
import * as agentTeamsControllerModule from 'agent-teams-controller';
import { spawn, type ChildProcess } from 'child_process';
import { type ChildProcess, type spawn } from 'child_process';
import { randomUUID } from 'crypto';
import * as fs from 'fs';
import * as os from 'os';
import * as path from 'path';
import { atomicWriteAsync } from './atomicWrite';
import { buildActionModeProtocol } from './actionModeInstructions';
import { atomicWriteAsync } from './atomicWrite';
import { ClaudeBinaryResolver } from './ClaudeBinaryResolver';
import { withFileLock } from './fileLock';
import { withInboxLock } from './inboxLock';
@ -231,10 +231,10 @@ interface ProvisioningRun {
rejectOnce: (error: string) => void;
timeoutHandle: NodeJS.Timeout;
} | null;
activeCrossTeamReplyHints: Array<{
activeCrossTeamReplyHints: {
toTeam: string;
conversationId: string;
}>;
}[];
/** Monotonic counter for individual lead assistant messages. */
leadMsgSeq: number;
/** Accumulated tool_use details between text messages. */
@ -1122,11 +1122,11 @@ interface CachedProbeResult {
cachedAtMs: number;
}
type ProbeResult = {
interface ProbeResult {
claudePath: string;
authSource: ProvisioningAuthSource;
warning?: string;
};
}
type AuthWarningSource = 'probe' | 'stdout' | 'stderr' | 'assistant' | 'pre-complete';
@ -1607,21 +1607,21 @@ export class TeamProvisioningService {
private async matchCrossTeamLeadInboxMessages(
teamName: string,
leadName: string,
deliveredBlocks: Array<{
deliveredBlocks: {
teammateId: string;
content: string;
toTeam: string;
conversationId: string;
}>
}[]
): Promise<
Array<{
{
teammateId: string;
content: string;
toTeam: string;
conversationId: string;
messageId: string;
wasRead: boolean;
}>
}[]
> {
if (deliveredBlocks.length === 0) return [];
@ -1633,14 +1633,14 @@ export class TeamProvisioningService {
}
const usedMessageIds = new Set<string>();
const matches: Array<{
const matches: {
teammateId: string;
content: string;
toTeam: string;
conversationId: string;
messageId: string;
wasRead: boolean;
}> = [];
}[] = [];
for (const block of deliveredBlocks) {
const matchesBlock = (message: InboxMessage, requireExactText: boolean): boolean => {
if (message.source !== CROSS_TEAM_SOURCE) return false;
@ -1816,7 +1816,7 @@ export class TeamProvisioningService {
private rememberPendingInboxRelayCandidates(
run: ProvisioningRun,
recipient: string,
messages: Array<Pick<InboxMessage, 'messageId' | 'text' | 'summary'>>
messages: Pick<InboxMessage, 'messageId' | 'text' | 'summary'>[]
): string[] {
const candidates = this.prunePendingInboxRelayCandidates(run);
const queuedAtMs = Date.now();
@ -4167,7 +4167,7 @@ export class TeamProvisioningService {
(mistakenToolHint ? { teamName: mistakenToolHint.toTeam, memberName: 'team-lead' } : null);
if (crossTeamRecipient && this.crossTeamSender) {
const inferredReplyMeta =
mistakenToolHint && mistakenToolHint.toTeam === crossTeamRecipient.teamName
mistakenToolHint?.toTeam === crossTeamRecipient.teamName
? {
conversationId: mistakenToolHint.conversationId,
replyToConversationId: mistakenToolHint.conversationId,

View file

@ -1,5 +1,5 @@
import { getTaskChangeSummariesBasePath } from '@main/utils/pathDecoder';
import { atomicWriteAsync } from '@main/utils/atomicWrite';
import { getTaskChangeSummariesBasePath } from '@main/utils/pathDecoder';
import { createLogger } from '@shared/utils/logger';
import * as fs from 'fs';
import * as path from 'path';

View file

@ -1,7 +1,7 @@
import { TASK_CHANGE_SUMMARY_CACHE_SCHEMA_VERSION } from './taskChangeSummaryCacheTypes';
import type { FileChangeSummary, TaskChangeSetV2 } from '@shared/types';
import type { PersistedTaskChangeSummaryEntry } from './taskChangeSummaryCacheTypes';
import type { FileChangeSummary, TaskChangeSetV2 } from '@shared/types';
function normalizeIsoString(value: unknown): string | null {
if (typeof value !== 'string' || value.trim() === '') return null;

View file

@ -707,8 +707,8 @@ async function readTasksDirForTeam(
? (parsed.attachments as unknown[])
: undefined,
sourceMessageId:
typeof parsed.sourceMessageId === 'string' && (parsed.sourceMessageId as string).trim()
? (parsed.sourceMessageId as string).trim()
typeof parsed.sourceMessageId === 'string' && parsed.sourceMessageId.trim()
? parsed.sourceMessageId.trim()
: undefined,
sourceMessage:
parsed.sourceMessage &&

View file

@ -2,6 +2,11 @@ import { WINDOW_ZOOM_FACTOR_CHANGED_CHANNEL } from '@shared/constants';
import { contextBridge, ipcRenderer } from 'electron';
import {
API_KEYS_DELETE,
API_KEYS_LIST,
API_KEYS_LOOKUP,
API_KEYS_SAVE,
API_KEYS_STORAGE_STATUS,
APP_RELAUNCH,
CLI_INSTALLER_GET_STATUS,
CLI_INSTALLER_INSTALL,
@ -34,28 +39,30 @@ import {
HTTP_SERVER_GET_STATUS,
HTTP_SERVER_START,
HTTP_SERVER_STOP,
MCP_GITHUB_STARS,
MCP_REGISTRY_BROWSE,
MCP_REGISTRY_DIAGNOSE,
MCP_REGISTRY_GET_BY_ID,
MCP_REGISTRY_GET_INSTALLED,
MCP_REGISTRY_INSTALL,
MCP_REGISTRY_INSTALL_CUSTOM,
MCP_REGISTRY_SEARCH,
MCP_REGISTRY_UNINSTALL,
PLUGIN_GET_ALL,
PLUGIN_GET_README,
PLUGIN_INSTALL,
PLUGIN_UNINSTALL,
PROJECT_LIST_FILES,
RENDERER_BOOT,
RENDERER_HEARTBEAT,
RENDERER_LOG,
SCHEDULE_CHANGE,
SCHEDULE_CREATE,
SCHEDULE_DELETE,
SCHEDULE_GET,
SCHEDULE_GET_RUN_LOGS,
SCHEDULE_GET_RUNS,
SCHEDULE_LIST,
SCHEDULE_PAUSE,
SCHEDULE_RESUME,
SCHEDULE_TRIGGER_NOW,
SCHEDULE_UPDATE,
REVIEW_APPLY_DECISIONS,
REVIEW_CHECK_CONFLICT,
REVIEW_CLEAR_DECISIONS,
REVIEW_FILE_CHANGE,
REVIEW_GET_AGENT_CHANGES,
REVIEW_GET_CHANGE_STATS,
REVIEW_GET_FILE_CONTENT,
REVIEW_FILE_CHANGE,
REVIEW_GET_GIT_FILE_LOG,
REVIEW_GET_TASK_CHANGES,
REVIEW_INVALIDATE_TASK_CHANGE_SUMMARIES,
@ -67,6 +74,27 @@ import {
REVIEW_SAVE_EDITED_FILE,
REVIEW_UNWATCH_FILES,
REVIEW_WATCH_FILES,
SCHEDULE_CHANGE,
SCHEDULE_CREATE,
SCHEDULE_DELETE,
SCHEDULE_GET,
SCHEDULE_GET_RUN_LOGS,
SCHEDULE_GET_RUNS,
SCHEDULE_LIST,
SCHEDULE_PAUSE,
SCHEDULE_RESUME,
SCHEDULE_TRIGGER_NOW,
SCHEDULE_UPDATE,
SKILLS_APPLY_IMPORT,
SKILLS_APPLY_UPSERT,
SKILLS_CHANGED,
SKILLS_DELETE,
SKILLS_GET_DETAIL,
SKILLS_LIST,
SKILLS_PREVIEW_IMPORT,
SKILLS_PREVIEW_UPSERT,
SKILLS_START_WATCHING,
SKILLS_STOP_WATCHING,
SSH_CONNECT,
SSH_DISCONNECT,
SSH_GET_CONFIG_HOSTS,
@ -101,8 +129,8 @@ import {
TEAM_LAUNCH,
TEAM_LEAD_ACTIVITY,
TEAM_LEAD_CONTEXT,
TEAM_MEMBER_SPAWN_STATUSES,
TEAM_LIST,
TEAM_MEMBER_SPAWN_STATUSES,
TEAM_PERMANENTLY_DELETE,
TEAM_PREPARE_PROVISIONING,
TEAM_PROCESS_ALIVE,
@ -149,34 +177,6 @@ import {
WINDOW_IS_MAXIMIZED,
WINDOW_MAXIMIZE,
WINDOW_MINIMIZE,
PLUGIN_GET_ALL,
PLUGIN_GET_README,
PLUGIN_INSTALL,
PLUGIN_UNINSTALL,
MCP_REGISTRY_SEARCH,
MCP_REGISTRY_BROWSE,
MCP_REGISTRY_DIAGNOSE,
MCP_REGISTRY_GET_BY_ID,
MCP_REGISTRY_GET_INSTALLED,
MCP_REGISTRY_INSTALL,
MCP_REGISTRY_INSTALL_CUSTOM,
MCP_REGISTRY_UNINSTALL,
MCP_GITHUB_STARS,
SKILLS_APPLY_IMPORT,
SKILLS_APPLY_UPSERT,
SKILLS_CHANGED,
SKILLS_DELETE,
SKILLS_GET_DETAIL,
SKILLS_LIST,
SKILLS_PREVIEW_IMPORT,
SKILLS_PREVIEW_UPSERT,
SKILLS_START_WATCHING,
SKILLS_STOP_WATCHING,
API_KEYS_LIST,
API_KEYS_SAVE,
API_KEYS_DELETE,
API_KEYS_LOOKUP,
API_KEYS_STORAGE_STATUS,
} from './constants/ipcChannels';
import {
CONFIG_ADD_CUSTOM_PROJECT_PATH,
@ -236,9 +236,9 @@ import type {
KanbanColumnId,
LeadActivitySnapshot,
LeadContextUsageSnapshot,
MemberSpawnStatusesSnapshot,
MemberFullStats,
MemberLogSummary,
MemberSpawnStatusesSnapshot,
NotificationTrigger,
RejectResult,
ReplaceMembersRequest,
@ -281,28 +281,6 @@ import type {
UpdateSchedulePatch,
WslClaudeRootCandidate,
} from '@shared/types';
import type {
ApiKeyEntry,
ApiKeyLookupResult,
ApiKeySaveRequest,
ApiKeyStorageStatus,
EnrichedPlugin,
InstalledMcpEntry,
McpCatalogItem,
McpCustomInstallRequest,
McpInstallRequest,
McpServerDiagnostic,
McpSearchResult,
OperationResult,
PluginInstallRequest,
SkillCatalogItem,
SkillDeleteRequest,
SkillDetail,
SkillImportRequest,
SkillReviewPreview,
SkillUpsertRequest,
SkillWatcherEvent,
} from '@shared/types/extensions';
import type {
BinaryPreviewResult,
CreateDirResponse,
@ -318,6 +296,28 @@ import type {
SearchInFilesResult,
WriteFileResponse,
} from '@shared/types/editor';
import type {
ApiKeyEntry,
ApiKeyLookupResult,
ApiKeySaveRequest,
ApiKeyStorageStatus,
EnrichedPlugin,
InstalledMcpEntry,
McpCatalogItem,
McpCustomInstallRequest,
McpInstallRequest,
McpSearchResult,
McpServerDiagnostic,
OperationResult,
PluginInstallRequest,
SkillCatalogItem,
SkillDeleteRequest,
SkillDetail,
SkillImportRequest,
SkillReviewPreview,
SkillUpsertRequest,
SkillWatcherEvent,
} from '@shared/types/extensions';
import type { PtySpawnOptions } from '@shared/types/terminal';
import type { CliArgsValidationResult } from '@shared/utils/cliArgsParser';

View file

@ -27,7 +27,6 @@ import {
import { getTeamColorSet, getThemedBadge } from '@renderer/constants/teamColors';
import { useTheme } from '@renderer/hooks/useTheme';
import { useStore } from '@renderer/store';
import type { SearchMatch } from '@renderer/store/types';
import { REHYPE_PLUGINS, REHYPE_PLUGINS_NO_HIGHLIGHT } from '@renderer/utils/markdownPlugins';
import { nameColorSet } from '@renderer/utils/projectColor';
import { parseTaskLinkHref } from '@renderer/utils/taskReferenceUtils';
@ -44,6 +43,8 @@ import {
import { FileLink, isRelativeUrl } from './FileLink';
import { MermaidDiagram } from './MermaidDiagram';
import type { SearchMatch } from '@renderer/store/types';
// =============================================================================
// Types
// =============================================================================
@ -69,7 +70,7 @@ interface MarkdownViewerProps {
onTeamClick?: (teamName: string) => void;
}
const EMPTY_TEAMS: Array<{ teamName?: string; displayName?: string; color?: string }> = [];
const EMPTY_TEAMS: { teamName?: string; displayName?: string; color?: string }[] = [];
const EMPTY_TEAM_COLOR_MAP = new Map<string, string>();
const EMPTY_SEARCH_MATCHES: SearchMatch[] = [];
const NOOP_TEAM_CLICK = (): void => undefined;
@ -94,31 +95,136 @@ function allowCustomProtocols(url: string): string {
* that appear in agent messages and cause React "unrecognized tag" warnings.
*/
const STANDARD_HTML_TAGS = new Set([
'a', 'abbr', 'address', 'area', 'article', 'aside', 'audio',
'b', 'base', 'bdi', 'bdo', 'blockquote', 'body', 'br', 'button',
'canvas', 'caption', 'cite', 'code', 'col', 'colgroup',
'data', 'datalist', 'dd', 'del', 'details', 'dfn', 'dialog', 'div', 'dl', 'dt',
'em', 'embed',
'fieldset', 'figcaption', 'figure', 'footer', 'form',
'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'head', 'header', 'hgroup', 'hr', 'html',
'i', 'iframe', 'img', 'input', 'ins',
'a',
'abbr',
'address',
'area',
'article',
'aside',
'audio',
'b',
'base',
'bdi',
'bdo',
'blockquote',
'body',
'br',
'button',
'canvas',
'caption',
'cite',
'code',
'col',
'colgroup',
'data',
'datalist',
'dd',
'del',
'details',
'dfn',
'dialog',
'div',
'dl',
'dt',
'em',
'embed',
'fieldset',
'figcaption',
'figure',
'footer',
'form',
'h1',
'h2',
'h3',
'h4',
'h5',
'h6',
'head',
'header',
'hgroup',
'hr',
'html',
'i',
'iframe',
'img',
'input',
'ins',
'kbd',
'label', 'legend', 'li', 'link',
'main', 'map', 'mark', 'menu', 'meta', 'meter',
'nav', 'noscript',
'object', 'ol', 'optgroup', 'option', 'output',
'p', 'picture', 'pre', 'progress',
'label',
'legend',
'li',
'link',
'main',
'map',
'mark',
'menu',
'meta',
'meter',
'nav',
'noscript',
'object',
'ol',
'optgroup',
'option',
'output',
'p',
'picture',
'pre',
'progress',
'q',
'rp', 'rt', 'ruby',
's', 'samp', 'script', 'search', 'section', 'select', 'slot', 'small', 'source', 'span',
'strong', 'style', 'sub', 'summary', 'sup',
'table', 'tbody', 'td', 'template', 'textarea', 'tfoot', 'th', 'thead', 'time', 'title', 'tr', 'track',
'u', 'ul',
'var', 'video',
'rp',
'rt',
'ruby',
's',
'samp',
'script',
'search',
'section',
'select',
'slot',
'small',
'source',
'span',
'strong',
'style',
'sub',
'summary',
'sup',
'table',
'tbody',
'td',
'template',
'textarea',
'tfoot',
'th',
'thead',
'time',
'title',
'tr',
'track',
'u',
'ul',
'var',
'video',
'wbr',
// SVG elements commonly used inline
'svg', 'path', 'circle', 'rect', 'line', 'polyline', 'polygon', 'g', 'defs', 'use',
'text', 'tspan', 'clippath', 'mask', 'pattern', 'image', 'foreignobject',
'svg',
'path',
'circle',
'rect',
'line',
'polyline',
'polygon',
'g',
'defs',
'use',
'text',
'tspan',
'clippath',
'mask',
'pattern',
'image',
'foreignobject',
]);
/**

View file

@ -1,14 +1,13 @@
import React, { Component, type ErrorInfo, type ReactNode } from 'react';
import { useStore } from '@renderer/store';
import { createLogger } from '@shared/utils/logger';
import { AlertTriangle, Bug, Check, Copy, RefreshCw } from 'lucide-react';
import {
type BugReportContext,
buildBugReportText,
buildGitHubBugReportUrl,
type BugReportContext,
} from '@renderer/utils/bugReportUtils';
import { createLogger } from '@shared/utils/logger';
import { AlertTriangle, Bug, Check, Copy, RefreshCw } from 'lucide-react';
const logger = createLogger('Component:ErrorBoundary');

View file

@ -8,9 +8,6 @@ import { useCallback, useEffect, useMemo, useState } from 'react';
import { api } from '@renderer/api';
import { Button } from '@renderer/components/ui/button';
import { useTabIdOptional } from '@renderer/contexts/useTabUIContext';
import { useExtensionsTabState } from '@renderer/hooks/useExtensionsTabState';
import { useStore } from '@renderer/store';
import { Tabs, TabsContent, TabsList } from '@renderer/components/ui/tabs';
import {
Tooltip,
@ -18,14 +15,17 @@ import {
TooltipProvider,
TooltipTrigger,
} from '@renderer/components/ui/tooltip';
import { useTabIdOptional } from '@renderer/contexts/useTabUIContext';
import { useExtensionsTabState } from '@renderer/hooks/useExtensionsTabState';
import { useStore } from '@renderer/store';
import { AlertTriangle, BookOpen, Info, Key, Plus, Puzzle, RefreshCw, Server } from 'lucide-react';
import { ApiKeysPanel } from './apikeys/ApiKeysPanel';
import { ExtensionsSubTabTrigger } from './ExtensionsSubTabTrigger';
import { CustomMcpServerDialog } from './mcp/CustomMcpServerDialog';
import { McpServersPanel } from './mcp/McpServersPanel';
import { PluginsPanel } from './plugins/PluginsPanel';
import { SkillsPanel } from './skills/SkillsPanel';
import { ExtensionsSubTabTrigger } from './ExtensionsSubTabTrigger';
export const ExtensionStoreView = (): React.JSX.Element => {
const tabId = useTabIdOptional();

View file

@ -1,10 +1,9 @@
import type { LucideIcon } from 'lucide-react';
import { TabsTrigger } from '@renderer/components/ui/tabs';
import { Tooltip, TooltipContent, TooltipTrigger } from '@renderer/components/ui/tooltip';
import { Info } from 'lucide-react';
import type { LucideIcon } from 'lucide-react';
interface ExtensionsSubTabTriggerProps {
value: 'plugins' | 'mcp-servers' | 'skills' | 'api-keys';
label: string;
@ -21,7 +20,7 @@ export const ExtensionsSubTabTrigger = ({
return (
<TabsTrigger
value={value}
className="relative gap-1.5 rounded-b-none pr-7 data-[state=active]:z-10 data-[state=active]:-mb-px data-[state=active]:bg-[var(--color-surface)] data-[state=active]:shadow-none data-[state=active]:after:absolute data-[state=active]:after:-bottom-px data-[state=active]:after:left-0 data-[state=active]:after:right-0 data-[state=active]:after:h-1 data-[state=active]:after:bg-[var(--color-surface)] data-[state=active]:after:content-['']"
className="relative gap-1.5 rounded-b-none pr-7 data-[state=active]:z-10 data-[state=active]:-mb-px data-[state=active]:bg-[var(--color-surface)] data-[state=active]:shadow-none data-[state=active]:after:absolute data-[state=active]:after:inset-x-0 data-[state=active]:after:-bottom-px data-[state=active]:after:h-1 data-[state=active]:after:bg-[var(--color-surface)] data-[state=active]:after:content-['']"
>
<Icon className="size-3.5" />
{label}

View file

@ -13,7 +13,7 @@ import {
TooltipTrigger,
} from '@renderer/components/ui/tooltip';
import { useStore } from '@renderer/store';
import { Copy, Check, Pencil, Trash2 } from 'lucide-react';
import { Check, Copy, Pencil, Trash2 } from 'lucide-react';
import type { ApiKeyEntry } from '@shared/types/extensions';

View file

@ -5,8 +5,6 @@
import { useEffect, useState } from 'react';
import { Check, Loader2, Trash2 } from 'lucide-react';
import { Button } from '@renderer/components/ui/button';
import {
Tooltip,
@ -15,6 +13,7 @@ import {
TooltipTrigger,
} from '@renderer/components/ui/tooltip';
import { useStore } from '@renderer/store';
import { Check, Loader2, Trash2 } from 'lucide-react';
import type { ExtensionOperationState } from '@shared/types/extensions';
@ -28,7 +27,7 @@ interface InstallButtonProps {
errorMessage?: string;
}
export function InstallButton({
export const InstallButton = ({
state,
isInstalled,
onInstall,
@ -36,7 +35,7 @@ export function InstallButton({
disabled,
size = 'sm',
errorMessage,
}: InstallButtonProps) {
}: InstallButtonProps) => {
const cliStatus = useStore((s) => s.cliStatus);
const cliMissing = cliStatus !== null && !cliStatus.installed;
const isDisabled = disabled || cliMissing;
@ -52,7 +51,7 @@ export function InstallButton({
if (state === 'pending') {
return (
<Button size={size} variant="outline" disabled>
<Loader2 className="h-3.5 w-3.5 animate-spin" />
<Loader2 className="size-3.5 animate-spin" />
<span className="ml-1.5">
{pendingAction === 'uninstall' ? 'Removing...' : 'Installing...'}
</span>
@ -63,7 +62,7 @@ export function InstallButton({
if (state === 'success') {
return (
<Button size={size} variant="outline" disabled className="text-green-400">
<Check className="h-3.5 w-3.5" />
<Check className="size-3.5" />
<span className="ml-1.5">Done</span>
</Button>
);
@ -121,7 +120,7 @@ export function InstallButton({
}}
disabled={isDisabled}
>
<Trash2 className="h-3.5 w-3.5" />
<Trash2 className="size-3.5" />
<span className="ml-1.5">Uninstall</span>
</Button>
) : (
@ -153,4 +152,4 @@ export function InstallButton({
}
return button;
}
};

View file

@ -2,9 +2,8 @@
* InstallCountBadge formatted download count with icon.
*/
import { Download } from 'lucide-react';
import { formatInstallCount } from '@shared/utils/extensionNormalizers';
import { Download } from 'lucide-react';
interface InstallCountBadgeProps {
count: number;

View file

@ -5,6 +5,7 @@
import { useEffect, useState } from 'react';
import { api } from '@renderer/api';
import { Button } from '@renderer/components/ui/button';
import {
Dialog,
@ -23,7 +24,6 @@ import {
SelectValue,
} from '@renderer/components/ui/select';
import { useStore } from '@renderer/store';
import { api } from '@renderer/api';
import { Plus, Server, Trash2 } from 'lucide-react';
import type {

View file

@ -5,20 +5,18 @@
import { useState } from 'react';
import { api } from '@renderer/api';
import { Badge } from '@renderer/components/ui/badge';
import { Button } from '@renderer/components/ui/button';
import { Tooltip, TooltipContent, TooltipTrigger } from '@renderer/components/ui/tooltip';
import { useStore } from '@renderer/store';
import { api } from '@renderer/api';
import { formatCompactNumber, formatRelativeTime } from '@renderer/utils/formatters';
import { Cloud, Clock, Globe, KeyRound, Lock, Monitor, Star, Tag, Wrench } from 'lucide-react';
// eslint-disable-next-line @typescript-eslint/no-deprecated -- lucide naming migration, alias is stable
import { sanitizeMcpServerName } from '@shared/utils/extensionNormalizers';
import { Clock, Cloud, Globe, KeyRound, Lock, Monitor, Star, Tag, Wrench } from 'lucide-react';
import { Github as GithubIcon } from 'lucide-react';
import { InstallButton } from '../common/InstallButton';
import { SourceBadge } from '../common/SourceBadge';
import { sanitizeMcpServerName } from '@shared/utils/extensionNormalizers';
import type { McpCatalogItem, McpServerDiagnostic } from '@shared/types/extensions';
@ -82,7 +80,7 @@ export const McpServerCard = ({
{hasIcon && (
<div className="flex size-9 shrink-0 items-center justify-center rounded-lg border border-border bg-surface-raised">
<img
src={server.iconUrl!}
src={server.iconUrl}
alt=""
className="size-7 rounded object-contain"
onError={() => setImgError(true)}

View file

@ -5,6 +5,9 @@
import { useEffect, useState } from 'react';
import { api } from '@renderer/api';
import { Badge } from '@renderer/components/ui/badge';
import { Button } from '@renderer/components/ui/button';
import {
Dialog,
DialogContent,
@ -12,8 +15,6 @@ import {
DialogHeader,
DialogTitle,
} from '@renderer/components/ui/dialog';
import { Badge } from '@renderer/components/ui/badge';
import { Button } from '@renderer/components/ui/button';
import { Input } from '@renderer/components/ui/input';
import { Label } from '@renderer/components/ui/label';
import {
@ -24,12 +25,11 @@ import {
SelectValue,
} from '@renderer/components/ui/select';
import { useStore } from '@renderer/store';
import { api } from '@renderer/api';
import { sanitizeMcpServerName } from '@shared/utils/extensionNormalizers';
import { ExternalLink, Lock, Plus, Star, Trash2, Wrench } from 'lucide-react';
import { InstallButton } from '../common/InstallButton';
import { SourceBadge } from '../common/SourceBadge';
import { sanitizeMcpServerName } from '@shared/utils/extensionNormalizers';
import type { McpCatalogItem, McpHeaderDef, McpServerDiagnostic } from '@shared/types/extensions';
@ -214,7 +214,7 @@ export const McpServerDetailDialog = ({
{hasIcon && (
<div className="flex size-10 shrink-0 items-center justify-center rounded-lg border border-border bg-surface-raised">
<img
src={server.iconUrl!}
src={server.iconUrl}
alt=""
className="size-8 rounded object-contain"
onError={() => setImgError(true)}

View file

@ -15,6 +15,7 @@ import {
} from '@renderer/components/ui/select';
import { useStore } from '@renderer/store';
import { formatRelativeTime } from '@renderer/utils/formatters';
import { sanitizeMcpServerName } from '@shared/utils/extensionNormalizers';
import { AlertTriangle, RefreshCw, Search, Server } from 'lucide-react';
import { SearchInput } from '../common/SearchInput';
@ -27,7 +28,6 @@ import type {
McpCatalogItem,
McpServerDiagnostic,
} from '@shared/types/extensions';
import { sanitizeMcpServerName } from '@shared/utils/extensionNormalizers';
type McpSortValue = 'name-asc' | 'name-desc' | 'tools-desc';

View file

@ -53,8 +53,8 @@ export const PluginCard = ({ plugin, index, onClick }: PluginCardProps): React.J
}`}
>
{plugin.source === 'official' && (
<div className="pointer-events-none absolute -left-[1px] -top-[1px] size-16 overflow-hidden">
<div className="absolute left-[-24px] top-[4px] w-[80px] rotate-[-45deg] bg-blue-500/90 text-center text-[9px] font-semibold leading-[18px] text-white shadow-sm">
<div className="pointer-events-none absolute -left-px -top-px size-16 overflow-hidden">
<div className="absolute left-[-24px] top-[4px] w-[80px] -rotate-45 bg-blue-500/90 text-center text-[9px] font-semibold leading-[18px] text-white shadow-sm">
Official
</div>
</div>

View file

@ -4,6 +4,10 @@
import { useEffect, useState } from 'react';
import { api } from '@renderer/api';
import { MarkdownViewer } from '@renderer/components/chat/viewers/MarkdownViewer';
import { Badge } from '@renderer/components/ui/badge';
import { Button } from '@renderer/components/ui/button';
import {
Dialog,
DialogContent,
@ -11,8 +15,6 @@ import {
DialogHeader,
DialogTitle,
} from '@renderer/components/ui/dialog';
import { Badge } from '@renderer/components/ui/badge';
import { Button } from '@renderer/components/ui/button';
import { Label } from '@renderer/components/ui/label';
import {
Select,
@ -22,7 +24,6 @@ import {
SelectValue,
} from '@renderer/components/ui/select';
import { useStore } from '@renderer/store';
import { api } from '@renderer/api';
import {
getCapabilityLabel,
inferCapabilities,
@ -34,8 +35,6 @@ import { InstallButton } from '../common/InstallButton';
import { InstallCountBadge } from '../common/InstallCountBadge';
import { SourceBadge } from '../common/SourceBadge';
import { MarkdownViewer } from '@renderer/components/chat/viewers/MarkdownViewer';
import type { EnrichedPlugin, InstallScope } from '@shared/types/extensions';
interface PluginDetailDialogProps {

View file

@ -18,8 +18,8 @@ import {
keymap,
lineNumbers,
} from '@codemirror/view';
import { baseEditorTheme } from '@renderer/utils/codemirrorTheme';
import { getSyncLanguageExtension } from '@renderer/utils/codemirrorLanguages';
import { baseEditorTheme } from '@renderer/utils/codemirrorTheme';
const skillEditorTheme = EditorView.theme({
'&': {

View file

@ -26,13 +26,13 @@ import { useStore } from '@renderer/store';
import { FileSearch, RotateCcw, X } from 'lucide-react';
import { SkillCodeEditor } from './SkillCodeEditor';
import { SkillReviewDialog } from './SkillReviewDialog';
import {
buildSkillDraftFiles,
buildSkillTemplate,
readSkillTemplateContent,
updateSkillTemplateFrontmatter,
} from './skillDraftUtils';
import { SkillReviewDialog } from './SkillReviewDialog';
import type {
SkillDetail,
@ -792,7 +792,7 @@ export const SkillEditorDialog = ({
<X className="mr-1.5 size-3.5" />
Cancel
</Button>
<div className="min-w-[16rem] flex-1">
<div className="min-w-64 flex-1">
<p className="text-sm text-text-muted">
Review the file changes first, then confirm save in the next step.
</p>

View file

@ -255,7 +255,7 @@ export const SkillImportDialog = ({
<X className="mr-1.5 size-3.5" />
Cancel
</Button>
<p className="min-w-[16rem] flex-1 text-sm text-text-muted">
<p className="min-w-64 flex-1 text-sm text-text-muted">
Review the copied files first, then confirm the import in the next step.
</p>
<Button

View file

@ -4,8 +4,8 @@ import { api } from '@renderer/api';
import { Badge } from '@renderer/components/ui/badge';
import { Button } from '@renderer/components/ui/button';
import { Popover, PopoverContent, PopoverTrigger } from '@renderer/components/ui/popover';
import { useStore } from '@renderer/store';
import { Tooltip, TooltipContent, TooltipTrigger } from '@renderer/components/ui/tooltip';
import { useStore } from '@renderer/store';
import {
AlertTriangle,
ArrowUpAZ,
@ -25,8 +25,8 @@ import { SkillDetailDialog } from './SkillDetailDialog';
import { SkillEditorDialog } from './SkillEditorDialog';
import { SkillImportDialog } from './SkillImportDialog';
import type { SkillCatalogItem, SkillDetail } from '@shared/types/extensions';
import type { SkillsSortState } from '@renderer/hooks/useExtensionsTabState';
import type { SkillCatalogItem, SkillDetail } from '@shared/types/extensions';
const SUCCESS_BANNER_MS = 2500;
const NEW_SKILL_HIGHLIGHT_MS = 4000;
@ -233,7 +233,7 @@ export const SkillsPanel = ({
<div className="flex w-full flex-col gap-3 xl:w-auto xl:min-w-[32rem] xl:max-w-[40rem]">
<div className="flex flex-col gap-3 lg:flex-row lg:flex-wrap lg:items-center xl:justify-end">
<div className="w-full lg:min-w-[18rem] lg:flex-1 xl:w-80 xl:flex-none">
<div className="w-full lg:min-w-72 lg:flex-1 xl:w-80 xl:flex-none">
<SearchInput
value={skillsSearchQuery}
onChange={setSkillsSearchQuery}
@ -318,7 +318,7 @@ export const SkillsPanel = ({
['personal', 'Personal'],
['needs-attention', 'Needs attention'],
['has-scripts', 'Has scripts'],
] as Array<[SkillsQuickFilter, string]>
] as [SkillsQuickFilter, string][]
).map(([value, label]) => (
<Button
key={value}

View file

@ -65,7 +65,7 @@ export function buildSkillTemplate(input: SkillDraftTemplateInput): string {
export function readSkillTemplateContent(rawContent: string): SkillTemplateParseResult {
const content = rawContent.replace(/^\uFEFF/u, '');
const match = content.match(SKILL_FRONTMATTER_PATTERN);
const match = SKILL_FRONTMATTER_PATTERN.exec(content);
if (!match) {
return {
hasStructuredSections: false,
@ -130,7 +130,7 @@ export function updateSkillTemplateFrontmatter(
input: SkillDraftTemplateInput
): string {
const content = rawContent.replace(/^\uFEFF/u, '');
const match = content.match(SKILL_FRONTMATTER_PATTERN);
const match = SKILL_FRONTMATTER_PATTERN.exec(content);
const body = match ? (match[2] ?? '') : content;
let data: Record<string, unknown> = {};

View file

@ -8,7 +8,7 @@ import { useMemo, useState } from 'react';
import { isElectronMode } from '@renderer/api';
import { useStore } from '@renderer/store';
import { Bell, Puzzle, Settings, Users, PanelRight } from 'lucide-react';
import { Bell, PanelRight, Puzzle, Settings, Users } from 'lucide-react';
import { useShallow } from 'zustand/react/shallow';
import { MoreMenu } from './MoreMenu';

View file

@ -23,10 +23,10 @@ import {
Zap,
} from 'lucide-react';
import { LaunchTeamDialog } from '../team/dialogs/LaunchTeamDialog';
import { ScheduleRunLogDialog } from '../team/schedule/ScheduleRunLogDialog';
import { ScheduleRunRow } from '../team/schedule/ScheduleRunRow';
import { ScheduleStatusBadge } from '../team/schedule/ScheduleStatusBadge';
import { LaunchTeamDialog } from '../team/dialogs/LaunchTeamDialog';
import type { Schedule, ScheduleRun, ScheduleStatus } from '@shared/types';
@ -34,7 +34,7 @@ import type { Schedule, ScheduleRun, ScheduleStatus } from '@shared/types';
// Constants
// =============================================================================
const STATUS_OPTIONS: Array<{ value: ScheduleStatus | 'all'; label: string }> = [
const STATUS_OPTIONS: { value: ScheduleStatus | 'all'; label: string }[] = [
{ value: 'all', label: 'All' },
{ value: 'active', label: 'Active' },
{ value: 'paused', label: 'Paused' },

View file

@ -181,7 +181,7 @@ export const ConfigEditorDialog = ({
keymap.of([...defaultKeymap, ...historyKeymap, ...foldKeymap, ...searchKeymap]),
baseEditorTheme,
configEditorTheme,
// eslint-disable-next-line sonarjs/no-nested-functions -- CodeMirror listener callback within useEffect setup
EditorView.updateListener.of((update) => {
if (update.docChanged) {
const text = update.state.doc.toString();

View file

@ -14,18 +14,18 @@ import {
ArrowRightLeft,
Bell,
BellRing,
CheckCircle2,
CirclePlus,
Clock,
ExternalLink,
EyeOff,
GitBranch,
HelpCircle,
Inbox,
Info,
Mail,
MessageSquare,
PartyPopper,
CirclePlus,
CheckCircle2,
GitBranch,
Send,
Users,
Volume2,
@ -39,7 +39,9 @@ import type { NotificationTrigger } from '@renderer/types/data';
import type { TeamReviewState, TeamTaskStatus } from '@shared/types';
/** Notification targets span workflow status plus the explicit review axis. */
type NotifiableStatus = TeamTaskStatus | Extract<TeamReviewState, 'review' | 'needsFix' | 'approved'>;
type NotifiableStatus =
| TeamTaskStatus
| Extract<TeamReviewState, 'review' | 'needsFix' | 'approved'>;
// Snooze duration options
const SNOOZE_OPTIONS = [

View file

@ -358,7 +358,6 @@ export const GlobalTaskList = ({
// Reset showArchived when archive becomes empty
useEffect(() => {
if (showArchived && !hasArchivedTasks) {
// eslint-disable-next-line react-hooks/set-state-in-effect -- intentional sync on prop change
setShowArchived(false);
}
}, [showArchived, hasArchivedTasks]);

View file

@ -5,7 +5,7 @@ import { getTeamColorSet } from '@renderer/constants/teamColors';
import { useTheme } from '@renderer/hooks/useTheme';
import { useUnreadCommentCount } from '@renderer/hooks/useUnreadCommentCount';
import { useStore } from '@renderer/store';
import { REVIEW_STATE_DISPLAY, buildMemberColorMap } from '@renderer/utils/memberHelpers';
import { buildMemberColorMap, REVIEW_STATE_DISPLAY } from '@renderer/utils/memberHelpers';
import { nameColorSet } from '@renderer/utils/projectColor';
import { projectColor } from '@renderer/utils/projectColor';
import { projectLabelFromPath } from '@renderer/utils/taskGrouping';

View file

@ -4,7 +4,7 @@ import { MarkdownViewer } from '@renderer/components/chat/viewers/MarkdownViewer
import { MemberBadge } from '@renderer/components/team/MemberBadge';
import { Tooltip, TooltipContent, TooltipTrigger } from '@renderer/components/ui/tooltip';
import { useStore } from '@renderer/store';
import { REVIEW_STATE_DISPLAY, buildMemberColorMap } from '@renderer/utils/memberHelpers';
import { buildMemberColorMap, REVIEW_STATE_DISPLAY } from '@renderer/utils/memberHelpers';
import { linkifyTaskIdsInMarkdown } from '@renderer/utils/taskReferenceUtils';
import { getTaskKanbanColumn } from '@shared/utils/reviewState';
import { formatTaskDisplayLabel, taskMatchesRef } from '@shared/utils/taskIdentity';

View file

@ -32,6 +32,7 @@ import {
type TaskChangeRequestOptions,
} from '@renderer/utils/taskChangeRequest';
import { stripAgentBlocks } from '@shared/constants/agentBlocks';
import { isLeadAgentType, isLeadMember } from '@shared/utils/leadDetection';
import { deriveTaskDisplayId, formatTaskDisplayLabel } from '@shared/utils/taskIdentity';
import {
AlertTriangle,
@ -50,11 +51,9 @@ import {
UserPlus,
Users,
} from 'lucide-react';
import { isLeadAgentType, isLeadMember } from '@shared/utils/leadDetection';
import { useShallow } from 'zustand/react/shallow';
import { AddMemberDialog } from './dialogs/AddMemberDialog';
import type { AddMemberEntry } from './dialogs/AddMemberDialog';
import { CreateTaskDialog } from './dialogs/CreateTaskDialog';
import { EditTeamDialog } from './dialogs/EditTeamDialog';
import { LaunchTeamDialog } from './dialogs/LaunchTeamDialog';
@ -67,16 +66,18 @@ import { KanbanSearchInput } from './kanban/KanbanSearchInput';
import { TrashDialog } from './kanban/TrashDialog';
import { MemberDetailDialog } from './members/MemberDetailDialog';
import type { AddMemberEntry } from './dialogs/AddMemberDialog';
const ProjectEditorOverlay = lazy(() =>
import('./editor/ProjectEditorOverlay').then((m) => ({ default: m.ProjectEditorOverlay }))
);
import { MemberList } from './members/MemberList';
import { MessagesPanel } from './messages/MessagesPanel';
import { ChangeReviewDialog } from './review/ChangeReviewDialog';
import { ScheduleSection } from './schedule/ScheduleSection';
import { ClaudeLogsSection } from './ClaudeLogsSection';
import { CollapsibleTeamSection } from './CollapsibleTeamSection';
import { ProcessesSection } from './ProcessesSection';
import { ScheduleSection } from './schedule/ScheduleSection';
import { TeamProvisioningBanner } from './TeamProvisioningBanner';
import { TeamSessionsSection } from './TeamSessionsSection';

View file

@ -99,7 +99,7 @@ export const TeamProvisioningBanner = ({
if (isReady) {
const readyMessage = allTeammatesOnline
? `Team launched — all ${teamMembers!.length} teammates online`
? `Team launched — all ${teamMembers.length} teammates online`
: 'Team launched — teammates may still be starting';
return (

View file

@ -5,10 +5,10 @@ import { useTheme } from '@renderer/hooks/useTheme';
import { useStore } from '@renderer/store';
import { FileText, Search, Terminal } from 'lucide-react';
import type { ToolApprovalRequest } from '@shared/types';
import { ToolApprovalSettingsPanel } from './dialogs/ToolApprovalSettingsPanel';
import type { ToolApprovalRequest } from '@shared/types';
// ---------------------------------------------------------------------------
// Tool icon mapping
// ---------------------------------------------------------------------------

View file

@ -93,7 +93,7 @@ export const ActiveTasksBlock = ({
className="size-5 rounded-full bg-[var(--color-surface-raised)]"
loading="lazy"
/>
<span className="absolute -bottom-0.5 -right-0.5 flex h-2.5 w-2.5">
<span className="absolute -bottom-0.5 -right-0.5 flex size-2.5">
<span
className={`absolute inline-flex size-full animate-ping rounded-full ${dotPing} opacity-70`}
/>

View file

@ -99,13 +99,13 @@ export function getCrossTeamSentMemberName(value: string | undefined): string |
return parseQualifiedRecipient(value)?.memberName ?? null;
}
function CrossTeamTeamBadge({
const CrossTeamTeamBadge = ({
teamName,
onClick,
}: {
teamName: string;
onClick?: (teamName: string) => void;
}): React.JSX.Element {
}): React.JSX.Element => {
if (onClick) {
return (
<button
@ -135,7 +135,7 @@ function CrossTeamTeamBadge({
{teamName}
</span>
);
}
};
interface ActivityItemProps {
message: InboxMessage;

View file

@ -8,9 +8,8 @@ import {
import { toMessageKey } from '@renderer/utils/teamMessageKey';
import { Layers } from 'lucide-react';
import { buildMessageContext, resolveMessageRenderProps } from './activityMessageContext';
import { ActivityItem, isNoiseMessage } from './ActivityItem';
import { buildMessageContext, resolveMessageRenderProps } from './activityMessageContext';
import { AnimatedHeightReveal } from './AnimatedHeightReveal';
import { findNewestMessageIndex, resolveTimelineCollapseState } from './collapseState';
import {

View file

@ -17,9 +17,10 @@ import {
areThoughtMessagesEquivalentForRender,
} from '@renderer/utils/messageRenderEquality';
import { toMessageKey } from '@renderer/utils/teamMessageKey';
import { formatToolSummary, parseToolSummary } from '@shared/utils/toolSummary';
import { extractMarkdownPlainText } from '@shared/utils/markdownTextSearch';
import { formatToolSummary, parseToolSummary } from '@shared/utils/toolSummary';
import { ChevronDown, ChevronRight, ChevronUp, Maximize2 } from 'lucide-react';
import {
AnimatedHeightReveal,
ENTRY_REVEAL_ANIMATION_MS,

View file

@ -11,12 +11,13 @@ import { CARD_ICON_MUTED } from '@renderer/constants/cssVariables';
import { getTeamColorSet } from '@renderer/constants/teamColors';
import { agentAvatarUrl } from '@renderer/utils/memberHelpers';
import { MemberBadge } from '../MemberBadge';
import { ActivityItem } from './ActivityItem';
import { buildMessageContext, resolveMessageRenderProps } from './activityMessageContext';
import { MemberBadge } from '../MemberBadge';
import { ThoughtBodyContent } from './ThoughtBodyContent';
import type { TimelineItem, LeadThoughtGroup } from './LeadThoughtsGroup';
import type { LeadThoughtGroup, TimelineItem } from './LeadThoughtsGroup';
import type { InboxMessage, ResolvedTeamMember } from '@shared/types';
function formatTime(timestamp: string): string {

View file

@ -86,7 +86,7 @@ export const PendingRepliesBlock = ({
className="size-5 rounded-full bg-[var(--color-surface-raised)]"
loading="lazy"
/>
<span className="absolute -bottom-0.5 -right-0.5 flex h-2.5 w-2.5">
<span className="absolute -bottom-0.5 -right-0.5 flex size-2.5">
<span className="absolute inline-flex size-full animate-ping rounded-full bg-emerald-400 opacity-70" />
<span className="relative inline-flex size-full rounded-full bg-emerald-500" />
</span>
@ -151,7 +151,7 @@ export const PendingRepliesBlock = ({
<div className="flex items-center gap-2 px-3 py-2">
<span className="relative inline-flex shrink-0 items-center justify-center rounded-full bg-[var(--color-surface-raised)] p-1">
<Users size={12} style={{ color: colors.border }} />
<span className="absolute -bottom-0.5 -right-0.5 flex h-2.5 w-2.5">
<span className="absolute -bottom-0.5 -right-0.5 flex size-2.5">
<span className="absolute inline-flex size-full animate-ping rounded-full bg-emerald-400 opacity-70" />
<span className="relative inline-flex size-full rounded-full bg-emerald-500" />
</span>

View file

@ -4,12 +4,12 @@ import { MarkdownViewer } from '@renderer/components/chat/viewers/MarkdownViewer
import { CopyButton } from '@renderer/components/common/CopyButton';
import { Tooltip, TooltipContent, TooltipTrigger } from '@renderer/components/ui/tooltip';
import { CARD_ICON_MUTED, CARD_TEXT_LIGHT } from '@renderer/constants/cssVariables';
import { linkifyAllMentionsInMarkdown } from '@renderer/utils/mentionLinkify';
import {
areStringArraysEqual,
areStringMapsEqual,
areThoughtMessagesEquivalentForRender,
} from '@renderer/utils/messageRenderEquality';
import { linkifyAllMentionsInMarkdown } from '@renderer/utils/mentionLinkify';
import { linkifyTaskIdsInMarkdown, parseTaskLinkHref } from '@renderer/utils/taskReferenceUtils';
import { Reply } from 'lucide-react';

View file

@ -1,7 +1,7 @@
import 'yet-another-react-lightbox/styles.css';
import 'yet-another-react-lightbox/plugins/counter.css';
import { createContext, useContext, useEffect, useRef, useMemo } from 'react';
import { createContext, useContext, useEffect, useMemo, useRef } from 'react';
import Lightbox from 'yet-another-react-lightbox';
import Counter from 'yet-another-react-lightbox/plugins/counter';

View file

@ -1,6 +1,5 @@
import { Info } from 'lucide-react';
import { Tooltip, TooltipContent, TooltipTrigger } from '@renderer/components/ui/tooltip';
import { Info } from 'lucide-react';
import { AttachmentDisplay } from './AttachmentDisplay';
@ -27,9 +26,7 @@ export const SourceMessageAttachments = ({
}));
const truncatedText =
sourceMessage.text.length > 300
? sourceMessage.text.slice(0, 297) + '...'
: sourceMessage.text;
sourceMessage.text.length > 300 ? sourceMessage.text.slice(0, 297) + '...' : sourceMessage.text;
const formattedDate = (() => {
try {

View file

@ -1,12 +1,12 @@
import { useCallback, useEffect, useMemo, useState } from 'react';
import { getNextSuggestedMemberName } from '@renderer/components/team/members/memberNameSets';
import {
buildMembersFromDrafts,
createMemberDraft,
MembersEditorSection,
validateMemberNameInline,
} from '@renderer/components/team/members/MembersEditorSection';
import { getNextSuggestedMemberName } from '@renderer/components/team/members/memberNameSets';
import { Button } from '@renderer/components/ui/button';
import {
Dialog,

View file

@ -40,8 +40,8 @@ import { LimitContextCheckbox } from './LimitContextCheckbox';
import { OptionalSettingsSection } from './OptionalSettingsSection';
import { ProjectPathSelector } from './ProjectPathSelector';
import { SkipPermissionsCheckbox } from './SkipPermissionsCheckbox';
import { getNextSuggestedTeamName } from './teamNameSets';
import { computeEffectiveTeamModel, TeamModelSelector } from './TeamModelSelector';
import { getNextSuggestedTeamName } from './teamNameSets';
import type { MemberDraft } from '@renderer/components/team/members/membersEditorTypes';
@ -694,7 +694,7 @@ export const CreateTeamDialog = ({
if (!validation.valid) {
const errors = validation.errors ?? {};
setFieldErrors(errors);
const messages = Object.values(errors).filter(Boolean) as string[];
const messages = Object.values(errors).filter(Boolean);
setLocalError(messages.join(' · ') || 'Check form fields');
return;
}
@ -745,7 +745,7 @@ export const CreateTeamDialog = ({
if (!prev.teamName) return prev;
// eslint-disable-next-line sonarjs/no-unused-vars -- destructured to omit teamName from rest
const { teamName: _teamName, ...rest } = prev;
const remaining = Object.values(rest).filter(Boolean) as string[];
const remaining = Object.values(rest).filter(Boolean);
if (remaining.length === 0) {
setLocalError(null);
} else {

View file

@ -17,12 +17,12 @@ import {
import { Input } from '@renderer/components/ui/input';
import { Label } from '@renderer/components/ui/label';
import { MentionableTextarea } from '@renderer/components/ui/MentionableTextarea';
import { getTeamColorSet } from '@renderer/constants/teamColors';
import { useChipDraftPersistence } from '@renderer/hooks/useChipDraftPersistence';
import { useDraftPersistence } from '@renderer/hooks/useDraftPersistence';
import { useFileListCacheWarmer } from '@renderer/hooks/useFileListCacheWarmer';
import { useTheme } from '@renderer/hooks/useTheme';
import { useStore } from '@renderer/store';
import { getTeamColorSet } from '@renderer/constants/teamColors';
import { formatAgentRole } from '@renderer/utils/formatAgentRole';
import { buildMemberColorMap } from '@renderer/utils/memberHelpers';
import { normalizePath } from '@renderer/utils/pathNormalize';
@ -38,12 +38,13 @@ import {
X,
} from 'lucide-react';
import { CronScheduleInput } from '../schedule/CronScheduleInput';
import { AdvancedCliSection } from './AdvancedCliSection';
import { EffortLevelSelector } from './EffortLevelSelector';
import { OptionalSettingsSection } from './OptionalSettingsSection';
import { ProjectPathSelector } from './ProjectPathSelector';
import { computeEffectiveTeamModel, TeamModelSelector } from './TeamModelSelector';
import { CronScheduleInput } from '../schedule/CronScheduleInput';
import type { ActiveTeamRef } from './CreateTeamDialog';
import type { MentionSuggestion } from '@renderer/types/mention';
@ -112,7 +113,7 @@ export const LaunchTeamDialog = (props: LaunchTeamDialogProps): React.JSX.Elemen
const { isLight } = useTheme();
const isLaunch = props.mode === 'launch';
const isSchedule = props.mode === 'schedule';
const schedule = isSchedule ? ((props as LaunchDialogScheduleMode).schedule ?? null) : null;
const schedule = isSchedule ? (props.schedule ?? null) : null;
const isEditing = isSchedule && !!schedule;
// Team name: always present for launch mode, may be absent in schedule mode (standalone page)
@ -338,7 +339,7 @@ export const LaunchTeamDialog = (props: LaunchTeamDialogProps): React.JSX.Elemen
// Clear stale provisioning error when dialog opens
useEffect(() => {
if (!open || !isLaunch) return;
(props as LaunchDialogLaunchMode).clearProvisioningError?.(effectiveTeamName);
props.clearProvisioningError?.(effectiveTeamName);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [open, isLaunch, effectiveTeamName]);
@ -443,9 +444,7 @@ export const LaunchTeamDialog = (props: LaunchTeamDialogProps): React.JSX.Elemen
}, [open, repositoryGroups]);
// Pre-select defaultProjectPath (launch mode) or first project
const defaultProjectPath = isLaunch
? (props as LaunchDialogLaunchMode).defaultProjectPath
: undefined;
const defaultProjectPath = isLaunch ? props.defaultProjectPath : undefined;
useEffect(() => {
if (!open || cwdMode !== 'project' || selectedProjectPath || projects.length === 0) return;
@ -466,7 +465,7 @@ export const LaunchTeamDialog = (props: LaunchTeamDialogProps): React.JSX.Elemen
// Launch-only: conflict detection
// ---------------------------------------------------------------------------
const activeTeams = isLaunch ? (props as LaunchDialogLaunchMode).activeTeams : undefined;
const activeTeams = isLaunch ? props.activeTeams : undefined;
const conflictingTeam = useMemo(() => {
if (!isLaunch || !activeTeams?.length || !effectiveCwd) return null;
@ -487,7 +486,7 @@ export const LaunchTeamDialog = (props: LaunchTeamDialogProps): React.JSX.Elemen
// ---------------------------------------------------------------------------
const storeMembers = useStore((s) => s.selectedTeamData?.members ?? []);
const members = isLaunch ? (props as LaunchDialogLaunchMode).members : storeMembers;
const members = isLaunch ? props.members : storeMembers;
const colorMap = useMemo(() => buildMemberColorMap(members), [members]);
const mentionSuggestions = useMemo<MentionSuggestion[]>(
@ -564,7 +563,7 @@ export const LaunchTeamDialog = (props: LaunchTeamDialogProps): React.JSX.Elemen
// Error
// ---------------------------------------------------------------------------
const provisioningError = isLaunch ? (props as LaunchDialogLaunchMode).provisioningError : null;
const provisioningError = isLaunch ? props.provisioningError : null;
const activeError = localError ?? provisioningError;
// ---------------------------------------------------------------------------
@ -586,7 +585,7 @@ export const LaunchTeamDialog = (props: LaunchTeamDialogProps): React.JSX.Elemen
void (async () => {
try {
if (isLaunch) {
await (props as LaunchDialogLaunchMode).onLaunch({
await props.onLaunch({
teamName: effectiveTeamName,
cwd: effectiveCwd,
prompt: promptDraft.value.trim() || undefined,

View file

@ -1,7 +1,5 @@
import React from 'react';
import { Info } from 'lucide-react';
import { Checkbox } from '@renderer/components/ui/checkbox';
import { Label } from '@renderer/components/ui/label';
import {
@ -10,6 +8,7 @@ import {
TooltipProvider,
TooltipTrigger,
} from '@renderer/components/ui/tooltip';
import { Info } from 'lucide-react';
interface LimitContextCheckboxProps {
id: string;

View file

@ -65,7 +65,7 @@ export const OptionalSettingsSection = ({
>
<button
type="button"
className="flex w-full items-start justify-between gap-3 px-3 py-3 text-left transition-colors hover:bg-[var(--color-surface-raised)]"
className="flex w-full items-start justify-between gap-3 p-3 text-left transition-colors hover:bg-[var(--color-surface-raised)]"
onClick={() => setIsOpen((prev) => !prev)}
aria-expanded={isOpen}
>

View file

@ -42,7 +42,7 @@ export const WorkflowTimeline = ({ events, memberColorMap }: WorkflowTimelinePro
{/* Content */}
<Tooltip>
<TooltipTrigger asChild>
<div className="flex w-full items-center gap-2 rounded px-1.5 py-1.5 text-xs text-[var(--color-text-secondary)]">
<div className="flex w-full items-center gap-2 rounded p-1.5 text-xs text-[var(--color-text-secondary)]">
<span className="shrink-0 font-mono text-[10px] text-[var(--color-text-muted)]">
{time}
</span>

View file

@ -1,5 +1,8 @@
import { useCallback, useMemo, useRef, useState } from 'react';
import { MarkdownViewer } from '@renderer/components/chat/viewers/MarkdownViewer';
import { ImageLightbox } from '@renderer/components/team/attachments/ImageLightbox';
import { MemberBadge } from '@renderer/components/team/MemberBadge';
import { MentionableTextarea } from '@renderer/components/ui/MentionableTextarea';
import { Tooltip, TooltipContent, TooltipTrigger } from '@renderer/components/ui/tooltip';
import { useChipDraftPersistence } from '@renderer/hooks/useChipDraftPersistence';
@ -7,10 +10,10 @@ import { useDraftPersistence } from '@renderer/hooks/useDraftPersistence';
import { useTaskSuggestions } from '@renderer/hooks/useTaskSuggestions';
import { useTeamSuggestions } from '@renderer/hooks/useTeamSuggestions';
import { useStore } from '@renderer/store';
import { serializeChipsWithText } from '@renderer/types/inlineChip';
import { buildReplyBlock } from '@renderer/utils/agentMessageFormatting';
import { formatAgentRole } from '@renderer/utils/formatAgentRole';
import { buildMemberColorMap } from '@renderer/utils/memberHelpers';
import { serializeChipsWithText } from '@renderer/types/inlineChip';
import {
extractTaskRefsFromText,
stripEncodedTaskReferenceMetadata,
@ -18,10 +21,6 @@ import {
import { MAX_TEXT_LENGTH } from '@shared/constants';
import { ImagePlus, Mic, Send, Trash2, X } from 'lucide-react';
import { MarkdownViewer } from '@renderer/components/chat/viewers/MarkdownViewer';
import { ImageLightbox } from '@renderer/components/team/attachments/ImageLightbox';
import { MemberBadge } from '@renderer/components/team/MemberBadge';
import type { MentionSuggestion } from '@renderer/types/mention';
import type { CommentAttachmentPayload, ResolvedTeamMember } from '@shared/types';

View file

@ -6,12 +6,6 @@ import {
ImageLightbox,
LightboxLockProvider,
} from '@renderer/components/team/attachments/ImageLightbox';
import {
getTeamColorSet,
getThemedBadge,
getThemedBorder,
getThemedText,
} from '@renderer/constants/teamColors';
import { CollapsibleTeamSection } from '@renderer/components/team/CollapsibleTeamSection';
import { FileIcon } from '@renderer/components/team/editor/FileIcon';
import { MemberBadge } from '@renderer/components/team/MemberBadge';
@ -30,19 +24,25 @@ import { Input } from '@renderer/components/ui/input';
import { MemberSelect } from '@renderer/components/ui/MemberSelect';
import { TiptapEditor } from '@renderer/components/ui/tiptap';
import { Tooltip, TooltipContent, TooltipTrigger } from '@renderer/components/ui/tooltip';
import { getLegacyCutoff, getReadCommentIds } from '@renderer/services/commentReadStorage';
import { useStore } from '@renderer/store';
import {
getTeamColorSet,
getThemedBadge,
getThemedBorder,
getThemedText,
} from '@renderer/constants/teamColors';
import { useTheme } from '@renderer/hooks/useTheme';
import { useViewportCommentRead } from '@renderer/hooks/useViewportCommentRead';
import { getLegacyCutoff, getReadCommentIds } from '@renderer/services/commentReadStorage';
import { useStore } from '@renderer/store';
import { isImageMimeType } from '@renderer/utils/attachmentUtils';
import {
agentAvatarUrl,
buildMemberColorMap,
displayMemberName,
KANBAN_COLUMN_DISPLAY,
REVIEW_STATE_DISPLAY,
TASK_STATUS_LABELS,
TASK_STATUS_STYLES,
agentAvatarUrl,
displayMemberName,
} from '@renderer/utils/memberHelpers';
import { buildTaskChangeRequestOptions, deriveTaskSince } from '@renderer/utils/taskChangeRequest';
import { linkifyTaskIdsInMarkdown, parseTaskLinkHref } from '@renderer/utils/taskReferenceUtils';
@ -78,9 +78,10 @@ import {
X,
} from 'lucide-react';
import { SourceMessageAttachments } from '../attachments/SourceMessageAttachments';
import { WorkflowTimeline } from './StatusHistoryTimeline';
import { TaskAttachments } from './TaskAttachments';
import { SourceMessageAttachments } from '../attachments/SourceMessageAttachments';
import { TaskCommentAwaitingReply } from './TaskCommentAwaitingReply';
import { TaskCommentInput } from './TaskCommentInput';
import { TaskCommentsSection } from './TaskCommentsSection';

View file

@ -4,9 +4,8 @@ import { Button } from '@renderer/components/ui/button';
import { Checkbox } from '@renderer/components/ui/checkbox';
import { Popover, PopoverContent, PopoverTrigger } from '@renderer/components/ui/popover';
import { Tooltip, TooltipContent, TooltipTrigger } from '@renderer/components/ui/tooltip';
import { Crown, Filter } from 'lucide-react';
import { displayMemberName } from '@renderer/utils/memberHelpers';
import { Crown, Filter } from 'lucide-react';
import type { Session } from '@renderer/types/data';
import type { KanbanColumnId, ResolvedTeamMember } from '@shared/types';

View file

@ -39,7 +39,7 @@ export const KanbanSearchInput = ({
// Detect `#` trigger and extract filter text after it
const hashMatch = useMemo(() => {
const match = value.match(/#(\S*)$/);
const match = /#(\S*)$/.exec(value);
return match ? match[1] : null;
}, [value]);

View file

@ -7,7 +7,7 @@ import { Popover, PopoverContent, PopoverTrigger } from '@renderer/components/ui
import { Tooltip, TooltipContent, TooltipTrigger } from '@renderer/components/ui/tooltip';
import { useUnreadCommentCount } from '@renderer/hooks/useUnreadCommentCount';
import { useStore } from '@renderer/store';
import { REVIEW_STATE_DISPLAY, buildMemberColorMap } from '@renderer/utils/memberHelpers';
import { buildMemberColorMap, REVIEW_STATE_DISPLAY } from '@renderer/utils/memberHelpers';
import {
buildTaskChangePresenceKey,
buildTaskChangeRequestOptions,

View file

@ -197,7 +197,7 @@ export const MemberDraftRow = ({
<Button
variant="outline"
size="sm"
className="h-8 w-8 shrink-0 border-red-500/40 px-0 text-red-300 hover:bg-red-500/10 hover:text-red-200"
className="size-8 shrink-0 border-red-500/40 px-0 text-red-300 hover:bg-red-500/10 hover:text-red-200"
aria-label={`Remove ${member.name || `member ${index + 1}`}`}
title="Remove member"
onClick={() => onRemove(member.id)}

View file

@ -7,12 +7,12 @@ import {
SubagentRecentMessagesPreview,
} from '@renderer/components/team/members/SubagentRecentMessagesPreview';
import { Tooltip, TooltipContent, TooltipTrigger } from '@renderer/components/ui/tooltip';
import { getTeamColorSet } from '@renderer/constants/teamColors';
import { asEnhancedChunkArray } from '@renderer/types/data';
import { enhanceAIGroup } from '@renderer/utils/aiGroupEnhancer';
import { formatDuration } from '@renderer/utils/formatters';
import { transformChunksToConversation } from '@renderer/utils/groupTransformer';
import { getMemberColorByName } from '@shared/constants/memberColors';
import { getTeamColorSet } from '@renderer/constants/teamColors';
import {
AlertCircle,
ChevronDown,
@ -271,7 +271,12 @@ export const MemberLogsTab = ({
if (!previewLog) return [];
// Use task-scoped recentPreviews when available
if (previewLog.recentPreviews && previewLog.recentPreviews.length > 0 && taskWorkIntervals && taskWorkIntervals.length > 0) {
if (
previewLog.recentPreviews &&
previewLog.recentPreviews.length > 0 &&
taskWorkIntervals &&
taskWorkIntervals.length > 0
) {
const GRACE_BEFORE = 30_000;
const GRACE_AFTER = 15_000;
const now = Date.now();
@ -280,7 +285,10 @@ export const MemberLogsTab = ({
const s = Date.parse(i.startedAt);
if (!Number.isFinite(s)) return null;
const e = typeof i.completedAt === 'string' ? Date.parse(i.completedAt) : null;
return { startMs: s - GRACE_BEFORE, endMs: e != null && Number.isFinite(e) ? e + GRACE_AFTER : now + GRACE_AFTER };
return {
startMs: s - GRACE_BEFORE,
endMs: e != null && Number.isFinite(e) ? e + GRACE_AFTER : now + GRACE_AFTER,
};
})
.filter((v): v is { startMs: number; endMs: number } => v !== null);
@ -291,9 +299,7 @@ export const MemberLogsTab = ({
return intervals.some((i) => ms >= i.startMs && ms <= i.endMs);
});
if (scoped.length > 0) {
return scoped
.reverse()
.map((p, idx) => ({
return scoped.reverse().map((p, idx) => ({
id: `${previewLog.sessionId}:recent:${idx}`,
timestamp: new Date(p.timestamp),
kind: 'output' as const,

View file

@ -1,6 +1,6 @@
import { CUSTOM_ROLE, NO_ROLE } from '@renderer/constants/teamRoles';
import { buildMemberColorMap } from '@renderer/utils/memberHelpers';
import { serializeChipsWithText } from '@renderer/types/inlineChip';
import { buildMemberColorMap } from '@renderer/utils/memberHelpers';
import type { MemberDraft } from './membersEditorTypes';
import type { MentionSuggestion } from '@renderer/types/mention';
@ -37,7 +37,7 @@ export function createMemberDraft(initial?: Partial<MemberDraft>): MemberDraft {
}
export function buildMemberDraftColorMap(
members: ReadonlyArray<Pick<MemberDraft, 'name'>>
members: readonly Pick<MemberDraft, 'name'>[]
): Map<string, string> {
return buildMemberColorMap(
members

View file

@ -1,10 +1,11 @@
import { cn } from '@renderer/lib/utils';
import {
Tooltip,
TooltipContent,
TooltipProvider,
TooltipTrigger,
} from '@renderer/components/ui/tooltip';
import { cn } from '@renderer/lib/utils';
import type { AgentActionMode } from '@shared/types';
export type ActionMode = AgentActionMode;

View file

@ -3,11 +3,12 @@ import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { api } from '@renderer/api';
import { AttachmentPreviewList } from '@renderer/components/team/attachments/AttachmentPreviewList';
import { DropZoneOverlay } from '@renderer/components/team/attachments/DropZoneOverlay';
import { ActionModeSelector } from '@renderer/components/team/messages/ActionModeSelector';
import { MemberBadge } from '@renderer/components/team/MemberBadge';
import { ActionModeSelector } from '@renderer/components/team/messages/ActionModeSelector';
import { MentionableTextarea } from '@renderer/components/ui/MentionableTextarea';
import { Popover, PopoverContent, PopoverTrigger } from '@renderer/components/ui/popover';
import { Tooltip, TooltipContent, TooltipTrigger } from '@renderer/components/ui/tooltip';
import { getTeamColorSet } from '@renderer/constants/teamColors';
import { useComposerDraft } from '@renderer/hooks/useComposerDraft';
import { useTaskSuggestions } from '@renderer/hooks/useTaskSuggestions';
import { useTeamSuggestions } from '@renderer/hooks/useTeamSuggestions';
@ -17,7 +18,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 { getTeamColorSet } from '@renderer/constants/teamColors';
import { nameColorSet } from '@renderer/utils/projectColor';
import {
extractTaskRefsFromText,
@ -27,8 +27,8 @@ import { MAX_TEXT_LENGTH } from '@shared/constants';
import { isLeadMember } from '@shared/utils/leadDetection';
import { AlertCircle, Check, ChevronDown, ImagePlus, Mic, Search, Send } from 'lucide-react';
import type { MentionSuggestion } from '@renderer/types/mention';
import type { ActionMode } from '@renderer/components/team/messages/ActionModeSelector';
import type { MentionSuggestion } from '@renderer/types/mention';
import type {
AttachmentPayload,
ResolvedTeamMember,
@ -84,7 +84,6 @@ export const MessageComposer = ({
(externalTextareaRef as React.MutableRefObject<HTMLTextAreaElement | null>).current = node;
}
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [externalTextareaRef]);
const [recipient, setRecipient] = useState<string>(() => {
const lead = members.find((m) => isLeadMember(m));

View file

@ -3,9 +3,9 @@ import { memo, useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { Badge } from '@renderer/components/ui/badge';
import { Button } from '@renderer/components/ui/button';
import { Tooltip, TooltipContent, TooltipTrigger } from '@renderer/components/ui/tooltip';
import { useStableTeamMentionMeta } from '@renderer/hooks/useStableTeamMentionMeta';
import { useTeamMessagesExpanded } from '@renderer/hooks/useTeamMessagesExpanded';
import { useTeamMessagesRead } from '@renderer/hooks/useTeamMessagesRead';
import { useStableTeamMentionMeta } from '@renderer/hooks/useStableTeamMentionMeta';
import { useStore } from '@renderer/store';
import { filterTeamMessages } from '@renderer/utils/teamMessageFiltering';
import { toMessageKey } from '@renderer/utils/teamMessageKey';
@ -16,8 +16,8 @@ import {
ChevronsDownUp,
ChevronsUpDown,
MessageSquare,
PanelLeftClose,
PanelLeft,
PanelLeftClose,
Search,
X,
} from 'lucide-react';
@ -26,13 +26,14 @@ import { ActivityTimeline } from '../activity/ActivityTimeline';
import { getThoughtGroupKey, groupTimelineItems } from '../activity/LeadThoughtsGroup';
import { MessageExpandDialog } from '../activity/MessageExpandDialog';
import { CollapsibleTeamSection } from '../CollapsibleTeamSection';
import { MessageComposer } from './MessageComposer';
import { MessagesFilterPopover } from './MessagesFilterPopover';
import { StatusBlock } from './StatusBlock';
import type { MessagesFilterState } from './MessagesFilterPopover';
import type { ActionMode } from './ActionModeSelector';
import type { TimelineItem } from '../activity/LeadThoughtsGroup';
import type { ActionMode } from './ActionModeSelector';
import type { MessagesFilterState } from './MessagesFilterPopover';
import type { InboxMessage, ResolvedTeamMember, TaskRef, TeamTaskWithKanban } from '@shared/types';
interface TimeWindow {
@ -631,7 +632,7 @@ export const MessagesPanel = memo(function MessagesPanel({
</>
}
defaultOpen
action={<div className="flex items-center gap-2 pl-2 pr-2">{searchAndFilterBar}</div>}
action={<div className="flex items-center gap-2 px-2">{searchAndFilterBar}</div>}
>
{messagesContent}
</CollapsibleTeamSection>

View file

@ -1,4 +1,5 @@
import { useCallback, useEffect, useRef, useState } from 'react';
import { Check, FileCode, FileDiff, FileText, GitBranch, GitCommit, Search } from 'lucide-react';
/* ── Fake diff lines for the mini-terminal ─────────────────────────── */
@ -156,10 +157,7 @@ export const ChangesLoadingAnimation = (): React.JSX.Element => {
</svg>
{/* Rotating dashed orbit ring */}
<svg
className="clda-orbit-ring pointer-events-none absolute h-40 w-40"
viewBox="0 0 160 160"
>
<svg className="clda-orbit-ring pointer-events-none absolute size-40" viewBox="0 0 160 160">
<circle
cx="80"
cy="80"

View file

@ -32,7 +32,7 @@ export const FullDiffLoadingBanner = ({
return (
<div className="bg-surface/95 border-b border-border px-4 py-3">
<div className="bg-surface-raised/80 rounded-xl border border-border shadow-sm">
<div className="flex items-start gap-3 px-3 py-3">
<div className="flex items-start gap-3 p-3">
<div className="relative mt-0.5 flex size-9 shrink-0 items-center justify-center rounded-xl border border-border bg-surface-sidebar">
<div className="absolute inset-1 rounded-lg bg-emerald-500/10 blur-sm" />
<LoaderCircle

View file

@ -13,6 +13,7 @@ import { useStore } from '@renderer/store';
import { AlertTriangle, Clock, Loader2, Terminal } from 'lucide-react';
import { CliLogsRichView } from '../CliLogsRichView';
import { RunStatusBadge } from './ScheduleStatusBadge';
import type { ScheduleRun } from '@shared/types';

View file

@ -18,6 +18,7 @@ import {
} from 'lucide-react';
import { LaunchTeamDialog } from '../dialogs/LaunchTeamDialog';
import { ScheduleEmptyState } from './ScheduleEmptyState';
import { ScheduleRunLogDialog } from './ScheduleRunLogDialog';
import { ScheduleRunRow } from './ScheduleRunRow';
@ -241,7 +242,6 @@ export const ScheduleSection = ({ teamName }: ScheduleSectionProps): React.JSX.E
try {
await deleteSchedule(id);
} catch (err) {
// eslint-disable-next-line no-console
console.error('Failed to delete schedule:', err);
}
},
@ -253,7 +253,6 @@ export const ScheduleSection = ({ teamName }: ScheduleSectionProps): React.JSX.E
try {
await triggerNow(id);
} catch (err) {
// eslint-disable-next-line no-console
console.error('Failed to trigger schedule:', err);
}
},

Some files were not shown because too many files have changed in this diff Show more