@yancyyu/openhermit 1.6.28 → 1.6.30
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist-renderer/assets/ProjectEditorOverlay-DsQt4FHy.js +52 -0
- package/dist-renderer/assets/{TeamGraphOverlay-Ba5njic5.js → TeamGraphOverlay-BjZC53xf.js} +1 -1
- package/dist-renderer/assets/{_basePickBy-BvnK-OC1.js → _basePickBy-CrWocIjq.js} +1 -1
- package/dist-renderer/assets/{_baseUniq-DmFYXx9G.js → _baseUniq-B6d8ysWi.js} +1 -1
- package/dist-renderer/assets/{arc-DX4ZQFY4.js → arc-DAIYCFP8.js} +1 -1
- package/dist-renderer/assets/{architectureDiagram-VXUJARFQ-DfYr3vEN.js → architectureDiagram-VXUJARFQ-B3UudXJh.js} +1 -1
- package/dist-renderer/assets/{blockDiagram-VD42YOAC-DuXdVeWn.js → blockDiagram-VD42YOAC-DbptKQ4W.js} +1 -1
- package/dist-renderer/assets/{c4Diagram-YG6GDRKO-Bw2nixXe.js → c4Diagram-YG6GDRKO-C4WQuZpV.js} +1 -1
- package/dist-renderer/assets/channel-DbjZvWii.js +1 -0
- package/dist-renderer/assets/{chunk-4BX2VUAB-DLiNGQoE.js → chunk-4BX2VUAB-Dp7fVpI_.js} +1 -1
- package/dist-renderer/assets/{chunk-55IACEB6-B1L_8VIF.js → chunk-55IACEB6-B8KGfbAy.js} +1 -1
- package/dist-renderer/assets/{chunk-B4BG7PRW-DaZMWKGk.js → chunk-B4BG7PRW-BG1oJrjA.js} +1 -1
- package/dist-renderer/assets/{chunk-DI55MBZ5-ku-dflJG.js → chunk-DI55MBZ5-DRmxNjht.js} +1 -1
- package/dist-renderer/assets/{chunk-FMBD7UC4-DV-mF1dP.js → chunk-FMBD7UC4-D6VLvy16.js} +1 -1
- package/dist-renderer/assets/{chunk-QN33PNHL-ByGcDFQ0.js → chunk-QN33PNHL-DZou1667.js} +1 -1
- package/dist-renderer/assets/{chunk-QZHKN3VN-7dv-Min8.js → chunk-QZHKN3VN-CghmasSh.js} +1 -1
- package/dist-renderer/assets/{chunk-TZMSLE5B-WdXL5fTu.js → chunk-TZMSLE5B-B7apcMPK.js} +1 -1
- package/dist-renderer/assets/classDiagram-2ON5EDUG-D_FGxxsl.js +1 -0
- package/dist-renderer/assets/classDiagram-v2-WZHVMYZB-D_FGxxsl.js +1 -0
- package/dist-renderer/assets/clone-CJ1kxO2J.js +1 -0
- package/dist-renderer/assets/{cose-bilkent-S5V4N54A-CNcsvqPl.js → cose-bilkent-S5V4N54A-05e5uQDp.js} +1 -1
- package/dist-renderer/assets/{dagre-6UL2VRFP-DBNx4qqx.js → dagre-6UL2VRFP-B06bRykF.js} +1 -1
- package/dist-renderer/assets/{diagram-PSM6KHXK-BfVlT6sT.js → diagram-PSM6KHXK-CY7VYQ7c.js} +1 -1
- package/dist-renderer/assets/{diagram-QEK2KX5R-HvVjs0K6.js → diagram-QEK2KX5R-BjKEH7dD.js} +1 -1
- package/dist-renderer/assets/{diagram-S2PKOQOG-DYb_KnWS.js → diagram-S2PKOQOG-Bf4ELS1_.js} +1 -1
- package/dist-renderer/assets/{erDiagram-Q2GNP2WA-Ba-IgI5G.js → erDiagram-Q2GNP2WA-DJ753_L9.js} +1 -1
- package/dist-renderer/assets/{flowDiagram-NV44I4VS-2iDN8Kpj.js → flowDiagram-NV44I4VS-B71S-lC-.js} +1 -1
- package/dist-renderer/assets/{ganttDiagram-JELNMOA3-Byjf8Fa3.js → ganttDiagram-JELNMOA3-C_U42mSZ.js} +1 -1
- package/dist-renderer/assets/{gitGraphDiagram-V2S2FVAM-DbKvfZ_j.js → gitGraphDiagram-V2S2FVAM-DKUJU4Ns.js} +1 -1
- package/dist-renderer/assets/{graph-Enirf-f8.js → graph-DY3qbzqj.js} +1 -1
- package/dist-renderer/assets/{index-DY1zqsb6.js → index-BlOrAXp3.js} +551 -537
- package/dist-renderer/assets/{index-AjxP_rE_.js → index-Bs27J5gB.js} +1 -1
- package/dist-renderer/assets/{index-CtlzGepK.js → index-C8B_nKOF.js} +1 -1
- package/dist-renderer/assets/index-CmZPUEhS.css +1 -0
- package/dist-renderer/assets/{index-COZPUWJW.js → index-DLKyDr4T.js} +1 -1
- package/dist-renderer/assets/{index-DdhqolqE.js → index-Dhsk3_DD.js} +1 -1
- package/dist-renderer/assets/{index-ChR1D6ZF.js → index-GpUvV2xs.js} +1 -1
- package/dist-renderer/assets/{infoDiagram-HS3SLOUP-D6uicwz1.js → infoDiagram-HS3SLOUP-BNs0y3IG.js} +1 -1
- package/dist-renderer/assets/{journeyDiagram-XKPGCS4Q-DqwZsXlQ.js → journeyDiagram-XKPGCS4Q-CqPnw4UV.js} +1 -1
- package/dist-renderer/assets/{kanban-definition-3W4ZIXB7-fCDVhVUm.js → kanban-definition-3W4ZIXB7-SLlzcUJ2.js} +1 -1
- package/dist-renderer/assets/{layout-CPFgj98r.js → layout-BZLlNmbr.js} +1 -1
- package/dist-renderer/assets/{linear-CYiQ7Y3M.js → linear-qz6v45xy.js} +1 -1
- package/dist-renderer/assets/{mindmap-definition-VGOIOE7T-D31dS2KE.js → mindmap-definition-VGOIOE7T-B1-kmEWV.js} +1 -1
- package/dist-renderer/assets/{pieDiagram-ADFJNKIX-BOsCJfds.js → pieDiagram-ADFJNKIX-B8a02iNx.js} +1 -1
- package/dist-renderer/assets/{quadrantDiagram-AYHSOK5B-CYTVQCfr.js → quadrantDiagram-AYHSOK5B-BKv1Xfou.js} +1 -1
- package/dist-renderer/assets/{requirementDiagram-UZGBJVZJ-CODCFpkt.js → requirementDiagram-UZGBJVZJ-B3DUpZi2.js} +1 -1
- package/dist-renderer/assets/{sankeyDiagram-TZEHDZUN-Z4ce9ZtZ.js → sankeyDiagram-TZEHDZUN-DmPzuTsy.js} +1 -1
- package/dist-renderer/assets/{sequenceDiagram-WL72ISMW-CmS9TxhW.js → sequenceDiagram-WL72ISMW-Bo7RelRb.js} +1 -1
- package/dist-renderer/assets/{stateDiagram-FKZM4ZOC-o9k-ns3q.js → stateDiagram-FKZM4ZOC-1epX98gV.js} +1 -1
- package/dist-renderer/assets/{stateDiagram-v2-4FDKWEC3-CxHMyEt1.js → stateDiagram-v2-4FDKWEC3-03Ym9PTr.js} +1 -1
- package/dist-renderer/assets/{timeline-definition-IT6M3QCI-B6T3zrde.js → timeline-definition-IT6M3QCI-r6isC62H.js} +1 -1
- package/dist-renderer/assets/{treemap-GDKQZRPO-CVd5GNDw.js → treemap-GDKQZRPO-CGKpOUF2.js} +1 -1
- package/dist-renderer/assets/{xychartDiagram-PRI3JC2R-CleBrdqc.js → xychartDiagram-PRI3JC2R-t4-rwdAw.js} +1 -1
- package/dist-renderer/index.html +2 -2
- package/package.json +4 -1
- package/src/main/ipc/extensions.ts +353 -0
- package/src/main/server.ts +907 -184
- package/src/main/services/extensions/ExtensionFacadeService.ts +135 -0
- package/src/main/services/extensions/catalog/GlamaMcpEnrichmentService.ts +190 -0
- package/src/main/services/extensions/catalog/McpCatalogAggregator.ts +150 -0
- package/src/main/services/extensions/catalog/OfficialMcpRegistryService.ts +381 -0
- package/src/main/services/extensions/catalog/PluginCatalogService.ts +392 -0
- package/src/main/services/extensions/credentials/CredentialService.ts +343 -0
- package/src/main/services/extensions/install/McpInstallService.ts +407 -0
- package/src/main/services/extensions/install/PluginInstallService.ts +198 -0
- package/src/main/services/extensions/runtime/ClaudeCodeAdapter.ts +199 -0
- package/src/main/services/extensions/runtime/CodexAdapter.ts +100 -0
- package/src/main/services/extensions/runtime/CursorAdapter.ts +154 -0
- package/src/main/services/extensions/runtime/ExtensionsRuntimeAdapter.ts +172 -0
- package/src/main/services/extensions/runtime/GeminiAdapter.ts +91 -0
- package/src/main/services/extensions/runtime/HarnessInstallAdapter.ts +49 -0
- package/src/main/services/extensions/runtime/McpConfigStateReader.ts +209 -0
- package/src/main/services/extensions/runtime/OpenCodeAdapter.ts +91 -0
- package/src/main/services/extensions/runtime/adapterRegistry.ts +54 -0
- package/src/main/services/extensions/runtime/mcpDiagnosticsParser.ts +214 -0
- package/src/main/services/extensions/runtime/mcpRuntimeJson.ts +45 -0
- package/src/main/services/extensions/skills/SkillImportService.ts +155 -0
- package/src/main/services/extensions/skills/SkillMetadataParser.ts +323 -0
- package/src/main/services/extensions/skills/SkillPlanService.ts +411 -0
- package/src/main/services/extensions/skills/SkillReviewService.ts +73 -0
- package/src/main/services/extensions/skills/SkillRootsResolver.ts +49 -0
- package/src/main/services/extensions/skills/SkillScaffoldService.ts +89 -0
- package/src/main/services/extensions/skills/SkillScanner.ts +117 -0
- package/src/main/services/extensions/skills/SkillValidator.ts +69 -0
- package/src/main/services/extensions/skills/SkillsCatalogService.ts +92 -0
- package/src/main/services/extensions/skills/SkillsMutationService.ts +146 -0
- package/src/main/services/extensions/skills/SkillsWatcherService.ts +134 -0
- package/src/main/services/extensions/state/McpInstallationStateService.ts +42 -0
- package/src/main/services/extensions/state/PluginInstallationStateService.ts +281 -0
- package/src/main/services/identity/AgentTeamsIdentityStore.ts +218 -0
- package/src/main/services/runtime/providerAwareCliEnv.ts +60 -0
- package/src/main/services/session-intelligence/UsageTelemetryService.ts +33 -18
- package/src/main/services/team/ClaudeBinaryResolver.ts +469 -0
- package/src/main/services/team/ClaudeDoctorProbe.ts +0 -0
- package/src/main/services/team/cliFlavor.ts +54 -0
- package/src/main/services/teams-mvp/CollaborationBoardService.ts +310 -0
- package/src/main/services/teams-mvp/TaskDispatchService.ts +883 -95
- package/src/main/services/teams-mvp/TeamProvisioningService.ts +58 -19
- package/src/main/services/teams-mvp/TeamWorkspaceService.ts +25 -2
- package/src/main/services/teams-mvp/index.ts +3 -0
- package/src/main/utils/atomicWrite.ts +72 -0
- package/src/main/utils/childProcess.ts +554 -0
- package/src/main/utils/cliEnv.ts +54 -0
- package/src/main/utils/cliPathMerge.ts +97 -0
- package/src/main/utils/pathDecoder.ts +664 -0
- package/src/main/utils/pathValidation.ts +432 -0
- package/src/main/utils/shellEnv.ts +331 -0
- package/src/renderer/App.tsx +5 -0
- package/src/renderer/api/httpClient.ts +128 -0
- package/src/renderer/components/extensions/ExtensionStoreView.tsx +59 -34
- package/src/renderer/components/extensions/ExtensionsSubTabTrigger.tsx +1 -1
- package/src/renderer/components/extensions/common/ExtensionToast.tsx +141 -0
- package/src/renderer/components/extensions/common/HarnessSelector.tsx +71 -0
- package/src/renderer/components/extensions/env/EnvVarPanel.tsx +335 -0
- package/src/renderer/components/extensions/env/ProjectEnvPanel.tsx +239 -0
- package/src/renderer/components/extensions/mcp/CustomMcpServerDialog.tsx +14 -223
- package/src/renderer/components/extensions/mcp/McpServerDetailDialog.tsx +11 -0
- package/src/renderer/components/extensions/mcp/McpServersPanel.tsx +51 -1
- package/src/renderer/components/extensions/skills/SkillsPanel.tsx +1 -126
- package/src/renderer/components/layout/PaneContent.tsx +2 -0
- package/src/renderer/components/layout/SortableTab.tsx +1 -0
- package/src/renderer/components/layout/TabBarActions.tsx +12 -12
- package/src/renderer/components/schedules/SchedulesView.tsx +54 -22
- package/src/renderer/components/settings/sections/AdvancedSection.tsx +1 -1
- package/src/renderer/components/settings/sections/HarnessSection.tsx +2 -6
- package/src/renderer/components/settings/sections/TaskBusSection.tsx +144 -84
- package/src/renderer/components/sidebar/SidebarSessions.tsx +23 -0
- package/src/renderer/components/sidebar/WorkspaceBrowser.tsx +1 -7
- package/src/renderer/components/tasks/TasksView.tsx +343 -0
- package/src/renderer/components/team/HarnessSelect.tsx +71 -0
- package/src/renderer/components/team/TeamDetailView.tsx +55 -98
- package/src/renderer/components/team/dialogs/CreateTeamDialog.tsx +21 -12
- package/src/renderer/components/team/dialogs/EditTeamDialog.tsx +8 -13
- package/src/renderer/components/team/dialogs/LaunchTeamDialog.tsx +1 -1
- package/src/renderer/components/team/editor/EditorContextMenu.tsx +8 -23
- package/src/renderer/components/team/editor/EditorFileTree.tsx +0 -4
- package/src/renderer/components/team/editor/EditorSelectionMenu.tsx +1 -8
- package/src/renderer/components/team/editor/ProjectEditorOverlay.tsx +0 -10
- package/src/renderer/components/team/kanban/KanbanBoard.tsx +31 -65
- package/src/renderer/components/team/members/MemberDetailDialog.tsx +8 -33
- package/src/renderer/components/team/messages/MessageComposer.tsx +39 -3
- package/src/renderer/components/team/messages/MessagesPanel.tsx +100 -26
- package/src/renderer/components/team/messages/StatusBlock.tsx +2 -24
- package/src/renderer/components/team/schedule/ScheduleEmptyState.tsx +1 -1
- package/src/renderer/components/terminal/TerminalPanel.tsx +156 -0
- package/src/renderer/components/ui/MentionableTextarea.tsx +0 -1
- package/src/renderer/hooks/useExtensionsTabState.ts +2 -2
- package/src/renderer/store/slices/extensionsSlice.ts +42 -107
- package/src/renderer/store/slices/scheduleSlice.ts +21 -0
- package/src/renderer/store/slices/teamSlice.ts +67 -25
- package/src/renderer/types/tabs.ts +1 -0
- package/src/shared/types/api.ts +58 -0
- package/src/shared/types/extensions/index.ts +1 -0
- package/src/shared/types/extensions/mcp.ts +2 -0
- package/src/shared/types/extensions/plugin.ts +2 -1
- package/src/shared/types/extensions/skill.ts +7 -0
- package/src/shared/types/team.ts +104 -1
- package/src/shared/utils/providerExtensionCapabilities.ts +1 -1
- package/dist-renderer/assets/ProjectEditorOverlay-A4DZTvSy.js +0 -57
- package/dist-renderer/assets/channel-Pre42N5O.js +0 -1
- package/dist-renderer/assets/classDiagram-2ON5EDUG-CdJsTJsj.js +0 -1
- package/dist-renderer/assets/classDiagram-v2-WZHVMYZB-CdJsTJsj.js +0 -1
- package/dist-renderer/assets/clone-BjQBiNfj.js +0 -1
- package/dist-renderer/assets/index-BIOJremZ.css +0 -1
- package/src/features/recent-projects/main/adapters/input/http/registerRecentProjectsHttp.ts +0 -30
- package/src/features/recent-projects/main/adapters/output/presenters/DashboardRecentProjectsPresenter.ts +0 -27
- package/src/features/recent-projects/main/adapters/output/sources/ClaudeRecentProjectsSourceAdapter.ts +0 -91
- package/src/features/recent-projects/main/adapters/output/sources/CodexRecentProjectsSourceAdapter.ts +0 -326
- package/src/features/recent-projects/main/composition/createRecentProjectsFeature.ts +0 -43
- package/src/features/recent-projects/main/index.ts +0 -3
- package/src/features/recent-projects/main/infrastructure/cache/InMemoryRecentProjectsCache.ts +0 -34
- package/src/features/recent-projects/main/infrastructure/codex/CodexAppServerClient.ts +0 -116
- package/src/features/recent-projects/main/infrastructure/identity/RecentProjectIdentityResolver.ts +0 -20
- package/src/features/recent-projects/main/infrastructure/identity/normalizeIdentityPath.ts +0 -10
- package/src/renderer/components/extensions/apikeys/ApiKeyCard.tsx +0 -143
- package/src/renderer/components/extensions/apikeys/ApiKeyFormDialog.tsx +0 -282
- package/src/renderer/components/extensions/apikeys/ApiKeysPanel.tsx +0 -280
|
@@ -1,326 +0,0 @@
|
|
|
1
|
-
import { normalizeIdentityPath } from '@features/recent-projects/main/infrastructure/identity/normalizeIdentityPath';
|
|
2
|
-
import { isEphemeralProjectPath } from '@shared/utils/ephemeralProjectPath';
|
|
3
|
-
import path from 'path';
|
|
4
|
-
|
|
5
|
-
import type { LoggerPort } from '@features/recent-projects/core/application/ports/LoggerPort';
|
|
6
|
-
import type {
|
|
7
|
-
RecentProjectsSourcePort,
|
|
8
|
-
RecentProjectsSourceResult,
|
|
9
|
-
} from '@features/recent-projects/core/application/ports/RecentProjectsSourcePort';
|
|
10
|
-
import type { RecentProjectCandidate } from '@features/recent-projects/core/domain/models/RecentProjectCandidate';
|
|
11
|
-
import type {
|
|
12
|
-
CodexAppServerClient,
|
|
13
|
-
CodexRecentThreadsResult,
|
|
14
|
-
CodexThreadSummary,
|
|
15
|
-
} from '@features/recent-projects/main/infrastructure/codex/CodexAppServerClient';
|
|
16
|
-
import type { RecentProjectIdentityResolver } from '@features/recent-projects/main/infrastructure/identity/RecentProjectIdentityResolver';
|
|
17
|
-
import type { ServiceContext } from '@main/services';
|
|
18
|
-
|
|
19
|
-
const CODEX_THREAD_LIMIT = 20;
|
|
20
|
-
const CODEX_INITIALIZE_TIMEOUT_MS = 6_000;
|
|
21
|
-
const CODEX_LIVE_FETCH_TIMEOUT_MS = 18_000;
|
|
22
|
-
const CODEX_ARCHIVED_FETCH_TIMEOUT_MS = 6_000;
|
|
23
|
-
const CODEX_SESSION_OVERHEAD_TIMEOUT_MS = 1_500;
|
|
24
|
-
const CODEX_TOTAL_FETCH_TIMEOUT_MS =
|
|
25
|
-
CODEX_INITIALIZE_TIMEOUT_MS +
|
|
26
|
-
CODEX_ARCHIVED_FETCH_TIMEOUT_MS +
|
|
27
|
-
CODEX_LIVE_FETCH_TIMEOUT_MS +
|
|
28
|
-
CODEX_SESSION_OVERHEAD_TIMEOUT_MS;
|
|
29
|
-
const CODEX_SOURCE_TIMEOUT_MS = CODEX_TOTAL_FETCH_TIMEOUT_MS + 500;
|
|
30
|
-
const CODEX_LIVE_ONLY_FALLBACK_TOTAL_TIMEOUT_MS =
|
|
31
|
-
CODEX_INITIALIZE_TIMEOUT_MS + CODEX_LIVE_FETCH_TIMEOUT_MS + CODEX_SESSION_OVERHEAD_TIMEOUT_MS;
|
|
32
|
-
const CODEX_STALE_CANDIDATES_TTL_MS = 5 * 60_000;
|
|
33
|
-
const CODEX_FULL_FAILURE_COOLDOWN_MS = 30_000;
|
|
34
|
-
|
|
35
|
-
interface StaleCodexCandidatesSnapshot {
|
|
36
|
-
candidates: RecentProjectCandidate[];
|
|
37
|
-
capturedAt: number;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
function isInteractiveSource(source: unknown): boolean {
|
|
41
|
-
return source === 'vscode' || source === 'cli';
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
function normalizeTimestamp(value: number | undefined): number {
|
|
45
|
-
if (!value) {
|
|
46
|
-
return 0;
|
|
47
|
-
}
|
|
48
|
-
return value < 1_000_000_000_000 ? value * 1000 : value;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
function isDegradedThreadResult(result: CodexRecentThreadsResult): boolean {
|
|
52
|
-
return Boolean(result.live.error || result.archived.error);
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
function getFullFailureReason(result: CodexRecentThreadsResult): string | null {
|
|
56
|
-
if (!result.live.error || !result.archived.error) {
|
|
57
|
-
return null;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
if (result.live.error === result.archived.error) {
|
|
61
|
-
return result.live.error;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
if (result.archived.skipped) {
|
|
65
|
-
return result.live.error;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
return `live: ${result.live.error}; archived: ${result.archived.error}`;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
export class CodexRecentProjectsSourceAdapter implements RecentProjectsSourcePort {
|
|
72
|
-
readonly sourceId = 'codex';
|
|
73
|
-
readonly timeoutMs = CODEX_SOURCE_TIMEOUT_MS;
|
|
74
|
-
#staleCandidatesSnapshot: StaleCodexCandidatesSnapshot | null = null;
|
|
75
|
-
#fullFailureCooldownUntil = 0;
|
|
76
|
-
#fullFailureCooldownReason: string | null = null;
|
|
77
|
-
|
|
78
|
-
constructor(
|
|
79
|
-
private readonly deps: {
|
|
80
|
-
getActiveContext: () => ServiceContext;
|
|
81
|
-
getLocalContext: () => ServiceContext | undefined;
|
|
82
|
-
resolveBinary: () => Promise<string | null>;
|
|
83
|
-
appServerClient: CodexAppServerClient;
|
|
84
|
-
identityResolver: RecentProjectIdentityResolver;
|
|
85
|
-
logger: LoggerPort;
|
|
86
|
-
}
|
|
87
|
-
) {}
|
|
88
|
-
|
|
89
|
-
async list(): Promise<RecentProjectsSourceResult> {
|
|
90
|
-
const activeContext = this.deps.getActiveContext();
|
|
91
|
-
const localContext = this.deps.getLocalContext();
|
|
92
|
-
|
|
93
|
-
if (activeContext.type !== 'local' || activeContext.id !== localContext?.id) {
|
|
94
|
-
return {
|
|
95
|
-
candidates: [],
|
|
96
|
-
degraded: false,
|
|
97
|
-
};
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
const binaryPath = await this.deps.resolveBinary();
|
|
101
|
-
if (!binaryPath) {
|
|
102
|
-
this.deps.logger.info('codex recent-projects source skipped - binary unavailable');
|
|
103
|
-
return {
|
|
104
|
-
candidates: [],
|
|
105
|
-
degraded: false,
|
|
106
|
-
};
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
const cooldown = this.#getActiveCooldown();
|
|
110
|
-
if (cooldown) {
|
|
111
|
-
this.deps.logger.info('codex recent-projects source cooldown active', cooldown);
|
|
112
|
-
return {
|
|
113
|
-
candidates: this.#getFreshStaleCandidates() ?? [],
|
|
114
|
-
degraded: true,
|
|
115
|
-
};
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
const threadSegments = await this.#listRecentThreadsSafe(binaryPath);
|
|
119
|
-
const degraded = isDegradedThreadResult(threadSegments);
|
|
120
|
-
const fullFailureReason = getFullFailureReason(threadSegments);
|
|
121
|
-
this.#updateFullFailureCooldown(fullFailureReason);
|
|
122
|
-
this.#logSegmentFailure(threadSegments, 'live');
|
|
123
|
-
this.#logSegmentFailure(threadSegments, 'archived');
|
|
124
|
-
const liveThreads = threadSegments.live.threads;
|
|
125
|
-
const archivedThreads = threadSegments.archived.threads;
|
|
126
|
-
|
|
127
|
-
const interactiveThreads = [...liveThreads, ...archivedThreads].filter(
|
|
128
|
-
(thread) => Boolean(thread.cwd) && isInteractiveSource(thread.source)
|
|
129
|
-
);
|
|
130
|
-
|
|
131
|
-
const candidates = (
|
|
132
|
-
await Promise.all(interactiveThreads.map((thread) => this.#toCandidate(thread)))
|
|
133
|
-
).filter((candidate): candidate is RecentProjectCandidate => candidate !== null);
|
|
134
|
-
|
|
135
|
-
if (!degraded) {
|
|
136
|
-
this.#rememberHealthyCandidates(candidates);
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
if (degraded && candidates.length === 0) {
|
|
140
|
-
const staleCandidates = this.#getFreshStaleCandidates();
|
|
141
|
-
if (staleCandidates) {
|
|
142
|
-
this.deps.logger.info('codex recent-projects served stale candidates', {
|
|
143
|
-
count: staleCandidates.length,
|
|
144
|
-
reason: fullFailureReason ?? 'degraded-empty-result',
|
|
145
|
-
});
|
|
146
|
-
|
|
147
|
-
return {
|
|
148
|
-
candidates: staleCandidates,
|
|
149
|
-
degraded: true,
|
|
150
|
-
};
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
this.deps.logger.info('codex recent-projects source loaded', {
|
|
155
|
-
count: candidates.length,
|
|
156
|
-
degraded,
|
|
157
|
-
});
|
|
158
|
-
|
|
159
|
-
return {
|
|
160
|
-
candidates,
|
|
161
|
-
degraded,
|
|
162
|
-
};
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
#getActiveCooldown(): { retryAfterMs: number; reason: string | null } | null {
|
|
166
|
-
const retryAfterMs = this.#fullFailureCooldownUntil - Date.now();
|
|
167
|
-
if (retryAfterMs <= 0) {
|
|
168
|
-
return null;
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
return {
|
|
172
|
-
retryAfterMs,
|
|
173
|
-
reason: this.#fullFailureCooldownReason,
|
|
174
|
-
};
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
#updateFullFailureCooldown(reason: string | null): void {
|
|
178
|
-
if (!reason) {
|
|
179
|
-
this.#fullFailureCooldownUntil = 0;
|
|
180
|
-
this.#fullFailureCooldownReason = null;
|
|
181
|
-
return;
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
this.#fullFailureCooldownUntil = Date.now() + CODEX_FULL_FAILURE_COOLDOWN_MS;
|
|
185
|
-
this.#fullFailureCooldownReason = reason;
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
#rememberHealthyCandidates(candidates: RecentProjectCandidate[]): void {
|
|
189
|
-
this.#staleCandidatesSnapshot =
|
|
190
|
-
candidates.length > 0
|
|
191
|
-
? {
|
|
192
|
-
candidates,
|
|
193
|
-
capturedAt: Date.now(),
|
|
194
|
-
}
|
|
195
|
-
: null;
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
#getFreshStaleCandidates(): RecentProjectCandidate[] | null {
|
|
199
|
-
const snapshot = this.#staleCandidatesSnapshot;
|
|
200
|
-
if (!snapshot) {
|
|
201
|
-
return null;
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
if (Date.now() - snapshot.capturedAt > CODEX_STALE_CANDIDATES_TTL_MS) {
|
|
205
|
-
this.#staleCandidatesSnapshot = null;
|
|
206
|
-
return null;
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
return [...snapshot.candidates];
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
async #listRecentThreads(binaryPath: string): Promise<CodexRecentThreadsResult> {
|
|
213
|
-
const result = await this.deps.appServerClient.listRecentThreads(binaryPath, {
|
|
214
|
-
limit: CODEX_THREAD_LIMIT,
|
|
215
|
-
liveRequestTimeoutMs: CODEX_LIVE_FETCH_TIMEOUT_MS,
|
|
216
|
-
archivedRequestTimeoutMs: CODEX_ARCHIVED_FETCH_TIMEOUT_MS,
|
|
217
|
-
initializeTimeoutMs: CODEX_INITIALIZE_TIMEOUT_MS,
|
|
218
|
-
totalTimeoutMs: CODEX_TOTAL_FETCH_TIMEOUT_MS,
|
|
219
|
-
});
|
|
220
|
-
|
|
221
|
-
this.deps.logger.info('codex recent-projects thread lists loaded', {
|
|
222
|
-
liveCount: result.live.threads.length,
|
|
223
|
-
archivedCount: result.archived.threads.length,
|
|
224
|
-
});
|
|
225
|
-
return result;
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
#logSegmentFailure(result: CodexRecentThreadsResult, segment: 'live' | 'archived'): void {
|
|
229
|
-
const error = result[segment].error;
|
|
230
|
-
if (!error) {
|
|
231
|
-
return;
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
if (result[segment].skipped) {
|
|
235
|
-
this.deps.logger.info('codex recent-projects thread list skipped', {
|
|
236
|
-
segment,
|
|
237
|
-
reason: error,
|
|
238
|
-
});
|
|
239
|
-
return;
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
if (segment === 'archived' && !result.live.error) {
|
|
243
|
-
this.deps.logger.info('codex recent-projects archived thread list degraded', {
|
|
244
|
-
error,
|
|
245
|
-
});
|
|
246
|
-
return;
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
this.deps.logger.warn('codex recent-projects thread list failed', {
|
|
250
|
-
segment,
|
|
251
|
-
error,
|
|
252
|
-
});
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
async #listRecentThreadsSafe(binaryPath: string): Promise<CodexRecentThreadsResult> {
|
|
256
|
-
try {
|
|
257
|
-
return await this.#listRecentThreads(binaryPath);
|
|
258
|
-
} catch (error) {
|
|
259
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
260
|
-
this.deps.logger.warn('codex recent-projects thread list session failed', {
|
|
261
|
-
error: message,
|
|
262
|
-
});
|
|
263
|
-
|
|
264
|
-
if (message.toLowerCase().includes('timed out')) {
|
|
265
|
-
return {
|
|
266
|
-
live: { threads: [], error: message },
|
|
267
|
-
archived: { threads: [], error: message },
|
|
268
|
-
};
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
try {
|
|
272
|
-
const liveFallback = await this.deps.appServerClient.listRecentLiveThreads(binaryPath, {
|
|
273
|
-
limit: CODEX_THREAD_LIMIT,
|
|
274
|
-
requestTimeoutMs: CODEX_LIVE_FETCH_TIMEOUT_MS,
|
|
275
|
-
initializeTimeoutMs: CODEX_INITIALIZE_TIMEOUT_MS,
|
|
276
|
-
totalTimeoutMs: CODEX_LIVE_ONLY_FALLBACK_TOTAL_TIMEOUT_MS,
|
|
277
|
-
});
|
|
278
|
-
|
|
279
|
-
this.deps.logger.info('codex recent-projects recovered with live-only fallback', {
|
|
280
|
-
liveCount: liveFallback.threads.length,
|
|
281
|
-
});
|
|
282
|
-
|
|
283
|
-
return {
|
|
284
|
-
live: liveFallback,
|
|
285
|
-
archived: { threads: [], error: message },
|
|
286
|
-
};
|
|
287
|
-
} catch (fallbackError) {
|
|
288
|
-
const fallbackMessage =
|
|
289
|
-
fallbackError instanceof Error ? fallbackError.message : String(fallbackError);
|
|
290
|
-
this.deps.logger.warn('codex recent-projects live-only fallback failed', {
|
|
291
|
-
error: fallbackMessage,
|
|
292
|
-
});
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
return {
|
|
296
|
-
live: { threads: [], error: message },
|
|
297
|
-
archived: { threads: [], error: message },
|
|
298
|
-
};
|
|
299
|
-
}
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
async #toCandidate(thread: CodexThreadSummary): Promise<RecentProjectCandidate | null> {
|
|
303
|
-
const cwd = thread.cwd?.trim();
|
|
304
|
-
if (!cwd || isEphemeralProjectPath(cwd)) {
|
|
305
|
-
return null;
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
const identity = await this.deps.identityResolver.resolve(cwd);
|
|
309
|
-
const displayName = identity?.name ?? path.basename(cwd) ?? thread.name?.trim() ?? cwd;
|
|
310
|
-
|
|
311
|
-
return {
|
|
312
|
-
identity: identity?.id ?? `path:${normalizeIdentityPath(cwd)}`,
|
|
313
|
-
displayName,
|
|
314
|
-
primaryPath: cwd,
|
|
315
|
-
associatedPaths: [cwd],
|
|
316
|
-
lastActivityAt: normalizeTimestamp(thread.updatedAt ?? thread.createdAt),
|
|
317
|
-
providerIds: ['codex'],
|
|
318
|
-
sourceKind: 'codex',
|
|
319
|
-
openTarget: {
|
|
320
|
-
type: 'synthetic-path',
|
|
321
|
-
path: cwd,
|
|
322
|
-
},
|
|
323
|
-
branchName: thread.gitInfo?.branch ?? undefined,
|
|
324
|
-
};
|
|
325
|
-
}
|
|
326
|
-
}
|
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
type DashboardRecentProjectsPayload,
|
|
3
|
-
normalizeDashboardRecentProjectsPayload,
|
|
4
|
-
} from '@features/recent-projects/contracts';
|
|
5
|
-
|
|
6
|
-
import { ListDashboardRecentProjectsUseCase } from '../../core/application/use-cases/ListDashboardRecentProjectsUseCase';
|
|
7
|
-
import { DashboardRecentProjectsPresenter } from '../adapters/output/presenters/DashboardRecentProjectsPresenter';
|
|
8
|
-
import { ClaudeRecentProjectsSourceAdapter } from '../adapters/output/sources/ClaudeRecentProjectsSourceAdapter';
|
|
9
|
-
import { InMemoryRecentProjectsCache } from '../infrastructure/cache/InMemoryRecentProjectsCache';
|
|
10
|
-
|
|
11
|
-
import type { ClockPort } from '../../core/application/ports/ClockPort';
|
|
12
|
-
import type { LoggerPort } from '../../core/application/ports/LoggerPort';
|
|
13
|
-
import type { ServiceContext } from '@main/services';
|
|
14
|
-
|
|
15
|
-
export interface RecentProjectsFeatureFacade {
|
|
16
|
-
listDashboardRecentProjects(): Promise<DashboardRecentProjectsPayload>;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
export function createRecentProjectsFeature(deps: {
|
|
20
|
-
getActiveContext: () => ServiceContext;
|
|
21
|
-
getLocalContext: () => ServiceContext | undefined;
|
|
22
|
-
logger: LoggerPort;
|
|
23
|
-
}): RecentProjectsFeatureFacade {
|
|
24
|
-
const cache = new InMemoryRecentProjectsCache<DashboardRecentProjectsPayload>();
|
|
25
|
-
const presenter = new DashboardRecentProjectsPresenter();
|
|
26
|
-
const clock: ClockPort = { now: () => Date.now() };
|
|
27
|
-
const sources = [new ClaudeRecentProjectsSourceAdapter(deps.getActiveContext, deps.logger)];
|
|
28
|
-
const useCase = new ListDashboardRecentProjectsUseCase({
|
|
29
|
-
sources,
|
|
30
|
-
cache,
|
|
31
|
-
output: presenter,
|
|
32
|
-
clock,
|
|
33
|
-
logger: deps.logger,
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
return {
|
|
37
|
-
listDashboardRecentProjects: async () => {
|
|
38
|
-
const activeContext = deps.getActiveContext();
|
|
39
|
-
const payload = await useCase.execute(`dashboard-recent-projects:${activeContext.id}`);
|
|
40
|
-
return normalizeDashboardRecentProjectsPayload(payload) ?? { projects: [], degraded: true };
|
|
41
|
-
},
|
|
42
|
-
};
|
|
43
|
-
}
|
|
@@ -1,3 +0,0 @@
|
|
|
1
|
-
export { registerRecentProjectsHttp } from './adapters/input/http/registerRecentProjectsHttp';
|
|
2
|
-
export type { RecentProjectsFeatureFacade } from './composition/createRecentProjectsFeature';
|
|
3
|
-
export { createRecentProjectsFeature } from './composition/createRecentProjectsFeature';
|
package/src/features/recent-projects/main/infrastructure/cache/InMemoryRecentProjectsCache.ts
DELETED
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
import type { RecentProjectsCachePort } from '../../../core/application/ports/RecentProjectsCachePort';
|
|
2
|
-
|
|
3
|
-
interface CacheEntry<T> {
|
|
4
|
-
value: T;
|
|
5
|
-
expiresAt: number;
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
export class InMemoryRecentProjectsCache<T> implements RecentProjectsCachePort<T> {
|
|
9
|
-
readonly #entries = new Map<string, CacheEntry<T>>();
|
|
10
|
-
|
|
11
|
-
async get(key: string): Promise<T | null> {
|
|
12
|
-
const entry = this.#entries.get(key);
|
|
13
|
-
if (!entry) {
|
|
14
|
-
return null;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
if (entry.expiresAt <= Date.now()) {
|
|
18
|
-
return null;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
return entry.value;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
async getStale(key: string): Promise<T | null> {
|
|
25
|
-
return this.#entries.get(key)?.value ?? null;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
async set(key: string, value: T, ttlMs: number): Promise<void> {
|
|
29
|
-
this.#entries.set(key, {
|
|
30
|
-
value,
|
|
31
|
-
expiresAt: Date.now() + ttlMs,
|
|
32
|
-
});
|
|
33
|
-
}
|
|
34
|
-
}
|
|
@@ -1,116 +0,0 @@
|
|
|
1
|
-
// ---------------------------------------------------------------------------
|
|
2
|
-
// Inline type stubs for deleted codexAppServer module
|
|
3
|
-
// ---------------------------------------------------------------------------
|
|
4
|
-
|
|
5
|
-
/** Minimal stub for the deleted JsonRpcSession interface */
|
|
6
|
-
interface JsonRpcSession {
|
|
7
|
-
request<T>(method: string, params?: unknown, timeoutMs?: number): Promise<T>;
|
|
8
|
-
notify(method: string, params?: unknown): Promise<void>;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
/** Minimal stub for the deleted JsonRpcStdioClient interface */
|
|
12
|
-
interface JsonRpcStdioClient {
|
|
13
|
-
withSession<T>(
|
|
14
|
-
options: {
|
|
15
|
-
binaryPath: string;
|
|
16
|
-
args: string[];
|
|
17
|
-
requestTimeoutMs: number;
|
|
18
|
-
totalTimeoutMs: number;
|
|
19
|
-
label: string;
|
|
20
|
-
},
|
|
21
|
-
handler: (session: JsonRpcSession) => Promise<T>
|
|
22
|
-
): Promise<T>;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
const DEFAULT_REQUEST_TIMEOUT_MS = 3_000;
|
|
26
|
-
const DEFAULT_TOTAL_TIMEOUT_MS = 8_000;
|
|
27
|
-
const DEFAULT_INITIALIZE_TIMEOUT_MS = 6_000;
|
|
28
|
-
const MIN_SESSION_OVERHEAD_TIMEOUT_MS = 1_500;
|
|
29
|
-
const SUPPRESSED_NOTIFICATION_METHODS = [
|
|
30
|
-
'thread/started',
|
|
31
|
-
'thread/status/changed',
|
|
32
|
-
'thread/archived',
|
|
33
|
-
'thread/unarchived',
|
|
34
|
-
'thread/closed',
|
|
35
|
-
'thread/name/updated',
|
|
36
|
-
'turn/started',
|
|
37
|
-
'turn/completed',
|
|
38
|
-
'item/agentMessage/delta',
|
|
39
|
-
'item/agentReasoning/delta',
|
|
40
|
-
'item/execCommandOutputDelta',
|
|
41
|
-
];
|
|
42
|
-
|
|
43
|
-
interface ThreadListResponse {
|
|
44
|
-
data?: CodexThreadSummary[];
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
interface CodexGitInfo {
|
|
48
|
-
branch?: string | null;
|
|
49
|
-
originUrl?: string | null;
|
|
50
|
-
sha?: string | null;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
export interface CodexThreadSummary {
|
|
54
|
-
id: string;
|
|
55
|
-
createdAt?: number;
|
|
56
|
-
updatedAt?: number;
|
|
57
|
-
cwd?: string | null;
|
|
58
|
-
source?: unknown;
|
|
59
|
-
modelProvider?: string | null;
|
|
60
|
-
gitInfo?: CodexGitInfo | null;
|
|
61
|
-
name?: string | null;
|
|
62
|
-
path?: string | null;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
export interface CodexThreadSegmentResult {
|
|
66
|
-
threads: CodexThreadSummary[];
|
|
67
|
-
error?: string;
|
|
68
|
-
skipped?: boolean;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
export interface CodexRecentThreadsResult {
|
|
72
|
-
live: CodexThreadSegmentResult;
|
|
73
|
-
archived: CodexThreadSegmentResult;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
interface ThreadListSessionOptions {
|
|
77
|
-
binaryPath: string;
|
|
78
|
-
requestTimeoutMs: number;
|
|
79
|
-
initializeTimeoutMs: number;
|
|
80
|
-
totalTimeoutMs: number;
|
|
81
|
-
label: string;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
export class CodexAppServerClient {
|
|
85
|
-
constructor(private readonly rpcClient: JsonRpcStdioClient) {}
|
|
86
|
-
|
|
87
|
-
async listRecentLiveThreads(
|
|
88
|
-
_binaryPath: string,
|
|
89
|
-
_options: {
|
|
90
|
-
limit: number;
|
|
91
|
-
requestTimeoutMs?: number;
|
|
92
|
-
initializeTimeoutMs?: number;
|
|
93
|
-
totalTimeoutMs?: number;
|
|
94
|
-
}
|
|
95
|
-
): Promise<CodexThreadSegmentResult> {
|
|
96
|
-
// Codex app server module has been removed — return empty results
|
|
97
|
-
return { threads: [] };
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
async listRecentThreads(
|
|
101
|
-
_binaryPath: string,
|
|
102
|
-
_options: {
|
|
103
|
-
limit: number;
|
|
104
|
-
liveRequestTimeoutMs?: number;
|
|
105
|
-
archivedRequestTimeoutMs?: number;
|
|
106
|
-
initializeTimeoutMs?: number;
|
|
107
|
-
totalTimeoutMs?: number;
|
|
108
|
-
}
|
|
109
|
-
): Promise<CodexRecentThreadsResult> {
|
|
110
|
-
// Codex app server module has been removed — return empty results
|
|
111
|
-
return {
|
|
112
|
-
live: { threads: [] },
|
|
113
|
-
archived: { threads: [] },
|
|
114
|
-
};
|
|
115
|
-
}
|
|
116
|
-
}
|
package/src/features/recent-projects/main/infrastructure/identity/RecentProjectIdentityResolver.ts
DELETED
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
import { gitIdentityResolver } from '@main/services/parsing/GitIdentityResolver';
|
|
2
|
-
|
|
3
|
-
export interface RecentProjectIdentity {
|
|
4
|
-
id: string;
|
|
5
|
-
name?: string;
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
export class RecentProjectIdentityResolver {
|
|
9
|
-
async resolve(projectPath: string): Promise<RecentProjectIdentity | null> {
|
|
10
|
-
const identity = await gitIdentityResolver.resolveIdentity(projectPath);
|
|
11
|
-
if (!identity) {
|
|
12
|
-
return null;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
return {
|
|
16
|
-
id: identity.id,
|
|
17
|
-
name: identity.name,
|
|
18
|
-
};
|
|
19
|
-
}
|
|
20
|
-
}
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
import path from 'path';
|
|
2
|
-
|
|
3
|
-
export function normalizeIdentityPath(projectPath: string): string {
|
|
4
|
-
let normalized = path.normalize(projectPath);
|
|
5
|
-
while (normalized.length > 1 && (normalized.endsWith('/') || normalized.endsWith('\\'))) {
|
|
6
|
-
normalized = normalized.slice(0, -1);
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
return process.platform === 'win32' ? normalized.toLowerCase() : normalized;
|
|
10
|
-
}
|
|
@@ -1,143 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* ApiKeyCard — displays a single API key entry with edit/delete controls.
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import { useState } from 'react';
|
|
6
|
-
|
|
7
|
-
import { Badge } from '@renderer/components/ui/badge';
|
|
8
|
-
import { Button } from '@renderer/components/ui/button';
|
|
9
|
-
import {
|
|
10
|
-
Tooltip,
|
|
11
|
-
TooltipContent,
|
|
12
|
-
TooltipProvider,
|
|
13
|
-
TooltipTrigger,
|
|
14
|
-
} from '@renderer/components/ui/tooltip';
|
|
15
|
-
import { useStore } from '@renderer/store';
|
|
16
|
-
import { Check, Copy, Pencil, Trash2 } from 'lucide-react';
|
|
17
|
-
|
|
18
|
-
import type { ApiKeyEntry } from '@shared/types/extensions';
|
|
19
|
-
|
|
20
|
-
interface ApiKeyCardProps {
|
|
21
|
-
apiKey: ApiKeyEntry;
|
|
22
|
-
onEdit: (key: ApiKeyEntry) => void;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
export const ApiKeyCard = ({ apiKey, onEdit }: ApiKeyCardProps): React.JSX.Element => {
|
|
26
|
-
const deleteApiKey = useStore((s) => s.deleteApiKey);
|
|
27
|
-
const [copied, setCopied] = useState(false);
|
|
28
|
-
const [confirmDelete, setConfirmDelete] = useState(false);
|
|
29
|
-
|
|
30
|
-
const handleCopyEnvVar = async (): Promise<void> => {
|
|
31
|
-
await navigator.clipboard.writeText(apiKey.envVarName);
|
|
32
|
-
setCopied(true);
|
|
33
|
-
setTimeout(() => setCopied(false), 1500);
|
|
34
|
-
};
|
|
35
|
-
|
|
36
|
-
const handleDelete = (): void => {
|
|
37
|
-
if (!confirmDelete) {
|
|
38
|
-
setConfirmDelete(true);
|
|
39
|
-
setTimeout(() => setConfirmDelete(false), 3000);
|
|
40
|
-
return;
|
|
41
|
-
}
|
|
42
|
-
void deleteApiKey(apiKey.id).catch(() => undefined);
|
|
43
|
-
setConfirmDelete(false);
|
|
44
|
-
};
|
|
45
|
-
|
|
46
|
-
const createdDate = new Date(apiKey.createdAt).toLocaleDateString(undefined, {
|
|
47
|
-
month: 'short',
|
|
48
|
-
day: 'numeric',
|
|
49
|
-
year: 'numeric',
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
return (
|
|
53
|
-
<div className="flex flex-col gap-2.5 rounded-lg border border-border bg-surface p-4 transition-colors hover:border-border-emphasis">
|
|
54
|
-
{/* Name + scope badge */}
|
|
55
|
-
<div className="flex items-start justify-between gap-2">
|
|
56
|
-
<h3 className="truncate text-sm font-medium text-text">{apiKey.name}</h3>
|
|
57
|
-
<Badge
|
|
58
|
-
variant="outline"
|
|
59
|
-
className={
|
|
60
|
-
apiKey.scope === 'user'
|
|
61
|
-
? 'border-blue-500/30 bg-blue-500/10 text-blue-400'
|
|
62
|
-
: 'border-purple-500/30 bg-purple-500/10 text-purple-400'
|
|
63
|
-
}
|
|
64
|
-
>
|
|
65
|
-
{apiKey.scope}
|
|
66
|
-
</Badge>
|
|
67
|
-
</div>
|
|
68
|
-
|
|
69
|
-
{apiKey.scope === 'project' && apiKey.projectPath && (
|
|
70
|
-
<p className="truncate text-xs text-text-muted" title={apiKey.projectPath}>
|
|
71
|
-
{apiKey.projectPath}
|
|
72
|
-
</p>
|
|
73
|
-
)}
|
|
74
|
-
|
|
75
|
-
{/* Env var name */}
|
|
76
|
-
<div className="flex items-center gap-1.5">
|
|
77
|
-
<code className="rounded bg-surface-raised px-1.5 py-0.5 text-xs text-blue-400">
|
|
78
|
-
{apiKey.envVarName}
|
|
79
|
-
</code>
|
|
80
|
-
<TooltipProvider>
|
|
81
|
-
<Tooltip>
|
|
82
|
-
<TooltipTrigger asChild>
|
|
83
|
-
<Button
|
|
84
|
-
variant="ghost"
|
|
85
|
-
size="icon"
|
|
86
|
-
className="size-5"
|
|
87
|
-
onClick={() => void handleCopyEnvVar()}
|
|
88
|
-
>
|
|
89
|
-
{copied ? (
|
|
90
|
-
<Check className="size-3 text-emerald-400" />
|
|
91
|
-
) : (
|
|
92
|
-
<Copy className="size-3 text-text-muted" />
|
|
93
|
-
)}
|
|
94
|
-
</Button>
|
|
95
|
-
</TooltipTrigger>
|
|
96
|
-
<TooltipContent>{copied ? 'Copied!' : 'Copy env var name'}</TooltipContent>
|
|
97
|
-
</Tooltip>
|
|
98
|
-
</TooltipProvider>
|
|
99
|
-
</div>
|
|
100
|
-
|
|
101
|
-
{/* Masked value */}
|
|
102
|
-
<p className="font-mono text-xs text-text-muted">{apiKey.maskedValue}</p>
|
|
103
|
-
|
|
104
|
-
{/* Footer: date + actions */}
|
|
105
|
-
<div className="flex items-center justify-between pt-1">
|
|
106
|
-
<span className="text-xs text-text-muted">{createdDate}</span>
|
|
107
|
-
<div className="flex items-center gap-1">
|
|
108
|
-
<TooltipProvider>
|
|
109
|
-
<Tooltip>
|
|
110
|
-
<TooltipTrigger asChild>
|
|
111
|
-
<Button
|
|
112
|
-
variant="ghost"
|
|
113
|
-
size="icon"
|
|
114
|
-
className="size-7"
|
|
115
|
-
onClick={() => onEdit(apiKey)}
|
|
116
|
-
>
|
|
117
|
-
<Pencil className="size-3.5 text-text-muted" />
|
|
118
|
-
</Button>
|
|
119
|
-
</TooltipTrigger>
|
|
120
|
-
<TooltipContent>Edit</TooltipContent>
|
|
121
|
-
</Tooltip>
|
|
122
|
-
</TooltipProvider>
|
|
123
|
-
|
|
124
|
-
<TooltipProvider>
|
|
125
|
-
<Tooltip>
|
|
126
|
-
<TooltipTrigger asChild>
|
|
127
|
-
<Button
|
|
128
|
-
variant="ghost"
|
|
129
|
-
size="icon"
|
|
130
|
-
className={`size-7 ${confirmDelete ? 'text-red-400 hover:bg-red-500/10' : 'text-text-muted'}`}
|
|
131
|
-
onClick={handleDelete}
|
|
132
|
-
>
|
|
133
|
-
<Trash2 className="size-3.5" />
|
|
134
|
-
</Button>
|
|
135
|
-
</TooltipTrigger>
|
|
136
|
-
<TooltipContent>{confirmDelete ? 'Click again to confirm' : 'Delete'}</TooltipContent>
|
|
137
|
-
</Tooltip>
|
|
138
|
-
</TooltipProvider>
|
|
139
|
-
</div>
|
|
140
|
-
</div>
|
|
141
|
-
</div>
|
|
142
|
-
);
|
|
143
|
-
};
|