@yancyyu/openhermit 1.6.42 → 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.
- package/README.md +98 -89
- package/bin/hermit.mjs +96 -0
- package/dist-renderer/assets/{ProjectEditorOverlay-DlFQ6mai.js → ProjectEditorOverlay-C98qSs7-.js} +1 -1
- package/dist-renderer/assets/{TeamGraphOverlay-D2TPMPGR.js → TeamGraphOverlay-CsBbZwcL.js} +1 -1
- package/dist-renderer/assets/{_basePickBy-Cmd0RHLQ.js → _basePickBy-ZOyLWjMK.js} +1 -1
- package/dist-renderer/assets/{_baseUniq-BI_iy8ea.js → _baseUniq-DBb726rt.js} +1 -1
- package/dist-renderer/assets/{arc-NzW2mjTP.js → arc-CdiTaR_R.js} +1 -1
- package/dist-renderer/assets/{architectureDiagram-VXUJARFQ-Bzq85AYv.js → architectureDiagram-VXUJARFQ-Cz3sc5TH.js} +1 -1
- package/dist-renderer/assets/{blockDiagram-VD42YOAC-D1PvYS-b.js → blockDiagram-VD42YOAC-DE4c-KJ3.js} +1 -1
- package/dist-renderer/assets/{c4Diagram-YG6GDRKO-D49RKzPC.js → c4Diagram-YG6GDRKO-CmTMDTrV.js} +1 -1
- package/dist-renderer/assets/channel-KTpqi9eT.js +1 -0
- package/dist-renderer/assets/{chunk-4BX2VUAB-fmI_MQmQ.js → chunk-4BX2VUAB-rhHy3tFl.js} +1 -1
- package/dist-renderer/assets/{chunk-55IACEB6-Xsv9RCXZ.js → chunk-55IACEB6-fLZBzuo_.js} +1 -1
- package/dist-renderer/assets/{chunk-B4BG7PRW-BE1KO8Um.js → chunk-B4BG7PRW-DOzxQhim.js} +1 -1
- package/dist-renderer/assets/{chunk-DI55MBZ5-tqJ7Mv7f.js → chunk-DI55MBZ5-COQCcXC5.js} +1 -1
- package/dist-renderer/assets/{chunk-FMBD7UC4-DMD45MVJ.js → chunk-FMBD7UC4-IKU9U_Y4.js} +1 -1
- package/dist-renderer/assets/{chunk-QN33PNHL-DOhGrz-q.js → chunk-QN33PNHL-D6WV154X.js} +1 -1
- package/dist-renderer/assets/{chunk-QZHKN3VN-D8yDgJdD.js → chunk-QZHKN3VN-D90_2DQp.js} +1 -1
- package/dist-renderer/assets/{chunk-TZMSLE5B-BcsEDu7A.js → chunk-TZMSLE5B-BQEil57G.js} +1 -1
- package/dist-renderer/assets/classDiagram-2ON5EDUG-lpzulY5X.js +1 -0
- package/dist-renderer/assets/classDiagram-v2-WZHVMYZB-lpzulY5X.js +1 -0
- package/dist-renderer/assets/clone-CriGymY9.js +1 -0
- package/dist-renderer/assets/{cose-bilkent-S5V4N54A-DlSqGHMX.js → cose-bilkent-S5V4N54A-6WiK6U2P.js} +1 -1
- package/dist-renderer/assets/{dagre-6UL2VRFP-BTT9tSAx.js → dagre-6UL2VRFP-DF4MMuTn.js} +1 -1
- package/dist-renderer/assets/{diagram-PSM6KHXK-Du-U-mK2.js → diagram-PSM6KHXK-CcF1eZ7E.js} +1 -1
- package/dist-renderer/assets/{diagram-QEK2KX5R-jFdHeKas.js → diagram-QEK2KX5R-DYlOVPQB.js} +1 -1
- package/dist-renderer/assets/{diagram-S2PKOQOG-DKLNK2bu.js → diagram-S2PKOQOG-BHXWsZOP.js} +1 -1
- package/dist-renderer/assets/{erDiagram-Q2GNP2WA-CZxHgIIo.js → erDiagram-Q2GNP2WA-GjmuBx8d.js} +1 -1
- package/dist-renderer/assets/{flowDiagram-NV44I4VS-v4XStCD0.js → flowDiagram-NV44I4VS-BuS7YVHk.js} +1 -1
- package/dist-renderer/assets/{ganttDiagram-JELNMOA3-DJjD_BEL.js → ganttDiagram-JELNMOA3-3Teu5tAa.js} +1 -1
- package/dist-renderer/assets/{gitGraphDiagram-V2S2FVAM-BNy-jr03.js → gitGraphDiagram-V2S2FVAM-BiLdCYu5.js} +1 -1
- package/dist-renderer/assets/{graph-DDTrn6je.js → graph-CDP_R8ct.js} +1 -1
- package/dist-renderer/assets/{index-BBp78BAu.js → index-BSZdT-g-.js} +1 -1
- package/dist-renderer/assets/{index-eotrJaYy.js → index-BhWvMqsz.js} +1 -1
- package/dist-renderer/assets/{index-D8_B-cfs.js → index-C2_AupSj.js} +1 -1
- package/dist-renderer/assets/{index-BQrwHZ-k.js → index-C5ujiwAR.js} +580 -588
- package/dist-renderer/assets/index-CIS2CTK9.css +1 -0
- package/dist-renderer/assets/{index-CRKQSG9S.js → index-CVNjLwkq.js} +1 -1
- package/dist-renderer/assets/{index-DR6Wz52b.js → index-CwG3se0q.js} +1 -1
- package/dist-renderer/assets/{infoDiagram-HS3SLOUP-DqnOsuza.js → infoDiagram-HS3SLOUP-DLHUFo72.js} +1 -1
- package/dist-renderer/assets/{journeyDiagram-XKPGCS4Q-DTobaO1d.js → journeyDiagram-XKPGCS4Q-BE07RpJD.js} +1 -1
- package/dist-renderer/assets/{kanban-definition-3W4ZIXB7-HbwVOvWc.js → kanban-definition-3W4ZIXB7-DDHZy4NB.js} +1 -1
- package/dist-renderer/assets/{layout--VYmTcw2.js → layout-5nA5wUxO.js} +1 -1
- package/dist-renderer/assets/{linear-BsJh89Mr.js → linear-BtF1i2qN.js} +1 -1
- package/dist-renderer/assets/{mindmap-definition-VGOIOE7T-BZqUZePd.js → mindmap-definition-VGOIOE7T-Z1Ui9Sqy.js} +1 -1
- package/dist-renderer/assets/{pieDiagram-ADFJNKIX-B1q_nH6P.js → pieDiagram-ADFJNKIX-LCjxckWv.js} +1 -1
- package/dist-renderer/assets/{quadrantDiagram-AYHSOK5B-UD8QhSEu.js → quadrantDiagram-AYHSOK5B-BOwKjSco.js} +1 -1
- package/dist-renderer/assets/{requirementDiagram-UZGBJVZJ-BA_i7Nw8.js → requirementDiagram-UZGBJVZJ-pChP8Znd.js} +1 -1
- package/dist-renderer/assets/{sankeyDiagram-TZEHDZUN-CMTnX-2d.js → sankeyDiagram-TZEHDZUN-DifZ2qpo.js} +1 -1
- package/dist-renderer/assets/{sequenceDiagram-WL72ISMW-BQXDB615.js → sequenceDiagram-WL72ISMW-CJg-WYyY.js} +1 -1
- package/dist-renderer/assets/{splashScene-D0YB9uxm.js → splashScene-94xWCzLA.js} +1 -1
- package/dist-renderer/assets/{stateDiagram-FKZM4ZOC-BAsPXy6X.js → stateDiagram-FKZM4ZOC-DWHOoFdv.js} +1 -1
- package/dist-renderer/assets/stateDiagram-v2-4FDKWEC3-CGYZOoMb.js +1 -0
- package/dist-renderer/assets/{timeline-definition-IT6M3QCI-BdasmVkC.js → timeline-definition-IT6M3QCI-CPgokIo8.js} +1 -1
- package/dist-renderer/assets/{treemap-GDKQZRPO-BkKQqIui.js → treemap-GDKQZRPO-DAVqSR9L.js} +1 -1
- package/dist-renderer/assets/{xychartDiagram-PRI3JC2R-EAlPHOdx.js → xychartDiagram-PRI3JC2R-CCOcGbrD.js} +1 -1
- package/dist-renderer/chat-community-qr.jpg +0 -0
- package/dist-renderer/fonts/Agave-Bold.ttf +0 -0
- package/dist-renderer/fonts/Agave-Regular.ttf +0 -0
- package/dist-renderer/icon.png +0 -0
- package/dist-renderer/icon.rar +0 -0
- package/dist-renderer/index.html +3 -3
- package/package.json +21 -26
- package/src/features/worker-society/core/application/WorkerSocietyService.test.ts +802 -0
- package/src/features/worker-society/core/application/WorkerSocietyService.ts +428 -0
- package/src/features/worker-society/core/application/fakes.ts +101 -0
- package/src/features/worker-society/core/application/ports.ts +70 -0
- package/src/features/worker-society/core/domain/models/society.ts +141 -0
- package/src/features/worker-society/core/domain/policies/societyPolicies.test.ts +739 -0
- package/src/features/worker-society/core/domain/policies/societyPolicies.ts +496 -0
- package/src/features/worker-society/main/adapters/input/societyMcp.test.ts +317 -0
- package/src/features/worker-society/main/adapters/input/societyMcp.ts +257 -0
- package/src/features/worker-society/main/adapters/input/societyRoutes.test.ts +695 -0
- package/src/features/worker-society/main/adapters/input/societyRoutes.ts +194 -0
- package/src/features/worker-society/main/composition/societyComposition.test.ts +74 -0
- package/src/features/worker-society/main/composition/societyComposition.ts +70 -0
- package/src/features/worker-society/main/composition/workerSocietyPlugin.test.ts +69 -0
- package/src/features/worker-society/main/composition/workerSocietyPlugin.ts +67 -0
- package/src/features/worker-society/main/infrastructure/crossTeamMessageGateway.test.ts +132 -0
- package/src/features/worker-society/main/infrastructure/crossTeamMessageGateway.ts +84 -0
- package/src/features/worker-society/main/infrastructure/fsStores.test.ts +216 -0
- package/src/features/worker-society/main/infrastructure/fsStores.ts +113 -0
- package/src/features/worker-society/main/infrastructure/mergingProfileStore.test.ts +195 -0
- package/src/features/worker-society/main/infrastructure/mergingProfileStore.ts +96 -0
- package/src/features/worker-society/renderer/SocietyGraph.tsx +166 -0
- package/src/features/worker-society/renderer/SocietyNodeLabels.tsx +139 -0
- package/src/features/worker-society/renderer/SocietyNodeOverlay.tsx +339 -0
- package/src/features/worker-society/renderer/SocietyView.tsx +437 -0
- package/src/features/worker-society/renderer/index.ts +11 -0
- package/src/features/worker-society/renderer/societyApi.test.ts +259 -0
- package/src/features/worker-society/renderer/societyApi.ts +144 -0
- package/src/features/worker-society/renderer/societyGraphAdapter.test.ts +321 -0
- package/src/features/worker-society/renderer/societyGraphAdapter.ts +240 -0
- package/src/features/worker-society/renderer/societyOverlayActions.test.ts +57 -0
- package/src/features/worker-society/renderer/societyOverlayActions.ts +49 -0
- package/src/features/worker-society/renderer/societyStore.test.ts +218 -0
- package/src/features/worker-society/renderer/societyStore.ts +146 -0
- package/src/features/worker-society/renderer/societyViewUtils.test.ts +81 -0
- package/src/features/worker-society/renderer/societyViewUtils.ts +68 -0
- package/src/main/ipc/extensions.ts +27 -0
- package/src/main/server.ts +1709 -534
- package/src/main/services/ccConnect/CcConnectBridge.ts +26 -11
- package/src/main/services/ccConnect/CcConnectClient.ts +9 -2
- package/src/main/services/ccConnect/workDirReconcile.test.ts +57 -0
- package/src/main/services/ccConnect/workDirReconcile.ts +36 -0
- package/src/main/services/direct-cli/DirectCliSessionManager.test.ts +397 -0
- package/src/main/services/direct-cli/DirectCliSessionManager.ts +508 -0
- package/src/main/services/direct-cli/DirectCliSessionStore.test.ts +79 -0
- package/src/main/services/direct-cli/DirectCliSessionStore.ts +97 -0
- package/src/main/services/direct-cli/__tests__/directCliMessageId.test.ts +40 -0
- package/src/main/services/direct-cli/directCliMessageId.ts +21 -0
- package/src/main/services/direct-cli/index.ts +17 -0
- package/src/main/services/extensions/capability-packs/CapabilityPackLoaderService.ts +637 -0
- package/src/main/services/extensions/catalog/PluginCatalogService.ts +2 -2
- package/src/main/services/loop-assets/LoopAssetsScannerService.ts +657 -0
- package/src/main/services/runtime/providerAwareCliEnv.ts +33 -5
- package/src/main/services/session-intelligence/LocalSessionScanner.ts +156 -71
- package/src/main/services/session-intelligence/SessionUsageParser.ts +103 -8
- package/src/main/services/session-intelligence/UsageTelemetryService.ts +11 -0
- package/src/main/services/session-intelligence/__tests__/teamSessionListMapper.test.ts +104 -0
- package/src/main/services/session-intelligence/teamSessionListMapper.ts +78 -0
- package/src/main/services/system-manager/AdminLoopInitializer.ts +95 -0
- package/src/main/services/system-manager/BuiltinWorkflowSeeder.ts +679 -74
- package/src/main/services/system-manager/SystemManagerConfigService.ts +19 -1
- package/src/main/services/system-manager/WorkflowPromptService.ts +58 -5
- package/src/main/services/system-manager/__tests__/AdminLoopInitializer.test.ts +129 -0
- package/src/main/services/system-manager/__tests__/SystemManagerConfigService.test.ts +60 -0
- package/src/main/services/teams-mvp/CollaborationBoardService.ts +2 -0
- package/src/main/services/teams-mvp/OpsRunbookContext.ts +60 -0
- package/src/main/services/teams-mvp/TaskDispatchService.test.ts +305 -0
- package/src/main/services/teams-mvp/TaskDispatchService.ts +250 -131
- package/src/main/services/teams-mvp/TeamProvisioningService.ts +12 -2
- package/src/main/services/teams-mvp/TeamWorkspaceService.test.ts +207 -0
- package/src/main/services/teams-mvp/TeamWorkspaceService.ts +104 -51
- package/src/main/services/teams-mvp/index.ts +6 -0
- package/src/main/utils/externalPlatformSessionRouting.ts +92 -0
- package/src/main/utils/toolApprovalRules.ts +151 -0
- package/src/renderer/App.tsx +24 -89
- package/src/renderer/api/httpClient.ts +115 -37
- package/src/renderer/api/providers.ts +5 -16
- package/src/renderer/components/chat/CommunityChatView.tsx +81 -0
- package/src/renderer/components/dashboard/DashboardView.tsx +130 -84
- package/src/renderer/components/extensions/ExtensionStoreView.tsx +39 -5
- package/src/renderer/components/extensions/ExtensionsSubTabTrigger.tsx +2 -1
- package/src/renderer/components/extensions/capability-packs/CapabilityPacksPanel.tsx +170 -0
- package/src/renderer/components/layout/PaneContent.tsx +10 -2
- package/src/renderer/components/layout/SortableTab.tsx +4 -0
- package/src/renderer/components/layout/TabBarActions.tsx +13 -16
- package/src/renderer/components/runtime/ProviderRuntimeSettingsDialog.tsx +4 -135
- package/src/renderer/components/schedules/SchedulesView.tsx +22 -14
- package/src/renderer/components/schedules/calendar/CalendarEventBlock.tsx +7 -6
- package/src/renderer/components/settings/SettingsTabs.tsx +24 -21
- package/src/renderer/components/settings/SettingsView.tsx +22 -13
- package/src/renderer/components/settings/components/SettingRow.tsx +13 -5
- package/src/renderer/components/settings/components/SettingsSectionCard.tsx +53 -0
- package/src/renderer/components/settings/components/SettingsSectionHeader.tsx +10 -6
- package/src/renderer/components/settings/components/SettingsSelect.tsx +12 -9
- package/src/renderer/components/settings/components/SettingsToggle.tsx +6 -5
- package/src/renderer/components/settings/components/index.ts +1 -0
- package/src/renderer/components/settings/sections/AdvancedSection.tsx +78 -59
- package/src/renderer/components/settings/sections/CliStatusSection.tsx +32 -44
- package/src/renderer/components/settings/sections/ConfigEditorDialog.tsx +1 -1
- package/src/renderer/components/settings/sections/GeneralSection.tsx +216 -186
- package/src/renderer/components/settings/sections/PlatformsSection.tsx +25 -17
- package/src/renderer/components/settings/sections/TaskBusSection.tsx +63 -22
- package/src/renderer/components/sidebar/SidebarSessions.tsx +120 -80
- package/src/renderer/components/sidebar/SidebarTaskItem.tsx +1 -1
- package/src/renderer/components/splash/splashScene.ts +6 -2
- package/src/renderer/components/system-manager/SystemManagerView.tsx +169 -255
- package/src/renderer/components/tasks/TasksView.tsx +63 -37
- package/src/renderer/components/team/CcSessionsSection.tsx +124 -89
- package/src/renderer/components/team/HarnessBrandLogos.tsx +318 -0
- package/src/renderer/components/team/HarnessSelect.tsx +25 -26
- package/src/renderer/components/team/TeamDetailView.tsx +137 -153
- package/src/renderer/components/team/TeamEmptyState.tsx +9 -37
- package/src/renderer/components/team/TeamListView.tsx +143 -30
- package/src/renderer/components/team/__tests__/CcSessionsSection.hasLocalFile.test.tsx +128 -0
- package/src/renderer/components/team/activity/ActivityItem.tsx +21 -9
- package/src/renderer/components/team/activity/ActivityTimeline.tsx +2 -2
- package/src/renderer/components/team/dialogs/AdvancedCliSection.tsx +1 -1
- package/src/renderer/components/team/dialogs/CreateTaskDialog.tsx +13 -10
- package/src/renderer/components/team/dialogs/CreateTeamDialog.tsx +156 -83
- package/src/renderer/components/team/dialogs/EditTeamDialog.tsx +9 -157
- package/src/renderer/components/team/dialogs/LaunchTeamDialog.tsx +19 -15
- package/src/renderer/components/team/dialogs/PlatformBindingDialog.tsx +48 -10
- package/src/renderer/components/team/dialogs/PlatformManualForm.tsx +11 -12
- package/src/renderer/components/team/dialogs/PlatformSetupQR.tsx +39 -37
- package/src/renderer/components/team/dialogs/RuntimeConfigDialog.tsx +434 -64
- package/src/renderer/components/team/dialogs/SendMessageDialog.tsx +12 -10
- package/src/renderer/components/team/dialogs/TaskDetailDialog.tsx +2 -2
- package/src/renderer/components/team/dialogs/__tests__/CreateTeamDialog.bindProject.test.tsx +399 -0
- package/src/renderer/components/team/dialogs/__tests__/CreateTeamDialog.chineseRepro.test.tsx +253 -0
- package/src/renderer/components/team/dialogs/platformAllowUtils.ts +91 -0
- package/src/renderer/components/team/dialogs/teammateRuntimeCompatibility.tsx +1 -1
- package/src/renderer/components/team/kanban/KanbanTaskCard.test.tsx +41 -0
- package/src/renderer/components/team/kanban/KanbanTaskCard.tsx +41 -86
- package/src/renderer/components/team/loop-console/LoopCommandComposer.tsx +310 -0
- package/src/renderer/components/team/loop-console/LoopConsolePanel.tsx +372 -0
- package/src/renderer/components/team/loop-console/loopSendIntent.test.ts +85 -0
- package/src/renderer/components/team/loop-console/loopSendIntent.ts +221 -0
- package/src/renderer/components/team/loop-console/useLeadSessionToolActivity.ts +74 -0
- package/src/renderer/components/team/loop-console/useLoopCommandSuggestions.ts +165 -0
- package/src/renderer/components/team/loop-console/useLoopConsoleController.ts +266 -0
- package/src/renderer/components/team/members/LeadModelRow.test.tsx +1 -1
- package/src/renderer/components/team/members/LeadModelRow.tsx +5 -3
- package/src/renderer/components/team/members/MemberDetailDialog.tsx +11 -0
- package/src/renderer/components/team/members/MemberDetailStats.tsx +13 -3
- package/src/renderer/components/team/members/MemberDraftRow.test.tsx +1 -1
- package/src/renderer/components/team/members/MemberDraftRow.tsx +1 -1
- package/src/renderer/components/team/members/MemberMessagesTab.tsx +2 -2
- package/src/renderer/components/team/members/MemberStatsTab.tsx +1 -1
- package/src/renderer/components/team/messages/MessageComposer.tsx +150 -44
- package/src/renderer/components/team/messages/MessagesFilterPopover.tsx +2 -2
- package/src/renderer/components/team/messages/MessagesPanel.tsx +34 -28
- package/src/renderer/components/team/schedule/CcCronScheduleDialog.tsx +6 -6
- package/src/renderer/components/team/taskLogs/ExactTaskLogCard.tsx +2 -2
- package/src/renderer/components/team/taskLogs/TaskLogStreamSection.tsx +1 -1
- package/src/renderer/components/terminal/TerminalPanel.tsx +2 -3
- package/src/renderer/components/ui/MentionableTextarea.tsx +5 -1
- package/src/renderer/constants/teamColors.ts +5 -5
- package/src/renderer/hooks/useExtensionsTabState.ts +1 -1
- package/src/renderer/hooks/useMentionDetection.ts +5 -1
- package/src/renderer/hooks/useProjectWorkflowCommands.ts +57 -0
- package/src/renderer/hooks/useTeamSuggestions.ts +2 -0
- package/src/renderer/index.css +19 -2
- package/src/renderer/main.tsx +7 -1
- package/src/renderer/store/index.ts +18 -1
- package/src/renderer/store/slices/extensionsSlice.ts +83 -0
- package/src/renderer/store/slices/tabSlice.ts +61 -0
- package/src/renderer/store/slices/teamSlice.ts +138 -9
- package/src/renderer/types/mention.ts +8 -0
- package/src/renderer/types/tabs.ts +3 -1
- package/src/renderer/utils/__tests__/bindProjectSlug.test.ts +69 -0
- package/src/renderer/utils/__tests__/groupTransformer.test.ts +148 -0
- package/src/renderer/utils/__tests__/initialRoute.test.ts +101 -0
- package/src/renderer/utils/__tests__/leadToolActivity.test.ts +124 -0
- package/src/renderer/utils/__tests__/mergeTeamMessages.test.ts +81 -0
- package/src/renderer/utils/__tests__/teamMessageFiltering.test.ts +213 -0
- package/src/renderer/utils/__tests__/teamMessageKey.test.ts +75 -0
- package/src/renderer/utils/__tests__/workflowCommandExecution.test.ts +173 -0
- package/src/renderer/utils/__tests__/workflowCommandSuggestions.test.ts +59 -0
- package/src/renderer/utils/bindProjectSlug.ts +57 -0
- package/src/renderer/utils/capabilityCommandExecution.ts +113 -0
- package/src/renderer/utils/initialRoute.ts +89 -0
- package/src/renderer/utils/leadToolActivity.ts +117 -0
- package/src/renderer/utils/loopShortcutSuggestions.ts +106 -0
- package/src/renderer/utils/mentionSuggestions.ts +1 -1
- package/src/renderer/utils/slashCommandRegistry.ts +231 -0
- package/src/renderer/utils/teamMentionDirective.ts +31 -0
- package/src/renderer/utils/workflowCommandExecution.ts +96 -0
- package/src/renderer/utils/workflowCommandSuggestions.ts +49 -0
- package/src/shared/types/api.ts +79 -4
- package/src/shared/types/ccConnect.ts +1 -0
- package/src/shared/types/extensions/api.ts +19 -0
- package/src/shared/types/extensions/capabilityPack.ts +118 -0
- package/src/shared/types/extensions/index.ts +29 -1
- package/src/shared/types/index.ts +6 -0
- package/src/shared/types/loopAssets.ts +54 -0
- package/src/shared/types/providers.ts +0 -16
- package/src/shared/types/systemManager.ts +26 -1
- package/src/shared/types/team.ts +41 -5
- package/src/shared/types/terminal.ts +2 -36
- package/src/shared/types/worker.test.ts +28 -0
- package/src/shared/types/worker.ts +3 -0
- package/src/shared/utils/__tests__/effortLevels.test.ts +88 -0
- package/src/shared/utils/__tests__/providerBackend.test.ts +88 -0
- package/src/shared/utils/__tests__/providerLaunchArgs.test.ts +220 -0
- package/src/shared/utils/claudeStreamJson.test.ts +187 -0
- package/src/shared/utils/claudeStreamJson.ts +153 -0
- package/src/shared/utils/providerLaunchArgs.ts +217 -0
- package/src/shared/utils/slashCommands.ts +10 -0
- package/src/types/node-pty.d.ts +8 -0
- package/dist-renderer/assets/channel-Ch7JrfUu.js +0 -1
- package/dist-renderer/assets/classDiagram-2ON5EDUG-z9I4AnFy.js +0 -1
- package/dist-renderer/assets/classDiagram-v2-WZHVMYZB-z9I4AnFy.js +0 -1
- package/dist-renderer/assets/clone-Dfi1Jx6l.js +0 -1
- package/dist-renderer/assets/index-iyjkpSus.css +0 -32
- package/dist-renderer/assets/stateDiagram-v2-4FDKWEC3-DTUIBfce.js +0 -1
- package/src/main/services/system-manager/SystemManagerPtyService.ts +0 -233
- package/src/renderer/components/common/TerminalPane.tsx +0 -213
- 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
|
|
10
|
-
*
|
|
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 {
|
|
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 (
|
|
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
|
|
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
|
|
321
|
+
private async resolveJsonlDirs(workDir: string): Promise<string[]> {
|
|
307
322
|
const projectsBase = getProjectsBasePath();
|
|
308
|
-
const
|
|
309
|
-
|
|
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
|
|
391
|
+
const jsonlDirs = await this.resolveJsonlDirs(workDir);
|
|
318
392
|
const summaries: LocalSessionSummary[] = [];
|
|
319
393
|
const now = Date.now();
|
|
320
394
|
|
|
321
|
-
for
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
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
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
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
|
-
|
|
337
|
-
|
|
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
|
|
398
|
-
|
|
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 {
|
|
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: {
|
|
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
|
-
|
|
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] = {
|
|
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) : [],
|