@yancyyu/openhermit 1.6.29 → 1.6.31

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.
Files changed (157) hide show
  1. package/dist-renderer/assets/{ProjectEditorOverlay-CQm6jUR1.js → ProjectEditorOverlay-DkXfi2pg.js} +1 -1
  2. package/dist-renderer/assets/{TeamGraphOverlay-h0WDfifv.js → TeamGraphOverlay-CHNNVraw.js} +1 -1
  3. package/dist-renderer/assets/{_basePickBy-CgG_tjgX.js → _basePickBy-Do-Ff83V.js} +1 -1
  4. package/dist-renderer/assets/{_baseUniq-DwPTU9lP.js → _baseUniq-nDLhSuJI.js} +1 -1
  5. package/dist-renderer/assets/{arc-7nIrGRzY.js → arc-Bp7fA6sx.js} +1 -1
  6. package/dist-renderer/assets/{architectureDiagram-VXUJARFQ-BYhA6Ev2.js → architectureDiagram-VXUJARFQ-CPC1HdGy.js} +1 -1
  7. package/dist-renderer/assets/{blockDiagram-VD42YOAC-BVpZUGDg.js → blockDiagram-VD42YOAC-DTVKyNTO.js} +1 -1
  8. package/dist-renderer/assets/{c4Diagram-YG6GDRKO-DsdreMQ9.js → c4Diagram-YG6GDRKO-XVu-AN00.js} +1 -1
  9. package/dist-renderer/assets/channel-CIwbNcUO.js +1 -0
  10. package/dist-renderer/assets/{chunk-4BX2VUAB-CcoAs7Jd.js → chunk-4BX2VUAB-BcWmVyA-.js} +1 -1
  11. package/dist-renderer/assets/{chunk-55IACEB6-CGGAOoXd.js → chunk-55IACEB6-Co4Z2jsE.js} +1 -1
  12. package/dist-renderer/assets/{chunk-B4BG7PRW-FhpTEPvD.js → chunk-B4BG7PRW-C8q9gfDT.js} +1 -1
  13. package/dist-renderer/assets/{chunk-DI55MBZ5-DoYySbm1.js → chunk-DI55MBZ5-qDgb1gxO.js} +1 -1
  14. package/dist-renderer/assets/{chunk-FMBD7UC4-e9l2tGHG.js → chunk-FMBD7UC4-Cm8Gu2gu.js} +1 -1
  15. package/dist-renderer/assets/{chunk-QN33PNHL-DeiXVTCy.js → chunk-QN33PNHL-DYji1BRS.js} +1 -1
  16. package/dist-renderer/assets/{chunk-QZHKN3VN-DC2UJLJM.js → chunk-QZHKN3VN-DWAS568H.js} +1 -1
  17. package/dist-renderer/assets/{chunk-TZMSLE5B-BHFD9eZI.js → chunk-TZMSLE5B-CLFzXLA8.js} +1 -1
  18. package/dist-renderer/assets/classDiagram-2ON5EDUG-04A-pvql.js +1 -0
  19. package/dist-renderer/assets/classDiagram-v2-WZHVMYZB-04A-pvql.js +1 -0
  20. package/dist-renderer/assets/clone-DQnvTIEM.js +1 -0
  21. package/dist-renderer/assets/{cose-bilkent-S5V4N54A-BdybQraU.js → cose-bilkent-S5V4N54A-CZdGhX_3.js} +1 -1
  22. package/dist-renderer/assets/{dagre-6UL2VRFP-DdF3pwM3.js → dagre-6UL2VRFP-BVY-G6nO.js} +1 -1
  23. package/dist-renderer/assets/{diagram-PSM6KHXK-B9Ldd3nh.js → diagram-PSM6KHXK-CUACvAwG.js} +1 -1
  24. package/dist-renderer/assets/{diagram-QEK2KX5R-XEqkrbpu.js → diagram-QEK2KX5R-3SfnesSG.js} +1 -1
  25. package/dist-renderer/assets/{diagram-S2PKOQOG-CipwtY59.js → diagram-S2PKOQOG-E3ksXClJ.js} +1 -1
  26. package/dist-renderer/assets/{erDiagram-Q2GNP2WA-BB-2ISGo.js → erDiagram-Q2GNP2WA-aYjGXss7.js} +1 -1
  27. package/dist-renderer/assets/{flowDiagram-NV44I4VS-B8XmJ0u2.js → flowDiagram-NV44I4VS-JMHrrTQs.js} +1 -1
  28. package/dist-renderer/assets/{ganttDiagram-JELNMOA3-D-8XglBb.js → ganttDiagram-JELNMOA3-CVQ-R5rN.js} +1 -1
  29. package/dist-renderer/assets/{gitGraphDiagram-V2S2FVAM-DL4ChakD.js → gitGraphDiagram-V2S2FVAM-OLn9jq61.js} +1 -1
  30. package/dist-renderer/assets/{graph-BiFNoBjP.js → graph-BAb2J0l8.js} +1 -1
  31. package/dist-renderer/assets/{index-qNBNjW4K.js → index-BSoCjBWn.js} +1 -1
  32. package/dist-renderer/assets/{index-6m1ZAymG.js → index-BtG3HbqP.js} +1 -1
  33. package/dist-renderer/assets/{index-BowUl0Jb.js → index-CH8e7g1f.js} +583 -573
  34. package/dist-renderer/assets/index-CSt8DTcn.css +1 -0
  35. package/dist-renderer/assets/{index-Dp3kJTEe.js → index-Ca4iNkRA.js} +1 -1
  36. package/dist-renderer/assets/{index-vAykq1H1.js → index-DU9PGgZJ.js} +1 -1
  37. package/dist-renderer/assets/{index-TOpt_T7A.js → index-DtMzIS9o.js} +1 -1
  38. package/dist-renderer/assets/{infoDiagram-HS3SLOUP-DRIBfHDi.js → infoDiagram-HS3SLOUP-CY_ptQNL.js} +1 -1
  39. package/dist-renderer/assets/{journeyDiagram-XKPGCS4Q-BOMiigU4.js → journeyDiagram-XKPGCS4Q-C2vuHEo_.js} +1 -1
  40. package/dist-renderer/assets/{kanban-definition-3W4ZIXB7-DDxeyjod.js → kanban-definition-3W4ZIXB7-mbdNfu8h.js} +1 -1
  41. package/dist-renderer/assets/{layout-DNANbrI4.js → layout-Do_ArEB1.js} +1 -1
  42. package/dist-renderer/assets/{linear-DxEJi1yT.js → linear-BMlMKyiq.js} +1 -1
  43. package/dist-renderer/assets/{mindmap-definition-VGOIOE7T-nBfGriW8.js → mindmap-definition-VGOIOE7T-Dfntn-o2.js} +1 -1
  44. package/dist-renderer/assets/{pieDiagram-ADFJNKIX-Din5j6sV.js → pieDiagram-ADFJNKIX-LiWHsGMV.js} +1 -1
  45. package/dist-renderer/assets/{quadrantDiagram-AYHSOK5B-DMVK2BEQ.js → quadrantDiagram-AYHSOK5B-D87St8AF.js} +1 -1
  46. package/dist-renderer/assets/{requirementDiagram-UZGBJVZJ-6SC94Gg_.js → requirementDiagram-UZGBJVZJ-DAa6lHBx.js} +1 -1
  47. package/dist-renderer/assets/{sankeyDiagram-TZEHDZUN-CD2gghhu.js → sankeyDiagram-TZEHDZUN-VOUngars.js} +1 -1
  48. package/dist-renderer/assets/{sequenceDiagram-WL72ISMW-BnhkN7nZ.js → sequenceDiagram-WL72ISMW-BzwzmFr2.js} +1 -1
  49. package/dist-renderer/assets/{stateDiagram-FKZM4ZOC-Bn8XdYX-.js → stateDiagram-FKZM4ZOC-BjAQEJ52.js} +1 -1
  50. package/dist-renderer/assets/{stateDiagram-v2-4FDKWEC3-1b6sI1_g.js → stateDiagram-v2-4FDKWEC3-BDwy4GJm.js} +1 -1
  51. package/dist-renderer/assets/{timeline-definition-IT6M3QCI-CNs3RPoa.js → timeline-definition-IT6M3QCI-Y5XBZt3W.js} +1 -1
  52. package/dist-renderer/assets/treemap-GDKQZRPO-DzkdUEow.js +162 -0
  53. package/dist-renderer/assets/{xychartDiagram-PRI3JC2R-B8o5J2f3.js → xychartDiagram-PRI3JC2R-D-zbvJOv.js} +1 -1
  54. package/dist-renderer/index.html +2 -2
  55. package/package.json +4 -1
  56. package/src/main/ipc/extensions.ts +353 -0
  57. package/src/main/server.ts +209 -6
  58. package/src/main/services/extensions/ExtensionFacadeService.ts +135 -0
  59. package/src/main/services/extensions/catalog/GlamaMcpEnrichmentService.ts +190 -0
  60. package/src/main/services/extensions/catalog/McpCatalogAggregator.ts +150 -0
  61. package/src/main/services/extensions/catalog/OfficialMcpRegistryService.ts +381 -0
  62. package/src/main/services/extensions/catalog/PluginCatalogService.ts +392 -0
  63. package/src/main/services/extensions/credentials/CredentialService.ts +343 -0
  64. package/src/main/services/extensions/install/McpInstallService.ts +407 -0
  65. package/src/main/services/extensions/install/PluginInstallService.ts +198 -0
  66. package/src/main/services/extensions/runtime/ClaudeCodeAdapter.ts +199 -0
  67. package/src/main/services/extensions/runtime/CodexAdapter.ts +100 -0
  68. package/src/main/services/extensions/runtime/CursorAdapter.ts +154 -0
  69. package/src/main/services/extensions/runtime/ExtensionsRuntimeAdapter.ts +172 -0
  70. package/src/main/services/extensions/runtime/GeminiAdapter.ts +91 -0
  71. package/src/main/services/extensions/runtime/HarnessInstallAdapter.ts +49 -0
  72. package/src/main/services/extensions/runtime/McpConfigStateReader.ts +209 -0
  73. package/src/main/services/extensions/runtime/OpenCodeAdapter.ts +91 -0
  74. package/src/main/services/extensions/runtime/adapterRegistry.ts +54 -0
  75. package/src/main/services/extensions/runtime/mcpDiagnosticsParser.ts +214 -0
  76. package/src/main/services/extensions/runtime/mcpRuntimeJson.ts +45 -0
  77. package/src/main/services/extensions/skills/SkillImportService.ts +155 -0
  78. package/src/main/services/extensions/skills/SkillMetadataParser.ts +323 -0
  79. package/src/main/services/extensions/skills/SkillPlanService.ts +411 -0
  80. package/src/main/services/extensions/skills/SkillReviewService.ts +73 -0
  81. package/src/main/services/extensions/skills/SkillRootsResolver.ts +49 -0
  82. package/src/main/services/extensions/skills/SkillScaffoldService.ts +89 -0
  83. package/src/main/services/extensions/skills/SkillScanner.ts +117 -0
  84. package/src/main/services/extensions/skills/SkillValidator.ts +69 -0
  85. package/src/main/services/extensions/skills/SkillsCatalogService.ts +92 -0
  86. package/src/main/services/extensions/skills/SkillsMutationService.ts +146 -0
  87. package/src/main/services/extensions/skills/SkillsWatcherService.ts +134 -0
  88. package/src/main/services/extensions/state/McpInstallationStateService.ts +42 -0
  89. package/src/main/services/extensions/state/PluginInstallationStateService.ts +281 -0
  90. package/src/main/services/identity/AgentTeamsIdentityStore.ts +218 -0
  91. package/src/main/services/runtime/providerAwareCliEnv.ts +60 -0
  92. package/src/main/services/team/ClaudeBinaryResolver.ts +469 -0
  93. package/src/main/services/team/ClaudeDoctorProbe.ts +0 -0
  94. package/src/main/services/team/cliFlavor.ts +54 -0
  95. package/src/main/services/teams-mvp/TaskDispatchService.ts +3 -0
  96. package/src/main/utils/atomicWrite.ts +72 -0
  97. package/src/main/utils/childProcess.ts +554 -0
  98. package/src/main/utils/cliEnv.ts +54 -0
  99. package/src/main/utils/cliPathMerge.ts +97 -0
  100. package/src/main/utils/pathDecoder.ts +664 -0
  101. package/src/main/utils/pathValidation.ts +432 -0
  102. package/src/main/utils/shellEnv.ts +331 -0
  103. package/src/renderer/api/httpClient.ts +61 -0
  104. package/src/renderer/components/extensions/ExtensionStoreView.tsx +63 -35
  105. package/src/renderer/components/extensions/ExtensionsSubTabTrigger.tsx +1 -1
  106. package/src/renderer/components/extensions/common/ExtensionToast.tsx +141 -0
  107. package/src/renderer/components/extensions/common/HarnessSelector.tsx +71 -0
  108. package/src/renderer/components/extensions/env/EnvVarPanel.tsx +335 -0
  109. package/src/renderer/components/extensions/env/ProjectEnvPanel.tsx +239 -0
  110. package/src/renderer/components/extensions/mcp/CustomMcpServerDialog.tsx +14 -223
  111. package/src/renderer/components/extensions/mcp/McpServerDetailDialog.tsx +111 -15
  112. package/src/renderer/components/extensions/mcp/McpServersPanel.tsx +51 -1
  113. package/src/renderer/components/extensions/skills/SkillsPanel.tsx +1 -126
  114. package/src/renderer/components/settings/sections/HarnessSection.tsx +2 -6
  115. package/src/renderer/components/settings/sections/TaskBusSection.tsx +17 -7
  116. package/src/renderer/components/sidebar/SidebarSessions.tsx +23 -0
  117. package/src/renderer/components/sidebar/WorkspaceBrowser.tsx +1 -7
  118. package/src/renderer/components/team/HarnessSelect.tsx +71 -0
  119. package/src/renderer/components/team/TeamDetailView.tsx +74 -123
  120. package/src/renderer/components/team/TeamListFilterPopover.tsx +0 -16
  121. package/src/renderer/components/team/TeamListView.tsx +7 -32
  122. package/src/renderer/components/team/dialogs/CreateTeamDialog.tsx +21 -12
  123. package/src/renderer/components/team/dialogs/EditTeamDialog.tsx +287 -418
  124. package/src/renderer/components/team/dialogs/useTeamEditForm.ts +283 -0
  125. package/src/renderer/components/team/kanban/KanbanBoard.tsx +26 -64
  126. package/src/renderer/components/team/messages/MessagesPanel.tsx +28 -24
  127. package/src/renderer/components/terminal/TerminalPanel.tsx +156 -0
  128. package/src/renderer/hooks/useExtensionsTabState.ts +2 -2
  129. package/src/renderer/store/slices/extensionsSlice.ts +42 -107
  130. package/src/renderer/store/slices/teamSlice.ts +8 -2
  131. package/src/renderer/utils/multimodelProviderVisibility.ts +17 -0
  132. package/src/renderer/utils/openCodeRuntimeDeliveryDiagnostics.ts +29 -9
  133. package/src/shared/types/api.ts +29 -0
  134. package/src/shared/types/extensions/index.ts +1 -0
  135. package/src/shared/types/extensions/mcp.ts +2 -0
  136. package/src/shared/types/extensions/plugin.ts +2 -1
  137. package/src/shared/types/extensions/skill.ts +7 -0
  138. package/src/shared/utils/providerExtensionCapabilities.ts +1 -1
  139. package/dist-renderer/assets/channel-C0SqeFU7.js +0 -1
  140. package/dist-renderer/assets/classDiagram-2ON5EDUG-DWew1HpM.js +0 -1
  141. package/dist-renderer/assets/classDiagram-v2-WZHVMYZB-DWew1HpM.js +0 -1
  142. package/dist-renderer/assets/clone-Dm-k63Yr.js +0 -1
  143. package/dist-renderer/assets/index-BhellmRb.css +0 -1
  144. package/dist-renderer/assets/treemap-GDKQZRPO-DU_yr827.js +0 -162
  145. package/src/features/recent-projects/main/adapters/input/http/registerRecentProjectsHttp.ts +0 -30
  146. package/src/features/recent-projects/main/adapters/output/presenters/DashboardRecentProjectsPresenter.ts +0 -27
  147. package/src/features/recent-projects/main/adapters/output/sources/ClaudeRecentProjectsSourceAdapter.ts +0 -91
  148. package/src/features/recent-projects/main/adapters/output/sources/CodexRecentProjectsSourceAdapter.ts +0 -326
  149. package/src/features/recent-projects/main/composition/createRecentProjectsFeature.ts +0 -43
  150. package/src/features/recent-projects/main/index.ts +0 -3
  151. package/src/features/recent-projects/main/infrastructure/cache/InMemoryRecentProjectsCache.ts +0 -34
  152. package/src/features/recent-projects/main/infrastructure/codex/CodexAppServerClient.ts +0 -116
  153. package/src/features/recent-projects/main/infrastructure/identity/RecentProjectIdentityResolver.ts +0 -20
  154. package/src/features/recent-projects/main/infrastructure/identity/normalizeIdentityPath.ts +0 -10
  155. package/src/renderer/components/extensions/apikeys/ApiKeyCard.tsx +0 -143
  156. package/src/renderer/components/extensions/apikeys/ApiKeyFormDialog.tsx +0 -282
  157. package/src/renderer/components/extensions/apikeys/ApiKeysPanel.tsx +0 -280
@@ -1,30 +0,0 @@
1
- import {
2
- DASHBOARD_RECENT_PROJECTS_ROUTE,
3
- type DashboardRecentProjectsPayload,
4
- normalizeDashboardRecentProjectsPayload,
5
- } from '@features/recent-projects/contracts';
6
- import { createLogger } from '@shared/utils/logger';
7
-
8
- import type { RecentProjectsFeatureFacade } from '@features/recent-projects/main/composition/createRecentProjectsFeature';
9
- import type { FastifyInstance } from 'fastify';
10
-
11
- const logger = createLogger('Feature:RecentProjects:HTTP');
12
-
13
- export function registerRecentProjectsHttp(
14
- app: FastifyInstance,
15
- feature: RecentProjectsFeatureFacade
16
- ): void {
17
- app.get(DASHBOARD_RECENT_PROJECTS_ROUTE, async (): Promise<DashboardRecentProjectsPayload> => {
18
- try {
19
- return (
20
- normalizeDashboardRecentProjectsPayload(await feature.listDashboardRecentProjects()) ?? {
21
- projects: [],
22
- degraded: true,
23
- }
24
- );
25
- } catch (error) {
26
- logger.error('Failed to load dashboard recent projects via HTTP', error);
27
- return { projects: [], degraded: true };
28
- }
29
- });
30
- }
@@ -1,27 +0,0 @@
1
- import type {
2
- DashboardRecentProject,
3
- DashboardRecentProjectsPayload,
4
- } from '@features/recent-projects/contracts';
5
- import type { ListDashboardRecentProjectsResponse } from '@features/recent-projects/core/application/models/ListDashboardRecentProjectsResponse';
6
- import type { ListDashboardRecentProjectsOutputPort } from '@features/recent-projects/core/application/ports/ListDashboardRecentProjectsOutputPort';
7
-
8
- export class DashboardRecentProjectsPresenter implements ListDashboardRecentProjectsOutputPort<DashboardRecentProjectsPayload> {
9
- present(response: ListDashboardRecentProjectsResponse): DashboardRecentProjectsPayload {
10
- return {
11
- degraded: response.degraded,
12
- projects: response.projects.map(
13
- (aggregate): DashboardRecentProject => ({
14
- id: aggregate.identity,
15
- name: aggregate.displayName,
16
- primaryPath: aggregate.primaryPath,
17
- associatedPaths: aggregate.associatedPaths,
18
- mostRecentActivity: aggregate.lastActivityAt,
19
- providerIds: aggregate.providerIds,
20
- source: aggregate.source,
21
- openTarget: aggregate.openTarget,
22
- primaryBranch: aggregate.branchName,
23
- })
24
- ),
25
- };
26
- }
27
- }
@@ -1,91 +0,0 @@
1
- import { normalizeIdentityPath } from '@features/recent-projects/main/infrastructure/identity/normalizeIdentityPath';
2
- import { WorktreeGrouper } from '@main/services/discovery/WorktreeGrouper';
3
- import { getProjectsBasePath } from '@main/utils/pathDecoder';
4
- import { isEphemeralProjectPath } from '@shared/utils/ephemeralProjectPath';
5
-
6
- import type { LoggerPort } from '@features/recent-projects/core/application/ports/LoggerPort';
7
- import type {
8
- RecentProjectsSourcePort,
9
- RecentProjectsSourceResult,
10
- } from '@features/recent-projects/core/application/ports/RecentProjectsSourcePort';
11
- import type { RecentProjectCandidate } from '@features/recent-projects/core/domain/models/RecentProjectCandidate';
12
- import type { ServiceContext } from '@main/services';
13
- import type { RepositoryGroup, Worktree } from '@main/types';
14
-
15
- function selectPreferredWorktree(worktrees: readonly Worktree[]): Worktree | undefined {
16
- return worktrees.find((worktree) => worktree.isMainWorktree) ?? worktrees[0];
17
- }
18
-
19
- function toCandidate(repo: RepositoryGroup): RecentProjectCandidate | null {
20
- const selectableWorktrees = repo.worktrees.filter(
21
- (worktree) => !isEphemeralProjectPath(worktree.path)
22
- );
23
-
24
- if (!selectableWorktrees.length || !repo.mostRecentSession) {
25
- return null;
26
- }
27
-
28
- const preferredWorktree = selectPreferredWorktree(selectableWorktrees);
29
- if (!preferredWorktree) {
30
- return null;
31
- }
32
-
33
- return {
34
- identity: repo.identity?.id ?? `path:${normalizeIdentityPath(preferredWorktree.path)}`,
35
- displayName: repo.name,
36
- primaryPath: preferredWorktree.path,
37
- associatedPaths: selectableWorktrees.map((worktree) => worktree.path),
38
- lastActivityAt: repo.mostRecentSession,
39
- providerIds: ['anthropic'],
40
- sourceKind: 'claude',
41
- openTarget: {
42
- type: 'existing-worktree',
43
- repositoryId: repo.id,
44
- worktreeId: preferredWorktree.id,
45
- },
46
- branchName: preferredWorktree.gitBranch,
47
- };
48
- }
49
-
50
- export class ClaudeRecentProjectsSourceAdapter implements RecentProjectsSourcePort {
51
- readonly #localWorktreeGrouper = new WorktreeGrouper(getProjectsBasePath());
52
-
53
- constructor(
54
- private readonly getActiveContext: () => ServiceContext,
55
- private readonly logger: LoggerPort
56
- ) {}
57
-
58
- async list(): Promise<RecentProjectsSourceResult> {
59
- const activeContext = this.getActiveContext();
60
- const groups =
61
- activeContext.type === 'local'
62
- ? await this.#groupLocalProjects(activeContext)
63
- : await activeContext.projectScanner.scanWithWorktreeGrouping();
64
-
65
- const candidates = groups
66
- .map((group) => toCandidate(group))
67
- .filter((candidate): candidate is RecentProjectCandidate => candidate !== null);
68
-
69
- this.logger.info('claude recent-projects source loaded', {
70
- count: candidates.length,
71
- contextId: activeContext.id,
72
- });
73
-
74
- return {
75
- candidates,
76
- degraded: false,
77
- };
78
- }
79
-
80
- async #groupLocalProjects(activeContext: ServiceContext): Promise<RepositoryGroup[]> {
81
- try {
82
- const projects = await activeContext.projectScanner.scan();
83
- return await this.#localWorktreeGrouper.groupByRepository(projects);
84
- } catch (error) {
85
- this.logger.warn('claude recent-projects fell back to simplified grouping', {
86
- error: error instanceof Error ? error.message : String(error),
87
- });
88
- return activeContext.projectScanner.scanWithWorktreeGrouping();
89
- }
90
- }
91
- }
@@ -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';
@@ -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
- }
@@ -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
- }