@yancyyu/openhermit 1.6.41 → 1.6.43

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 (281) hide show
  1. package/README.md +98 -89
  2. package/bin/hermit.mjs +96 -0
  3. package/dist-renderer/assets/{ProjectEditorOverlay-Br0X83Jf.js → ProjectEditorOverlay-C98qSs7-.js} +1 -1
  4. package/dist-renderer/assets/{TeamGraphOverlay-DHMTbZPZ.js → TeamGraphOverlay-CsBbZwcL.js} +1 -1
  5. package/dist-renderer/assets/{_basePickBy-DzIiX7yH.js → _basePickBy-ZOyLWjMK.js} +1 -1
  6. package/dist-renderer/assets/{_baseUniq-6hZuzTLU.js → _baseUniq-DBb726rt.js} +1 -1
  7. package/dist-renderer/assets/{arc-CXgO6fx_.js → arc-CdiTaR_R.js} +1 -1
  8. package/dist-renderer/assets/{architectureDiagram-VXUJARFQ-DKWgtDHr.js → architectureDiagram-VXUJARFQ-Cz3sc5TH.js} +1 -1
  9. package/dist-renderer/assets/{blockDiagram-VD42YOAC-DOMUcC40.js → blockDiagram-VD42YOAC-DE4c-KJ3.js} +1 -1
  10. package/dist-renderer/assets/{c4Diagram-YG6GDRKO-B_k2L7qX.js → c4Diagram-YG6GDRKO-CmTMDTrV.js} +1 -1
  11. package/dist-renderer/assets/channel-KTpqi9eT.js +1 -0
  12. package/dist-renderer/assets/{chunk-4BX2VUAB-BeD_ccFy.js → chunk-4BX2VUAB-rhHy3tFl.js} +1 -1
  13. package/dist-renderer/assets/{chunk-55IACEB6-ClZfkA5w.js → chunk-55IACEB6-fLZBzuo_.js} +1 -1
  14. package/dist-renderer/assets/{chunk-B4BG7PRW-5XluxXsn.js → chunk-B4BG7PRW-DOzxQhim.js} +1 -1
  15. package/dist-renderer/assets/{chunk-DI55MBZ5-BzIjjNVm.js → chunk-DI55MBZ5-COQCcXC5.js} +1 -1
  16. package/dist-renderer/assets/{chunk-FMBD7UC4-HgH3MK_H.js → chunk-FMBD7UC4-IKU9U_Y4.js} +1 -1
  17. package/dist-renderer/assets/{chunk-QN33PNHL-WeC5T3Ba.js → chunk-QN33PNHL-D6WV154X.js} +1 -1
  18. package/dist-renderer/assets/{chunk-QZHKN3VN-Cu1ApHfW.js → chunk-QZHKN3VN-D90_2DQp.js} +1 -1
  19. package/dist-renderer/assets/{chunk-TZMSLE5B-BOhlynJM.js → chunk-TZMSLE5B-BQEil57G.js} +1 -1
  20. package/dist-renderer/assets/classDiagram-2ON5EDUG-lpzulY5X.js +1 -0
  21. package/dist-renderer/assets/classDiagram-v2-WZHVMYZB-lpzulY5X.js +1 -0
  22. package/dist-renderer/assets/clone-CriGymY9.js +1 -0
  23. package/dist-renderer/assets/{cose-bilkent-S5V4N54A-DGZSihDQ.js → cose-bilkent-S5V4N54A-6WiK6U2P.js} +1 -1
  24. package/dist-renderer/assets/{dagre-6UL2VRFP-CnxwCbku.js → dagre-6UL2VRFP-DF4MMuTn.js} +1 -1
  25. package/dist-renderer/assets/{diagram-PSM6KHXK-DsIhoxdI.js → diagram-PSM6KHXK-CcF1eZ7E.js} +1 -1
  26. package/dist-renderer/assets/{diagram-QEK2KX5R-Cmh9KUF5.js → diagram-QEK2KX5R-DYlOVPQB.js} +1 -1
  27. package/dist-renderer/assets/{diagram-S2PKOQOG-CKxV456A.js → diagram-S2PKOQOG-BHXWsZOP.js} +1 -1
  28. package/dist-renderer/assets/{erDiagram-Q2GNP2WA-EnvYjOjc.js → erDiagram-Q2GNP2WA-GjmuBx8d.js} +1 -1
  29. package/dist-renderer/assets/{flowDiagram-NV44I4VS-BmNeWY_A.js → flowDiagram-NV44I4VS-BuS7YVHk.js} +1 -1
  30. package/dist-renderer/assets/{ganttDiagram-JELNMOA3-D30fyK-u.js → ganttDiagram-JELNMOA3-3Teu5tAa.js} +1 -1
  31. package/dist-renderer/assets/{gitGraphDiagram-V2S2FVAM-CrUNiYg1.js → gitGraphDiagram-V2S2FVAM-BiLdCYu5.js} +1 -1
  32. package/dist-renderer/assets/{graph-CY1gTfTb.js → graph-CDP_R8ct.js} +1 -1
  33. package/dist-renderer/assets/{index-CaEbzwAU.js → index-BSZdT-g-.js} +1 -1
  34. package/dist-renderer/assets/{index-D5K-SjBG.js → index-BhWvMqsz.js} +1 -1
  35. package/dist-renderer/assets/{index-9_hO4N1e.js → index-C2_AupSj.js} +1 -1
  36. package/dist-renderer/assets/{index-59r209c1.js → index-C5ujiwAR.js} +580 -588
  37. package/dist-renderer/assets/index-CIS2CTK9.css +1 -0
  38. package/dist-renderer/assets/{index-DMR9B1UP.js → index-CVNjLwkq.js} +1 -1
  39. package/dist-renderer/assets/{index-BC2hXmg_.js → index-CwG3se0q.js} +1 -1
  40. package/dist-renderer/assets/{infoDiagram-HS3SLOUP-By_XUlcD.js → infoDiagram-HS3SLOUP-DLHUFo72.js} +1 -1
  41. package/dist-renderer/assets/{journeyDiagram-XKPGCS4Q-BM1LJE9m.js → journeyDiagram-XKPGCS4Q-BE07RpJD.js} +1 -1
  42. package/dist-renderer/assets/{kanban-definition-3W4ZIXB7-DHIW3aTA.js → kanban-definition-3W4ZIXB7-DDHZy4NB.js} +1 -1
  43. package/dist-renderer/assets/{layout-DAKiL_Mo.js → layout-5nA5wUxO.js} +1 -1
  44. package/dist-renderer/assets/{linear-DwOaRYea.js → linear-BtF1i2qN.js} +1 -1
  45. package/dist-renderer/assets/{mindmap-definition-VGOIOE7T-b7bJ2cha.js → mindmap-definition-VGOIOE7T-Z1Ui9Sqy.js} +1 -1
  46. package/dist-renderer/assets/{pieDiagram-ADFJNKIX-DxyL9Zr2.js → pieDiagram-ADFJNKIX-LCjxckWv.js} +1 -1
  47. package/dist-renderer/assets/{quadrantDiagram-AYHSOK5B-CR33pHlF.js → quadrantDiagram-AYHSOK5B-BOwKjSco.js} +1 -1
  48. package/dist-renderer/assets/{requirementDiagram-UZGBJVZJ-BAiSRSlh.js → requirementDiagram-UZGBJVZJ-pChP8Znd.js} +1 -1
  49. package/dist-renderer/assets/{sankeyDiagram-TZEHDZUN-C8JmDjoa.js → sankeyDiagram-TZEHDZUN-DifZ2qpo.js} +1 -1
  50. package/dist-renderer/assets/{sequenceDiagram-WL72ISMW-c1d0Wi1m.js → sequenceDiagram-WL72ISMW-CJg-WYyY.js} +1 -1
  51. package/dist-renderer/assets/{splashScene-D0YB9uxm.js → splashScene-94xWCzLA.js} +1 -1
  52. package/dist-renderer/assets/{stateDiagram-FKZM4ZOC-nT8BiH2O.js → stateDiagram-FKZM4ZOC-DWHOoFdv.js} +1 -1
  53. package/dist-renderer/assets/stateDiagram-v2-4FDKWEC3-CGYZOoMb.js +1 -0
  54. package/dist-renderer/assets/{timeline-definition-IT6M3QCI-DpoRepUA.js → timeline-definition-IT6M3QCI-CPgokIo8.js} +1 -1
  55. package/dist-renderer/assets/{treemap-GDKQZRPO-C41UJeIH.js → treemap-GDKQZRPO-DAVqSR9L.js} +1 -1
  56. package/dist-renderer/assets/{xychartDiagram-PRI3JC2R-KMjGARKN.js → xychartDiagram-PRI3JC2R-CCOcGbrD.js} +1 -1
  57. package/dist-renderer/chat-community-qr.jpg +0 -0
  58. package/dist-renderer/fonts/Agave-Bold.ttf +0 -0
  59. package/dist-renderer/fonts/Agave-Regular.ttf +0 -0
  60. package/dist-renderer/icon.png +0 -0
  61. package/dist-renderer/icon.rar +0 -0
  62. package/dist-renderer/index.html +3 -3
  63. package/package.json +21 -26
  64. package/src/features/worker-society/core/application/WorkerSocietyService.test.ts +802 -0
  65. package/src/features/worker-society/core/application/WorkerSocietyService.ts +428 -0
  66. package/src/features/worker-society/core/application/fakes.ts +101 -0
  67. package/src/features/worker-society/core/application/ports.ts +70 -0
  68. package/src/features/worker-society/core/domain/models/society.ts +141 -0
  69. package/src/features/worker-society/core/domain/policies/societyPolicies.test.ts +739 -0
  70. package/src/features/worker-society/core/domain/policies/societyPolicies.ts +496 -0
  71. package/src/features/worker-society/main/adapters/input/societyMcp.test.ts +317 -0
  72. package/src/features/worker-society/main/adapters/input/societyMcp.ts +257 -0
  73. package/src/features/worker-society/main/adapters/input/societyRoutes.test.ts +695 -0
  74. package/src/features/worker-society/main/adapters/input/societyRoutes.ts +194 -0
  75. package/src/features/worker-society/main/composition/societyComposition.test.ts +74 -0
  76. package/src/features/worker-society/main/composition/societyComposition.ts +70 -0
  77. package/src/features/worker-society/main/composition/workerSocietyPlugin.test.ts +69 -0
  78. package/src/features/worker-society/main/composition/workerSocietyPlugin.ts +67 -0
  79. package/src/features/worker-society/main/infrastructure/crossTeamMessageGateway.test.ts +132 -0
  80. package/src/features/worker-society/main/infrastructure/crossTeamMessageGateway.ts +84 -0
  81. package/src/features/worker-society/main/infrastructure/fsStores.test.ts +216 -0
  82. package/src/features/worker-society/main/infrastructure/fsStores.ts +113 -0
  83. package/src/features/worker-society/main/infrastructure/mergingProfileStore.test.ts +195 -0
  84. package/src/features/worker-society/main/infrastructure/mergingProfileStore.ts +96 -0
  85. package/src/features/worker-society/renderer/SocietyGraph.tsx +166 -0
  86. package/src/features/worker-society/renderer/SocietyNodeLabels.tsx +139 -0
  87. package/src/features/worker-society/renderer/SocietyNodeOverlay.tsx +339 -0
  88. package/src/features/worker-society/renderer/SocietyView.tsx +437 -0
  89. package/src/features/worker-society/renderer/index.ts +11 -0
  90. package/src/features/worker-society/renderer/societyApi.test.ts +259 -0
  91. package/src/features/worker-society/renderer/societyApi.ts +144 -0
  92. package/src/features/worker-society/renderer/societyGraphAdapter.test.ts +321 -0
  93. package/src/features/worker-society/renderer/societyGraphAdapter.ts +240 -0
  94. package/src/features/worker-society/renderer/societyOverlayActions.test.ts +57 -0
  95. package/src/features/worker-society/renderer/societyOverlayActions.ts +49 -0
  96. package/src/features/worker-society/renderer/societyStore.test.ts +218 -0
  97. package/src/features/worker-society/renderer/societyStore.ts +146 -0
  98. package/src/features/worker-society/renderer/societyViewUtils.test.ts +81 -0
  99. package/src/features/worker-society/renderer/societyViewUtils.ts +68 -0
  100. package/src/main/ipc/extensions.ts +27 -0
  101. package/src/main/server.ts +1731 -539
  102. package/src/main/services/ccConnect/CcConnectBridge.ts +26 -11
  103. package/src/main/services/ccConnect/CcConnectClient.ts +9 -2
  104. package/src/main/services/ccConnect/workDirReconcile.test.ts +57 -0
  105. package/src/main/services/ccConnect/workDirReconcile.ts +36 -0
  106. package/src/main/services/direct-cli/DirectCliSessionManager.test.ts +397 -0
  107. package/src/main/services/direct-cli/DirectCliSessionManager.ts +508 -0
  108. package/src/main/services/direct-cli/DirectCliSessionStore.test.ts +79 -0
  109. package/src/main/services/direct-cli/DirectCliSessionStore.ts +97 -0
  110. package/src/main/services/direct-cli/__tests__/directCliMessageId.test.ts +40 -0
  111. package/src/main/services/direct-cli/directCliMessageId.ts +21 -0
  112. package/src/main/services/direct-cli/index.ts +17 -0
  113. package/src/main/services/extensions/capability-packs/CapabilityPackLoaderService.ts +637 -0
  114. package/src/main/services/extensions/catalog/PluginCatalogService.ts +2 -2
  115. package/src/main/services/loop-assets/LoopAssetsScannerService.ts +657 -0
  116. package/src/main/services/runtime/providerAwareCliEnv.ts +33 -5
  117. package/src/main/services/session-intelligence/LocalSessionScanner.ts +156 -71
  118. package/src/main/services/session-intelligence/SessionUsageParser.ts +103 -8
  119. package/src/main/services/session-intelligence/UsageTelemetryService.ts +11 -0
  120. package/src/main/services/session-intelligence/__tests__/teamSessionListMapper.test.ts +104 -0
  121. package/src/main/services/session-intelligence/teamSessionListMapper.ts +78 -0
  122. package/src/main/services/system-manager/AdminLoopInitializer.ts +95 -0
  123. package/src/main/services/system-manager/BuiltinWorkflowSeeder.ts +744 -0
  124. package/src/main/services/system-manager/SystemManagerConfigService.ts +19 -1
  125. package/src/main/services/system-manager/WorkflowPromptService.ts +58 -5
  126. package/src/main/services/system-manager/__tests__/AdminLoopInitializer.test.ts +129 -0
  127. package/src/main/services/system-manager/__tests__/SystemManagerConfigService.test.ts +60 -0
  128. package/src/main/services/teams-mvp/CollaborationBoardService.ts +2 -0
  129. package/src/main/services/teams-mvp/OpsRunbookContext.ts +60 -0
  130. package/src/main/services/teams-mvp/TaskDispatchService.test.ts +305 -0
  131. package/src/main/services/teams-mvp/TaskDispatchService.ts +250 -131
  132. package/src/main/services/teams-mvp/TeamProvisioningService.ts +12 -2
  133. package/src/main/services/teams-mvp/TeamWorkspaceService.test.ts +207 -0
  134. package/src/main/services/teams-mvp/TeamWorkspaceService.ts +104 -51
  135. package/src/main/services/teams-mvp/index.ts +6 -0
  136. package/src/main/utils/externalPlatformSessionRouting.ts +92 -0
  137. package/src/main/utils/toolApprovalRules.ts +151 -0
  138. package/src/renderer/App.tsx +24 -89
  139. package/src/renderer/api/httpClient.ts +132 -52
  140. package/src/renderer/api/providers.ts +5 -16
  141. package/src/renderer/components/chat/CommunityChatView.tsx +81 -0
  142. package/src/renderer/components/dashboard/DashboardView.tsx +130 -84
  143. package/src/renderer/components/extensions/ExtensionStoreView.tsx +39 -5
  144. package/src/renderer/components/extensions/ExtensionsSubTabTrigger.tsx +2 -1
  145. package/src/renderer/components/extensions/capability-packs/CapabilityPacksPanel.tsx +170 -0
  146. package/src/renderer/components/layout/PaneContent.tsx +10 -2
  147. package/src/renderer/components/layout/SortableTab.tsx +4 -0
  148. package/src/renderer/components/layout/TabBarActions.tsx +13 -16
  149. package/src/renderer/components/runtime/ProviderRuntimeSettingsDialog.tsx +4 -135
  150. package/src/renderer/components/schedules/SchedulesView.tsx +22 -14
  151. package/src/renderer/components/schedules/calendar/CalendarEventBlock.tsx +7 -6
  152. package/src/renderer/components/settings/SettingsTabs.tsx +24 -21
  153. package/src/renderer/components/settings/SettingsView.tsx +22 -13
  154. package/src/renderer/components/settings/components/SettingRow.tsx +13 -5
  155. package/src/renderer/components/settings/components/SettingsSectionCard.tsx +53 -0
  156. package/src/renderer/components/settings/components/SettingsSectionHeader.tsx +10 -6
  157. package/src/renderer/components/settings/components/SettingsSelect.tsx +12 -9
  158. package/src/renderer/components/settings/components/SettingsToggle.tsx +6 -5
  159. package/src/renderer/components/settings/components/index.ts +1 -0
  160. package/src/renderer/components/settings/sections/AdvancedSection.tsx +78 -59
  161. package/src/renderer/components/settings/sections/CliStatusSection.tsx +32 -44
  162. package/src/renderer/components/settings/sections/ConfigEditorDialog.tsx +1 -1
  163. package/src/renderer/components/settings/sections/GeneralSection.tsx +216 -186
  164. package/src/renderer/components/settings/sections/PlatformsSection.tsx +25 -17
  165. package/src/renderer/components/settings/sections/TaskBusSection.tsx +63 -22
  166. package/src/renderer/components/sidebar/SidebarSessions.tsx +120 -80
  167. package/src/renderer/components/sidebar/SidebarTaskItem.tsx +1 -1
  168. package/src/renderer/components/splash/splashScene.ts +6 -2
  169. package/src/renderer/components/system-manager/SystemManagerView.tsx +169 -255
  170. package/src/renderer/components/tasks/TasksView.tsx +63 -37
  171. package/src/renderer/components/team/CcSessionsSection.tsx +124 -89
  172. package/src/renderer/components/team/HarnessBrandLogos.tsx +318 -0
  173. package/src/renderer/components/team/HarnessSelect.tsx +25 -26
  174. package/src/renderer/components/team/TeamDetailView.tsx +137 -153
  175. package/src/renderer/components/team/TeamEmptyState.tsx +9 -37
  176. package/src/renderer/components/team/TeamListView.tsx +144 -30
  177. package/src/renderer/components/team/__tests__/CcSessionsSection.hasLocalFile.test.tsx +128 -0
  178. package/src/renderer/components/team/activity/ActivityItem.tsx +21 -9
  179. package/src/renderer/components/team/activity/ActivityTimeline.tsx +2 -2
  180. package/src/renderer/components/team/dialogs/AdvancedCliSection.tsx +1 -1
  181. package/src/renderer/components/team/dialogs/CreateTaskDialog.tsx +13 -10
  182. package/src/renderer/components/team/dialogs/CreateTeamDialog.tsx +189 -57
  183. package/src/renderer/components/team/dialogs/EditTeamDialog.tsx +9 -157
  184. package/src/renderer/components/team/dialogs/LaunchTeamDialog.tsx +19 -15
  185. package/src/renderer/components/team/dialogs/PlatformBindingDialog.tsx +48 -10
  186. package/src/renderer/components/team/dialogs/PlatformManualForm.tsx +11 -12
  187. package/src/renderer/components/team/dialogs/PlatformSetupQR.tsx +39 -37
  188. package/src/renderer/components/team/dialogs/RuntimeConfigDialog.tsx +434 -64
  189. package/src/renderer/components/team/dialogs/SendMessageDialog.tsx +12 -10
  190. package/src/renderer/components/team/dialogs/TaskDetailDialog.tsx +2 -2
  191. package/src/renderer/components/team/dialogs/__tests__/CreateTeamDialog.bindProject.test.tsx +399 -0
  192. package/src/renderer/components/team/dialogs/__tests__/CreateTeamDialog.chineseRepro.test.tsx +253 -0
  193. package/src/renderer/components/team/dialogs/platformAllowUtils.ts +91 -0
  194. package/src/renderer/components/team/dialogs/teammateRuntimeCompatibility.tsx +1 -1
  195. package/src/renderer/components/team/kanban/KanbanTaskCard.test.tsx +41 -0
  196. package/src/renderer/components/team/kanban/KanbanTaskCard.tsx +41 -86
  197. package/src/renderer/components/team/loop-console/LoopCommandComposer.tsx +310 -0
  198. package/src/renderer/components/team/loop-console/LoopConsolePanel.tsx +372 -0
  199. package/src/renderer/components/team/loop-console/loopSendIntent.test.ts +85 -0
  200. package/src/renderer/components/team/loop-console/loopSendIntent.ts +221 -0
  201. package/src/renderer/components/team/loop-console/useLeadSessionToolActivity.ts +74 -0
  202. package/src/renderer/components/team/loop-console/useLoopCommandSuggestions.ts +165 -0
  203. package/src/renderer/components/team/loop-console/useLoopConsoleController.ts +266 -0
  204. package/src/renderer/components/team/members/LeadModelRow.test.tsx +1 -1
  205. package/src/renderer/components/team/members/LeadModelRow.tsx +5 -3
  206. package/src/renderer/components/team/members/MemberDetailDialog.tsx +11 -0
  207. package/src/renderer/components/team/members/MemberDetailStats.tsx +13 -3
  208. package/src/renderer/components/team/members/MemberDraftRow.test.tsx +1 -1
  209. package/src/renderer/components/team/members/MemberDraftRow.tsx +1 -1
  210. package/src/renderer/components/team/members/MemberMessagesTab.tsx +2 -2
  211. package/src/renderer/components/team/members/MemberStatsTab.tsx +1 -1
  212. package/src/renderer/components/team/messages/MessageComposer.tsx +150 -44
  213. package/src/renderer/components/team/messages/MessagesFilterPopover.tsx +2 -2
  214. package/src/renderer/components/team/messages/MessagesPanel.tsx +34 -28
  215. package/src/renderer/components/team/schedule/CcCronScheduleDialog.tsx +6 -6
  216. package/src/renderer/components/team/taskLogs/ExactTaskLogCard.tsx +2 -2
  217. package/src/renderer/components/team/taskLogs/TaskLogStreamSection.tsx +1 -1
  218. package/src/renderer/components/terminal/TerminalPanel.tsx +2 -3
  219. package/src/renderer/components/ui/MentionableTextarea.tsx +5 -1
  220. package/src/renderer/constants/teamColors.ts +5 -5
  221. package/src/renderer/hooks/useExtensionsTabState.ts +1 -1
  222. package/src/renderer/hooks/useMentionDetection.ts +5 -1
  223. package/src/renderer/hooks/useProjectWorkflowCommands.ts +57 -0
  224. package/src/renderer/hooks/useTeamSuggestions.ts +2 -0
  225. package/src/renderer/index.css +19 -2
  226. package/src/renderer/main.tsx +7 -1
  227. package/src/renderer/store/index.ts +18 -1
  228. package/src/renderer/store/slices/extensionsSlice.ts +83 -0
  229. package/src/renderer/store/slices/tabSlice.ts +61 -0
  230. package/src/renderer/store/slices/teamSlice.ts +138 -9
  231. package/src/renderer/types/mention.ts +8 -0
  232. package/src/renderer/types/tabs.ts +3 -1
  233. package/src/renderer/utils/__tests__/bindProjectSlug.test.ts +69 -0
  234. package/src/renderer/utils/__tests__/groupTransformer.test.ts +148 -0
  235. package/src/renderer/utils/__tests__/initialRoute.test.ts +101 -0
  236. package/src/renderer/utils/__tests__/leadToolActivity.test.ts +124 -0
  237. package/src/renderer/utils/__tests__/mergeTeamMessages.test.ts +81 -0
  238. package/src/renderer/utils/__tests__/teamMessageFiltering.test.ts +213 -0
  239. package/src/renderer/utils/__tests__/teamMessageKey.test.ts +75 -0
  240. package/src/renderer/utils/__tests__/workflowCommandExecution.test.ts +173 -0
  241. package/src/renderer/utils/__tests__/workflowCommandSuggestions.test.ts +59 -0
  242. package/src/renderer/utils/bindProjectSlug.ts +57 -0
  243. package/src/renderer/utils/capabilityCommandExecution.ts +113 -0
  244. package/src/renderer/utils/initialRoute.ts +89 -0
  245. package/src/renderer/utils/leadToolActivity.ts +117 -0
  246. package/src/renderer/utils/loopShortcutSuggestions.ts +106 -0
  247. package/src/renderer/utils/mentionSuggestions.ts +1 -1
  248. package/src/renderer/utils/slashCommandRegistry.ts +231 -0
  249. package/src/renderer/utils/teamMentionDirective.ts +31 -0
  250. package/src/renderer/utils/workflowCommandExecution.ts +96 -0
  251. package/src/renderer/utils/workflowCommandSuggestions.ts +49 -0
  252. package/src/shared/types/api.ts +79 -4
  253. package/src/shared/types/ccConnect.ts +1 -0
  254. package/src/shared/types/extensions/api.ts +19 -0
  255. package/src/shared/types/extensions/capabilityPack.ts +118 -0
  256. package/src/shared/types/extensions/index.ts +29 -1
  257. package/src/shared/types/index.ts +6 -0
  258. package/src/shared/types/loopAssets.ts +54 -0
  259. package/src/shared/types/providers.ts +0 -16
  260. package/src/shared/types/systemManager.ts +26 -1
  261. package/src/shared/types/team.ts +43 -3
  262. package/src/shared/types/terminal.ts +2 -36
  263. package/src/shared/types/worker.test.ts +28 -0
  264. package/src/shared/types/worker.ts +3 -0
  265. package/src/shared/utils/__tests__/effortLevels.test.ts +88 -0
  266. package/src/shared/utils/__tests__/providerBackend.test.ts +88 -0
  267. package/src/shared/utils/__tests__/providerLaunchArgs.test.ts +220 -0
  268. package/src/shared/utils/claudeStreamJson.test.ts +187 -0
  269. package/src/shared/utils/claudeStreamJson.ts +153 -0
  270. package/src/shared/utils/providerLaunchArgs.ts +217 -0
  271. package/src/shared/utils/slashCommands.ts +10 -0
  272. package/src/types/node-pty.d.ts +8 -0
  273. package/dist-renderer/assets/channel-D0XS_akr.js +0 -1
  274. package/dist-renderer/assets/classDiagram-2ON5EDUG-D13Ffs0U.js +0 -1
  275. package/dist-renderer/assets/classDiagram-v2-WZHVMYZB-D13Ffs0U.js +0 -1
  276. package/dist-renderer/assets/clone-B1ZrxI1D.js +0 -1
  277. package/dist-renderer/assets/index-iyjkpSus.css +0 -32
  278. package/dist-renderer/assets/stateDiagram-v2-4FDKWEC3-Dmibmlso.js +0 -1
  279. package/src/main/services/system-manager/SystemManagerPtyService.ts +0 -233
  280. package/src/renderer/components/common/TerminalPane.tsx +0 -213
  281. package/src/renderer/components/team/dialogs/useTeamEditForm.ts +0 -292
@@ -2,20 +2,33 @@
2
2
  * Provider-aware CLI environment builder.
3
3
  *
4
4
  * Builds an enriched environment for CLI processes that accounts for
5
- * provider-specific configuration (API keys, base URLs, etc.).
5
+ * provider-specific configuration (API keys, base URLs, etc.), and resolves the
6
+ * provider launch fields (model, effort, worktree, ...) into concrete CLI args
7
+ * via {@link resolveProviderLaunchArgs}.
6
8
  *
7
9
  * NOTE: The full source in claude_agent_teams_ui depends on several services
8
10
  * not yet available in this project (ProviderConnectionService, OpenCodeRuntime,
9
- * codex-runtime-installer). This module provides the core interface and
10
- * environment building, falling back gracefully when those services are absent.
11
+ * codex-runtime-installer). This module provides the core interface, environment
12
+ * building, and provider arg resolution, falling back gracefully when those
13
+ * services are absent.
11
14
  */
12
15
 
13
16
  import { buildEnrichedEnv } from '@main/utils/cliEnv';
17
+ import { resolveProviderLaunchArgs } from '@shared/utils/providerLaunchArgs';
18
+
19
+ import type { EffortLevel, TeamProviderId } from '@shared/types';
14
20
 
15
21
  export interface ProviderAwareCliEnvOptions {
16
22
  binaryPath?: string | null;
17
23
  providerId?: string;
18
24
  providerBackendId?: string | null;
25
+ model?: string | null;
26
+ effort?: EffortLevel | null;
27
+ skipPermissions?: boolean;
28
+ worktree?: string;
29
+ extraCliArgs?: string;
30
+ limitContext?: boolean;
31
+ clearContext?: boolean;
19
32
  shellEnv?: NodeJS.ProcessEnv | null;
20
33
  env?: NodeJS.ProcessEnv;
21
34
  connectionMode?: 'strict' | 'augment';
@@ -52,9 +65,24 @@ export async function buildProviderAwareCliEnv(
52
65
  }
53
66
  }
54
67
 
68
+ // Resolve provider launch fields into concrete CLI args. When no provider/model
69
+ // information is supplied, the resolver returns an empty arg list (backward
70
+ // compatible with callers that only need the enriched env).
71
+ const resolution = resolveProviderLaunchArgs({
72
+ providerId: options.providerId as TeamProviderId | undefined,
73
+ providerBackendId: options.providerBackendId ?? null,
74
+ model: options.model,
75
+ effort: options.effort,
76
+ skipPermissions: options.skipPermissions,
77
+ worktree: options.worktree,
78
+ extraCliArgs: options.extraCliArgs,
79
+ limitContext: options.limitContext,
80
+ clearContext: options.clearContext,
81
+ });
82
+
55
83
  return {
56
84
  env,
57
- connectionIssues: {},
58
- providerArgs: [],
85
+ connectionIssues: resolution.connectionIssues,
86
+ providerArgs: resolution.providerArgs,
59
87
  };
60
88
  }
@@ -11,7 +11,7 @@ import { readdir, stat } from 'node:fs/promises';
11
11
  import { createInterface } from 'node:readline';
12
12
  import * as path from 'node:path';
13
13
 
14
- import { getProjectsBasePath, encodePath } from '@main/utils/pathDecoder';
14
+ import { getProjectDirNameCandidates, getProjectsBasePath } from '@main/utils/pathDecoder';
15
15
 
16
16
  // ---------------------------------------------------------------------------
17
17
  // Types
@@ -28,6 +28,7 @@ export interface LocalSessionSummary {
28
28
  outputTokens: number;
29
29
  cacheReadTokens: number;
30
30
  cacheCreationTokens: number;
31
+ totalTokens: number;
31
32
  model: string;
32
33
  active: boolean;
33
34
  live: boolean;
@@ -58,6 +59,9 @@ export interface LocalSessionDetail {
58
59
  model: string;
59
60
  inputTokens: number;
60
61
  outputTokens: number;
62
+ cacheReadTokens: number;
63
+ cacheCreationTokens: number;
64
+ totalTokens: number;
61
65
  }
62
66
 
63
67
  // ---------------------------------------------------------------------------
@@ -186,7 +190,11 @@ async function* walkJsonl(dir: string): AsyncGenerator<string> {
186
190
  const full = path.join(dir, entry.name);
187
191
  if (entry.isDirectory()) {
188
192
  yield* walkJsonl(full);
189
- } else if (entry.isFile() && entry.name.endsWith('.jsonl') && !entry.name.startsWith('agent_')) {
193
+ } else if (
194
+ entry.isFile() &&
195
+ entry.name.endsWith('.jsonl') &&
196
+ !entry.name.startsWith('agent_')
197
+ ) {
190
198
  yield full;
191
199
  }
192
200
  }
@@ -251,6 +259,7 @@ async function scanSummaryLines(
251
259
  } else if (obj.type === 'user' || obj.type === 'assistant') {
252
260
  role = obj.type as string;
253
261
  content = obj.content;
262
+ usage = obj.usage as Record<string, unknown> | undefined;
254
263
  ts = obj.timestamp as string | undefined;
255
264
  }
256
265
 
@@ -297,16 +306,81 @@ async function scanSummaryLines(
297
306
 
298
307
  export class LocalSessionScanner {
299
308
  private summaryCache = new Map<string, SummaryCacheEntry>();
309
+ private sessionPathByWorkDirAndId = new Map<string, string>();
310
+
311
+ private sessionPathKey(workDir: string, sessionId: string): string {
312
+ return `${workDir}\0${sessionId}`;
313
+ }
300
314
 
301
315
  /**
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}/
316
+ * Resolve possible JSONL directories for a given workDir.
317
+ * workDir is usually the absolute filesystem path (e.g., "/Users/name/project").
318
+ * The JSONL files live at ~/.claude/projects/{encoded-workDir}/, but Claude
319
+ * installations can use portable/legacy encodings, so reuse path candidates.
305
320
  */
306
- private resolveJsonlDir(workDir: string): string {
321
+ private async resolveJsonlDirs(workDir: string): Promise<string[]> {
307
322
  const projectsBase = getProjectsBasePath();
308
- const encoded = encodePath(workDir);
309
- return path.join(projectsBase, encoded);
323
+ const candidateNames = getProjectDirNameCandidates(workDir).filter(
324
+ (candidate) => !path.isAbsolute(candidate)
325
+ );
326
+ const candidateDirs = Array.from(
327
+ new Set(candidateNames.map((candidate) => path.join(projectsBase, candidate)))
328
+ );
329
+
330
+ const existing: string[] = [];
331
+ for (const dir of candidateDirs) {
332
+ try {
333
+ if ((await stat(dir)).isDirectory()) existing.push(dir);
334
+ } catch {
335
+ // ignore missing candidate dirs
336
+ }
337
+ }
338
+
339
+ return existing.length > 0 ? existing : candidateDirs.slice(0, 1);
340
+ }
341
+
342
+ private async findSessionFile(
343
+ workDir: string,
344
+ sessionId: string
345
+ ): Promise<{ filePath: string; fileStat: Awaited<ReturnType<typeof stat>> } | null> {
346
+ const indexedPath = this.sessionPathByWorkDirAndId.get(this.sessionPathKey(workDir, sessionId));
347
+ if (indexedPath) {
348
+ try {
349
+ return { filePath: indexedPath, fileStat: await stat(indexedPath) };
350
+ } catch {
351
+ this.sessionPathByWorkDirAndId.delete(this.sessionPathKey(workDir, sessionId));
352
+ }
353
+ }
354
+
355
+ const jsonlDirs = await this.resolveJsonlDirs(workDir);
356
+ for (const jsonlDir of jsonlDirs) {
357
+ const directPath = path.join(jsonlDir, `${sessionId}.jsonl`);
358
+ try {
359
+ const fileStat = await stat(directPath);
360
+ this.sessionPathByWorkDirAndId.set(this.sessionPathKey(workDir, sessionId), directPath);
361
+ return { filePath: directPath, fileStat };
362
+ } catch {
363
+ // fall through to recursive lookup
364
+ }
365
+ }
366
+
367
+ for (const jsonlDir of jsonlDirs) {
368
+ for await (const candidatePath of walkJsonl(jsonlDir)) {
369
+ if (path.basename(candidatePath, '.jsonl') !== sessionId) continue;
370
+ try {
371
+ const fileStat = await stat(candidatePath);
372
+ this.sessionPathByWorkDirAndId.set(
373
+ this.sessionPathKey(workDir, sessionId),
374
+ candidatePath
375
+ );
376
+ return { filePath: candidatePath, fileStat };
377
+ } catch {
378
+ // stale file from the directory walk; keep searching
379
+ }
380
+ }
381
+ }
382
+
383
+ return null;
310
384
  }
311
385
 
312
386
  /**
@@ -314,63 +388,72 @@ export class LocalSessionScanner {
314
388
  * Uses file stat caching to skip unchanged files on subsequent calls.
315
389
  */
316
390
  async scanSummaries(workDir: string, projectId: string): Promise<LocalSessionSummary[]> {
317
- const jsonlDir = this.resolveJsonlDir(workDir);
391
+ const jsonlDirs = await this.resolveJsonlDirs(workDir);
318
392
  const summaries: LocalSessionSummary[] = [];
319
393
  const now = Date.now();
320
394
 
321
- for await (const filePath of walkJsonl(jsonlDir)) {
322
- let fileStat;
323
- try {
324
- fileStat = await stat(filePath);
325
- } catch {
326
- continue;
327
- }
395
+ for (const jsonlDir of jsonlDirs) {
396
+ for await (const filePath of walkJsonl(jsonlDir)) {
397
+ let fileStat;
398
+ try {
399
+ fileStat = await stat(filePath);
400
+ } catch {
401
+ continue;
402
+ }
328
403
 
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
- }
404
+ const sessionId = path.basename(filePath, '.jsonl');
405
+ this.sessionPathByWorkDirAndId.set(this.sessionPathKey(workDir, sessionId), filePath);
406
+
407
+ // Check cache
408
+ const cached = this.summaryCache.get(filePath);
409
+ if (cached && cached.size === fileStat.size && cached.mtimeMs === fileStat.mtimeMs) {
410
+ summaries.push(cached.summary);
411
+ continue;
412
+ }
413
+
414
+ const partial = await scanSummaryLines(filePath, sessionId, projectId);
415
+ if (!partial) continue;
416
+
417
+ const mtimeMs = fileStat.mtimeMs;
418
+ const active = now - mtimeMs < ACTIVE_THRESHOLD_MS;
419
+ const live = active && partial.lastRole === 'assistant';
420
+
421
+ // For a more accurate messageCount and token totals, we need the full file.
422
+ // But the first SUMMARY_SCAN_LINES is a good approximation for the list view.
423
+ // We'll mark the count as approximate if we stopped early.
424
+ const summary: LocalSessionSummary = {
425
+ id: sessionId,
426
+ title: partial.title || sessionId,
427
+ projectId,
428
+ messageCount: partial.messageCount,
429
+ userMessageCount: partial.userMessageCount,
430
+ assistantMessageCount: partial.assistantMessageCount,
431
+ inputTokens: partial.inputTokens,
432
+ outputTokens: partial.outputTokens,
433
+ cacheReadTokens: partial.cacheReadTokens,
434
+ cacheCreationTokens: partial.cacheCreationTokens,
435
+ totalTokens:
436
+ partial.inputTokens +
437
+ partial.outputTokens +
438
+ partial.cacheReadTokens +
439
+ partial.cacheCreationTokens,
440
+ model: partial.model,
441
+ active,
442
+ live,
443
+ startTime: partial.startTime,
444
+ endTime: partial.endTime,
445
+ createdAt: fileStat.birthtime?.toISOString() ?? new Date(mtimeMs).toISOString(),
446
+ updatedAt: new Date(mtimeMs).toISOString(),
447
+ };
448
+
449
+ this.summaryCache.set(filePath, {
450
+ size: fileStat.size,
451
+ mtimeMs,
452
+ summary,
453
+ });
335
454
 
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);
455
+ summaries.push(summary);
456
+ }
374
457
  }
375
458
 
376
459
  // Sort by endTime descending (most recent first)
@@ -394,16 +477,9 @@ export class LocalSessionScanner {
394
477
  sessionId: string,
395
478
  options?: { offset?: number; limit?: number }
396
479
  ): 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
- }
480
+ const resolvedFile = await this.findSessionFile(workDir, sessionId);
481
+ if (!resolvedFile) return null;
482
+ const { filePath, fileStat } = resolvedFile;
407
483
 
408
484
  const offset = options?.offset ?? 0;
409
485
  const limit = options?.limit ?? 200;
@@ -413,6 +489,8 @@ export class LocalSessionScanner {
413
489
  let model = '';
414
490
  let inputTokens = 0;
415
491
  let outputTokens = 0;
492
+ let cacheReadTokens = 0;
493
+ let cacheCreationTokens = 0;
416
494
  let firstTs: string | null = null;
417
495
  let lastTs: string | null = null;
418
496
 
@@ -446,6 +524,7 @@ export class LocalSessionScanner {
446
524
  } else if (obj.type === 'user' || obj.type === 'assistant') {
447
525
  role = obj.type as string;
448
526
  content = obj.content;
527
+ usage = obj.usage as Record<string, unknown> | undefined;
449
528
  ts = obj.timestamp as string | undefined;
450
529
  }
451
530
 
@@ -467,6 +546,8 @@ export class LocalSessionScanner {
467
546
  if (role === 'assistant' && usage && typeof usage === 'object') {
468
547
  inputTokens += Number(usage.input_tokens ?? 0) || 0;
469
548
  outputTokens += Number(usage.output_tokens ?? 0) || 0;
549
+ cacheReadTokens += Number(usage.cache_read_input_tokens ?? 0) || 0;
550
+ cacheCreationTokens += Number(usage.cache_creation_input_tokens ?? 0) || 0;
470
551
  }
471
552
 
472
553
  // Collect messages within the page range
@@ -482,7 +563,7 @@ export class LocalSessionScanner {
482
563
  if (totalMessages === 0) return null;
483
564
 
484
565
  const now = Date.now();
485
- const mtimeMs = fileStat.mtimeMs;
566
+ const mtimeMs = Number(fileStat.mtimeMs);
486
567
  const active = now - mtimeMs < ACTIVE_THRESHOLD_MS;
487
568
 
488
569
  return {
@@ -500,6 +581,9 @@ export class LocalSessionScanner {
500
581
  model,
501
582
  inputTokens,
502
583
  outputTokens,
584
+ cacheReadTokens,
585
+ cacheCreationTokens,
586
+ totalTokens: inputTokens + outputTokens + cacheReadTokens + cacheCreationTokens,
503
587
  };
504
588
  }
505
589
 
@@ -508,5 +592,6 @@ export class LocalSessionScanner {
508
592
  */
509
593
  clearCache(): void {
510
594
  this.summaryCache.clear();
595
+ this.sessionPathByWorkDirAndId.clear();
511
596
  }
512
597
  }
@@ -8,9 +8,10 @@
8
8
 
9
9
  import { createReadStream } from 'node:fs';
10
10
  import { readdir, stat } from 'node:fs/promises';
11
- import { createInterface } from 'node:readline';
12
11
  import * as path from 'node:path';
13
- import { getProjectsBasePath } from '@main/utils/pathDecoder';
12
+ import { createInterface } from 'node:readline';
13
+
14
+ import { getProjectDirNameCandidates, getProjectsBasePath } from '@main/utils/pathDecoder';
14
15
 
15
16
  export interface SessionEntry {
16
17
  relPath: string;
@@ -23,6 +24,7 @@ export interface SessionEntry {
23
24
  output: number;
24
25
  cacheRead: number;
25
26
  cacheCreation: number;
27
+ total: number;
26
28
  };
27
29
  startTime: string;
28
30
  endTime: string;
@@ -39,6 +41,7 @@ export interface UsageAggregate {
39
41
  output: number;
40
42
  cacheRead: number;
41
43
  cacheCreation: number;
44
+ total: number;
42
45
  };
43
46
  activeDays: number;
44
47
  daily: Record<string, DailyMetrics>;
@@ -55,6 +58,7 @@ export interface DailyMetrics {
55
58
  tokensOut: number;
56
59
  cacheRead: number;
57
60
  cacheCreation: number;
61
+ tokensTotal: number;
58
62
  workSeconds: number;
59
63
  }
60
64
 
@@ -64,6 +68,7 @@ export interface ProjectMetricsEntry {
64
68
  messages: number;
65
69
  tokensIn: number;
66
70
  tokensOut: number;
71
+ tokensTotal: number;
67
72
  }
68
73
 
69
74
  export interface EventEntry {
@@ -72,6 +77,7 @@ export interface EventEntry {
72
77
  tokensOut: number;
73
78
  cacheRead: number;
74
79
  cacheCreation: number;
80
+ tokensTotal: number;
75
81
  }
76
82
 
77
83
  export interface ParseResult {
@@ -137,7 +143,13 @@ interface ParsedSession {
137
143
  isWorktree: boolean;
138
144
  messageCount: number;
139
145
  toolCalls: Record<string, number>;
140
- tokens: { input: number; output: number; cacheRead: number; cacheCreation: number };
146
+ tokens: {
147
+ input: number;
148
+ output: number;
149
+ cacheRead: number;
150
+ cacheCreation: number;
151
+ total: number;
152
+ };
141
153
  startTime: string;
142
154
  endTime: string;
143
155
  dailyTokens: Record<string, DailyMetrics>;
@@ -151,7 +163,7 @@ async function parseJsonl(filePath: string): Promise<ParsedSession | null> {
151
163
  let rawCwd = '';
152
164
  let isWorktree = false;
153
165
  const toolCalls: Record<string, number> = {};
154
- const tokens = { input: 0, output: 0, cacheRead: 0, cacheCreation: 0 };
166
+ const tokens = { input: 0, output: 0, cacheRead: 0, cacheCreation: 0, total: 0 };
155
167
  let startTime = '';
156
168
  let endTime = '';
157
169
  const dailyTokens: Record<string, DailyMetrics> = {};
@@ -173,8 +185,7 @@ async function parseJsonl(filePath: string): Promise<ParsedSession | null> {
173
185
 
174
186
  if (!rawCwd && typeof obj.cwd === 'string') {
175
187
  rawCwd = obj.cwd;
176
- const { normalized, isWorktree: iw } = normalizeCwd(rawCwd);
177
- isWorktree = iw;
188
+ isWorktree = normalizeCwd(rawCwd).isWorktree;
178
189
  }
179
190
 
180
191
  const msg = obj.message as Record<string, unknown> | undefined;
@@ -191,6 +202,7 @@ async function parseJsonl(filePath: string): Promise<ParsedSession | null> {
191
202
  } else if (obj.type === 'user' || obj.type === 'assistant') {
192
203
  role = obj.type as string;
193
204
  content = obj.content;
205
+ usage = obj.usage as Record<string, unknown> | undefined;
194
206
  ts = obj.timestamp as string | undefined;
195
207
  }
196
208
 
@@ -211,11 +223,13 @@ async function parseJsonl(filePath: string): Promise<ParsedSession | null> {
211
223
  const out = Number(usage.output_tokens ?? 0) || 0;
212
224
  const cread = Number(usage.cache_read_input_tokens ?? 0) || 0;
213
225
  const ccreate = Number(usage.cache_creation_input_tokens ?? 0) || 0;
226
+ const total = inp + out + cread + ccreate;
214
227
 
215
228
  tokens.input += inp;
216
229
  tokens.output += out;
217
230
  tokens.cacheRead += cread;
218
231
  tokens.cacheCreation += ccreate;
232
+ tokens.total += total;
219
233
 
220
234
  // Daily aggregation
221
235
  const day = ts.slice(0, 10);
@@ -227,6 +241,7 @@ async function parseJsonl(filePath: string): Promise<ParsedSession | null> {
227
241
  tokensOut: 0,
228
242
  cacheRead: 0,
229
243
  cacheCreation: 0,
244
+ tokensTotal: 0,
230
245
  workSeconds: 0,
231
246
  });
232
247
  d.messages++;
@@ -234,6 +249,7 @@ async function parseJsonl(filePath: string): Promise<ParsedSession | null> {
234
249
  d.tokensOut += out;
235
250
  d.cacheRead += cread;
236
251
  d.cacheCreation += ccreate;
252
+ d.tokensTotal += total;
237
253
  }
238
254
 
239
255
  // Hourly distribution
@@ -251,6 +267,7 @@ async function parseJsonl(filePath: string): Promise<ParsedSession | null> {
251
267
  tokensOut: out,
252
268
  cacheRead: cread,
253
269
  cacheCreation: ccreate,
270
+ tokensTotal: total,
254
271
  });
255
272
  }
256
273
  }
@@ -331,12 +348,79 @@ function calcWorkSeconds(events: EventEntry[]): Record<string, number> {
331
348
  return workSeconds;
332
349
  }
333
350
 
351
+ export interface ProjectUsageStats {
352
+ sessions: number;
353
+ messages: number;
354
+ tokensIn: number;
355
+ tokensOut: number;
356
+ cacheRead: number;
357
+ cacheCreation: number;
358
+ totalTokens: number;
359
+ durationMs: number;
360
+ }
361
+
362
+ /**
363
+ * Full-file usage stats for one work directory.
364
+ * Full-file stats are used by the digital worker list to avoid the old first-200-line
365
+ * approximation. Callers decide whether their displayed total includes cache tokens.
366
+ */
367
+ export async function scanProjectStats(workDir: string): Promise<ProjectUsageStats | null> {
368
+ if (!workDir) return null;
369
+
370
+ const projectsRoot = getProjectsBasePath();
371
+ const jsonlDirs = Array.from(
372
+ new Set(
373
+ getProjectDirNameCandidates(workDir)
374
+ .filter((candidate) => !path.isAbsolute(candidate))
375
+ .map((candidate) => path.join(projectsRoot, candidate))
376
+ )
377
+ );
378
+ const normalizedWorkDir = normalizeCwd(workDir).normalized;
379
+ const stats: ProjectUsageStats = {
380
+ sessions: 0,
381
+ messages: 0,
382
+ tokensIn: 0,
383
+ tokensOut: 0,
384
+ cacheRead: 0,
385
+ cacheCreation: 0,
386
+ totalTokens: 0,
387
+ durationMs: 0,
388
+ };
389
+ let earliest = '';
390
+ let latest = '';
391
+
392
+ for (const jsonlDir of jsonlDirs) {
393
+ for await (const filePath of walkJsonl(jsonlDir)) {
394
+ const parsed = await parseJsonl(filePath);
395
+ if (!parsed) continue;
396
+ if (parsed.projectPath && parsed.projectPath !== normalizedWorkDir) continue;
397
+
398
+ stats.sessions++;
399
+ stats.messages += parsed.messageCount;
400
+ stats.tokensIn += parsed.tokens.input;
401
+ stats.tokensOut += parsed.tokens.output;
402
+ stats.cacheRead += parsed.tokens.cacheRead;
403
+ stats.cacheCreation += parsed.tokens.cacheCreation;
404
+ stats.totalTokens += parsed.tokens.total;
405
+ if (parsed.startTime && (!earliest || parsed.startTime < earliest))
406
+ earliest = parsed.startTime;
407
+ if (parsed.endTime && (!latest || parsed.endTime > latest)) latest = parsed.endTime;
408
+ }
409
+ }
410
+
411
+ if (stats.sessions === 0) return null;
412
+ if (earliest && latest) {
413
+ stats.durationMs = Math.max(0, Date.parse(latest) - Date.parse(earliest));
414
+ }
415
+ return stats;
416
+ }
417
+
334
418
  export async function scanSessions(): Promise<ParseResult> {
335
419
  const sessions: SessionEntry[] = [];
336
420
  const aggregate: UsageAggregate = {
337
421
  sessions: 0,
338
422
  messages: 0,
339
- tokens: { input: 0, output: 0, cacheRead: 0, cacheCreation: 0 },
423
+ tokens: { input: 0, output: 0, cacheRead: 0, cacheCreation: 0, total: 0 },
340
424
  activeDays: 0,
341
425
  daily: {},
342
426
  hourly: new Array(24).fill(0),
@@ -382,6 +466,7 @@ export async function scanSessions(): Promise<ParseResult> {
382
466
  aggregate.tokens.output += parsed.tokens.output;
383
467
  aggregate.tokens.cacheRead += parsed.tokens.cacheRead;
384
468
  aggregate.tokens.cacheCreation += parsed.tokens.cacheCreation;
469
+ aggregate.tokens.total += parsed.tokens.total;
385
470
 
386
471
  // Hourly
387
472
  for (let h = 0; h < 24; h++) {
@@ -401,6 +486,7 @@ export async function scanSessions(): Promise<ParseResult> {
401
486
  tokensOut: 0,
402
487
  cacheRead: 0,
403
488
  cacheCreation: 0,
489
+ tokensTotal: 0,
404
490
  workSeconds: 0,
405
491
  });
406
492
  d.sessions++;
@@ -409,18 +495,27 @@ export async function scanSessions(): Promise<ParseResult> {
409
495
  d.tokensOut += m.tokensOut;
410
496
  d.cacheRead += m.cacheRead;
411
497
  d.cacheCreation += m.cacheCreation;
498
+ d.tokensTotal += m.tokensTotal;
412
499
  }
413
500
 
414
501
  // Projects
415
502
  const proj = parsed.projectPath || '(untracked)';
416
503
  if (!projectMap[proj]) {
417
- projectMap[proj] = { cwd: proj, sessions: 0, messages: 0, tokensIn: 0, tokensOut: 0 };
504
+ projectMap[proj] = {
505
+ cwd: proj,
506
+ sessions: 0,
507
+ messages: 0,
508
+ tokensIn: 0,
509
+ tokensOut: 0,
510
+ tokensTotal: 0,
511
+ };
418
512
  }
419
513
  const p = projectMap[proj];
420
514
  p.sessions++;
421
515
  p.messages += parsed.messageCount;
422
516
  p.tokensIn += parsed.tokens.input;
423
517
  p.tokensOut += parsed.tokens.output;
518
+ p.tokensTotal += parsed.tokens.total;
424
519
  }
425
520
 
426
521
  // Work seconds per day
@@ -71,6 +71,7 @@ async function uploadMetrics(client: Redis, slug: string, result: ParseResult):
71
71
  tokens_out: m.tokensOut,
72
72
  cache_read: m.cacheRead,
73
73
  cache_creation: m.cacheCreation,
74
+ tokens_total: m.tokensTotal,
74
75
  work_seconds: m.workSeconds,
75
76
  });
76
77
  pipe.expire(KEY_DAILY(slug, day), 90 * 86400);
@@ -84,6 +85,7 @@ async function uploadMetrics(client: Redis, slug: string, result: ParseResult):
84
85
  tokens_out: aggregate.tokens.output,
85
86
  cache_read: aggregate.tokens.cacheRead,
86
87
  cache_creation: aggregate.tokens.cacheCreation,
88
+ tokens_total: aggregate.tokens.total,
87
89
  active_days: aggregate.activeDays,
88
90
  last_scan: new Date().toISOString(),
89
91
  });
@@ -172,6 +174,7 @@ interface TelemetryStatusResult {
172
174
  tokensOut: number;
173
175
  cacheRead: number;
174
176
  cacheCreation: number;
177
+ totalTokens: number;
175
178
  activeDays: number;
176
179
  hourly: number[];
177
180
  projects: Array<{
@@ -180,6 +183,7 @@ interface TelemetryStatusResult {
180
183
  messages: number;
181
184
  tokensIn: number;
182
185
  tokensOut: number;
186
+ tokensTotal: number;
183
187
  }>;
184
188
  workSecondsByDay: Record<string, number>;
185
189
  }
@@ -195,6 +199,7 @@ function statusFromParseResult(result: ParseResult, connected: boolean): Telemet
195
199
  tokensOut: aggregate.tokens.output,
196
200
  cacheRead: aggregate.tokens.cacheRead,
197
201
  cacheCreation: aggregate.tokens.cacheCreation,
202
+ totalTokens: aggregate.tokens.total,
198
203
  activeDays: aggregate.activeDays,
199
204
  hourly: aggregate.hourly,
200
205
  projects: aggregate.projects,
@@ -250,6 +255,12 @@ export async function getTelemetryStatus(
250
255
  tokensOut: Number(summary.tokens_out ?? 0),
251
256
  cacheRead: Number(summary.cache_read ?? 0),
252
257
  cacheCreation: Number(summary.cache_creation ?? 0),
258
+ totalTokens:
259
+ Number(summary.tokens_total) ||
260
+ Number(summary.tokens_in ?? 0) +
261
+ Number(summary.tokens_out ?? 0) +
262
+ Number(summary.cache_read ?? 0) +
263
+ Number(summary.cache_creation ?? 0),
253
264
  activeDays: Number(summary.active_days ?? 0),
254
265
  hourly: hourlyRaw ? JSON.parse(hourlyRaw) : new Array(24).fill(0),
255
266
  projects: projectsRaw ? JSON.parse(projectsRaw) : [],