@yancyyu/openhermit 1.6.37 → 1.6.39

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 (205) hide show
  1. package/dist-renderer/assets/ProjectEditorOverlay-krO5vQxX.js +58 -0
  2. package/dist-renderer/assets/{TeamGraphOverlay-DYT3bwFR.js → TeamGraphOverlay-DqhQzcTr.js} +1 -1
  3. package/dist-renderer/assets/{_basePickBy-Dbt_EU-e.js → _basePickBy-B7kSYPxr.js} +1 -1
  4. package/dist-renderer/assets/{_baseUniq-DWo68sXI.js → _baseUniq-CnjxqwAk.js} +1 -1
  5. package/dist-renderer/assets/{arc-DXH1iZQK.js → arc-CLeZuINP.js} +1 -1
  6. package/dist-renderer/assets/{architectureDiagram-VXUJARFQ-cjffS2Qr.js → architectureDiagram-VXUJARFQ-QKtqaqdY.js} +1 -1
  7. package/dist-renderer/assets/{blockDiagram-VD42YOAC-BKdZF02Y.js → blockDiagram-VD42YOAC-BqdrzO_f.js} +1 -1
  8. package/dist-renderer/assets/{c4Diagram-YG6GDRKO-CN27pqaI.js → c4Diagram-YG6GDRKO-gwPlCxDC.js} +1 -1
  9. package/dist-renderer/assets/channel-DpMHF50r.js +1 -0
  10. package/dist-renderer/assets/{chunk-4BX2VUAB-CXPCI7g_.js → chunk-4BX2VUAB-C6XLurL4.js} +1 -1
  11. package/dist-renderer/assets/{chunk-55IACEB6-BGAXQZRC.js → chunk-55IACEB6-Ds6quhEP.js} +1 -1
  12. package/dist-renderer/assets/{chunk-B4BG7PRW-TPDaA_KQ.js → chunk-B4BG7PRW-5UlA1_e9.js} +1 -1
  13. package/dist-renderer/assets/{chunk-DI55MBZ5-D1ADe_tq.js → chunk-DI55MBZ5-ywFrqIsY.js} +1 -1
  14. package/dist-renderer/assets/{chunk-FMBD7UC4-Beimtg3a.js → chunk-FMBD7UC4-C7ifUA17.js} +1 -1
  15. package/dist-renderer/assets/{chunk-QN33PNHL-OjNBu854.js → chunk-QN33PNHL-BxGCo80U.js} +1 -1
  16. package/dist-renderer/assets/{chunk-QZHKN3VN-DinqvbH8.js → chunk-QZHKN3VN-B2CuaZs6.js} +1 -1
  17. package/dist-renderer/assets/{chunk-TZMSLE5B-BfFtlPSZ.js → chunk-TZMSLE5B-Ds1hInvp.js} +1 -1
  18. package/dist-renderer/assets/classDiagram-2ON5EDUG-CBYCBVRl.js +1 -0
  19. package/dist-renderer/assets/classDiagram-v2-WZHVMYZB-CBYCBVRl.js +1 -0
  20. package/dist-renderer/assets/clone-DcMF6Psb.js +1 -0
  21. package/dist-renderer/assets/{cose-bilkent-S5V4N54A-D9z9Dgt7.js → cose-bilkent-S5V4N54A-Cz1GVtLp.js} +1 -1
  22. package/dist-renderer/assets/{dagre-6UL2VRFP-n1g-DhEE.js → dagre-6UL2VRFP-BrmR-P4h.js} +1 -1
  23. package/dist-renderer/assets/{diagram-PSM6KHXK-BvxFq-BE.js → diagram-PSM6KHXK-DbNjC5Rg.js} +1 -1
  24. package/dist-renderer/assets/{diagram-QEK2KX5R-wVnJuwza.js → diagram-QEK2KX5R-qkRX5_Mq.js} +1 -1
  25. package/dist-renderer/assets/{diagram-S2PKOQOG-B707WJQw.js → diagram-S2PKOQOG-CyL5rCv2.js} +1 -1
  26. package/dist-renderer/assets/{erDiagram-Q2GNP2WA-C-_1dGHs.js → erDiagram-Q2GNP2WA-Dox3-bA5.js} +1 -1
  27. package/dist-renderer/assets/{flowDiagram-NV44I4VS-CMTSi3H6.js → flowDiagram-NV44I4VS-BtkaxlDL.js} +1 -1
  28. package/dist-renderer/assets/{ganttDiagram-JELNMOA3-DZ0bNrAA.js → ganttDiagram-JELNMOA3-Dhy_d9GK.js} +1 -1
  29. package/dist-renderer/assets/{gitGraphDiagram-V2S2FVAM-DNVfGooQ.js → gitGraphDiagram-V2S2FVAM-B5XRhIQA.js} +1 -1
  30. package/dist-renderer/assets/{graph-865j_tM_.js → graph-CsoEwUhS.js} +1 -1
  31. package/dist-renderer/assets/{index-C_F9N5x-.js → index-BWPWmJNo.js} +1 -1
  32. package/dist-renderer/assets/{index-LwDIsXJN.js → index-Bu2R-Se7.js} +586 -740
  33. package/dist-renderer/assets/index-CnWV3BhG.css +32 -0
  34. package/dist-renderer/assets/{index-DuUaf8at.js → index-D-3KgskL.js} +1 -1
  35. package/dist-renderer/assets/{index-BTx1nc4T.js → index-DGEBzLNT.js} +1 -1
  36. package/dist-renderer/assets/{index-2EW-eu3q.js → index-NhHNs2Oo.js} +1 -1
  37. package/dist-renderer/assets/{index-4dEMStJj.js → index-h17WuEyf.js} +1 -1
  38. package/dist-renderer/assets/{infoDiagram-HS3SLOUP-CyqtElLq.js → infoDiagram-HS3SLOUP-hMGmNojH.js} +1 -1
  39. package/dist-renderer/assets/{journeyDiagram-XKPGCS4Q-BvjQ0Hm0.js → journeyDiagram-XKPGCS4Q-DXV2rBDl.js} +1 -1
  40. package/dist-renderer/assets/{kanban-definition-3W4ZIXB7-CJJ-k0zT.js → kanban-definition-3W4ZIXB7-Bf99WLRy.js} +1 -1
  41. package/dist-renderer/assets/{layout-CnV6rQAG.js → layout-C3XWrpwo.js} +1 -1
  42. package/dist-renderer/assets/{linear-Cw3UQgyX.js → linear-OEEcn8KN.js} +1 -1
  43. package/dist-renderer/assets/{mindmap-definition-VGOIOE7T-C5tDaGSK.js → mindmap-definition-VGOIOE7T-Dpi3S2x4.js} +1 -1
  44. package/dist-renderer/assets/{pieDiagram-ADFJNKIX-CiIpPsau.js → pieDiagram-ADFJNKIX-xTPPhtNx.js} +1 -1
  45. package/dist-renderer/assets/{quadrantDiagram-AYHSOK5B-C3gtowNj.js → quadrantDiagram-AYHSOK5B-euniyDlz.js} +1 -1
  46. package/dist-renderer/assets/{requirementDiagram-UZGBJVZJ-CXBTrAnU.js → requirementDiagram-UZGBJVZJ-D9Uiw4kF.js} +1 -1
  47. package/dist-renderer/assets/{sankeyDiagram-TZEHDZUN-wziX77xG.js → sankeyDiagram-TZEHDZUN-CySU4nED.js} +1 -1
  48. package/dist-renderer/assets/{sequenceDiagram-WL72ISMW-sYqopcrj.js → sequenceDiagram-WL72ISMW-JVGpET6V.js} +1 -1
  49. package/dist-renderer/assets/splashScene-D0YB9uxm.js +17 -0
  50. package/dist-renderer/assets/{stateDiagram-FKZM4ZOC-Bl1-0_Cp.js → stateDiagram-FKZM4ZOC-B2FY5qqi.js} +1 -1
  51. package/dist-renderer/assets/stateDiagram-v2-4FDKWEC3-DcoMiR8H.js +1 -0
  52. package/dist-renderer/assets/{timeline-definition-IT6M3QCI-CIRjJUBo.js → timeline-definition-IT6M3QCI-DmycNUUe.js} +1 -1
  53. package/dist-renderer/assets/{treemap-GDKQZRPO-CVPuNe1n.js → treemap-GDKQZRPO-DPq4gZuB.js} +1 -1
  54. package/dist-renderer/assets/{xychartDiagram-PRI3JC2R-3nT9yHwp.js → xychartDiagram-PRI3JC2R-J6VVJzRq.js} +1 -1
  55. package/dist-renderer/index.html +20 -53
  56. package/package.json +25 -18
  57. package/src/main/ipc/extensions.ts +30 -50
  58. package/src/main/server.ts +890 -247
  59. package/src/main/services/extensions/ExtensionFacadeService.ts +4 -56
  60. package/src/main/services/extensions/catalog/PluginCatalogService.ts +4 -2
  61. package/src/main/services/extensions/library/McpLibraryService.ts +243 -0
  62. package/src/main/services/session-intelligence/ConversationTelemetryService.ts +1101 -0
  63. package/src/main/services/session-intelligence/LocalSessionScanner.ts +512 -0
  64. package/src/main/services/session-intelligence/SessionUsageParser.ts +4 -4
  65. package/src/main/services/session-intelligence/UsageTelemetryService.ts +14 -1
  66. package/src/main/services/system-manager/SystemManagerConfigService.ts +122 -0
  67. package/src/main/services/system-manager/SystemManagerPtyService.ts +233 -0
  68. package/src/main/services/system-manager/WorkflowPromptService.ts +75 -0
  69. package/src/main/services/teams-mvp/TaskDispatchService.ts +32 -8
  70. package/src/main/services/teams-mvp/TeamProvisioningService.ts +39 -2
  71. package/src/main/services/teams-mvp/TeamWorkspaceService.ts +22 -4
  72. package/src/main/utils/teamProjectResolution.ts +15 -0
  73. package/src/renderer/App.tsx +8 -4
  74. package/src/renderer/api/httpClient.ts +174 -38
  75. package/src/renderer/api/providers.ts +23 -2
  76. package/src/renderer/assets/participant-avatars/01.svg +3 -0
  77. package/src/renderer/assets/participant-avatars/02.svg +3 -0
  78. package/src/renderer/assets/participant-avatars/03.svg +3 -0
  79. package/src/renderer/assets/participant-avatars/04.svg +3 -0
  80. package/src/renderer/assets/participant-avatars/05.svg +3 -0
  81. package/src/renderer/assets/participant-avatars/06.svg +3 -0
  82. package/src/renderer/assets/participant-avatars/07.svg +3 -0
  83. package/src/renderer/assets/participant-avatars/08.svg +3 -0
  84. package/src/renderer/assets/participant-avatars/09.svg +3 -0
  85. package/src/renderer/assets/participant-avatars/10.svg +3 -0
  86. package/src/renderer/assets/participant-avatars/11.svg +3 -0
  87. package/src/renderer/assets/participant-avatars/12.svg +3 -0
  88. package/src/renderer/assets/participant-avatars/13.svg +3 -0
  89. package/src/renderer/components/common/TerminalPane.tsx +213 -0
  90. package/src/renderer/components/dashboard/DashboardView.tsx +9 -36
  91. package/src/renderer/components/extensions/ExtensionStoreView.tsx +12 -221
  92. package/src/renderer/components/extensions/ExtensionsSubTabTrigger.tsx +1 -1
  93. package/src/renderer/components/extensions/mcp/McpLibraryEnableDialog.tsx +305 -0
  94. package/src/renderer/components/extensions/mcp/McpLibraryEntryDialog.tsx +418 -0
  95. package/src/renderer/components/extensions/mcp/McpLibraryPanel.tsx +404 -0
  96. package/src/renderer/components/extensions/plugins/PluginCard.tsx +10 -2
  97. package/src/renderer/components/extensions/plugins/PluginsPanel.tsx +40 -22
  98. package/src/renderer/components/extensions/skills/SkillsLibraryPanel.tsx +335 -0
  99. package/src/renderer/components/layout/PaneContent.tsx +8 -1
  100. package/src/renderer/components/layout/Sidebar.tsx +11 -54
  101. package/src/renderer/components/layout/SortableTab.tsx +20 -31
  102. package/src/renderer/components/layout/TabBar.tsx +1 -1
  103. package/src/renderer/components/layout/TabContextMenu.tsx +1 -1
  104. package/src/renderer/components/runtime/ProviderRuntimeSettingsDialog.tsx +768 -157
  105. package/src/renderer/components/schedules/SchedulesView.tsx +51 -462
  106. package/src/renderer/components/schedules/calendar/CalendarDayView.tsx +173 -0
  107. package/src/renderer/components/schedules/calendar/CalendarEventBlock.tsx +113 -0
  108. package/src/renderer/components/schedules/calendar/CalendarHeader.tsx +148 -0
  109. package/src/renderer/components/schedules/calendar/CalendarMonthView.tsx +142 -0
  110. package/src/renderer/components/schedules/calendar/CalendarWeekView.tsx +219 -0
  111. package/src/renderer/components/schedules/calendar/ScheduleCalendarBoard.tsx +41 -0
  112. package/src/renderer/components/schedules/calendar/TeamGanttView.tsx +405 -0
  113. package/src/renderer/components/schedules/calendar/computeOccurrences.ts +234 -0
  114. package/src/renderer/components/schedules/calendar/index.ts +2 -0
  115. package/src/renderer/components/schedules/calendar/types.ts +44 -0
  116. package/src/renderer/components/settings/SettingsTabs.tsx +50 -55
  117. package/src/renderer/components/settings/SettingsView.tsx +30 -35
  118. package/src/renderer/components/settings/components/SettingsSectionHeader.tsx +5 -1
  119. package/src/renderer/components/settings/components/SettingsSelect.tsx +5 -3
  120. package/src/renderer/components/settings/components/SettingsToggle.tsx +2 -2
  121. package/src/renderer/components/settings/sections/AdvancedSection.tsx +11 -42
  122. package/src/renderer/components/settings/sections/CliStatusSection.tsx +71 -112
  123. package/src/renderer/components/settings/sections/ConfigEditorDialog.tsx +1 -1
  124. package/src/renderer/components/settings/sections/GeneralSection.tsx +11 -3
  125. package/src/renderer/components/settings/sections/HarnessSection.tsx +18 -14
  126. package/src/renderer/components/settings/sections/PlatformsSection.tsx +3 -3
  127. package/src/renderer/components/settings/sections/TaskBusSection.tsx +33 -40
  128. package/src/renderer/components/settings/sections/index.ts +0 -1
  129. package/src/renderer/components/sidebar/SidebarSessions.tsx +182 -4
  130. package/src/renderer/components/sidebar/SidebarTaskItem.tsx +4 -4
  131. package/src/renderer/components/sidebar/WorkspaceBrowser.tsx +39 -4
  132. package/src/renderer/components/splash/splashScene.ts +121 -929
  133. package/src/renderer/components/system-manager/FolderBrowser.tsx +163 -0
  134. package/src/renderer/components/system-manager/SystemManagerView.tsx +351 -0
  135. package/src/renderer/components/tasks/TasksView.tsx +112 -134
  136. package/src/renderer/components/team/CcSessionsSection.tsx +431 -89
  137. package/src/renderer/components/team/CollapsibleTeamSection.tsx +17 -32
  138. package/src/renderer/components/team/TeamDetailView.tsx +325 -114
  139. package/src/renderer/components/team/TeamListView.tsx +108 -123
  140. package/src/renderer/components/team/dialogs/CreateTaskDialog.tsx +2 -2
  141. package/src/renderer/components/team/dialogs/CreateTeamDialog.tsx +84 -306
  142. package/src/renderer/components/team/dialogs/EditTeamDialog.tsx +259 -342
  143. package/src/renderer/components/team/dialogs/GlobalTaskDetailDialog.tsx +1 -1
  144. package/src/renderer/components/team/dialogs/LaunchTeamDialog.tsx +17 -15
  145. package/src/renderer/components/team/dialogs/PlatformBindingDialog.tsx +221 -0
  146. package/src/renderer/components/team/dialogs/PlatformManualForm.tsx +7 -0
  147. package/src/renderer/components/team/dialogs/PlatformSetupQR.tsx +1 -1
  148. package/src/renderer/components/team/dialogs/RuntimeConfigDialog.tsx +361 -0
  149. package/src/renderer/components/team/dialogs/platformMeta.ts +122 -11
  150. package/src/renderer/components/team/dialogs/useTeamEditForm.ts +17 -5
  151. package/src/renderer/components/team/kanban/KanbanBoard.tsx +9 -9
  152. package/src/renderer/components/team/members/MemberCard.tsx +14 -47
  153. package/src/renderer/components/team/members/MemberDetailDialog.tsx +3 -95
  154. package/src/renderer/components/team/members/MemberDetailStats.tsx +50 -65
  155. package/src/renderer/components/team/messages/MessageComposer.tsx +8 -110
  156. package/src/renderer/components/team/messages/MessagesPanel.tsx +131 -114
  157. package/src/renderer/components/team/schedule/ScheduleStatusBadge.tsx +2 -2
  158. package/src/renderer/components/team/tools/AddMcpInline.tsx +57 -0
  159. package/src/renderer/components/team/tools/AddSkillInline.tsx +61 -0
  160. package/src/renderer/components/team/tools/McpChip.tsx +45 -0
  161. package/src/renderer/components/team/tools/SkillChip.tsx +35 -0
  162. package/src/renderer/components/team/tools/ToolsSection.tsx +556 -0
  163. package/src/renderer/hooks/useExtensionsTabState.ts +3 -114
  164. package/src/renderer/index.css +39 -22
  165. package/src/renderer/index.html +17 -50
  166. package/src/renderer/store/index.ts +2 -1
  167. package/src/renderer/store/slices/scheduleSlice.ts +1 -1
  168. package/src/renderer/store/slices/teamSlice.ts +45 -168
  169. package/src/renderer/utils/claudeCodeOnlyProviders.ts +3 -10
  170. package/src/renderer/utils/memberHelpers.ts +5 -17
  171. package/src/renderer/utils/openCodeRuntimeDeliveryDiagnostics.ts +4 -2
  172. package/src/renderer/utils/providerSlashCommands.ts +0 -5
  173. package/src/renderer/utils/scheduleFormatters.ts +3 -1
  174. package/src/renderer/utils/teamMessageFiltering.ts +14 -1
  175. package/src/renderer/utils/teamModelAvailability.ts +18 -2
  176. package/src/shared/types/api.ts +121 -2
  177. package/src/shared/types/ccConnect.ts +2 -0
  178. package/src/shared/types/extensions/api.ts +9 -0
  179. package/src/shared/types/extensions/index.ts +4 -0
  180. package/src/shared/types/extensions/mcp.ts +41 -0
  181. package/src/shared/types/index.ts +3 -0
  182. package/src/shared/types/systemManager.ts +49 -0
  183. package/src/shared/types/team.ts +29 -0
  184. package/src/shared/types/terminal.ts +4 -2
  185. package/src/shared/utils/extensionNormalizers.ts +29 -0
  186. package/src/shared/utils/providerExtensionCapabilities.ts +2 -2
  187. package/dist-renderer/assets/ProjectEditorOverlay-Va_Vz-zz.js +0 -52
  188. package/dist-renderer/assets/channel-5dJIx68e.js +0 -1
  189. package/dist-renderer/assets/classDiagram-2ON5EDUG-BMGXWJ2d.js +0 -1
  190. package/dist-renderer/assets/classDiagram-v2-WZHVMYZB-BMGXWJ2d.js +0 -1
  191. package/dist-renderer/assets/clone-D7FWfGY9.js +0 -1
  192. package/dist-renderer/assets/index-B2z_IyRH.css +0 -1
  193. package/dist-renderer/assets/splashScene-C8lWNnm4.js +0 -1
  194. package/dist-renderer/assets/stateDiagram-v2-4FDKWEC3-DOYYvDbi.js +0 -1
  195. package/src/main/services/extensions/catalog/GlamaMcpEnrichmentService.ts +0 -190
  196. package/src/main/services/extensions/catalog/McpCatalogAggregator.ts +0 -150
  197. package/src/main/services/extensions/catalog/OfficialMcpRegistryService.ts +0 -381
  198. package/src/main/services/extensions/install/McpInstallService.ts +0 -407
  199. package/src/main/services/extensions/state/McpInstallationStateService.ts +0 -42
  200. package/src/renderer/components/extensions/mcp/McpServerCard.tsx +0 -314
  201. package/src/renderer/components/extensions/mcp/McpServerDetailDialog.tsx +0 -765
  202. package/src/renderer/components/extensions/mcp/McpServersPanel.tsx +0 -593
  203. package/src/renderer/components/extensions/skills/SkillDetailDialog.tsx +0 -372
  204. package/src/renderer/components/extensions/skills/SkillImportDialog.tsx +0 -343
  205. package/src/renderer/components/extensions/skills/SkillsPanel.tsx +0 -659
@@ -0,0 +1,512 @@
1
+ /**
2
+ * LocalSessionScanner — scans local JSONL session files for a team's workDir
3
+ * and returns lightweight summaries + on-demand message detail with pagination.
4
+ *
5
+ * Replaces the cc-connect dependency for team session listing/detail.
6
+ * Reuses patterns from SessionUsageParser (walkJsonl, streaming parse, stat caching).
7
+ */
8
+
9
+ import { createReadStream } from 'node:fs';
10
+ import { readdir, stat } from 'node:fs/promises';
11
+ import { createInterface } from 'node:readline';
12
+ import * as path from 'node:path';
13
+
14
+ import { getProjectsBasePath, encodePath } from '@main/utils/pathDecoder';
15
+
16
+ // ---------------------------------------------------------------------------
17
+ // Types
18
+ // ---------------------------------------------------------------------------
19
+
20
+ export interface LocalSessionSummary {
21
+ id: string;
22
+ title: string;
23
+ projectId: string;
24
+ messageCount: number;
25
+ userMessageCount: number;
26
+ assistantMessageCount: number;
27
+ inputTokens: number;
28
+ outputTokens: number;
29
+ cacheReadTokens: number;
30
+ cacheCreationTokens: number;
31
+ model: string;
32
+ active: boolean;
33
+ live: boolean;
34
+ startTime: string | null;
35
+ endTime: string | null;
36
+ createdAt: string;
37
+ updatedAt: string;
38
+ }
39
+
40
+ export interface LocalSessionMessage {
41
+ role: 'user' | 'assistant';
42
+ content: string;
43
+ timestamp: string;
44
+ }
45
+
46
+ export interface LocalSessionDetail {
47
+ id: string;
48
+ name: string;
49
+ sessionKey: string;
50
+ agentType: string;
51
+ active: boolean;
52
+ live: boolean;
53
+ historyCount: number;
54
+ createdAt: string;
55
+ updatedAt: string;
56
+ platform: string;
57
+ history: LocalSessionMessage[];
58
+ model: string;
59
+ inputTokens: number;
60
+ outputTokens: number;
61
+ }
62
+
63
+ // ---------------------------------------------------------------------------
64
+ // Internal types
65
+ // ---------------------------------------------------------------------------
66
+
67
+ interface SummaryCacheEntry {
68
+ size: number;
69
+ mtimeMs: number;
70
+ summary: LocalSessionSummary;
71
+ }
72
+
73
+ interface PartialSummary {
74
+ title: string;
75
+ model: string;
76
+ messageCount: number;
77
+ userMessageCount: number;
78
+ assistantMessageCount: number;
79
+ inputTokens: number;
80
+ outputTokens: number;
81
+ cacheReadTokens: number;
82
+ cacheCreationTokens: number;
83
+ startTime: string | null;
84
+ endTime: string | null;
85
+ lastRole: string;
86
+ }
87
+
88
+ // ---------------------------------------------------------------------------
89
+ // Constants
90
+ // ---------------------------------------------------------------------------
91
+
92
+ const ACTIVE_THRESHOLD_MS = 30 * 60 * 1000; // 30 minutes
93
+ const SUMMARY_SCAN_LINES = 200; // Read first N lines for lightweight summary
94
+
95
+ // ---------------------------------------------------------------------------
96
+ // Helpers (adapted from SessionUsageParser & ConversationTelemetryService)
97
+ // ---------------------------------------------------------------------------
98
+
99
+ /**
100
+ * Extract readable text from any content block type.
101
+ * Handles: text, thinking, tool_use, tool_result, image.
102
+ * Mirrors ConversationTelemetryService.extractText().
103
+ */
104
+ function extractText(content: unknown): string {
105
+ if (typeof content === 'string') return content;
106
+ if (!Array.isArray(content)) return '';
107
+
108
+ const parts: string[] = [];
109
+ for (const block of content) {
110
+ if (!block || typeof block !== 'object') continue;
111
+ const b = block as Record<string, unknown>;
112
+ if (b.type === 'text' && typeof b.text === 'string') {
113
+ parts.push(b.text);
114
+ } else if (b.type === 'thinking' && typeof b.thinking === 'string') {
115
+ parts.push('[Thinking omitted]');
116
+ } else if (b.type === 'tool_use' && typeof b.name === 'string') {
117
+ parts.push(`[Tool: ${b.name}${toolInputSummary(b.name, b.input)}]`);
118
+ } else if (b.type === 'tool_result') {
119
+ const nested = b.content;
120
+ if (typeof nested === 'string') parts.push(nested);
121
+ else if (Array.isArray(nested)) parts.push(extractText(nested));
122
+ } else if (b.type === 'image') {
123
+ parts.push('[Image]');
124
+ }
125
+ }
126
+ return parts.filter(Boolean).join('\n');
127
+ }
128
+
129
+ /**
130
+ * Produce a short human-readable summary of a tool_use input.
131
+ * e.g. "Read /src/main/server.ts", "Edit /src/main/server.ts", "Bash pnpm test"
132
+ */
133
+ function toolInputSummary(toolName: string, input: unknown): string {
134
+ if (!input || typeof input !== 'object') return '';
135
+ const inp = input as Record<string, unknown>;
136
+
137
+ // Common patterns by tool name
138
+ const fileKey = inp.file_path ?? inp.filePath ?? inp.path;
139
+ const cmdKey = inp.command ?? inp.description;
140
+
141
+ if (typeof fileKey === 'string') {
142
+ // Show only the last 2 path segments to keep it short
143
+ const short = fileKey.split('/').slice(-2).join('/');
144
+ return ` ${short}`;
145
+ }
146
+ if (typeof cmdKey === 'string') {
147
+ return ` ${cmdKey.slice(0, 80)}`;
148
+ }
149
+
150
+ // Generic: show first string-valued field
151
+ for (const value of Object.values(inp)) {
152
+ if (typeof value === 'string' && value.length > 0) {
153
+ return ` ${value.slice(0, 80)}`;
154
+ }
155
+ }
156
+ return '';
157
+ }
158
+
159
+ function extractFirstUserText(content: unknown): string {
160
+ const text = extractText(content);
161
+ return text.slice(0, 200).trim();
162
+ }
163
+
164
+ function smartTitle(text: string, maxLen = 90): string {
165
+ if (!text) return '';
166
+ let t = text;
167
+ if (t.startsWith('@') && t.includes(' ')) {
168
+ t = t.slice(t.indexOf(' ') + 1).trim();
169
+ }
170
+ let cut = t.length;
171
+ for (const sep of ['\n', '。', '?', '!', ';']) {
172
+ const i = t.indexOf(sep);
173
+ if (5 < i && i < maxLen && i < cut) cut = i;
174
+ }
175
+ return t.slice(0, cut < t.length ? cut : maxLen).trim();
176
+ }
177
+
178
+ async function* walkJsonl(dir: string): AsyncGenerator<string> {
179
+ let entries;
180
+ try {
181
+ entries = await readdir(dir, { withFileTypes: true });
182
+ } catch {
183
+ return;
184
+ }
185
+ for (const entry of entries) {
186
+ const full = path.join(dir, entry.name);
187
+ if (entry.isDirectory()) {
188
+ yield* walkJsonl(full);
189
+ } else if (entry.isFile() && entry.name.endsWith('.jsonl') && !entry.name.startsWith('agent_')) {
190
+ yield full;
191
+ }
192
+ }
193
+ }
194
+
195
+ /**
196
+ * Quickly scan the first N lines of a JSONL file to extract a lightweight summary.
197
+ * This avoids parsing the entire file for the session list.
198
+ */
199
+ async function scanSummaryLines(
200
+ filePath: string,
201
+ sessionId: string,
202
+ projectId: string
203
+ ): Promise<PartialSummary | null> {
204
+ const result: PartialSummary = {
205
+ title: '',
206
+ model: '',
207
+ messageCount: 0,
208
+ userMessageCount: 0,
209
+ assistantMessageCount: 0,
210
+ inputTokens: 0,
211
+ outputTokens: 0,
212
+ cacheReadTokens: 0,
213
+ cacheCreationTokens: 0,
214
+ startTime: null,
215
+ endTime: null,
216
+ lastRole: '',
217
+ };
218
+
219
+ let lineCount = 0;
220
+
221
+ const rl = createInterface({
222
+ input: createReadStream(filePath, 'utf-8'),
223
+ crlfDelay: Infinity,
224
+ });
225
+
226
+ for await (const rawLine of rl) {
227
+ lineCount++;
228
+ if (lineCount > SUMMARY_SCAN_LINES) break;
229
+
230
+ const line = rawLine.trim();
231
+ if (!line) continue;
232
+
233
+ let obj: Record<string, unknown>;
234
+ try {
235
+ obj = JSON.parse(line) as Record<string, unknown>;
236
+ } catch {
237
+ continue;
238
+ }
239
+
240
+ const msg = obj.message as Record<string, unknown> | undefined;
241
+ let role: string | undefined;
242
+ let content: unknown;
243
+ let usage: Record<string, unknown> | undefined;
244
+ let ts: string | undefined;
245
+
246
+ if (msg && typeof msg === 'object') {
247
+ role = msg.role as string | undefined;
248
+ content = msg.content;
249
+ usage = msg.usage as Record<string, unknown> | undefined;
250
+ ts = (obj.timestamp ?? msg.timestamp) as string | undefined;
251
+ } else if (obj.type === 'user' || obj.type === 'assistant') {
252
+ role = obj.type as string;
253
+ content = obj.content;
254
+ ts = obj.timestamp as string | undefined;
255
+ }
256
+
257
+ if (!role || !ts) continue;
258
+
259
+ result.messageCount++;
260
+ if (!result.startTime) result.startTime = ts;
261
+ result.endTime = ts;
262
+ result.lastRole = role;
263
+
264
+ if (role === 'user') {
265
+ result.userMessageCount++;
266
+ if (!result.title && content) {
267
+ result.title = smartTitle(extractFirstUserText(content));
268
+ }
269
+ } else if (role === 'assistant') {
270
+ result.assistantMessageCount++;
271
+
272
+ // Extract model
273
+ if (!result.model) {
274
+ const model = msg?.model ?? obj.model;
275
+ if (typeof model === 'string' && model) {
276
+ result.model = model;
277
+ }
278
+ }
279
+
280
+ // Accumulate token usage
281
+ if (usage && typeof usage === 'object') {
282
+ result.inputTokens += Number(usage.input_tokens ?? 0) || 0;
283
+ result.outputTokens += Number(usage.output_tokens ?? 0) || 0;
284
+ result.cacheReadTokens += Number(usage.cache_read_input_tokens ?? 0) || 0;
285
+ result.cacheCreationTokens += Number(usage.cache_creation_input_tokens ?? 0) || 0;
286
+ }
287
+ }
288
+ }
289
+
290
+ if (result.messageCount === 0) return null;
291
+ return result;
292
+ }
293
+
294
+ // ---------------------------------------------------------------------------
295
+ // LocalSessionScanner class
296
+ // ---------------------------------------------------------------------------
297
+
298
+ export class LocalSessionScanner {
299
+ private summaryCache = new Map<string, SummaryCacheEntry>();
300
+
301
+ /**
302
+ * Resolve the JSONL directory for a given workDir.
303
+ * workDir is the absolute filesystem path (e.g., "/Users/name/project").
304
+ * The JSONL files live at ~/.claude/projects/{encoded-workDir}/
305
+ */
306
+ private resolveJsonlDir(workDir: string): string {
307
+ const projectsBase = getProjectsBasePath();
308
+ const encoded = encodePath(workDir);
309
+ return path.join(projectsBase, encoded);
310
+ }
311
+
312
+ /**
313
+ * Scan all JSONL session files for a team's workDir and return lightweight summaries.
314
+ * Uses file stat caching to skip unchanged files on subsequent calls.
315
+ */
316
+ async scanSummaries(workDir: string, projectId: string): Promise<LocalSessionSummary[]> {
317
+ const jsonlDir = this.resolveJsonlDir(workDir);
318
+ const summaries: LocalSessionSummary[] = [];
319
+ const now = Date.now();
320
+
321
+ for await (const filePath of walkJsonl(jsonlDir)) {
322
+ let fileStat;
323
+ try {
324
+ fileStat = await stat(filePath);
325
+ } catch {
326
+ continue;
327
+ }
328
+
329
+ // Check cache
330
+ const cached = this.summaryCache.get(filePath);
331
+ if (cached && cached.size === fileStat.size && cached.mtimeMs === fileStat.mtimeMs) {
332
+ summaries.push(cached.summary);
333
+ continue;
334
+ }
335
+
336
+ const sessionId = path.basename(filePath, '.jsonl');
337
+ const partial = await scanSummaryLines(filePath, sessionId, projectId);
338
+ if (!partial) continue;
339
+
340
+ const mtimeMs = fileStat.mtimeMs;
341
+ const active = now - mtimeMs < ACTIVE_THRESHOLD_MS;
342
+ const live = active && partial.lastRole === 'assistant';
343
+
344
+ // For a more accurate messageCount and token totals, we need the full file.
345
+ // But the first SUMMARY_SCAN_LINES is a good approximation for the list view.
346
+ // We'll mark the count as approximate if we stopped early.
347
+ const summary: LocalSessionSummary = {
348
+ id: sessionId,
349
+ title: partial.title || sessionId,
350
+ projectId,
351
+ messageCount: partial.messageCount,
352
+ userMessageCount: partial.userMessageCount,
353
+ assistantMessageCount: partial.assistantMessageCount,
354
+ inputTokens: partial.inputTokens,
355
+ outputTokens: partial.outputTokens,
356
+ cacheReadTokens: partial.cacheReadTokens,
357
+ cacheCreationTokens: partial.cacheCreationTokens,
358
+ model: partial.model,
359
+ active,
360
+ live,
361
+ startTime: partial.startTime,
362
+ endTime: partial.endTime,
363
+ createdAt: fileStat.birthtime?.toISOString() ?? new Date(mtimeMs).toISOString(),
364
+ updatedAt: new Date(mtimeMs).toISOString(),
365
+ };
366
+
367
+ this.summaryCache.set(filePath, {
368
+ size: fileStat.size,
369
+ mtimeMs,
370
+ summary,
371
+ });
372
+
373
+ summaries.push(summary);
374
+ }
375
+
376
+ // Sort by endTime descending (most recent first)
377
+ summaries.sort((a, b) => {
378
+ const ta = a.endTime ? Date.parse(a.endTime) : 0;
379
+ const tb = b.endTime ? Date.parse(b.endTime) : 0;
380
+ return tb - ta;
381
+ });
382
+
383
+ return summaries;
384
+ }
385
+
386
+ /**
387
+ * Read a single session's detail with paginated message history.
388
+ * Messages are returned in chronological order.
389
+ * offset=0, limit=200 returns the first 200 messages.
390
+ * offset=200, limit=200 returns messages 201-400.
391
+ */
392
+ async readSessionDetail(
393
+ workDir: string,
394
+ sessionId: string,
395
+ options?: { offset?: number; limit?: number }
396
+ ): Promise<LocalSessionDetail | null> {
397
+ const jsonlDir = this.resolveJsonlDir(workDir);
398
+ const filePath = path.join(jsonlDir, `${sessionId}.jsonl`);
399
+
400
+ // Check file exists
401
+ let fileStat;
402
+ try {
403
+ fileStat = await stat(filePath);
404
+ } catch {
405
+ return null;
406
+ }
407
+
408
+ const offset = options?.offset ?? 0;
409
+ const limit = options?.limit ?? 200;
410
+
411
+ const messages: LocalSessionMessage[] = [];
412
+ let totalMessages = 0;
413
+ let model = '';
414
+ let inputTokens = 0;
415
+ let outputTokens = 0;
416
+ let firstTs: string | null = null;
417
+ let lastTs: string | null = null;
418
+
419
+ const rl = createInterface({
420
+ input: createReadStream(filePath, 'utf-8'),
421
+ crlfDelay: Infinity,
422
+ });
423
+
424
+ for await (const rawLine of rl) {
425
+ const line = rawLine.trim();
426
+ if (!line) continue;
427
+
428
+ let obj: Record<string, unknown>;
429
+ try {
430
+ obj = JSON.parse(line) as Record<string, unknown>;
431
+ } catch {
432
+ continue;
433
+ }
434
+
435
+ const msg = obj.message as Record<string, unknown> | undefined;
436
+ let role: string | undefined;
437
+ let content: unknown;
438
+ let usage: Record<string, unknown> | undefined;
439
+ let ts: string | undefined;
440
+
441
+ if (msg && typeof msg === 'object') {
442
+ role = msg.role as string | undefined;
443
+ content = msg.content;
444
+ usage = msg.usage as Record<string, unknown> | undefined;
445
+ ts = (obj.timestamp ?? msg.timestamp) as string | undefined;
446
+ } else if (obj.type === 'user' || obj.type === 'assistant') {
447
+ role = obj.type as string;
448
+ content = obj.content;
449
+ ts = obj.timestamp as string | undefined;
450
+ }
451
+
452
+ if (!role || !ts) continue;
453
+ if (role !== 'user' && role !== 'assistant') continue;
454
+
455
+ totalMessages++;
456
+
457
+ if (!firstTs) firstTs = ts;
458
+ lastTs = ts;
459
+
460
+ // Extract model from first assistant message
461
+ if (role === 'assistant' && !model) {
462
+ const m = msg?.model ?? obj.model;
463
+ if (typeof m === 'string' && m) model = m;
464
+ }
465
+
466
+ // Accumulate tokens
467
+ if (role === 'assistant' && usage && typeof usage === 'object') {
468
+ inputTokens += Number(usage.input_tokens ?? 0) || 0;
469
+ outputTokens += Number(usage.output_tokens ?? 0) || 0;
470
+ }
471
+
472
+ // Collect messages within the page range
473
+ if (totalMessages > offset && messages.length < limit) {
474
+ messages.push({
475
+ role: role as 'user' | 'assistant',
476
+ content: extractText(content),
477
+ timestamp: ts,
478
+ });
479
+ }
480
+ }
481
+
482
+ if (totalMessages === 0) return null;
483
+
484
+ const now = Date.now();
485
+ const mtimeMs = fileStat.mtimeMs;
486
+ const active = now - mtimeMs < ACTIVE_THRESHOLD_MS;
487
+
488
+ return {
489
+ id: sessionId,
490
+ name: '',
491
+ sessionKey: sessionId,
492
+ agentType: '',
493
+ active,
494
+ live: active,
495
+ historyCount: totalMessages,
496
+ createdAt: fileStat.birthtime?.toISOString() ?? new Date(mtimeMs).toISOString(),
497
+ updatedAt: new Date(mtimeMs).toISOString(),
498
+ platform: 'local',
499
+ history: messages,
500
+ model,
501
+ inputTokens,
502
+ outputTokens,
503
+ };
504
+ }
505
+
506
+ /**
507
+ * Clear the summary cache. Useful for testing or forced refresh.
508
+ */
509
+ clearCache(): void {
510
+ this.summaryCache.clear();
511
+ }
512
+ }
@@ -10,7 +10,7 @@ import { createReadStream } from 'node:fs';
10
10
  import { readdir, stat } from 'node:fs/promises';
11
11
  import { createInterface } from 'node:readline';
12
12
  import * as path from 'node:path';
13
- import * as os from 'node:os';
13
+ import { getProjectsBasePath } from '@main/utils/pathDecoder';
14
14
 
15
15
  export interface SessionEntry {
16
16
  relPath: string;
@@ -79,7 +79,6 @@ export interface ParseResult {
79
79
  aggregate: UsageAggregate;
80
80
  }
81
81
 
82
- const PROJECTS_ROOT = path.join(os.homedir(), '.claude', 'projects');
83
82
  const SEG_GAP_MS = 10 * 60 * 1000; // 10 minutes gap threshold
84
83
  const RECENT_DAYS = 7;
85
84
 
@@ -349,8 +348,9 @@ export async function scanSessions(): Promise<ParseResult> {
349
348
  const activeDaySet = new Set<string>();
350
349
  const allEvents: EventEntry[] = [];
351
350
  const projectMap: Record<string, ProjectMetricsEntry> = {};
351
+ const projectsRoot = getProjectsBasePath();
352
352
 
353
- for await (const filePath of walkJsonl(PROJECTS_ROOT)) {
353
+ for await (const filePath of walkJsonl(projectsRoot)) {
354
354
  let fileStat;
355
355
  try {
356
356
  fileStat = await stat(filePath);
@@ -361,7 +361,7 @@ export async function scanSessions(): Promise<ParseResult> {
361
361
  const parsed = await parseJsonl(filePath);
362
362
  if (!parsed) continue;
363
363
 
364
- const relPath = path.relative(PROJECTS_ROOT, filePath);
364
+ const relPath = path.relative(projectsRoot, filePath);
365
365
  sessions.push({
366
366
  relPath,
367
367
  projectPath: parsed.projectPath,
@@ -26,6 +26,8 @@ function redisConfig(cfg: TaskBusConfig) {
26
26
  password: cfg.redis.password,
27
27
  db: cfg.redis.db,
28
28
  lazyConnect: true,
29
+ maxRetriesPerRequest: 0,
30
+ retryStrategy: () => null,
29
31
  };
30
32
  }
31
33
 
@@ -39,6 +41,9 @@ async function getRedis(cfg: TaskBusConfig): Promise<Redis | null> {
39
41
  }
40
42
 
41
43
  const r = new Redis(redisConfig(cfg));
44
+ r.on('error', () => {
45
+ /* handled by connect/ping fallback */
46
+ });
42
47
  try {
43
48
  await r.connect();
44
49
  await r.ping();
@@ -107,7 +112,7 @@ async function doScan(cfg: TaskBusConfig): Promise<ParseResult | null> {
107
112
  const result = await scanSessions();
108
113
  lastLocalScan = statusFromParseResult(result, false);
109
114
 
110
- if (!cfg.telemetry.uploadEnabled) {
115
+ if (!cfg.enabled || !cfg.telemetry.uploadEnabled) {
111
116
  return result;
112
117
  }
113
118
 
@@ -212,10 +217,18 @@ export async function getTelemetryStatus(
212
217
 
213
218
  const cfg = { redis: redisCfg };
214
219
  const client = new Redis(redisConfig(cfg as TaskBusConfig));
220
+ client.on('error', () => {
221
+ /* handled by connect/ping fallback */
222
+ });
215
223
  try {
216
224
  await client.connect();
217
225
  await client.ping();
218
226
  } catch {
227
+ try {
228
+ client.disconnect();
229
+ } catch {
230
+ /* ignore */
231
+ }
219
232
  return lastLocalScan;
220
233
  }
221
234