@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.
Files changed (281) hide show
  1. package/README.md +98 -89
  2. package/bin/hermit.mjs +96 -0
  3. package/dist-renderer/assets/{ProjectEditorOverlay-DlFQ6mai.js → ProjectEditorOverlay-C98qSs7-.js} +1 -1
  4. package/dist-renderer/assets/{TeamGraphOverlay-D2TPMPGR.js → TeamGraphOverlay-CsBbZwcL.js} +1 -1
  5. package/dist-renderer/assets/{_basePickBy-Cmd0RHLQ.js → _basePickBy-ZOyLWjMK.js} +1 -1
  6. package/dist-renderer/assets/{_baseUniq-BI_iy8ea.js → _baseUniq-DBb726rt.js} +1 -1
  7. package/dist-renderer/assets/{arc-NzW2mjTP.js → arc-CdiTaR_R.js} +1 -1
  8. package/dist-renderer/assets/{architectureDiagram-VXUJARFQ-Bzq85AYv.js → architectureDiagram-VXUJARFQ-Cz3sc5TH.js} +1 -1
  9. package/dist-renderer/assets/{blockDiagram-VD42YOAC-D1PvYS-b.js → blockDiagram-VD42YOAC-DE4c-KJ3.js} +1 -1
  10. package/dist-renderer/assets/{c4Diagram-YG6GDRKO-D49RKzPC.js → c4Diagram-YG6GDRKO-CmTMDTrV.js} +1 -1
  11. package/dist-renderer/assets/channel-KTpqi9eT.js +1 -0
  12. package/dist-renderer/assets/{chunk-4BX2VUAB-fmI_MQmQ.js → chunk-4BX2VUAB-rhHy3tFl.js} +1 -1
  13. package/dist-renderer/assets/{chunk-55IACEB6-Xsv9RCXZ.js → chunk-55IACEB6-fLZBzuo_.js} +1 -1
  14. package/dist-renderer/assets/{chunk-B4BG7PRW-BE1KO8Um.js → chunk-B4BG7PRW-DOzxQhim.js} +1 -1
  15. package/dist-renderer/assets/{chunk-DI55MBZ5-tqJ7Mv7f.js → chunk-DI55MBZ5-COQCcXC5.js} +1 -1
  16. package/dist-renderer/assets/{chunk-FMBD7UC4-DMD45MVJ.js → chunk-FMBD7UC4-IKU9U_Y4.js} +1 -1
  17. package/dist-renderer/assets/{chunk-QN33PNHL-DOhGrz-q.js → chunk-QN33PNHL-D6WV154X.js} +1 -1
  18. package/dist-renderer/assets/{chunk-QZHKN3VN-D8yDgJdD.js → chunk-QZHKN3VN-D90_2DQp.js} +1 -1
  19. package/dist-renderer/assets/{chunk-TZMSLE5B-BcsEDu7A.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-DlSqGHMX.js → cose-bilkent-S5V4N54A-6WiK6U2P.js} +1 -1
  24. package/dist-renderer/assets/{dagre-6UL2VRFP-BTT9tSAx.js → dagre-6UL2VRFP-DF4MMuTn.js} +1 -1
  25. package/dist-renderer/assets/{diagram-PSM6KHXK-Du-U-mK2.js → diagram-PSM6KHXK-CcF1eZ7E.js} +1 -1
  26. package/dist-renderer/assets/{diagram-QEK2KX5R-jFdHeKas.js → diagram-QEK2KX5R-DYlOVPQB.js} +1 -1
  27. package/dist-renderer/assets/{diagram-S2PKOQOG-DKLNK2bu.js → diagram-S2PKOQOG-BHXWsZOP.js} +1 -1
  28. package/dist-renderer/assets/{erDiagram-Q2GNP2WA-CZxHgIIo.js → erDiagram-Q2GNP2WA-GjmuBx8d.js} +1 -1
  29. package/dist-renderer/assets/{flowDiagram-NV44I4VS-v4XStCD0.js → flowDiagram-NV44I4VS-BuS7YVHk.js} +1 -1
  30. package/dist-renderer/assets/{ganttDiagram-JELNMOA3-DJjD_BEL.js → ganttDiagram-JELNMOA3-3Teu5tAa.js} +1 -1
  31. package/dist-renderer/assets/{gitGraphDiagram-V2S2FVAM-BNy-jr03.js → gitGraphDiagram-V2S2FVAM-BiLdCYu5.js} +1 -1
  32. package/dist-renderer/assets/{graph-DDTrn6je.js → graph-CDP_R8ct.js} +1 -1
  33. package/dist-renderer/assets/{index-BBp78BAu.js → index-BSZdT-g-.js} +1 -1
  34. package/dist-renderer/assets/{index-eotrJaYy.js → index-BhWvMqsz.js} +1 -1
  35. package/dist-renderer/assets/{index-D8_B-cfs.js → index-C2_AupSj.js} +1 -1
  36. package/dist-renderer/assets/{index-BQrwHZ-k.js → index-C5ujiwAR.js} +580 -588
  37. package/dist-renderer/assets/index-CIS2CTK9.css +1 -0
  38. package/dist-renderer/assets/{index-CRKQSG9S.js → index-CVNjLwkq.js} +1 -1
  39. package/dist-renderer/assets/{index-DR6Wz52b.js → index-CwG3se0q.js} +1 -1
  40. package/dist-renderer/assets/{infoDiagram-HS3SLOUP-DqnOsuza.js → infoDiagram-HS3SLOUP-DLHUFo72.js} +1 -1
  41. package/dist-renderer/assets/{journeyDiagram-XKPGCS4Q-DTobaO1d.js → journeyDiagram-XKPGCS4Q-BE07RpJD.js} +1 -1
  42. package/dist-renderer/assets/{kanban-definition-3W4ZIXB7-HbwVOvWc.js → kanban-definition-3W4ZIXB7-DDHZy4NB.js} +1 -1
  43. package/dist-renderer/assets/{layout--VYmTcw2.js → layout-5nA5wUxO.js} +1 -1
  44. package/dist-renderer/assets/{linear-BsJh89Mr.js → linear-BtF1i2qN.js} +1 -1
  45. package/dist-renderer/assets/{mindmap-definition-VGOIOE7T-BZqUZePd.js → mindmap-definition-VGOIOE7T-Z1Ui9Sqy.js} +1 -1
  46. package/dist-renderer/assets/{pieDiagram-ADFJNKIX-B1q_nH6P.js → pieDiagram-ADFJNKIX-LCjxckWv.js} +1 -1
  47. package/dist-renderer/assets/{quadrantDiagram-AYHSOK5B-UD8QhSEu.js → quadrantDiagram-AYHSOK5B-BOwKjSco.js} +1 -1
  48. package/dist-renderer/assets/{requirementDiagram-UZGBJVZJ-BA_i7Nw8.js → requirementDiagram-UZGBJVZJ-pChP8Znd.js} +1 -1
  49. package/dist-renderer/assets/{sankeyDiagram-TZEHDZUN-CMTnX-2d.js → sankeyDiagram-TZEHDZUN-DifZ2qpo.js} +1 -1
  50. package/dist-renderer/assets/{sequenceDiagram-WL72ISMW-BQXDB615.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-BAsPXy6X.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-BdasmVkC.js → timeline-definition-IT6M3QCI-CPgokIo8.js} +1 -1
  55. package/dist-renderer/assets/{treemap-GDKQZRPO-BkKQqIui.js → treemap-GDKQZRPO-DAVqSR9L.js} +1 -1
  56. package/dist-renderer/assets/{xychartDiagram-PRI3JC2R-EAlPHOdx.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 +1709 -534
  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 +679 -74
  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 +115 -37
  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 +143 -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 +156 -83
  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 +41 -5
  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-Ch7JrfUu.js +0 -1
  274. package/dist-renderer/assets/classDiagram-2ON5EDUG-z9I4AnFy.js +0 -1
  275. package/dist-renderer/assets/classDiagram-v2-WZHVMYZB-z9I4AnFy.js +0 -1
  276. package/dist-renderer/assets/clone-Dfi1Jx6l.js +0 -1
  277. package/dist-renderer/assets/index-iyjkpSus.css +0 -32
  278. package/dist-renderer/assets/stateDiagram-v2-4FDKWEC3-DTUIBfce.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
@@ -0,0 +1,88 @@
1
+ import { describe, expect, it } from 'vitest';
2
+
3
+ import {
4
+ formatProviderBackendLabel,
5
+ getDefaultProviderBackendId,
6
+ isLegacyCodexProviderBackendId,
7
+ isTeamProviderBackendId,
8
+ migrateProviderBackendId,
9
+ } from '../providerBackend';
10
+
11
+ describe('getDefaultProviderBackendId', () => {
12
+ it('returns codex-native for Codex', () => {
13
+ expect(getDefaultProviderBackendId('codex')).toBe('codex-native');
14
+ });
15
+
16
+ it('returns undefined for non-Codex providers', () => {
17
+ expect(getDefaultProviderBackendId('anthropic')).toBeUndefined();
18
+ expect(getDefaultProviderBackendId('gemini')).toBeUndefined();
19
+ expect(getDefaultProviderBackendId('opencode')).toBeUndefined();
20
+ expect(getDefaultProviderBackendId(undefined)).toBeUndefined();
21
+ });
22
+ });
23
+
24
+ describe('isLegacyCodexProviderBackendId', () => {
25
+ it.each(['auto', 'adapter', 'api'])('identifies %s as legacy', (id) => {
26
+ expect(isLegacyCodexProviderBackendId(id)).toBe(true);
27
+ });
28
+
29
+ it.each(['codex-native', 'cli-sdk', undefined, null, '', 'unknown'])(
30
+ 'does not identify %s as legacy',
31
+ (id) => {
32
+ expect(isLegacyCodexProviderBackendId(id)).toBe(false);
33
+ }
34
+ );
35
+ });
36
+
37
+ describe('isTeamProviderBackendId', () => {
38
+ it.each(['auto', 'adapter', 'api', 'cli-sdk', 'codex-native'])(
39
+ 'accepts valid backend id %s',
40
+ (id) => {
41
+ expect(isTeamProviderBackendId(id)).toBe(true);
42
+ }
43
+ );
44
+
45
+ it.each(['', 'native', 'sdk', null, undefined])('rejects invalid %s', (id) => {
46
+ expect(isTeamProviderBackendId(id)).toBe(false);
47
+ });
48
+ });
49
+
50
+ describe('migrateProviderBackendId', () => {
51
+ it('forces codex-native for Codex when backend is missing or legacy', () => {
52
+ expect(migrateProviderBackendId('codex', null)).toBe('codex-native');
53
+ expect(migrateProviderBackendId('codex', undefined)).toBe('codex-native');
54
+ expect(migrateProviderBackendId('codex', 'auto')).toBe('codex-native');
55
+ expect(migrateProviderBackendId('codex', 'adapter')).toBe('codex-native');
56
+ });
57
+
58
+ it('keeps a valid non-legacy Codex backend', () => {
59
+ expect(migrateProviderBackendId('codex', 'cli-sdk')).toBe('cli-sdk');
60
+ });
61
+
62
+ it('passes through valid backends for non-Codex providers', () => {
63
+ expect(migrateProviderBackendId('gemini', 'cli-sdk')).toBe('cli-sdk');
64
+ expect(migrateProviderBackendId('anthropic', 'auto')).toBe('auto');
65
+ });
66
+
67
+ it('returns undefined for invalid backends on non-Codex providers', () => {
68
+ expect(migrateProviderBackendId('gemini', 'totally-bogus')).toBeUndefined();
69
+ });
70
+ });
71
+
72
+ describe('formatProviderBackendLabel', () => {
73
+ it('labels codex-native as "Codex native"', () => {
74
+ expect(formatProviderBackendLabel('codex', 'codex-native')).toBe('Codex native');
75
+ });
76
+
77
+ it('labels gemini cli-sdk as "CLI SDK"', () => {
78
+ expect(formatProviderBackendLabel('gemini', 'cli-sdk')).toBe('CLI SDK');
79
+ });
80
+
81
+ it('returns undefined for gemini auto backend', () => {
82
+ expect(formatProviderBackendLabel('gemini', 'auto')).toBeUndefined();
83
+ });
84
+
85
+ it('returns undefined when no backend resolves', () => {
86
+ expect(formatProviderBackendLabel('anthropic', undefined)).toBeUndefined();
87
+ });
88
+ });
@@ -0,0 +1,220 @@
1
+ import { describe, expect, it } from 'vitest';
2
+
3
+ import { resolveProviderLaunchArgs } from '../providerLaunchArgs';
4
+
5
+ describe('resolveProviderLaunchArgs', () => {
6
+ describe('model resolution', () => {
7
+ it('appends the [1m] suffix to opus for Anthropic by default', () => {
8
+ const result = resolveProviderLaunchArgs({
9
+ providerId: 'anthropic',
10
+ model: 'opus',
11
+ effort: 'high',
12
+ skipPermissions: true,
13
+ });
14
+ expect(result.resolvedModel).toBe('opus[1m]');
15
+ expect(result.providerArgs).toEqual([
16
+ '--model',
17
+ 'opus[1m]',
18
+ '--effort',
19
+ 'high',
20
+ '--dangerously-skip-permissions',
21
+ ]);
22
+ });
23
+
24
+ it('strips the [1m] suffix when limitContext is set', () => {
25
+ const result = resolveProviderLaunchArgs({
26
+ providerId: 'anthropic',
27
+ model: 'opus',
28
+ effort: 'high',
29
+ limitContext: true,
30
+ });
31
+ expect(result.resolvedModel).toBe('opus');
32
+ expect(result.providerArgs).toContain('--model');
33
+ expect(result.providerArgs).not.toContain('opus[1m]');
34
+ });
35
+
36
+ it('does not add [1m] to models that do not support it (haiku)', () => {
37
+ const result = resolveProviderLaunchArgs({
38
+ providerId: 'anthropic',
39
+ model: 'haiku',
40
+ });
41
+ expect(result.resolvedModel).toBe('haiku');
42
+ });
43
+
44
+ it('passes an explicit model through for non-Anthropic providers', () => {
45
+ const result = resolveProviderLaunchArgs({
46
+ providerId: 'gemini',
47
+ model: 'gemini-2.5-pro',
48
+ });
49
+ expect(result.resolvedModel).toBe('gemini-2.5-pro');
50
+ expect(result.providerArgs).toContain('gemini-2.5-pro');
51
+ });
52
+
53
+ it('drops the model arg when a non-Anthropic provider uses the default sentinel', () => {
54
+ const result = resolveProviderLaunchArgs({
55
+ providerId: 'gemini',
56
+ model: '__provider_default__',
57
+ });
58
+ expect(result.resolvedModel).toBeNull();
59
+ expect(result.providerArgs).not.toContain('--model');
60
+ });
61
+
62
+ it('infers the provider id from the model when none is given', () => {
63
+ const result = resolveProviderLaunchArgs({ model: 'gemini-2.5-pro' });
64
+ expect(result.resolvedProviderId).toBe('gemini');
65
+ });
66
+
67
+ it('reports an issue when Codex has no resolved model', () => {
68
+ const result = resolveProviderLaunchArgs({
69
+ providerId: 'codex',
70
+ model: '__provider_default__',
71
+ });
72
+ expect(result.resolvedModel).toBeNull();
73
+ expect(result.connectionIssues.model).toMatch(/Codex/);
74
+ });
75
+ });
76
+
77
+ describe('effort validation', () => {
78
+ it('keeps effort valid for the provider', () => {
79
+ const result = resolveProviderLaunchArgs({
80
+ providerId: 'anthropic',
81
+ model: 'opus',
82
+ effort: 'max',
83
+ });
84
+ expect(result.providerArgs).toContain('--effort');
85
+ expect(result.providerArgs).toContain('max');
86
+ });
87
+
88
+ it('drops effort that is invalid for the provider and records an issue', () => {
89
+ const result = resolveProviderLaunchArgs({
90
+ providerId: 'anthropic',
91
+ model: 'opus',
92
+ effort: 'minimal', // minimal is not valid for anthropic
93
+ });
94
+ expect(result.providerArgs).not.toContain('--effort');
95
+ expect(result.connectionIssues.effort).toMatch(/minimal/);
96
+ });
97
+
98
+ it('accepts minimal effort for Codex', () => {
99
+ const result = resolveProviderLaunchArgs({
100
+ providerId: 'codex',
101
+ model: 'gpt-5.2',
102
+ effort: 'minimal',
103
+ });
104
+ expect(result.providerArgs).toContain('minimal');
105
+ });
106
+ });
107
+
108
+ describe('permissions and worktree', () => {
109
+ it('adds --dangerously-skip-permissions only when explicitly opted in', () => {
110
+ const withFlag = resolveProviderLaunchArgs({
111
+ providerId: 'anthropic',
112
+ model: 'opus',
113
+ skipPermissions: true,
114
+ });
115
+ expect(withFlag.providerArgs).toContain('--dangerously-skip-permissions');
116
+
117
+ // No opt-in (e.g. the MCP diagnostic path) → flag is NOT added.
118
+ const noOptIn = resolveProviderLaunchArgs({
119
+ providerId: 'anthropic',
120
+ model: 'opus',
121
+ });
122
+ expect(noOptIn.providerArgs).not.toContain('--dangerously-skip-permissions');
123
+ });
124
+
125
+ it('omits --dangerously-skip-permissions when skipPermissions is false', () => {
126
+ const result = resolveProviderLaunchArgs({
127
+ providerId: 'anthropic',
128
+ model: 'opus',
129
+ skipPermissions: false,
130
+ });
131
+ expect(result.providerArgs).not.toContain('--dangerously-skip-permissions');
132
+ });
133
+
134
+ it('appends --worktree when a worktree name is given', () => {
135
+ const result = resolveProviderLaunchArgs({
136
+ providerId: 'anthropic',
137
+ model: 'opus',
138
+ worktree: 'feature-x',
139
+ });
140
+ expect(result.providerArgs).toContain('--worktree');
141
+ expect(result.providerArgs).toContain('feature-x');
142
+ });
143
+ });
144
+
145
+ describe('extra CLI args', () => {
146
+ it('appends shell-split custom args', () => {
147
+ const result = resolveProviderLaunchArgs({
148
+ providerId: 'anthropic',
149
+ model: 'opus',
150
+ extraCliArgs: '--verbose --max-turns 5',
151
+ });
152
+ expect(result.providerArgs).toContain('--verbose');
153
+ expect(result.providerArgs).toContain('--max-turns');
154
+ expect(result.providerArgs).toContain('5');
155
+ });
156
+
157
+ it('drops a conflicting value flag AND its value from extra args', () => {
158
+ const result = resolveProviderLaunchArgs({
159
+ providerId: 'anthropic',
160
+ model: 'opus',
161
+ extraCliArgs: '--model gpt-4 --verbose',
162
+ });
163
+ // The emitted --model opus[1m] stays (exactly once); the user's --model gpt-4
164
+ // and its value are dropped as a conflict.
165
+ expect(result.providerArgs.filter((a) => a === '--model')).toHaveLength(1);
166
+ expect(result.providerArgs).toContain('opus[1m]');
167
+ expect(result.providerArgs).not.toContain('gpt-4');
168
+ expect(result.providerArgs).toContain('--verbose');
169
+ expect(result.connectionIssues.extraCliArgs).toMatch(/--model/);
170
+ });
171
+
172
+ it('drops a conflicting boolean flag from extra args', () => {
173
+ const result = resolveProviderLaunchArgs({
174
+ providerId: 'anthropic',
175
+ model: 'opus',
176
+ skipPermissions: true,
177
+ extraCliArgs: '--dangerously-skip-permissions --verbose',
178
+ });
179
+ expect(result.connectionIssues.extraCliArgs).toMatch(/--dangerously-skip-permissions/);
180
+ // only one --dangerously-skip-permissions should remain (the one we emit)
181
+ const count = result.providerArgs.filter(
182
+ (a) => a === '--dangerously-skip-permissions'
183
+ ).length;
184
+ expect(count).toBe(1);
185
+ });
186
+
187
+ it('preserves an unmanaged flag the resolver did not emit', () => {
188
+ // No skipPermissions → the resolver does not emit --dangerously-skip-permissions,
189
+ // so the user's explicit copy must be preserved (not treated as a conflict).
190
+ const result = resolveProviderLaunchArgs({
191
+ providerId: 'anthropic',
192
+ model: 'opus',
193
+ extraCliArgs: '--dangerously-skip-permissions --verbose',
194
+ });
195
+ expect(result.connectionIssues.extraCliArgs).toBeUndefined();
196
+ expect(result.providerArgs).toContain('--dangerously-skip-permissions');
197
+ });
198
+ });
199
+
200
+ describe('backend migration', () => {
201
+ it('migrates legacy Codex backend to codex-native', () => {
202
+ const result = resolveProviderLaunchArgs({
203
+ providerId: 'codex',
204
+ model: 'gpt-5.2',
205
+ providerBackendId: 'auto',
206
+ });
207
+ expect(result.resolvedProviderBackendId).toBe('codex-native');
208
+ });
209
+ });
210
+
211
+ describe('empty input', () => {
212
+ it('returns empty args and issues with no provider info', () => {
213
+ const result = resolveProviderLaunchArgs();
214
+ expect(result.providerArgs).toEqual([]);
215
+ expect(result.connectionIssues).toEqual({});
216
+ expect(result.resolvedProviderId).toBeUndefined();
217
+ expect(result.resolvedModel).toBeNull();
218
+ });
219
+ });
220
+ });
@@ -0,0 +1,187 @@
1
+ import { describe, expect, it } from 'vitest';
2
+
3
+ import { classifyClaudeStreamLine } from './claudeStreamJson';
4
+
5
+ describe('classifyClaudeStreamLine', () => {
6
+ it('returns null for empty / whitespace-only lines', () => {
7
+ expect(classifyClaudeStreamLine('')).toBeNull();
8
+ expect(classifyClaudeStreamLine(' ')).toBeNull();
9
+ expect(classifyClaudeStreamLine('\n\t')).toBeNull();
10
+ });
11
+
12
+ it('classifies a system init line carrying session_id + model', () => {
13
+ const line = JSON.stringify({
14
+ type: 'system',
15
+ subtype: 'init',
16
+ session_id: 'abc-123',
17
+ model: 'claude-sonnet-4-6',
18
+ cwd: '/Users/x/hermit',
19
+ });
20
+ expect(classifyClaudeStreamLine(line)).toEqual({
21
+ type: 'session-init',
22
+ sessionId: 'abc-123',
23
+ model: 'claude-sonnet-4-6',
24
+ });
25
+ });
26
+
27
+ it('treats a system line without session_id as unknown (no id to resume)', () => {
28
+ expect(classifyClaudeStreamLine(JSON.stringify({ type: 'system', subtype: 'init' }))).toEqual({
29
+ type: 'unknown',
30
+ });
31
+ });
32
+
33
+ it('extracts text blocks from an assistant message (wrapped format)', () => {
34
+ const line = JSON.stringify({
35
+ type: 'assistant',
36
+ message: {
37
+ id: 'msg_1',
38
+ role: 'assistant',
39
+ content: [{ type: 'text', text: 'Hello there' }],
40
+ },
41
+ });
42
+ expect(classifyClaudeStreamLine(line)).toEqual({
43
+ type: 'assistant',
44
+ blocks: [{ kind: 'text', text: 'Hello there' }],
45
+ messageId: 'msg_1',
46
+ });
47
+ });
48
+
49
+ it('extracts tool_use blocks with name + input', () => {
50
+ const line = JSON.stringify({
51
+ type: 'assistant',
52
+ message: {
53
+ content: [{ type: 'tool_use', id: 'toolu_1', name: 'Bash', input: { command: 'ls -la' } }],
54
+ },
55
+ });
56
+ expect(classifyClaudeStreamLine(line)).toEqual({
57
+ type: 'assistant',
58
+ blocks: [
59
+ {
60
+ kind: 'tool-use',
61
+ toolName: 'Bash',
62
+ toolInput: { command: 'ls -la' },
63
+ toolId: 'toolu_1',
64
+ },
65
+ ],
66
+ messageId: undefined,
67
+ });
68
+ });
69
+
70
+ it('keeps all block kinds when an assistant message carries text + thinking + tool_use', () => {
71
+ const line = JSON.stringify({
72
+ type: 'assistant',
73
+ message: {
74
+ id: 'msg_2',
75
+ content: [
76
+ { type: 'thinking', thinking: 'planning...' },
77
+ { type: 'text', text: 'Running a command' },
78
+ { type: 'tool_use', id: 'toolu_2', name: 'Read', input: { file_path: '/a' } },
79
+ ],
80
+ },
81
+ });
82
+ expect(classifyClaudeStreamLine(line)).toEqual({
83
+ type: 'assistant',
84
+ messageId: 'msg_2',
85
+ blocks: [
86
+ { kind: 'thinking', text: 'planning...' },
87
+ { kind: 'text', text: 'Running a command' },
88
+ { kind: 'tool-use', toolName: 'Read', toolInput: { file_path: '/a' }, toolId: 'toolu_2' },
89
+ ],
90
+ });
91
+ });
92
+
93
+ it('drops empty text/thinking blocks but keeps tool_use', () => {
94
+ const line = JSON.stringify({
95
+ type: 'assistant',
96
+ message: {
97
+ content: [
98
+ { type: 'text', text: '' },
99
+ { type: 'tool_use', name: 'Grep' },
100
+ ],
101
+ },
102
+ });
103
+ expect(classifyClaudeStreamLine(line)).toEqual({
104
+ type: 'assistant',
105
+ blocks: [{ kind: 'tool-use', toolName: 'Grep', toolInput: undefined, toolId: undefined }],
106
+ messageId: undefined,
107
+ });
108
+ });
109
+
110
+ it('classifies a result line with the turn text + subtype + session_id', () => {
111
+ const line = JSON.stringify({
112
+ type: 'result',
113
+ subtype: 'success',
114
+ result: 'final answer',
115
+ session_id: 'abc-123',
116
+ total_cost_usd: 0.01,
117
+ });
118
+ expect(classifyClaudeStreamLine(line)).toEqual({
119
+ type: 'result',
120
+ text: 'final answer',
121
+ subtype: 'success',
122
+ sessionId: 'abc-123',
123
+ });
124
+ });
125
+
126
+ it('handles result lines missing result text or subtype', () => {
127
+ expect(classifyClaudeStreamLine(JSON.stringify({ type: 'result' }))).toEqual({
128
+ type: 'result',
129
+ text: '',
130
+ subtype: '',
131
+ sessionId: undefined,
132
+ });
133
+ });
134
+
135
+ it('classifies control_request / control_cancel_request as control-request', () => {
136
+ expect(
137
+ classifyClaudeStreamLine(
138
+ JSON.stringify({
139
+ type: 'control_request',
140
+ request_id: 'req_1',
141
+ request: { subtype: 'can_use_tool', tool_name: 'Bash', input: { command: 'ls' } },
142
+ })
143
+ )
144
+ ).toEqual({
145
+ type: 'control-request',
146
+ requestId: 'req_1',
147
+ subtype: 'can_use_tool',
148
+ toolName: 'Bash',
149
+ toolInput: { command: 'ls' },
150
+ });
151
+ expect(classifyClaudeStreamLine(JSON.stringify({ type: 'control_cancel_request' }))).toEqual({
152
+ type: 'control-request',
153
+ requestId: undefined,
154
+ subtype: undefined,
155
+ toolName: undefined,
156
+ toolInput: undefined,
157
+ });
158
+ });
159
+
160
+ it('treats user/tool_result lines as unknown', () => {
161
+ expect(
162
+ classifyClaudeStreamLine(
163
+ JSON.stringify({
164
+ type: 'user',
165
+ message: { content: [{ type: 'tool_result', content: 'ok' }] },
166
+ })
167
+ )
168
+ ).toEqual({ type: 'unknown' });
169
+ });
170
+
171
+ it('flags non-JSON lines as parse-error (keeps the raw line)', () => {
172
+ expect(classifyClaudeStreamLine('not json at all')).toEqual({
173
+ type: 'parse-error',
174
+ line: 'not json at all',
175
+ });
176
+ });
177
+
178
+ it('returns unknown for valid JSON we do not model', () => {
179
+ expect(classifyClaudeStreamLine(JSON.stringify({ type: 'stream_event', delta: 'x' }))).toEqual({
180
+ type: 'unknown',
181
+ });
182
+ // assistant with no content array
183
+ expect(classifyClaudeStreamLine(JSON.stringify({ type: 'assistant', message: {} }))).toEqual({
184
+ type: 'unknown',
185
+ });
186
+ });
187
+ });
@@ -0,0 +1,153 @@
1
+ /**
2
+ * Pure per-line classifier for `claude --output-format stream-json` (NDJSON) output.
3
+ *
4
+ * Shared between the main-process DirectCliSessionManager (stdout relay) and any
5
+ * renderer-side interpretation, so the wire-format knowledge lives in one place.
6
+ * Line types mirror the ones the cc-connect `claudecode` agent parses
7
+ * (`agent/claudecode/session.go`): `system` / `assistant` / `user` / `result` /
8
+ * `control_request` / `control_cancel_request`.
9
+ *
10
+ * Note: stream-json emits message-level events (one `assistant` line per completed
11
+ * message), not per-token deltas. Token-by-token feel comes from emitting each
12
+ * assistant message as it lands — consistent with the existing cc-connect path.
13
+ */
14
+
15
+ export interface ParsedAssistantBlock {
16
+ kind: 'text' | 'thinking' | 'tool-use';
17
+ text?: string;
18
+ toolName?: string;
19
+ toolInput?: unknown;
20
+ toolId?: string;
21
+ }
22
+
23
+ export type ClaudeStreamLine =
24
+ | { type: 'session-init'; sessionId: string; model?: string }
25
+ | { type: 'assistant'; blocks: ParsedAssistantBlock[]; messageId?: string }
26
+ | { type: 'result'; text: string; subtype: string; sessionId?: string }
27
+ | {
28
+ type: 'control-request';
29
+ requestId?: string;
30
+ /** `can_use_tool` = a tool needs interactive approval; other subtypes are auto-allowed. */
31
+ subtype?: string;
32
+ toolName?: string;
33
+ toolInput?: Record<string, unknown>;
34
+ }
35
+ | { type: 'unknown' }
36
+ | { type: 'parse-error'; line: string };
37
+
38
+ interface RawContentBlock {
39
+ type?: string;
40
+ text?: string;
41
+ thinking?: string;
42
+ id?: string;
43
+ name?: string;
44
+ input?: unknown;
45
+ }
46
+
47
+ /**
48
+ * Classify a single NDJSON line from `claude --output-format stream-json`.
49
+ * Returns `null` for blank lines so callers can skip without distinguishing
50
+ * from `unknown` (a valid JSON object we don't model).
51
+ */
52
+ export function classifyClaudeStreamLine(line: string): ClaudeStreamLine | null {
53
+ const trimmed = line.trim();
54
+ if (!trimmed) return null;
55
+
56
+ let raw: Record<string, unknown>;
57
+ try {
58
+ const parsed: unknown = JSON.parse(trimmed);
59
+ if (!parsed || typeof parsed !== 'object') return { type: 'unknown' };
60
+ raw = parsed as Record<string, unknown>;
61
+ } catch {
62
+ return { type: 'parse-error', line: trimmed };
63
+ }
64
+
65
+ const eventType = typeof raw.type === 'string' ? raw.type : '';
66
+
67
+ switch (eventType) {
68
+ case 'system': {
69
+ const sessionId = typeof raw.session_id === 'string' ? raw.session_id.trim() : '';
70
+ if (!sessionId) return { type: 'unknown' };
71
+ const model = typeof raw.model === 'string' && raw.model ? raw.model : undefined;
72
+ return { type: 'session-init', sessionId, model };
73
+ }
74
+
75
+ case 'assistant': {
76
+ const message = raw.message as Record<string, unknown> | undefined;
77
+ const rawContent = (message?.content ?? raw.content) as RawContentBlock[] | undefined;
78
+ if (!Array.isArray(rawContent)) return { type: 'unknown' };
79
+
80
+ const messageId =
81
+ typeof (message?.id ?? raw.id) === 'string'
82
+ ? ((message?.id ?? raw.id) as string)
83
+ : undefined;
84
+
85
+ const blocks: ParsedAssistantBlock[] = [];
86
+ for (const block of rawContent) {
87
+ if (!block || typeof block !== 'object') continue;
88
+ switch (block.type) {
89
+ case 'text':
90
+ if (typeof block.text === 'string' && block.text) {
91
+ blocks.push({ kind: 'text', text: block.text });
92
+ }
93
+ break;
94
+ case 'thinking':
95
+ if (typeof block.thinking === 'string' && block.thinking) {
96
+ blocks.push({ kind: 'thinking', text: block.thinking });
97
+ }
98
+ break;
99
+ case 'tool_use':
100
+ blocks.push({
101
+ kind: 'tool-use',
102
+ toolName: typeof block.name === 'string' ? block.name : 'Unknown',
103
+ toolInput: block.input,
104
+ toolId: typeof block.id === 'string' ? block.id : undefined,
105
+ });
106
+ break;
107
+ default:
108
+ break;
109
+ }
110
+ }
111
+ if (!blocks.length) return { type: 'unknown' };
112
+ return { type: 'assistant', blocks, messageId };
113
+ }
114
+
115
+ case 'user': {
116
+ // tool_result echo back from the CLI; not needed for streaming display.
117
+ return { type: 'unknown' };
118
+ }
119
+
120
+ case 'result': {
121
+ const text = typeof raw.result === 'string' ? raw.result : '';
122
+ const subtype = typeof raw.subtype === 'string' ? raw.subtype : '';
123
+ const sessionId = typeof raw.session_id === 'string' ? raw.session_id : undefined;
124
+ return { type: 'result', text, subtype, sessionId };
125
+ }
126
+
127
+ case 'control_request':
128
+ case 'control_cancel_request': {
129
+ // The CLI nests the gate details under `request`: { subtype, tool_name, input }.
130
+ // Only `subtype: can_use_tool` is a real tool-approval gate; other subtypes are
131
+ // surfaced so the caller can auto-allow them and avoid deadlocking the stream.
132
+ const req =
133
+ raw.request && typeof raw.request === 'object'
134
+ ? (raw.request as Record<string, unknown>)
135
+ : undefined;
136
+ const strField = (v: unknown): string | undefined => (typeof v === 'string' ? v : undefined);
137
+ const inputRaw = req?.input as unknown;
138
+ return {
139
+ type: 'control-request',
140
+ requestId: typeof raw.request_id === 'string' ? raw.request_id : undefined,
141
+ subtype: strField(req?.subtype),
142
+ toolName: strField(req?.tool_name),
143
+ toolInput:
144
+ inputRaw && typeof inputRaw === 'object'
145
+ ? (inputRaw as Record<string, unknown>)
146
+ : undefined,
147
+ };
148
+ }
149
+
150
+ default:
151
+ return { type: 'unknown' };
152
+ }
153
+ }