@yancyyu/openhermit 1.5.8

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 (980) hide show
  1. package/LICENSE +611 -0
  2. package/README.md +220 -0
  3. package/bin/hermit.mjs +364 -0
  4. package/bin/kill-dev.js +20 -0
  5. package/dist-renderer/assets/01-BApSFlV4.png +0 -0
  6. package/dist-renderer/assets/02-CRQGs29u.png +0 -0
  7. package/dist-renderer/assets/03-BFCM2jnD.png +0 -0
  8. package/dist-renderer/assets/04-B2FThbKO.png +0 -0
  9. package/dist-renderer/assets/05-D9p0Znkd.png +0 -0
  10. package/dist-renderer/assets/06-DZAfbDlP.png +0 -0
  11. package/dist-renderer/assets/07-B_PXWGCc.png +0 -0
  12. package/dist-renderer/assets/08-DGRMZ6sl.png +0 -0
  13. package/dist-renderer/assets/09-SGCQvc7U.png +0 -0
  14. package/dist-renderer/assets/10-Cve81Q3W.png +0 -0
  15. package/dist-renderer/assets/11-DGglolDW.png +0 -0
  16. package/dist-renderer/assets/12-C3lnu79c.png +0 -0
  17. package/dist-renderer/assets/13-M59meqdw.png +0 -0
  18. package/dist-renderer/assets/ProjectEditorOverlay-BNoDw9T1.js +57 -0
  19. package/dist-renderer/assets/TeamGraphOverlay-CfGRKQIu.js +1 -0
  20. package/dist-renderer/assets/_basePickBy-Ct8Hm5_h.js +1 -0
  21. package/dist-renderer/assets/_baseUniq-BofrAFBx.js +1 -0
  22. package/dist-renderer/assets/apl-B4CMkyY2.js +1 -0
  23. package/dist-renderer/assets/arc-AbJgatzR.js +1 -0
  24. package/dist-renderer/assets/architectureDiagram-VXUJARFQ-gpniCJVk.js +36 -0
  25. package/dist-renderer/assets/asciiarmor-Df11BRmG.js +1 -0
  26. package/dist-renderer/assets/asn1-EdZsLKOL.js +1 -0
  27. package/dist-renderer/assets/asterisk-B-8jnY81.js +1 -0
  28. package/dist-renderer/assets/blockDiagram-VD42YOAC-aBbbmONC.js +122 -0
  29. package/dist-renderer/assets/brainfuck-C4LP7Hcl.js +1 -0
  30. package/dist-renderer/assets/c4Diagram-YG6GDRKO-DJio1IsU.js +10 -0
  31. package/dist-renderer/assets/channel-CZ8sd5Xf.js +1 -0
  32. package/dist-renderer/assets/chunk-4BX2VUAB-D1_HKao2.js +1 -0
  33. package/dist-renderer/assets/chunk-55IACEB6-NAmVxF4k.js +1 -0
  34. package/dist-renderer/assets/chunk-B4BG7PRW-Ce829laz.js +165 -0
  35. package/dist-renderer/assets/chunk-DI55MBZ5-Ct2Le12y.js +220 -0
  36. package/dist-renderer/assets/chunk-FMBD7UC4-Cie3DzKk.js +15 -0
  37. package/dist-renderer/assets/chunk-QN33PNHL-4f5Yb50e.js +1 -0
  38. package/dist-renderer/assets/chunk-QZHKN3VN-D9ranl9c.js +1 -0
  39. package/dist-renderer/assets/chunk-TZMSLE5B-bdGZWlEy.js +1 -0
  40. package/dist-renderer/assets/classDiagram-2ON5EDUG-CMcfSKj5.js +1 -0
  41. package/dist-renderer/assets/classDiagram-v2-WZHVMYZB-CMcfSKj5.js +1 -0
  42. package/dist-renderer/assets/clike-B9uivgTg.js +1 -0
  43. package/dist-renderer/assets/clojure-BMjYHr_A.js +1 -0
  44. package/dist-renderer/assets/clone-CMuwA8RV.js +1 -0
  45. package/dist-renderer/assets/cmake-BQqOBYOt.js +1 -0
  46. package/dist-renderer/assets/cobol-CWcv1MsR.js +1 -0
  47. package/dist-renderer/assets/coffeescript-S37ZYGWr.js +1 -0
  48. package/dist-renderer/assets/commonlisp-DBKNyK5s.js +1 -0
  49. package/dist-renderer/assets/cose-bilkent-S5V4N54A-C6tvfcVi.js +1 -0
  50. package/dist-renderer/assets/crystal-SjHAIU92.js +1 -0
  51. package/dist-renderer/assets/css-BnMrqG3P.js +1 -0
  52. package/dist-renderer/assets/cypher-C_CwsFkJ.js +1 -0
  53. package/dist-renderer/assets/cytoscape.esm-DsxaTqgk.js +331 -0
  54. package/dist-renderer/assets/d-pRatUO7H.js +1 -0
  55. package/dist-renderer/assets/dagre-6UL2VRFP-B-4qcZam.js +4 -0
  56. package/dist-renderer/assets/defaultLocale-DX6XiGOO.js +1 -0
  57. package/dist-renderer/assets/diagram-PSM6KHXK-CwT3TLjx.js +24 -0
  58. package/dist-renderer/assets/diagram-QEK2KX5R-BWH6-ZFd.js +43 -0
  59. package/dist-renderer/assets/diagram-S2PKOQOG-DfpPnfi1.js +24 -0
  60. package/dist-renderer/assets/diff-DbItnlRl.js +1 -0
  61. package/dist-renderer/assets/dockerfile-BKs6k2Af.js +1 -0
  62. package/dist-renderer/assets/dtd-DF_7sFjM.js +1 -0
  63. package/dist-renderer/assets/dylan-DwRh75JA.js +1 -0
  64. package/dist-renderer/assets/ebnf-CDyGwa7X.js +1 -0
  65. package/dist-renderer/assets/ecl-Cabwm37j.js +1 -0
  66. package/dist-renderer/assets/eiffel-CnydiIhH.js +1 -0
  67. package/dist-renderer/assets/elm-vLlmbW-K.js +1 -0
  68. package/dist-renderer/assets/erDiagram-Q2GNP2WA-BFbEFR4x.js +60 -0
  69. package/dist-renderer/assets/erlang-BNw1qcRV.js +1 -0
  70. package/dist-renderer/assets/factor-kuTfRLto.js +1 -0
  71. package/dist-renderer/assets/favicon-B8xY-GVk.png +0 -0
  72. package/dist-renderer/assets/fcl-Kvtd6kyn.js +1 -0
  73. package/dist-renderer/assets/flowDiagram-NV44I4VS-Dg3cf5hW.js +162 -0
  74. package/dist-renderer/assets/forth-Ffai-XNe.js +1 -0
  75. package/dist-renderer/assets/fortran-DYz_wnZ1.js +1 -0
  76. package/dist-renderer/assets/ganttDiagram-JELNMOA3-B21y55W5.js +267 -0
  77. package/dist-renderer/assets/gas-Bneqetm1.js +1 -0
  78. package/dist-renderer/assets/gherkin-heZmZLOM.js +1 -0
  79. package/dist-renderer/assets/gitGraphDiagram-V2S2FVAM-BDV3BJzn.js +65 -0
  80. package/dist-renderer/assets/graph-BfaZ4hZt.js +1 -0
  81. package/dist-renderer/assets/groovy-D9Dt4D0W.js +1 -0
  82. package/dist-renderer/assets/haskell-BWDZoCOh.js +1 -0
  83. package/dist-renderer/assets/haxe-H-WmDvRZ.js +1 -0
  84. package/dist-renderer/assets/http-DBlCnlav.js +1 -0
  85. package/dist-renderer/assets/idl-BEugSyMb.js +1 -0
  86. package/dist-renderer/assets/index-BMXHMpkG.js +1 -0
  87. package/dist-renderer/assets/index-CCqtDawH.js +1 -0
  88. package/dist-renderer/assets/index-CVMSpK8C.js +1 -0
  89. package/dist-renderer/assets/index-CZltVMDP.js +1844 -0
  90. package/dist-renderer/assets/index-CaG9mf8s.css +1 -0
  91. package/dist-renderer/assets/index-Ct0-y9TF.js +1 -0
  92. package/dist-renderer/assets/index-pMg_LlsS.js +1 -0
  93. package/dist-renderer/assets/infoDiagram-HS3SLOUP-DvMlS0CL.js +2 -0
  94. package/dist-renderer/assets/init-Gi6I4Gst.js +1 -0
  95. package/dist-renderer/assets/javascript-qCveANmP.js +1 -0
  96. package/dist-renderer/assets/journeyDiagram-XKPGCS4Q-DIyMluRv.js +139 -0
  97. package/dist-renderer/assets/julia-DuME0IfC.js +1 -0
  98. package/dist-renderer/assets/kanban-definition-3W4ZIXB7-CVOx8f-7.js +89 -0
  99. package/dist-renderer/assets/katex-DGN8GczM.js +261 -0
  100. package/dist-renderer/assets/layout-BPKIXUf4.js +1 -0
  101. package/dist-renderer/assets/linear-CScZGLr2.js +1 -0
  102. package/dist-renderer/assets/livescript-BwQOo05w.js +1 -0
  103. package/dist-renderer/assets/lua-BgMRiT3U.js +1 -0
  104. package/dist-renderer/assets/mathematica-DTrFuWx2.js +1 -0
  105. package/dist-renderer/assets/mbox-CNhZ1qSd.js +1 -0
  106. package/dist-renderer/assets/mindmap-definition-VGOIOE7T-CmDQ7Wo6.js +68 -0
  107. package/dist-renderer/assets/mirc-CjQqDB4T.js +1 -0
  108. package/dist-renderer/assets/mllike-CXdrOF99.js +1 -0
  109. package/dist-renderer/assets/modelica-Dc1JOy9r.js +1 -0
  110. package/dist-renderer/assets/mscgen-BA5vi2Kp.js +1 -0
  111. package/dist-renderer/assets/mumps-BT43cFF4.js +1 -0
  112. package/dist-renderer/assets/nginx-DdIZxoE0.js +1 -0
  113. package/dist-renderer/assets/nsis-LdVXkNf5.js +1 -0
  114. package/dist-renderer/assets/ntriples-BfvgReVJ.js +1 -0
  115. package/dist-renderer/assets/octave-Ck1zUtKM.js +1 -0
  116. package/dist-renderer/assets/ordinal-Cboi1Yqb.js +1 -0
  117. package/dist-renderer/assets/oz-BzwKVEFT.js +1 -0
  118. package/dist-renderer/assets/pascal--L3eBynH.js +1 -0
  119. package/dist-renderer/assets/perl-CdXCOZ3F.js +1 -0
  120. package/dist-renderer/assets/pieDiagram-ADFJNKIX-DbVClin-.js +30 -0
  121. package/dist-renderer/assets/pig-CevX1Tat.js +1 -0
  122. package/dist-renderer/assets/powershell-CFHJl5sT.js +1 -0
  123. package/dist-renderer/assets/properties-C78fOPTZ.js +1 -0
  124. package/dist-renderer/assets/protobuf-ChK-085T.js +1 -0
  125. package/dist-renderer/assets/pug-DukmZTjD.js +1 -0
  126. package/dist-renderer/assets/puppet-DMA9R1ak.js +1 -0
  127. package/dist-renderer/assets/python-BuPzkPfP.js +1 -0
  128. package/dist-renderer/assets/q-pXgVlZs6.js +1 -0
  129. package/dist-renderer/assets/quadrantDiagram-AYHSOK5B-CAB0MYcW.js +7 -0
  130. package/dist-renderer/assets/r-DUYO_cvP.js +1 -0
  131. package/dist-renderer/assets/requirementDiagram-UZGBJVZJ-w2Lfpg3T.js +64 -0
  132. package/dist-renderer/assets/rpm-CTu-6PCP.js +1 -0
  133. package/dist-renderer/assets/ruby-B2Rjki9n.js +1 -0
  134. package/dist-renderer/assets/sankeyDiagram-TZEHDZUN-kvG1QoKY.js +10 -0
  135. package/dist-renderer/assets/sas-B4kiWyti.js +1 -0
  136. package/dist-renderer/assets/scheme-C41bIUwD.js +1 -0
  137. package/dist-renderer/assets/sequenceDiagram-WL72ISMW-DCVBQ23J.js +145 -0
  138. package/dist-renderer/assets/shell-CjFT_Tl9.js +1 -0
  139. package/dist-renderer/assets/sieve-C3Gn_uJK.js +1 -0
  140. package/dist-renderer/assets/simple-mode-GW_nhZxv.js +1 -0
  141. package/dist-renderer/assets/smalltalk-CnHTOXQT.js +1 -0
  142. package/dist-renderer/assets/solr-DehyRSwq.js +1 -0
  143. package/dist-renderer/assets/sparql-DkYu6x3z.js +1 -0
  144. package/dist-renderer/assets/splashScene-C8lWNnm4.js +1 -0
  145. package/dist-renderer/assets/spreadsheet-BCZA_wO0.js +1 -0
  146. package/dist-renderer/assets/sql-D0XecflT.js +1 -0
  147. package/dist-renderer/assets/stateDiagram-FKZM4ZOC-ItZ0JBvq.js +1 -0
  148. package/dist-renderer/assets/stateDiagram-v2-4FDKWEC3-Hpmw4dMm.js +1 -0
  149. package/dist-renderer/assets/stex-C3f8Ysf7.js +1 -0
  150. package/dist-renderer/assets/stylus-B533Al4x.js +1 -0
  151. package/dist-renderer/assets/swift-BzpIVaGY.js +1 -0
  152. package/dist-renderer/assets/tcl-DVfN8rqt.js +1 -0
  153. package/dist-renderer/assets/textile-CnDTJFAw.js +1 -0
  154. package/dist-renderer/assets/tiddlywiki-DO-Gjzrf.js +1 -0
  155. package/dist-renderer/assets/tiki-DGYXhP31.js +1 -0
  156. package/dist-renderer/assets/timeline-definition-IT6M3QCI-BzSFaAjV.js +61 -0
  157. package/dist-renderer/assets/toml-Bm5Em-hy.js +1 -0
  158. package/dist-renderer/assets/treemap-GDKQZRPO-fSz4hQn0.js +162 -0
  159. package/dist-renderer/assets/troff-wAsdV37c.js +1 -0
  160. package/dist-renderer/assets/ttcn-CfJYG6tj.js +1 -0
  161. package/dist-renderer/assets/ttcn-cfg-B9xdYoR4.js +1 -0
  162. package/dist-renderer/assets/turtle-B1tBg_DP.js +1 -0
  163. package/dist-renderer/assets/vb-CmGdzxic.js +1 -0
  164. package/dist-renderer/assets/vbscript-BuJXcnF6.js +1 -0
  165. package/dist-renderer/assets/velocity-D8B20fx6.js +1 -0
  166. package/dist-renderer/assets/verilog-C6RDOZhf.js +1 -0
  167. package/dist-renderer/assets/vhdl-lSbBsy5d.js +1 -0
  168. package/dist-renderer/assets/webidl-ZXfAyPTL.js +1 -0
  169. package/dist-renderer/assets/xquery-CQfU5ijd.js +1 -0
  170. package/dist-renderer/assets/xychartDiagram-PRI3JC2R-CT1kaGlv.js +7 -0
  171. package/dist-renderer/assets/yacas-BJ4BC0dw.js +1 -0
  172. package/dist-renderer/assets/z80-Hz9HOZM7.js +1 -0
  173. package/dist-renderer/index.html +1274 -0
  174. package/package.json +181 -0
  175. package/src/features/README.md +24 -0
  176. package/src/features/agent-graph/README.md +21 -0
  177. package/src/features/agent-graph/STABLE_SLOT_LAYOUT_PLAN.md +2846 -0
  178. package/src/features/agent-graph/core/domain/buildInlineActivityEntries.ts +416 -0
  179. package/src/features/agent-graph/core/domain/collapseOverflowStacks.ts +126 -0
  180. package/src/features/agent-graph/core/domain/graphOwnerIdentity.ts +55 -0
  181. package/src/features/agent-graph/core/domain/taskGraphSemantics.ts +48 -0
  182. package/src/features/agent-graph/renderer/adapters/TeamGraphAdapter.ts +1400 -0
  183. package/src/features/agent-graph/renderer/hooks/useGraphActivityContext.ts +34 -0
  184. package/src/features/agent-graph/renderer/hooks/useGraphCreateTaskDialog.tsx +126 -0
  185. package/src/features/agent-graph/renderer/hooks/useGraphMemberPopoverContext.ts +34 -0
  186. package/src/features/agent-graph/renderer/hooks/useGraphSidebarVisibility.ts +52 -0
  187. package/src/features/agent-graph/renderer/hooks/useTeamGraphAdapter.ts +173 -0
  188. package/src/features/agent-graph/renderer/hooks/useTeamGraphSurfaceActions.ts +98 -0
  189. package/src/features/agent-graph/renderer/index.ts +14 -0
  190. package/src/features/agent-graph/renderer/ui/GraphActivityCard.tsx +96 -0
  191. package/src/features/agent-graph/renderer/ui/GraphActivityHud.tsx +498 -0
  192. package/src/features/agent-graph/renderer/ui/GraphBlockingEdgePopover.tsx +207 -0
  193. package/src/features/agent-graph/renderer/ui/GraphNodePopover.tsx +573 -0
  194. package/src/features/agent-graph/renderer/ui/GraphProvisioningHud.tsx +113 -0
  195. package/src/features/agent-graph/renderer/ui/GraphTaskCard.tsx +149 -0
  196. package/src/features/agent-graph/renderer/ui/GraphTransientHandoffHud.tsx +176 -0
  197. package/src/features/agent-graph/renderer/ui/TeamGraphOverlay.tsx +224 -0
  198. package/src/features/agent-graph/renderer/ui/TeamGraphTab.tsx +257 -0
  199. package/src/features/agent-graph/renderer/ui/buildTransientHandoffMessage.ts +70 -0
  200. package/src/features/recent-projects/contracts/api.ts +5 -0
  201. package/src/features/recent-projects/contracts/channels.ts +2 -0
  202. package/src/features/recent-projects/contracts/dto.ts +24 -0
  203. package/src/features/recent-projects/contracts/index.ts +4 -0
  204. package/src/features/recent-projects/contracts/normalize.ts +32 -0
  205. package/src/features/recent-projects/core/application/models/ListDashboardRecentProjectsResponse.ts +6 -0
  206. package/src/features/recent-projects/core/application/ports/ClockPort.ts +3 -0
  207. package/src/features/recent-projects/core/application/ports/ListDashboardRecentProjectsOutputPort.ts +5 -0
  208. package/src/features/recent-projects/core/application/ports/LoggerPort.ts +5 -0
  209. package/src/features/recent-projects/core/application/ports/RecentProjectsCachePort.ts +5 -0
  210. package/src/features/recent-projects/core/application/ports/RecentProjectsSourcePort.ts +14 -0
  211. package/src/features/recent-projects/core/application/use-cases/ListDashboardRecentProjectsUseCase.ts +191 -0
  212. package/src/features/recent-projects/core/domain/models/ProviderId.ts +1 -0
  213. package/src/features/recent-projects/core/domain/models/RecentProjectAggregate.ts +14 -0
  214. package/src/features/recent-projects/core/domain/models/RecentProjectCandidate.ts +14 -0
  215. package/src/features/recent-projects/core/domain/models/RecentProjectOpenTarget.ts +3 -0
  216. package/src/features/recent-projects/core/domain/policies/mergeRecentProjectCandidates.ts +88 -0
  217. package/src/features/recent-projects/main/adapters/input/http/registerRecentProjectsHttp.ts +30 -0
  218. package/src/features/recent-projects/main/adapters/output/presenters/DashboardRecentProjectsPresenter.ts +27 -0
  219. package/src/features/recent-projects/main/adapters/output/sources/ClaudeRecentProjectsSourceAdapter.ts +91 -0
  220. package/src/features/recent-projects/main/adapters/output/sources/CodexRecentProjectsSourceAdapter.ts +326 -0
  221. package/src/features/recent-projects/main/composition/createRecentProjectsFeature.ts +43 -0
  222. package/src/features/recent-projects/main/index.ts +3 -0
  223. package/src/features/recent-projects/main/infrastructure/cache/InMemoryRecentProjectsCache.ts +34 -0
  224. package/src/features/recent-projects/main/infrastructure/codex/CodexAppServerClient.ts +116 -0
  225. package/src/features/recent-projects/main/infrastructure/identity/RecentProjectIdentityResolver.ts +20 -0
  226. package/src/features/recent-projects/main/infrastructure/identity/normalizeIdentityPath.ts +10 -0
  227. package/src/features/recent-projects/renderer/adapters/RecentProjectsSectionAdapter.ts +132 -0
  228. package/src/features/recent-projects/renderer/hooks/useOpenRecentProject.ts +143 -0
  229. package/src/features/recent-projects/renderer/hooks/useRecentProjectsSection.ts +289 -0
  230. package/src/features/recent-projects/renderer/index.ts +2 -0
  231. package/src/features/recent-projects/renderer/ui/RecentProjectCard.tsx +221 -0
  232. package/src/features/recent-projects/renderer/ui/RecentProjectsSection.tsx +167 -0
  233. package/src/features/recent-projects/renderer/utils/activeProjectTeams.ts +48 -0
  234. package/src/features/recent-projects/renderer/utils/navigation.ts +65 -0
  235. package/src/features/recent-projects/renderer/utils/projectDecorations.ts +11 -0
  236. package/src/features/recent-projects/renderer/utils/recentProjectOpenHistory.ts +268 -0
  237. package/src/features/recent-projects/renderer/utils/recentProjectsClientCache.ts +78 -0
  238. package/src/main/constants/messageTags.ts +46 -0
  239. package/src/main/constants/worktreePatterns.ts +47 -0
  240. package/src/main/server.ts +3705 -0
  241. package/src/main/services/UpdateService.ts +166 -0
  242. package/src/main/services/ccConnect/CcConnectBridge.ts +313 -0
  243. package/src/main/services/ccConnect/CcConnectClient.ts +397 -0
  244. package/src/main/services/ccConnect/MessageBridge.ts +162 -0
  245. package/src/main/services/ccConnect/ProjectMappingStore.ts +148 -0
  246. package/src/main/services/ccConnect/index.ts +8 -0
  247. package/src/main/services/teams-mvp/TeamProvisioningService.ts +275 -0
  248. package/src/main/services/teams-mvp/TeamWorkspaceService.ts +404 -0
  249. package/src/main/services/teams-mvp/index.ts +26 -0
  250. package/src/main/types/chunks.ts +506 -0
  251. package/src/main/types/domain.ts +342 -0
  252. package/src/main/types/index.ts +24 -0
  253. package/src/main/types/jsonl.ts +355 -0
  254. package/src/main/types/messages.ts +395 -0
  255. package/src/renderer/App.tsx +287 -0
  256. package/src/renderer/api/httpClient.ts +2207 -0
  257. package/src/renderer/api/index.ts +19 -0
  258. package/src/renderer/api/providers.ts +77 -0
  259. package/src/renderer/assets/participant-avatars/01.png +0 -0
  260. package/src/renderer/assets/participant-avatars/02.png +0 -0
  261. package/src/renderer/assets/participant-avatars/03.png +0 -0
  262. package/src/renderer/assets/participant-avatars/04.png +0 -0
  263. package/src/renderer/assets/participant-avatars/05.png +0 -0
  264. package/src/renderer/assets/participant-avatars/06.png +0 -0
  265. package/src/renderer/assets/participant-avatars/07.png +0 -0
  266. package/src/renderer/assets/participant-avatars/08.png +0 -0
  267. package/src/renderer/assets/participant-avatars/09.png +0 -0
  268. package/src/renderer/assets/participant-avatars/10.png +0 -0
  269. package/src/renderer/assets/participant-avatars/11.png +0 -0
  270. package/src/renderer/assets/participant-avatars/12.png +0 -0
  271. package/src/renderer/assets/participant-avatars/13.png +0 -0
  272. package/src/renderer/components/chat/AIChatGroup.tsx +519 -0
  273. package/src/renderer/components/chat/ChatHistory.tsx +1115 -0
  274. package/src/renderer/components/chat/ChatHistoryEmptyState.tsx +15 -0
  275. package/src/renderer/components/chat/ChatHistoryItem.tsx +144 -0
  276. package/src/renderer/components/chat/ChatHistoryLoadingState.tsx +45 -0
  277. package/src/renderer/components/chat/CompactBoundary.tsx +169 -0
  278. package/src/renderer/components/chat/ContextBadge.tsx +582 -0
  279. package/src/renderer/components/chat/DisplayItemList.tsx +431 -0
  280. package/src/renderer/components/chat/LastOutputDisplay.tsx +259 -0
  281. package/src/renderer/components/chat/SessionContextPanel/DirectoryTree/DirectoryTreeNode.tsx +125 -0
  282. package/src/renderer/components/chat/SessionContextPanel/DirectoryTree/buildDirectoryTree.ts +47 -0
  283. package/src/renderer/components/chat/SessionContextPanel/DirectoryTree/types.ts +12 -0
  284. package/src/renderer/components/chat/SessionContextPanel/components/ClaudeMdFilesSection.tsx +90 -0
  285. package/src/renderer/components/chat/SessionContextPanel/components/ClaudeMdSection.tsx +86 -0
  286. package/src/renderer/components/chat/SessionContextPanel/components/CollapsibleSection.tsx +77 -0
  287. package/src/renderer/components/chat/SessionContextPanel/components/FlatInjectionList.tsx +248 -0
  288. package/src/renderer/components/chat/SessionContextPanel/components/MentionedFilesSection.tsx +50 -0
  289. package/src/renderer/components/chat/SessionContextPanel/components/RankedInjectionList.tsx +284 -0
  290. package/src/renderer/components/chat/SessionContextPanel/components/SessionContextHeader.tsx +290 -0
  291. package/src/renderer/components/chat/SessionContextPanel/components/SessionContextHelpTooltip.tsx +165 -0
  292. package/src/renderer/components/chat/SessionContextPanel/components/TaskCoordinationSection.tsx +47 -0
  293. package/src/renderer/components/chat/SessionContextPanel/components/ThinkingTextSection.tsx +47 -0
  294. package/src/renderer/components/chat/SessionContextPanel/components/ToolOutputsSection.tsx +47 -0
  295. package/src/renderer/components/chat/SessionContextPanel/components/UserMessagesSection.tsx +47 -0
  296. package/src/renderer/components/chat/SessionContextPanel/index.tsx +314 -0
  297. package/src/renderer/components/chat/SessionContextPanel/items/ClaudeMdItem.tsx +76 -0
  298. package/src/renderer/components/chat/SessionContextPanel/items/MentionedFileItem.tsx +91 -0
  299. package/src/renderer/components/chat/SessionContextPanel/items/TaskCoordinationItem.tsx +116 -0
  300. package/src/renderer/components/chat/SessionContextPanel/items/ThinkingTextItem.tsx +96 -0
  301. package/src/renderer/components/chat/SessionContextPanel/items/ToolBreakdownItem.tsx +38 -0
  302. package/src/renderer/components/chat/SessionContextPanel/items/ToolOutputItem.tsx +113 -0
  303. package/src/renderer/components/chat/SessionContextPanel/items/UserMessageItem.tsx +69 -0
  304. package/src/renderer/components/chat/SessionContextPanel/types.ts +96 -0
  305. package/src/renderer/components/chat/SessionContextPanel/utils/formatting.ts +6 -0
  306. package/src/renderer/components/chat/SessionContextPanel/utils/pathParsing.ts +23 -0
  307. package/src/renderer/components/chat/SystemChatGroup.tsx +60 -0
  308. package/src/renderer/components/chat/UserChatGroup.tsx +668 -0
  309. package/src/renderer/components/chat/items/BaseItem.tsx +213 -0
  310. package/src/renderer/components/chat/items/ExecutionTrace.tsx +279 -0
  311. package/src/renderer/components/chat/items/LinkedToolItem.tsx +235 -0
  312. package/src/renderer/components/chat/items/MetricsPill.tsx +215 -0
  313. package/src/renderer/components/chat/items/SlashItem.tsx +81 -0
  314. package/src/renderer/components/chat/items/SubagentItem.tsx +592 -0
  315. package/src/renderer/components/chat/items/TeammateMessageItem.tsx +261 -0
  316. package/src/renderer/components/chat/items/TextItem.tsx +82 -0
  317. package/src/renderer/components/chat/items/ThinkingItem.tsx +82 -0
  318. package/src/renderer/components/chat/items/baseItemHelpers.ts +42 -0
  319. package/src/renderer/components/chat/items/linkedTool/CollapsibleOutputSection.tsx +57 -0
  320. package/src/renderer/components/chat/items/linkedTool/DefaultToolViewer.tsx +56 -0
  321. package/src/renderer/components/chat/items/linkedTool/EditToolViewer.tsx +74 -0
  322. package/src/renderer/components/chat/items/linkedTool/ReadToolViewer.tsx +102 -0
  323. package/src/renderer/components/chat/items/linkedTool/SkillToolViewer.tsx +67 -0
  324. package/src/renderer/components/chat/items/linkedTool/ToolErrorDisplay.tsx +43 -0
  325. package/src/renderer/components/chat/items/linkedTool/WriteToolViewer.tsx +66 -0
  326. package/src/renderer/components/chat/items/linkedTool/index.ts +13 -0
  327. package/src/renderer/components/chat/items/linkedTool/renderHelpers.tsx +259 -0
  328. package/src/renderer/components/chat/markdownComponents.tsx +257 -0
  329. package/src/renderer/components/chat/markdownCopyUtils.ts +18 -0
  330. package/src/renderer/components/chat/searchHighlightUtils.ts +166 -0
  331. package/src/renderer/components/chat/viewers/CodeBlockViewer.tsx +244 -0
  332. package/src/renderer/components/chat/viewers/DiffViewer.tsx +459 -0
  333. package/src/renderer/components/chat/viewers/FileLink.tsx +182 -0
  334. package/src/renderer/components/chat/viewers/MarkdownViewer.tsx +1093 -0
  335. package/src/renderer/components/chat/viewers/MermaidDiagram.tsx +116 -0
  336. package/src/renderer/components/chat/viewers/index.ts +3 -0
  337. package/src/renderer/components/chat/viewers/syntaxHighlighter.ts +583 -0
  338. package/src/renderer/components/common/AppLogo.tsx +61 -0
  339. package/src/renderer/components/common/CliInstallWarningBanner.tsx +57 -0
  340. package/src/renderer/components/common/ConfirmDialog.tsx +176 -0
  341. package/src/renderer/components/common/ConnectionStatusBadge.tsx +56 -0
  342. package/src/renderer/components/common/ContextSwitchOverlay.tsx +38 -0
  343. package/src/renderer/components/common/CopyButton.tsx +85 -0
  344. package/src/renderer/components/common/CopyablePath.tsx +68 -0
  345. package/src/renderer/components/common/ErrorBoundary.tsx +211 -0
  346. package/src/renderer/components/common/ExportDropdown.tsx +142 -0
  347. package/src/renderer/components/common/FileTree.tsx +182 -0
  348. package/src/renderer/components/common/GlobalProviderStatusHeader.tsx +316 -0
  349. package/src/renderer/components/common/OngoingIndicator.tsx +67 -0
  350. package/src/renderer/components/common/ProviderBrandLogo.tsx +206 -0
  351. package/src/renderer/components/common/RepositoryDropdown.tsx +230 -0
  352. package/src/renderer/components/common/TokenUsageDisplay.tsx +581 -0
  353. package/src/renderer/components/common/WarningBanner.tsx +25 -0
  354. package/src/renderer/components/common/WorkspaceIndicator.tsx +183 -0
  355. package/src/renderer/components/common/WorktreeBadge.tsx +123 -0
  356. package/src/renderer/components/dashboard/CliStatusBanner.tsx +1845 -0
  357. package/src/renderer/components/dashboard/DashboardView.tsx +274 -0
  358. package/src/renderer/components/extensions/ExtensionStoreView.tsx +591 -0
  359. package/src/renderer/components/extensions/ExtensionsSubTabTrigger.tsx +52 -0
  360. package/src/renderer/components/extensions/apikeys/ApiKeyCard.tsx +143 -0
  361. package/src/renderer/components/extensions/apikeys/ApiKeyFormDialog.tsx +282 -0
  362. package/src/renderer/components/extensions/apikeys/ApiKeysPanel.tsx +280 -0
  363. package/src/renderer/components/extensions/common/InstallButton.tsx +186 -0
  364. package/src/renderer/components/extensions/common/InstallCountBadge.tsx +21 -0
  365. package/src/renderer/components/extensions/common/SearchInput.tsx +70 -0
  366. package/src/renderer/components/extensions/common/SourceBadge.tsx +31 -0
  367. package/src/renderer/components/extensions/mcp/CustomMcpServerDialog.tsx +560 -0
  368. package/src/renderer/components/extensions/mcp/McpServerCard.tsx +314 -0
  369. package/src/renderer/components/extensions/mcp/McpServerDetailDialog.tsx +669 -0
  370. package/src/renderer/components/extensions/mcp/McpServersPanel.tsx +543 -0
  371. package/src/renderer/components/extensions/plugins/CapabilityChips.tsx +70 -0
  372. package/src/renderer/components/extensions/plugins/CategoryChips.tsx +67 -0
  373. package/src/renderer/components/extensions/plugins/PluginCard.tsx +146 -0
  374. package/src/renderer/components/extensions/plugins/PluginDetailDialog.tsx +270 -0
  375. package/src/renderer/components/extensions/plugins/PluginsPanel.tsx +436 -0
  376. package/src/renderer/components/extensions/skills/SkillCodeEditor.tsx +117 -0
  377. package/src/renderer/components/extensions/skills/SkillDetailDialog.tsx +372 -0
  378. package/src/renderer/components/extensions/skills/SkillEditorDialog.tsx +856 -0
  379. package/src/renderer/components/extensions/skills/SkillImportDialog.tsx +343 -0
  380. package/src/renderer/components/extensions/skills/SkillReviewDialog.tsx +166 -0
  381. package/src/renderer/components/extensions/skills/SkillsPanel.tsx +784 -0
  382. package/src/renderer/components/extensions/skills/skillDraftUtils.ts +234 -0
  383. package/src/renderer/components/extensions/skills/skillFolderNameUtils.ts +19 -0
  384. package/src/renderer/components/extensions/skills/skillProjectUtils.ts +13 -0
  385. package/src/renderer/components/extensions/skills/skillValidationUtils.ts +31 -0
  386. package/src/renderer/components/layout/MiddlePanel.tsx +18 -0
  387. package/src/renderer/components/layout/MoreMenu.tsx +243 -0
  388. package/src/renderer/components/layout/PaneContainer.tsx +27 -0
  389. package/src/renderer/components/layout/PaneContent.tsx +84 -0
  390. package/src/renderer/components/layout/PaneResizeHandle.tsx +85 -0
  391. package/src/renderer/components/layout/PaneSplitDropZone.tsx +54 -0
  392. package/src/renderer/components/layout/PaneView.tsx +75 -0
  393. package/src/renderer/components/layout/SessionTabContent.tsx +102 -0
  394. package/src/renderer/components/layout/Sidebar.tsx +205 -0
  395. package/src/renderer/components/layout/SortableTab.tsx +261 -0
  396. package/src/renderer/components/layout/TabBar.tsx +354 -0
  397. package/src/renderer/components/layout/TabBarActions.tsx +176 -0
  398. package/src/renderer/components/layout/TabBarRow.tsx +99 -0
  399. package/src/renderer/components/layout/TabContextMenu.tsx +171 -0
  400. package/src/renderer/components/layout/TabbedLayout.tsx +186 -0
  401. package/src/renderer/components/layout/TeamTabSectionNav.tsx +146 -0
  402. package/src/renderer/components/notifications/NotificationRow.tsx +228 -0
  403. package/src/renderer/components/notifications/NotificationsView.tsx +371 -0
  404. package/src/renderer/components/report/AssessmentBadge.tsx +78 -0
  405. package/src/renderer/components/report/ReportSection.tsx +58 -0
  406. package/src/renderer/components/report/SessionReportTab.tsx +102 -0
  407. package/src/renderer/components/report/sections/CostSection.tsx +259 -0
  408. package/src/renderer/components/report/sections/ErrorSection.tsx +100 -0
  409. package/src/renderer/components/report/sections/FrictionSection.tsx +91 -0
  410. package/src/renderer/components/report/sections/GitSection.tsx +72 -0
  411. package/src/renderer/components/report/sections/InsightsSection.tsx +207 -0
  412. package/src/renderer/components/report/sections/KeyTakeawaysSection.tsx +55 -0
  413. package/src/renderer/components/report/sections/OverviewSection.tsx +64 -0
  414. package/src/renderer/components/report/sections/QualitySection.tsx +151 -0
  415. package/src/renderer/components/report/sections/SubagentSection.tsx +88 -0
  416. package/src/renderer/components/report/sections/TimelineSection.tsx +111 -0
  417. package/src/renderer/components/report/sections/TokenSection.tsx +116 -0
  418. package/src/renderer/components/report/sections/ToolSection.tsx +77 -0
  419. package/src/renderer/components/runtime/ProviderModelBadges.tsx +142 -0
  420. package/src/renderer/components/runtime/ProviderRuntimeBackendSelector.tsx +327 -0
  421. package/src/renderer/components/runtime/ProviderRuntimeSettingsDialog.tsx +288 -0
  422. package/src/renderer/components/runtime/providerConnectionUi.ts +408 -0
  423. package/src/renderer/components/schedules/SchedulesView.tsx +529 -0
  424. package/src/renderer/components/search/CommandPalette.tsx +610 -0
  425. package/src/renderer/components/search/SearchBar.tsx +171 -0
  426. package/src/renderer/components/settings/NotificationTriggerSettings/components/AddTriggerForm.tsx +233 -0
  427. package/src/renderer/components/settings/NotificationTriggerSettings/components/ColorPaletteSelector.tsx +144 -0
  428. package/src/renderer/components/settings/NotificationTriggerSettings/components/DynamicConfigSection.tsx +189 -0
  429. package/src/renderer/components/settings/NotificationTriggerSettings/components/GeneralInfoSection.tsx +68 -0
  430. package/src/renderer/components/settings/NotificationTriggerSettings/components/IgnorePatternsSection.tsx +73 -0
  431. package/src/renderer/components/settings/NotificationTriggerSettings/components/ModeSelector.tsx +45 -0
  432. package/src/renderer/components/settings/NotificationTriggerSettings/components/RepositoryScopeSection.tsx +63 -0
  433. package/src/renderer/components/settings/NotificationTriggerSettings/components/SectionHeader.tsx +15 -0
  434. package/src/renderer/components/settings/NotificationTriggerSettings/components/TriggerCard.tsx +150 -0
  435. package/src/renderer/components/settings/NotificationTriggerSettings/components/TriggerCardHeader.tsx +125 -0
  436. package/src/renderer/components/settings/NotificationTriggerSettings/components/TriggerConfiguration.tsx +342 -0
  437. package/src/renderer/components/settings/NotificationTriggerSettings/components/TriggerPreview.tsx +108 -0
  438. package/src/renderer/components/settings/NotificationTriggerSettings/hooks/useAddTriggerFormHandlers.ts +218 -0
  439. package/src/renderer/components/settings/NotificationTriggerSettings/hooks/useAddTriggerFormState.ts +135 -0
  440. package/src/renderer/components/settings/NotificationTriggerSettings/hooks/useRepositoryLookup.ts +47 -0
  441. package/src/renderer/components/settings/NotificationTriggerSettings/hooks/useTriggerCardState.ts +281 -0
  442. package/src/renderer/components/settings/NotificationTriggerSettings/hooks/useTriggerForm.ts +185 -0
  443. package/src/renderer/components/settings/NotificationTriggerSettings/index.tsx +87 -0
  444. package/src/renderer/components/settings/NotificationTriggerSettings/types.ts +39 -0
  445. package/src/renderer/components/settings/NotificationTriggerSettings/utils/constants.ts +50 -0
  446. package/src/renderer/components/settings/NotificationTriggerSettings/utils/trigger.ts +113 -0
  447. package/src/renderer/components/settings/SettingsTabs.tsx +110 -0
  448. package/src/renderer/components/settings/SettingsView.tsx +153 -0
  449. package/src/renderer/components/settings/components/SettingRow.tsx +44 -0
  450. package/src/renderer/components/settings/components/SettingsSectionHeader.tsx +24 -0
  451. package/src/renderer/components/settings/components/SettingsSelect.tsx +100 -0
  452. package/src/renderer/components/settings/components/SettingsToggle.tsx +45 -0
  453. package/src/renderer/components/settings/components/index.ts +8 -0
  454. package/src/renderer/components/settings/hooks/index.ts +6 -0
  455. package/src/renderer/components/settings/hooks/useSettingsConfig.ts +270 -0
  456. package/src/renderer/components/settings/hooks/useSettingsHandlers.ts +468 -0
  457. package/src/renderer/components/settings/sections/AdvancedSection.tsx +234 -0
  458. package/src/renderer/components/settings/sections/CliStatusSection.tsx +930 -0
  459. package/src/renderer/components/settings/sections/ConfigEditorDialog.tsx +391 -0
  460. package/src/renderer/components/settings/sections/GeneralSection.tsx +665 -0
  461. package/src/renderer/components/settings/sections/HarnessSection.tsx +133 -0
  462. package/src/renderer/components/settings/sections/PlatformsSection.tsx +517 -0
  463. package/src/renderer/components/settings/sections/index.ts +8 -0
  464. package/src/renderer/components/sidebar/DateGroupedSessions.tsx +1115 -0
  465. package/src/renderer/components/sidebar/GlobalTaskList.tsx +853 -0
  466. package/src/renderer/components/sidebar/SessionContextMenu.tsx +182 -0
  467. package/src/renderer/components/sidebar/SessionFiltersPopover.tsx +115 -0
  468. package/src/renderer/components/sidebar/SessionItem.tsx +393 -0
  469. package/src/renderer/components/sidebar/SidebarSessions.tsx +542 -0
  470. package/src/renderer/components/sidebar/SidebarTaskItem.tsx +286 -0
  471. package/src/renderer/components/sidebar/TaskContextMenu.tsx +86 -0
  472. package/src/renderer/components/sidebar/TaskFiltersPopover.tsx +203 -0
  473. package/src/renderer/components/sidebar/WorkspaceBrowser.tsx +370 -0
  474. package/src/renderer/components/sidebar/dateGroupedSessionsSelection.ts +33 -0
  475. package/src/renderer/components/sidebar/projectGroupPagination.ts +89 -0
  476. package/src/renderer/components/sidebar/taskFiltersState.ts +82 -0
  477. package/src/renderer/components/splash/splashScene.ts +979 -0
  478. package/src/renderer/components/team/CcSessionsSection.tsx +202 -0
  479. package/src/renderer/components/team/ClaudeLogsDialog.tsx +71 -0
  480. package/src/renderer/components/team/ClaudeLogsFilterPopover.tsx +213 -0
  481. package/src/renderer/components/team/ClaudeLogsPanel.tsx +170 -0
  482. package/src/renderer/components/team/ClaudeLogsSection.tsx +154 -0
  483. package/src/renderer/components/team/CliLogsRichView.tsx +640 -0
  484. package/src/renderer/components/team/CollapsibleTeamSection.tsx +177 -0
  485. package/src/renderer/components/team/HarnessCards.ts +38 -0
  486. package/src/renderer/components/team/MemberBadge.tsx +117 -0
  487. package/src/renderer/components/team/ProcessesSection.tsx +193 -0
  488. package/src/renderer/components/team/ProvisioningProgressBlock.tsx +389 -0
  489. package/src/renderer/components/team/RoleSelect.tsx +171 -0
  490. package/src/renderer/components/team/StepProgressBar.tsx +165 -0
  491. package/src/renderer/components/team/TaskTooltip.tsx +197 -0
  492. package/src/renderer/components/team/TeamDetailView.tsx +3002 -0
  493. package/src/renderer/components/team/TeamEmptyState.tsx +102 -0
  494. package/src/renderer/components/team/TeamListFilterPopover.tsx +183 -0
  495. package/src/renderer/components/team/TeamListView.tsx +1336 -0
  496. package/src/renderer/components/team/TeamProvisioningBanner.tsx +16 -0
  497. package/src/renderer/components/team/TeamProvisioningPanel.tsx +115 -0
  498. package/src/renderer/components/team/TeamSessionsSection.tsx +267 -0
  499. package/src/renderer/components/team/ToolApprovalDiffPreview.tsx +206 -0
  500. package/src/renderer/components/team/ToolApprovalSheet.tsx +675 -0
  501. package/src/renderer/components/team/UnreadCommentsBadge.tsx +37 -0
  502. package/src/renderer/components/team/activity/ActiveTasksBlock.tsx +191 -0
  503. package/src/renderer/components/team/activity/ActivityItem.tsx +1649 -0
  504. package/src/renderer/components/team/activity/ActivityTimeline.tsx +959 -0
  505. package/src/renderer/components/team/activity/AnimatedHeightReveal.tsx +117 -0
  506. package/src/renderer/components/team/activity/LeadThoughtsGroup.tsx +1152 -0
  507. package/src/renderer/components/team/activity/MessageExpandDialog.tsx +213 -0
  508. package/src/renderer/components/team/activity/PendingRepliesBlock.tsx +275 -0
  509. package/src/renderer/components/team/activity/ReplyQuoteBlock.tsx +79 -0
  510. package/src/renderer/components/team/activity/ThoughtBodyContent.tsx +150 -0
  511. package/src/renderer/components/team/activity/activityMarkdown.ts +36 -0
  512. package/src/renderer/components/team/activity/activityMessageContext.ts +68 -0
  513. package/src/renderer/components/team/activity/collapseState.ts +66 -0
  514. package/src/renderer/components/team/activity/useNewItemKeys.ts +70 -0
  515. package/src/renderer/components/team/attachments/AttachmentDisplay.tsx +132 -0
  516. package/src/renderer/components/team/attachments/AttachmentPreviewItem.tsx +62 -0
  517. package/src/renderer/components/team/attachments/AttachmentPreviewList.tsx +193 -0
  518. package/src/renderer/components/team/attachments/AttachmentThumbnail.tsx +42 -0
  519. package/src/renderer/components/team/attachments/DropZoneOverlay.tsx +54 -0
  520. package/src/renderer/components/team/attachments/ImageLightbox.tsx +132 -0
  521. package/src/renderer/components/team/attachments/SourceMessageAttachments.tsx +70 -0
  522. package/src/renderer/components/team/dialogs/AddMemberDialog.tsx +222 -0
  523. package/src/renderer/components/team/dialogs/AdvancedCliSection.tsx +347 -0
  524. package/src/renderer/components/team/dialogs/AnthropicFastModeSelector.tsx +120 -0
  525. package/src/renderer/components/team/dialogs/CreateTaskDialog.tsx +489 -0
  526. package/src/renderer/components/team/dialogs/CreateTeamDialog.tsx +484 -0
  527. package/src/renderer/components/team/dialogs/EditTeamDialog.tsx +448 -0
  528. package/src/renderer/components/team/dialogs/EffortLevelSelector.tsx +69 -0
  529. package/src/renderer/components/team/dialogs/GlobalTaskDetailDialog.tsx +165 -0
  530. package/src/renderer/components/team/dialogs/LaunchTeamDialog.tsx +2859 -0
  531. package/src/renderer/components/team/dialogs/LimitContextCheckbox.tsx +57 -0
  532. package/src/renderer/components/team/dialogs/MembersJsonEditor.tsx +123 -0
  533. package/src/renderer/components/team/dialogs/OptionalSettingsSection.tsx +124 -0
  534. package/src/renderer/components/team/dialogs/PlatformManualForm.tsx +145 -0
  535. package/src/renderer/components/team/dialogs/PlatformSetupQR.tsx +289 -0
  536. package/src/renderer/components/team/dialogs/ProjectPathSelector.tsx +330 -0
  537. package/src/renderer/components/team/dialogs/ProvisioningProviderStatusList.tsx +744 -0
  538. package/src/renderer/components/team/dialogs/ReviewDialog.tsx +130 -0
  539. package/src/renderer/components/team/dialogs/SendMessageDialog.tsx +530 -0
  540. package/src/renderer/components/team/dialogs/SkipPermissionsCheckbox.tsx +62 -0
  541. package/src/renderer/components/team/dialogs/StatusHistoryTimeline.tsx +229 -0
  542. package/src/renderer/components/team/dialogs/TaskAttachments.tsx +394 -0
  543. package/src/renderer/components/team/dialogs/TaskCommentAwaitingReply.tsx +57 -0
  544. package/src/renderer/components/team/dialogs/TaskCommentInput.tsx +420 -0
  545. package/src/renderer/components/team/dialogs/TaskCommentsSection.tsx +620 -0
  546. package/src/renderer/components/team/dialogs/TaskDetailDialog.tsx +1480 -0
  547. package/src/renderer/components/team/dialogs/TeamModelSelector.tsx +560 -0
  548. package/src/renderer/components/team/dialogs/TeammateRuntimeCompatibilityNotice.tsx +60 -0
  549. package/src/renderer/components/team/dialogs/ToolApprovalSettingsPanel.tsx +173 -0
  550. package/src/renderer/components/team/dialogs/editTeamRuntimeChanges.ts +179 -0
  551. package/src/renderer/components/team/dialogs/globalTaskDetailDialogLoading.ts +34 -0
  552. package/src/renderer/components/team/dialogs/launchDialogPrefill.ts +125 -0
  553. package/src/renderer/components/team/dialogs/memberModelScope.ts +77 -0
  554. package/src/renderer/components/team/dialogs/platformMeta.ts +118 -0
  555. package/src/renderer/components/team/dialogs/projectPathOptions.ts +50 -0
  556. package/src/renderer/components/team/dialogs/providerPrepareDiagnostics.ts +222 -0
  557. package/src/renderer/components/team/dialogs/providerPrepareRequestSignature.ts +122 -0
  558. package/src/renderer/components/team/dialogs/provisioningMemberScope.ts +10 -0
  559. package/src/renderer/components/team/dialogs/provisioningModelIssues.ts +124 -0
  560. package/src/renderer/components/team/dialogs/teamNameSets.ts +67 -0
  561. package/src/renderer/components/team/dialogs/teamRelaunchFlow.ts +30 -0
  562. package/src/renderer/components/team/dialogs/teammateLaunchMode.ts +49 -0
  563. package/src/renderer/components/team/dialogs/teammateRuntimeCompatibility.tsx +101 -0
  564. package/src/renderer/components/team/editor/CodeMirrorEditor.tsx +506 -0
  565. package/src/renderer/components/team/editor/EditorBinaryPlaceholder.tsx +43 -0
  566. package/src/renderer/components/team/editor/EditorBinaryState.tsx +29 -0
  567. package/src/renderer/components/team/editor/EditorBreadcrumb.tsx +84 -0
  568. package/src/renderer/components/team/editor/EditorContextMenu.tsx +213 -0
  569. package/src/renderer/components/team/editor/EditorEmptyState.tsx +35 -0
  570. package/src/renderer/components/team/editor/EditorErrorBoundary.tsx +63 -0
  571. package/src/renderer/components/team/editor/EditorErrorState.tsx +41 -0
  572. package/src/renderer/components/team/editor/EditorFileTree.tsx +903 -0
  573. package/src/renderer/components/team/editor/EditorImagePreview.tsx +138 -0
  574. package/src/renderer/components/team/editor/EditorSearchPanel.tsx +508 -0
  575. package/src/renderer/components/team/editor/EditorSelectionMenu.tsx +112 -0
  576. package/src/renderer/components/team/editor/EditorShortcutsHelp.tsx +125 -0
  577. package/src/renderer/components/team/editor/EditorStatusBar.tsx +72 -0
  578. package/src/renderer/components/team/editor/EditorTabBar.tsx +265 -0
  579. package/src/renderer/components/team/editor/EditorTabContextMenu.tsx +88 -0
  580. package/src/renderer/components/team/editor/EditorToolbar.tsx +163 -0
  581. package/src/renderer/components/team/editor/FileIcon.tsx +66 -0
  582. package/src/renderer/components/team/editor/GitStatusBadge.tsx +47 -0
  583. package/src/renderer/components/team/editor/GoToLineDialog.tsx +186 -0
  584. package/src/renderer/components/team/editor/MarkdownPreviewPane.tsx +57 -0
  585. package/src/renderer/components/team/editor/MarkdownSplitView.tsx +127 -0
  586. package/src/renderer/components/team/editor/NewFileDialog.tsx +131 -0
  587. package/src/renderer/components/team/editor/ProjectEditorOverlay.tsx +924 -0
  588. package/src/renderer/components/team/editor/QuickOpenDialog.tsx +163 -0
  589. package/src/renderer/components/team/editor/SearchInFilesPanel.tsx +358 -0
  590. package/src/renderer/components/team/editor/fileIcons.ts +222 -0
  591. package/src/renderer/components/team/kanban/KanbanBoard.tsx +664 -0
  592. package/src/renderer/components/team/kanban/KanbanColumn.tsx +61 -0
  593. package/src/renderer/components/team/kanban/KanbanFilterPopover.tsx +210 -0
  594. package/src/renderer/components/team/kanban/KanbanGridLayout.tsx +460 -0
  595. package/src/renderer/components/team/kanban/KanbanSearchInput.tsx +284 -0
  596. package/src/renderer/components/team/kanban/KanbanSortPopover.tsx +140 -0
  597. package/src/renderer/components/team/kanban/KanbanTaskCard.test.tsx +199 -0
  598. package/src/renderer/components/team/kanban/KanbanTaskCard.tsx +446 -0
  599. package/src/renderer/components/team/kanban/TrashDialog.tsx +112 -0
  600. package/src/renderer/components/team/members/CurrentTaskIndicator.tsx +56 -0
  601. package/src/renderer/components/team/members/LeadModelRow.test.tsx +133 -0
  602. package/src/renderer/components/team/members/LeadModelRow.tsx +183 -0
  603. package/src/renderer/components/team/members/MemberCard.tsx +665 -0
  604. package/src/renderer/components/team/members/MemberDetailDialog.tsx +309 -0
  605. package/src/renderer/components/team/members/MemberDetailHeader.tsx +183 -0
  606. package/src/renderer/components/team/members/MemberDetailStats.tsx +80 -0
  607. package/src/renderer/components/team/members/MemberDraftRow.test.tsx +184 -0
  608. package/src/renderer/components/team/members/MemberDraftRow.tsx +515 -0
  609. package/src/renderer/components/team/members/MemberExecutionLog.tsx +224 -0
  610. package/src/renderer/components/team/members/MemberHoverCard.tsx +292 -0
  611. package/src/renderer/components/team/members/MemberLaunchDiagnosticsButton.tsx +60 -0
  612. package/src/renderer/components/team/members/MemberList.tsx +405 -0
  613. package/src/renderer/components/team/members/MemberLogsTab.tsx +958 -0
  614. package/src/renderer/components/team/members/MemberMessagesTab.tsx +251 -0
  615. package/src/renderer/components/team/members/MemberPresenceDot.tsx +28 -0
  616. package/src/renderer/components/team/members/MemberRoleEditor.tsx +84 -0
  617. package/src/renderer/components/team/members/MemberStatsTab.tsx +299 -0
  618. package/src/renderer/components/team/members/MemberTasksTab.tsx +87 -0
  619. package/src/renderer/components/team/members/MemberWorkspaceTab.tsx +141 -0
  620. package/src/renderer/components/team/members/MembersEditorSection.tsx +495 -0
  621. package/src/renderer/components/team/members/SubagentRecentMessagesPreview.tsx +125 -0
  622. package/src/renderer/components/team/members/TeamRosterEditorSection.tsx +153 -0
  623. package/src/renderer/components/team/members/memberActivityEntries.ts +42 -0
  624. package/src/renderer/components/team/members/memberDetailTypes.ts +3 -0
  625. package/src/renderer/components/team/members/memberNameSets.ts +65 -0
  626. package/src/renderer/components/team/members/membersEditorTypes.ts +21 -0
  627. package/src/renderer/components/team/members/membersEditorUtils.ts +267 -0
  628. package/src/renderer/components/team/messages/MessageComposer.tsx +939 -0
  629. package/src/renderer/components/team/messages/MessagesFilterPopover.tsx +228 -0
  630. package/src/renderer/components/team/messages/MessagesPanel.tsx +1508 -0
  631. package/src/renderer/components/team/messages/OpenCodeDeliveryWarning.tsx +151 -0
  632. package/src/renderer/components/team/messages/StatusBlock.tsx +126 -0
  633. package/src/renderer/components/team/provisioningSteps.ts +363 -0
  634. package/src/renderer/components/team/review/ChangeReviewDialog.tsx +133 -0
  635. package/src/renderer/components/team/schedule/CcCronScheduleDialog.tsx +218 -0
  636. package/src/renderer/components/team/schedule/CronScheduleInput.tsx +254 -0
  637. package/src/renderer/components/team/schedule/ScheduleEmptyState.tsx +15 -0
  638. package/src/renderer/components/team/schedule/ScheduleRunLogDialog.tsx +277 -0
  639. package/src/renderer/components/team/schedule/ScheduleRunRow.tsx +106 -0
  640. package/src/renderer/components/team/schedule/ScheduleSection.tsx +281 -0
  641. package/src/renderer/components/team/schedule/ScheduleStatusBadge.tsx +55 -0
  642. package/src/renderer/components/team/sidebar/TeamSidebarHost.tsx +76 -0
  643. package/src/renderer/components/team/sidebar/TeamSidebarPortalManager.ts +173 -0
  644. package/src/renderer/components/team/sidebar/TeamSidebarPortalSource.tsx +66 -0
  645. package/src/renderer/components/team/sidebar/TeamSidebarRail.tsx +68 -0
  646. package/src/renderer/components/team/sidebar/teamSidebarUiState.ts +136 -0
  647. package/src/renderer/components/team/taskLogs/ExactTaskLogCard.tsx +132 -0
  648. package/src/renderer/components/team/taskLogs/ExactTaskLogsSection.tsx +258 -0
  649. package/src/renderer/components/team/taskLogs/ExecutionSessionsSection.tsx +48 -0
  650. package/src/renderer/components/team/taskLogs/TaskActivityLinkedToolCard.tsx +31 -0
  651. package/src/renderer/components/team/taskLogs/TaskActivitySection.tsx +462 -0
  652. package/src/renderer/components/team/taskLogs/TaskLogStreamSection.tsx +375 -0
  653. package/src/renderer/components/team/taskLogs/TaskLogsPanel.tsx +294 -0
  654. package/src/renderer/components/team/taskLogs/featureGates.ts +22 -0
  655. package/src/renderer/components/team/tasks/TaskList.tsx +111 -0
  656. package/src/renderer/components/team/tasks/TaskRow.tsx +65 -0
  657. package/src/renderer/components/team/teamProjectSelection.ts +156 -0
  658. package/src/renderer/components/team/teamSessionFetchGuards.ts +26 -0
  659. package/src/renderer/components/team/useClaudeLogsController.ts +668 -0
  660. package/src/renderer/components/team/useTeamProvisioningPresentation.ts +54 -0
  661. package/src/renderer/components/terminal/TerminalLogPanel.tsx +37 -0
  662. package/src/renderer/components/ui/ChipInteractionLayer.tsx +255 -0
  663. package/src/renderer/components/ui/CodeChipBadge.tsx +37 -0
  664. package/src/renderer/components/ui/ExpandableContent.tsx +110 -0
  665. package/src/renderer/components/ui/MemberSelect.tsx +209 -0
  666. package/src/renderer/components/ui/MentionInteractionLayer.tsx +121 -0
  667. package/src/renderer/components/ui/MentionSuggestionList.tsx +274 -0
  668. package/src/renderer/components/ui/MentionableTextarea.tsx +1426 -0
  669. package/src/renderer/components/ui/SlashCommandInteractionLayer.tsx +88 -0
  670. package/src/renderer/components/ui/TaskReferenceInteractionLayer.tsx +101 -0
  671. package/src/renderer/components/ui/UrlInteractionLayer.tsx +102 -0
  672. package/src/renderer/components/ui/alert-dialog.tsx +127 -0
  673. package/src/renderer/components/ui/auto-resize-textarea.tsx +93 -0
  674. package/src/renderer/components/ui/badge.tsx +37 -0
  675. package/src/renderer/components/ui/button.tsx +54 -0
  676. package/src/renderer/components/ui/checkbox.tsx +29 -0
  677. package/src/renderer/components/ui/combobox.tsx +168 -0
  678. package/src/renderer/components/ui/context-menu.tsx +124 -0
  679. package/src/renderer/components/ui/dialog.tsx +114 -0
  680. package/src/renderer/components/ui/hover-card.tsx +30 -0
  681. package/src/renderer/components/ui/input.tsx +22 -0
  682. package/src/renderer/components/ui/label.tsx +21 -0
  683. package/src/renderer/components/ui/popover.tsx +31 -0
  684. package/src/renderer/components/ui/select.tsx +150 -0
  685. package/src/renderer/components/ui/tabs.tsx +52 -0
  686. package/src/renderer/components/ui/textarea.tsx +21 -0
  687. package/src/renderer/components/ui/tiptap/TiptapBubbleMenu.tsx +75 -0
  688. package/src/renderer/components/ui/tiptap/TiptapEditor.tsx +73 -0
  689. package/src/renderer/components/ui/tiptap/TiptapToolbar.tsx +269 -0
  690. package/src/renderer/components/ui/tiptap/index.ts +3 -0
  691. package/src/renderer/components/ui/tiptap/presets.ts +46 -0
  692. package/src/renderer/components/ui/tiptap/tiptapStyles.css +235 -0
  693. package/src/renderer/components/ui/tiptap/types.ts +32 -0
  694. package/src/renderer/components/ui/tiptap/useTiptapEditor.ts +94 -0
  695. package/src/renderer/components/ui/tooltip.tsx +32 -0
  696. package/src/renderer/constants/cssVariables.ts +226 -0
  697. package/src/renderer/constants/layout.ts +6 -0
  698. package/src/renderer/constants/teamColors.ts +397 -0
  699. package/src/renderer/constants/teamRoles.ts +41 -0
  700. package/src/renderer/contexts/TabUIContext.tsx +51 -0
  701. package/src/renderer/contexts/useTabUIContext.ts +18 -0
  702. package/src/renderer/favicon.png +0 -0
  703. package/src/renderer/features/CLAUDE.md +19 -0
  704. package/src/renderer/hooks/navigation/utils.ts +263 -0
  705. package/src/renderer/hooks/useAttachments.ts +312 -0
  706. package/src/renderer/hooks/useAutoScrollBottom.ts +285 -0
  707. package/src/renderer/hooks/useBranchSync.ts +105 -0
  708. package/src/renderer/hooks/useChipDraftPersistence.ts +172 -0
  709. package/src/renderer/hooks/useCliInstaller.ts +106 -0
  710. package/src/renderer/hooks/useCollapsedGroups.ts +71 -0
  711. package/src/renderer/hooks/useComposerDraft.ts +504 -0
  712. package/src/renderer/hooks/useContinuousScrollNav.ts +50 -0
  713. package/src/renderer/hooks/useCreateTeamDraft.ts +280 -0
  714. package/src/renderer/hooks/useDraftPersistence.ts +140 -0
  715. package/src/renderer/hooks/useEditorKeyboardShortcuts.ts +257 -0
  716. package/src/renderer/hooks/useEffectiveCliProviderStatus.ts +66 -0
  717. package/src/renderer/hooks/useExtensionsTabState.ts +206 -0
  718. package/src/renderer/hooks/useFileListCacheWarmer.ts +38 -0
  719. package/src/renderer/hooks/useFileSuggestions.ts +255 -0
  720. package/src/renderer/hooks/useKeyboardShortcuts.ts +363 -0
  721. package/src/renderer/hooks/useLazyFileContent.ts +150 -0
  722. package/src/renderer/hooks/useMarkCommentsRead.ts +27 -0
  723. package/src/renderer/hooks/useMarkdownScrollSync.ts +158 -0
  724. package/src/renderer/hooks/useMemberStats.ts +45 -0
  725. package/src/renderer/hooks/useMentionDetection.ts +375 -0
  726. package/src/renderer/hooks/usePersistedGridLayout.ts +109 -0
  727. package/src/renderer/hooks/useResizableColumns.ts +140 -0
  728. package/src/renderer/hooks/useResizablePanel.ts +144 -0
  729. package/src/renderer/hooks/useStableTeamMentionMeta.ts +68 -0
  730. package/src/renderer/hooks/useSyncedAnimationStyle.ts +29 -0
  731. package/src/renderer/hooks/useTabNavigationController.ts +524 -0
  732. package/src/renderer/hooks/useTabUI.ts +252 -0
  733. package/src/renderer/hooks/useTaskLocalState.ts +163 -0
  734. package/src/renderer/hooks/useTaskSuggestions.ts +131 -0
  735. package/src/renderer/hooks/useTeamMessagesExpanded.ts +34 -0
  736. package/src/renderer/hooks/useTeamMessagesRead.ts +52 -0
  737. package/src/renderer/hooks/useTeamSuggestions.ts +78 -0
  738. package/src/renderer/hooks/useTheme.ts +138 -0
  739. package/src/renderer/hooks/useToolApprovalDiff.ts +212 -0
  740. package/src/renderer/hooks/useUnreadCommentCount.ts +14 -0
  741. package/src/renderer/hooks/useViewedFiles.ts +74 -0
  742. package/src/renderer/hooks/useViewportCommentRead.ts +147 -0
  743. package/src/renderer/hooks/useViewportObserver.ts +138 -0
  744. package/src/renderer/hooks/useVisibleAIGroup.ts +122 -0
  745. package/src/renderer/hooks/useVisibleFileSection.ts +114 -0
  746. package/src/renderer/hooks/useZoomFactor.ts +36 -0
  747. package/src/renderer/index.css +1560 -0
  748. package/src/renderer/index.html +1293 -0
  749. package/src/renderer/lib/utils.ts +6 -0
  750. package/src/renderer/main.tsx +30 -0
  751. package/src/renderer/sentry.ts +104 -0
  752. package/src/renderer/services/__tests__/createTeamPreferences.test.ts +67 -0
  753. package/src/renderer/services/commentReadStorage.ts +349 -0
  754. package/src/renderer/services/composerDraftStorage.ts +271 -0
  755. package/src/renderer/services/contextStorage.ts +201 -0
  756. package/src/renderer/services/createTeamDraftStorage.ts +151 -0
  757. package/src/renderer/services/createTeamPreferences.ts +361 -0
  758. package/src/renderer/services/dashboardCliStatusBannerPreference.ts +20 -0
  759. package/src/renderer/services/draftStorage.ts +128 -0
  760. package/src/renderer/services/layout-system/BrowserGridLayoutRepository.ts +111 -0
  761. package/src/renderer/services/layout-system/GridLayoutRepository.ts +8 -0
  762. package/src/renderer/services/layout-system/gridLayoutSchema.ts +137 -0
  763. package/src/renderer/services/layout-system/gridLayoutTypes.ts +17 -0
  764. package/src/renderer/store/index.ts +1556 -0
  765. package/src/renderer/store/slices/changeReviewSlice.ts +1694 -0
  766. package/src/renderer/store/slices/cliInstallerSlice.ts +689 -0
  767. package/src/renderer/store/slices/configSlice.ts +111 -0
  768. package/src/renderer/store/slices/connectionSlice.ts +221 -0
  769. package/src/renderer/store/slices/contextSlice.ts +394 -0
  770. package/src/renderer/store/slices/conversationSlice.ts +510 -0
  771. package/src/renderer/store/slices/editorSlice.ts +1455 -0
  772. package/src/renderer/store/slices/extensionsSlice.ts +1415 -0
  773. package/src/renderer/store/slices/notificationSlice.ts +277 -0
  774. package/src/renderer/store/slices/paneSlice.ts +357 -0
  775. package/src/renderer/store/slices/projectSlice.ts +70 -0
  776. package/src/renderer/store/slices/repositorySlice.ts +165 -0
  777. package/src/renderer/store/slices/scheduleSlice.ts +246 -0
  778. package/src/renderer/store/slices/sessionDetailSlice.ts +755 -0
  779. package/src/renderer/store/slices/sessionSlice.ts +539 -0
  780. package/src/renderer/store/slices/subagentSlice.ts +145 -0
  781. package/src/renderer/store/slices/tabSlice.ts +842 -0
  782. package/src/renderer/store/slices/tabUISlice.ts +319 -0
  783. package/src/renderer/store/slices/teamSlice.ts +5080 -0
  784. package/src/renderer/store/slices/uiSlice.ts +45 -0
  785. package/src/renderer/store/types.ts +103 -0
  786. package/src/renderer/store/utils/paneHelpers.ts +134 -0
  787. package/src/renderer/store/utils/pathResolution.ts +121 -0
  788. package/src/renderer/store/utils/stateResetHelpers.ts +72 -0
  789. package/src/renderer/types/api.ts +8 -0
  790. package/src/renderer/types/claudeMd.ts +74 -0
  791. package/src/renderer/types/contextInjection.ts +309 -0
  792. package/src/renderer/types/data.ts +144 -0
  793. package/src/renderer/types/groups.ts +406 -0
  794. package/src/renderer/types/inlineChip.ts +110 -0
  795. package/src/renderer/types/mention.ts +38 -0
  796. package/src/renderer/types/notifications.ts +18 -0
  797. package/src/renderer/types/panes.ts +35 -0
  798. package/src/renderer/types/sessionReport.ts +386 -0
  799. package/src/renderer/types/tabs.ts +249 -0
  800. package/src/renderer/types/teamMessagesPanelMode.ts +1 -0
  801. package/src/renderer/utils/__tests__/teamEffortOptions.test.ts +217 -0
  802. package/src/renderer/utils/__tests__/teamModelAvailability.codexCatalog.test.ts +383 -0
  803. package/src/renderer/utils/agentMessageFormatting.ts +139 -0
  804. package/src/renderer/utils/aiGroupEnhancer.ts +78 -0
  805. package/src/renderer/utils/aiGroupHelpers.ts +208 -0
  806. package/src/renderer/utils/attachmentUtils.ts +60 -0
  807. package/src/renderer/utils/bootstrapPromptSanitizer.ts +225 -0
  808. package/src/renderer/utils/bugReportUtils.ts +157 -0
  809. package/src/renderer/utils/buildSelectionAction.ts +116 -0
  810. package/src/renderer/utils/chipUtils.ts +372 -0
  811. package/src/renderer/utils/claudeCodeOnlyProviders.ts +126 -0
  812. package/src/renderer/utils/claudeMdTracker.ts +644 -0
  813. package/src/renderer/utils/codemirrorLanguages.ts +141 -0
  814. package/src/renderer/utils/codemirrorSelectionInfo.ts +41 -0
  815. package/src/renderer/utils/codemirrorTheme.ts +138 -0
  816. package/src/renderer/utils/contextMath.ts +55 -0
  817. package/src/renderer/utils/contextTracker.ts +1100 -0
  818. package/src/renderer/utils/crossTeamPendingReplies.ts +92 -0
  819. package/src/renderer/utils/dateGrouping.ts +91 -0
  820. package/src/renderer/utils/diffViewedStorage.ts +120 -0
  821. package/src/renderer/utils/displayItemBuilder.ts +587 -0
  822. package/src/renderer/utils/displaySummary.ts +74 -0
  823. package/src/renderer/utils/editorBridge.ts +90 -0
  824. package/src/renderer/utils/fileTreeBuilder.ts +110 -0
  825. package/src/renderer/utils/formatAgentRole.ts +24 -0
  826. package/src/renderer/utils/formatters.ts +61 -0
  827. package/src/renderer/utils/groupTransformer.ts +744 -0
  828. package/src/renderer/utils/idleNotificationSemantics.ts +76 -0
  829. package/src/renderer/utils/keyboardUtils.ts +92 -0
  830. package/src/renderer/utils/lastOutputDetector.ts +150 -0
  831. package/src/renderer/utils/markdownPlugins.ts +60 -0
  832. package/src/renderer/utils/memberAvatarCatalog.ts +38 -0
  833. package/src/renderer/utils/memberHelpers.ts +858 -0
  834. package/src/renderer/utils/memberLaunchDiagnostics.ts +216 -0
  835. package/src/renderer/utils/memberRuntimeSummary.ts +122 -0
  836. package/src/renderer/utils/memberSpawnStatusPolling.ts +29 -0
  837. package/src/renderer/utils/mentionLinkify.ts +90 -0
  838. package/src/renderer/utils/mentionSuggestions.ts +34 -0
  839. package/src/renderer/utils/mergeTeamMessages.ts +27 -0
  840. package/src/renderer/utils/messageRenderEquality.ts +158 -0
  841. package/src/renderer/utils/modelExtractor.ts +90 -0
  842. package/src/renderer/utils/multimodelProviderVisibility.ts +32 -0
  843. package/src/renderer/utils/openCodeModelRecommendations.ts +1326 -0
  844. package/src/renderer/utils/openCodeRuntimeDeliveryDiagnostics.ts +79 -0
  845. package/src/renderer/utils/pathDisplay.ts +149 -0
  846. package/src/renderer/utils/pathNormalize.ts +61 -0
  847. package/src/renderer/utils/pathUtils.ts +47 -0
  848. package/src/renderer/utils/platformKeys.ts +24 -0
  849. package/src/renderer/utils/previewRegistry.ts +45 -0
  850. package/src/renderer/utils/projectColor.ts +54 -0
  851. package/src/renderer/utils/projectLookup.ts +64 -0
  852. package/src/renderer/utils/providerBackendIdentity.ts +43 -0
  853. package/src/renderer/utils/providerSlashCommands.ts +122 -0
  854. package/src/renderer/utils/quickOpenCache.ts +40 -0
  855. package/src/renderer/utils/refreshCliStatus.ts +17 -0
  856. package/src/renderer/utils/reportAssessments.ts +555 -0
  857. package/src/renderer/utils/reviewDecisionScope.ts +81 -0
  858. package/src/renderer/utils/reviewKey.ts +132 -0
  859. package/src/renderer/utils/runtimeDisplayName.ts +26 -0
  860. package/src/renderer/utils/scheduleFormatters.ts +45 -0
  861. package/src/renderer/utils/sessionAnalyzer.ts +1346 -0
  862. package/src/renderer/utils/sessionExporter.ts +427 -0
  863. package/src/renderer/utils/sessionTitleParser.ts +69 -0
  864. package/src/renderer/utils/skillCommandSuggestions.ts +77 -0
  865. package/src/renderer/utils/slashCommandExtractor.ts +154 -0
  866. package/src/renderer/utils/streamJsonParser.ts +393 -0
  867. package/src/renderer/utils/stringUtils.ts +50 -0
  868. package/src/renderer/utils/syntaxHighlighter.ts +158 -0
  869. package/src/renderer/utils/tabLabelDisambiguation.ts +99 -0
  870. package/src/renderer/utils/taskChangePresence.ts +19 -0
  871. package/src/renderer/utils/taskChangeRequest.ts +122 -0
  872. package/src/renderer/utils/taskCommentPendingReply.ts +74 -0
  873. package/src/renderer/utils/taskGrouping.ts +151 -0
  874. package/src/renderer/utils/taskReferenceUtils.ts +331 -0
  875. package/src/renderer/utils/teamEffortOptions.ts +150 -0
  876. package/src/renderer/utils/teamLaunchSummaryCopy.ts +17 -0
  877. package/src/renderer/utils/teamMessageExpandStorage.ts +39 -0
  878. package/src/renderer/utils/teamMessageFiltering.ts +95 -0
  879. package/src/renderer/utils/teamMessageKey.ts +14 -0
  880. package/src/renderer/utils/teamMessageReadStorage.ts +49 -0
  881. package/src/renderer/utils/teamModelAvailability.ts +465 -0
  882. package/src/renderer/utils/teamModelCatalog.ts +502 -0
  883. package/src/renderer/utils/teamModelContext.ts +34 -0
  884. package/src/renderer/utils/teamProvisioningPresentation.ts +772 -0
  885. package/src/renderer/utils/teamRuntimeSummary.ts +53 -0
  886. package/src/renderer/utils/toolLinkingEngine.ts +117 -0
  887. package/src/renderer/utils/toolRendering/index.ts +14 -0
  888. package/src/renderer/utils/toolRendering/toolContentChecks.ts +58 -0
  889. package/src/renderer/utils/toolRendering/toolSummaryHelpers.ts +276 -0
  890. package/src/renderer/utils/toolRendering/toolTokens.ts +57 -0
  891. package/src/renderer/utils/unwrapIpc.ts +31 -0
  892. package/src/renderer/utils/urlMatchUtils.ts +45 -0
  893. package/src/renderer/vite-env.d.ts +21 -0
  894. package/src/shared/constants/agentBlocks.ts +129 -0
  895. package/src/shared/constants/attachments.ts +175 -0
  896. package/src/shared/constants/cache.ts +12 -0
  897. package/src/shared/constants/cli.ts +15 -0
  898. package/src/shared/constants/crossTeam.ts +126 -0
  899. package/src/shared/constants/index.ts +15 -0
  900. package/src/shared/constants/kanban.ts +9 -0
  901. package/src/shared/constants/memberColors.ts +140 -0
  902. package/src/shared/constants/opencodeTaskLogAttribution.ts +1 -0
  903. package/src/shared/constants/teamLimits.ts +2 -0
  904. package/src/shared/constants/trafficLights.ts +60 -0
  905. package/src/shared/constants/triggerColors.ts +126 -0
  906. package/src/shared/constants/window.ts +12 -0
  907. package/src/shared/types/api.ts +1062 -0
  908. package/src/shared/types/ccConnect.ts +399 -0
  909. package/src/shared/types/cliInstaller.ts +335 -0
  910. package/src/shared/types/editor.ts +279 -0
  911. package/src/shared/types/extensions/api.ts +90 -0
  912. package/src/shared/types/extensions/apikey.ts +40 -0
  913. package/src/shared/types/extensions/common.ts +16 -0
  914. package/src/shared/types/extensions/index.ts +67 -0
  915. package/src/shared/types/extensions/mcp.ts +132 -0
  916. package/src/shared/types/extensions/plugin.ts +85 -0
  917. package/src/shared/types/extensions/skill.ts +173 -0
  918. package/src/shared/types/index.ts +48 -0
  919. package/src/shared/types/ipc.ts +5 -0
  920. package/src/shared/types/notifications.ts +407 -0
  921. package/src/shared/types/providers.ts +116 -0
  922. package/src/shared/types/review.ts +319 -0
  923. package/src/shared/types/schedule.ts +124 -0
  924. package/src/shared/types/team.ts +1726 -0
  925. package/src/shared/types/terminal.ts +49 -0
  926. package/src/shared/types/visualization.ts +60 -0
  927. package/src/shared/utils/__tests__/contextMetrics.test.ts +260 -0
  928. package/src/shared/utils/__tests__/ephemeralProjectPath.test.ts +42 -0
  929. package/src/shared/utils/__tests__/teamProvider.test.ts +29 -0
  930. package/src/shared/utils/agentLanguage.ts +122 -0
  931. package/src/shared/utils/anthropicLaunchModel.ts +96 -0
  932. package/src/shared/utils/anthropicModelDefaults.ts +3 -0
  933. package/src/shared/utils/apiErrorDetector.ts +13 -0
  934. package/src/shared/utils/boardTaskActivityLabels.ts +128 -0
  935. package/src/shared/utils/boardTaskActivityPresentation.ts +75 -0
  936. package/src/shared/utils/cliArgsParser.ts +129 -0
  937. package/src/shared/utils/contentSanitizer.ts +207 -0
  938. package/src/shared/utils/contextMetrics.ts +236 -0
  939. package/src/shared/utils/costFormatting.ts +45 -0
  940. package/src/shared/utils/diffContextHash.ts +22 -0
  941. package/src/shared/utils/effortLevels.ts +73 -0
  942. package/src/shared/utils/ephemeralProjectPath.ts +41 -0
  943. package/src/shared/utils/errorHandling.ts +26 -0
  944. package/src/shared/utils/extensionNormalizers.ts +336 -0
  945. package/src/shared/utils/idleNotificationSemantics.ts +86 -0
  946. package/src/shared/utils/inboxNoise.ts +158 -0
  947. package/src/shared/utils/leadDetection.ts +63 -0
  948. package/src/shared/utils/logger.ts +69 -0
  949. package/src/shared/utils/markdownTextSearch.ts +210 -0
  950. package/src/shared/utils/mcpScopes.ts +36 -0
  951. package/src/shared/utils/modelParser.ts +158 -0
  952. package/src/shared/utils/opencodeModelRef.ts +78 -0
  953. package/src/shared/utils/platformPath.ts +103 -0
  954. package/src/shared/utils/pricing.ts +129 -0
  955. package/src/shared/utils/providerBackend.ts +90 -0
  956. package/src/shared/utils/providerExtensionCapabilities.ts +92 -0
  957. package/src/shared/utils/providerModelSelection.ts +5 -0
  958. package/src/shared/utils/providerModelVisibility.ts +47 -0
  959. package/src/shared/utils/rateLimitDetector.ts +334 -0
  960. package/src/shared/utils/reviewState.ts +74 -0
  961. package/src/shared/utils/sentryConfig.ts +26 -0
  962. package/src/shared/utils/skillRoots.ts +93 -0
  963. package/src/shared/utils/slashCommands.ts +128 -0
  964. package/src/shared/utils/taskChangePresence.ts +35 -0
  965. package/src/shared/utils/taskChangeSince.ts +51 -0
  966. package/src/shared/utils/taskChangeState.ts +49 -0
  967. package/src/shared/utils/taskHistory.ts +82 -0
  968. package/src/shared/utils/taskIdentity.ts +32 -0
  969. package/src/shared/utils/teamGraphDefaultLayout.ts +97 -0
  970. package/src/shared/utils/teamMemberColors.ts +107 -0
  971. package/src/shared/utils/teamMemberName.ts +77 -0
  972. package/src/shared/utils/teamProvider.ts +73 -0
  973. package/src/shared/utils/teamStableOwnerId.ts +12 -0
  974. package/src/shared/utils/teammateMessageParser.ts +52 -0
  975. package/src/shared/utils/tokenFormatting.ts +91 -0
  976. package/src/shared/utils/toolSummary.ts +279 -0
  977. package/src/shared/utils/version.ts +29 -0
  978. package/src/types/agent-teams-controller.d.ts +163 -0
  979. package/src/types/node-pty.d.ts +22 -0
  980. package/src/types/pidusage.d.ts +23 -0
@@ -0,0 +1,1694 @@
1
+ import { api } from '@renderer/api';
2
+ import {
3
+ getReviewChangeSetIdentityToken,
4
+ type ReviewChangeSetLike,
5
+ } from '@renderer/utils/reviewDecisionScope';
6
+ import {
7
+ buildHunkDecisionKey,
8
+ getFileReviewKey,
9
+ getReviewKeyForFilePath,
10
+ normalizePersistedReviewState,
11
+ } from '@renderer/utils/reviewKey';
12
+ import {
13
+ resolveTaskChangePresenceFromResult,
14
+ shouldBackgroundRevalidateTaskPresence,
15
+ } from '@renderer/utils/taskChangePresence';
16
+ import {
17
+ buildTaskChangePresenceKey,
18
+ isTaskSummaryCacheableForOptions,
19
+ type TaskChangeRequestOptions,
20
+ } from '@renderer/utils/taskChangeRequest';
21
+ import { computeDiffContextHash } from '@shared/utils/diffContextHash';
22
+ import { createLogger } from '@shared/utils/logger';
23
+ import { isWindowsishPath, normalizePathForComparison } from '@shared/utils/platformPath';
24
+ import { structuredPatch } from 'diff';
25
+
26
+ /** Tracks in-flight checkTaskHasChanges calls to avoid duplicate requests */
27
+ const taskChangesCheckInFlight = new Set<string>();
28
+ /** Tracks background presence revalidation for optimistic terminal summary hits */
29
+ const taskChangesPresenceRevalidationInFlight = new Set<string>();
30
+ /** Negative results cached with timestamp — recheck after 30s */
31
+ const taskChangesNegativeCache = new Map<string, number>();
32
+ const NEGATIVE_CACHE_TTL = 30_000;
33
+ const TASK_CHANGE_WARM_CONCURRENCY = 4;
34
+ const CHANGE_REVIEW_SLICE_BOOT_TIME = Date.now();
35
+ let latestAgentChangesRequestToken = 0;
36
+ let latestTaskChangesRequestToken = 0;
37
+ let latestDecisionLoadRequestToken = 0;
38
+
39
+ /** Debounce timer for persisting decisions to disk */
40
+ const persistDebounceTimers = new Map<string, ReturnType<typeof setTimeout>>();
41
+ const PERSIST_DEBOUNCE_MS = 500;
42
+
43
+ import type { AppState } from '../types';
44
+ import type {
45
+ AgentChangeSet,
46
+ ApplyReviewRequest,
47
+ ApplyReviewResult,
48
+ ChangeStats,
49
+ FileChangeSummary,
50
+ FileChangeWithContent,
51
+ FileReviewDecision,
52
+ HunkDecision,
53
+ SnippetDiff,
54
+ TaskChangePresenceState,
55
+ TaskChangeSet,
56
+ TaskChangeSetV2,
57
+ } from '@shared/types';
58
+ import type { StateCreator } from 'zustand';
59
+
60
+ const logger = createLogger('changeReviewSlice');
61
+
62
+ function reviewPathsEqual(left: string, right: string): boolean {
63
+ const caseInsensitive = isWindowsReviewPath(left) || isWindowsReviewPath(right);
64
+ return (
65
+ normalizeReviewPathForComparison(left, caseInsensitive) ===
66
+ normalizeReviewPathForComparison(right, caseInsensitive)
67
+ );
68
+ }
69
+
70
+ function normalizeReviewPathForComparison(filePath: string, caseInsensitive: boolean): string {
71
+ const normalized = normalizePathForComparison(filePath);
72
+ return caseInsensitive ? normalized.toLowerCase() : normalized;
73
+ }
74
+
75
+ function isWindowsReviewPath(filePath: string): boolean {
76
+ return isWindowsishPath(filePath) || filePath.includes('\\');
77
+ }
78
+
79
+ function findReviewFileByPath(
80
+ files: readonly FileChangeSummary[] | null | undefined,
81
+ filePath: string
82
+ ): FileChangeSummary | undefined {
83
+ return files?.find((file) => reviewPathsEqual(file.filePath, filePath));
84
+ }
85
+
86
+ /** Snapshot of review decisions for undo support */
87
+ interface DecisionSnapshot {
88
+ hunkDecisions: Record<string, HunkDecision>;
89
+ fileDecisions: Record<string, HunkDecision>;
90
+ }
91
+
92
+ export interface ReviewExternalChange {
93
+ type: 'change' | 'add' | 'unlink';
94
+ }
95
+
96
+ const MAX_REVIEW_UNDO_DEPTH = 10;
97
+
98
+ /**
99
+ * When true, rejected hunks are immediately applied to disk (no need for "Apply All Changes").
100
+ * When false, decisions are batched and applied manually via "Apply All Changes" button.
101
+ */
102
+ export const REVIEW_INSTANT_APPLY = true;
103
+
104
+ function mapReviewError(error: unknown): string {
105
+ const message = error instanceof Error ? error.message : String(error);
106
+ if (message.includes('conflict')) return 'File has been modified since agent changes.';
107
+ if (message.includes('ENOENT')) return 'File no longer exists on disk.';
108
+ if (message.includes('EACCES') || message.includes('Permission')) return 'Permission denied.';
109
+ return message || 'Failed to apply review changes';
110
+ }
111
+
112
+ function clearPersistDecisionTimer(scopeStorageKey: string): void {
113
+ const timer = persistDebounceTimers.get(scopeStorageKey);
114
+ if (!timer) return;
115
+ clearTimeout(timer);
116
+ persistDebounceTimers.delete(scopeStorageKey);
117
+ }
118
+
119
+ function buildPersistDecisionScopeKey(
120
+ teamName: string,
121
+ scopeKey: string,
122
+ scopeToken?: string
123
+ ): string {
124
+ return scopeToken ? `${teamName}:${scopeKey}:${scopeToken}` : `${teamName}:${scopeKey}`;
125
+ }
126
+
127
+ function clearAllPersistDecisionTimers(): void {
128
+ for (const timer of persistDebounceTimers.values()) {
129
+ clearTimeout(timer);
130
+ }
131
+ persistDebounceTimers.clear();
132
+ }
133
+
134
+ function applyTaskChangePresenceCacheUpdate(
135
+ taskChangePresenceByKey: Record<string, Exclude<TaskChangePresenceState, 'unknown'>>,
136
+ cacheKey: string,
137
+ presence: TaskChangePresenceState | null
138
+ ): Record<string, Exclude<TaskChangePresenceState, 'unknown'>> {
139
+ const nextTaskChangePresenceByKey = { ...taskChangePresenceByKey };
140
+ if (presence && presence !== 'unknown') {
141
+ nextTaskChangePresenceByKey[cacheKey] = presence;
142
+ } else {
143
+ delete nextTaskChangePresenceByKey[cacheKey];
144
+ }
145
+ return nextTaskChangePresenceByKey;
146
+ }
147
+
148
+ function syncTaskChangeNegativeCache(
149
+ cacheKey: string,
150
+ presence: TaskChangePresenceState | null
151
+ ): void {
152
+ if (presence === 'has_changes' || presence === 'needs_attention') {
153
+ taskChangesNegativeCache.delete(cacheKey);
154
+ } else if (presence === 'no_changes') {
155
+ taskChangesNegativeCache.set(cacheKey, Date.now());
156
+ } else {
157
+ taskChangesNegativeCache.delete(cacheKey);
158
+ }
159
+ }
160
+
161
+ export interface ChangeReviewSlice {
162
+ // Phase 1 state
163
+ activeChangeSet: AgentChangeSet | TaskChangeSet | TaskChangeSetV2 | null;
164
+ activeTaskChangeRequestOptions: TaskChangeRequestOptions | null;
165
+ changeSetLoading: boolean;
166
+ changeSetError: string | null;
167
+ selectedReviewFilePath: string | null;
168
+ changeStatsCache: Record<string, ChangeStats>;
169
+
170
+ // Phase 2 state
171
+ hunkDecisions: Record<string, HunkDecision>;
172
+ fileDecisions: Record<string, HunkDecision>;
173
+ /** Actual CodeMirror chunk count per file (may differ from snippets.length) */
174
+ fileChunkCounts: Record<string, number>;
175
+ /** Undo stack for bulk review operations (Accept All / Reject All) */
176
+ reviewUndoStack: DecisionSnapshot[];
177
+ /** filePath -> (hunkIndex -> contextHash), persisted for robust replay */
178
+ hunkContextHashesByFile: Record<string, Record<number, string>>;
179
+ fileContents: Record<string, FileChangeWithContent>;
180
+ fileContentsLoading: Record<string, boolean>;
181
+ changeSetEpoch: number;
182
+ fileContentVersionByPath: Record<string, number>;
183
+ reviewExternalChangesByFile: Record<string, ReviewExternalChange>;
184
+ collapseUnchanged: boolean;
185
+ applyError: string | null;
186
+ applying: boolean;
187
+
188
+ // Editable diff state
189
+ editedContents: Record<string, string>;
190
+
191
+ /** Cache: "teamName:taskId:signature" → resolved task change presence */
192
+ taskChangePresenceByKey: Record<string, Exclude<TaskChangePresenceState, 'unknown'>>;
193
+
194
+ // Phase 1 actions
195
+ fetchAgentChanges: (teamName: string, memberName: string) => Promise<void>;
196
+ fetchTaskChanges: (
197
+ teamName: string,
198
+ taskId: string,
199
+ options: TaskChangeRequestOptions
200
+ ) => Promise<void>;
201
+ recordTaskChangePresence: (
202
+ teamName: string,
203
+ taskId: string,
204
+ options: TaskChangeRequestOptions,
205
+ presence: TaskChangePresenceState | null
206
+ ) => void;
207
+ selectReviewFile: (filePath: string | null) => void;
208
+ clearChangeReview: () => void;
209
+ clearChangeReviewCache: () => void;
210
+ resetAllReviewState: () => void;
211
+ fetchChangeStats: (teamName: string, memberName: string) => Promise<void>;
212
+
213
+ // Decision persistence actions
214
+ loadDecisionsFromDisk: (teamName: string, scopeKey: string, scopeToken: string) => Promise<void>;
215
+ persistDecisions: (teamName: string, scopeKey: string, scopeToken: string) => void;
216
+ clearDecisionsFromDisk: (
217
+ teamName: string,
218
+ scopeKey: string,
219
+ scopeToken?: string
220
+ ) => Promise<void>;
221
+
222
+ // Phase 2 actions
223
+ /**
224
+ * Set decision for a hunk at the current (visible) CM index.
225
+ * Returns the stable/original hunk index used as the decision key.
226
+ */
227
+ setHunkDecision: (filePath: string, hunkIndex: number, decision: HunkDecision) => number;
228
+ /** Clear a persisted decision using the stable/original hunk index */
229
+ clearHunkDecisionByOriginalIndex: (filePath: string, originalIndex: number) => void;
230
+ setFileDecision: (filePath: string, decision: HunkDecision) => void;
231
+ setFileChunkCount: (filePath: string, count: number) => void;
232
+ pushReviewUndoSnapshot: () => void;
233
+ undoBulkReview: () => boolean;
234
+ acceptAllFile: (filePath: string) => void;
235
+ rejectAllFile: (filePath: string) => void;
236
+ acceptAll: () => void;
237
+ rejectAll: () => void;
238
+ setCollapseUnchanged: (collapse: boolean) => void;
239
+ fetchFileContent: (
240
+ teamName: string,
241
+ memberName: string | undefined,
242
+ filePath: string
243
+ ) => Promise<void>;
244
+ applyReview: (teamName: string, taskId?: string, memberName?: string) => Promise<void>;
245
+ applySingleFileDecision: (
246
+ teamName: string,
247
+ filePath: string,
248
+ taskId?: string,
249
+ memberName?: string
250
+ ) => Promise<ApplyReviewResult | null>;
251
+ /** Remove a file from the current review set (used for rejecting new files) */
252
+ removeReviewFile: (filePath: string) => void;
253
+ /** Re-add a file to the current review set (used for undoing new-file reject) */
254
+ addReviewFile: (
255
+ file: FileChangeSummary,
256
+ options?: { index?: number; content?: FileChangeWithContent }
257
+ ) => void;
258
+ /**
259
+ * Clear in-memory review state for a single file after applying changes to disk.
260
+ * Prevents stale decisions from being re-applied later and forces fresh content resolve.
261
+ */
262
+ clearReviewStateForFile: (filePath: string) => void;
263
+ invalidateResolvedFileContent: (filePath: string) => void;
264
+ markReviewFileExternallyChanged: (filePath: string, type: ReviewExternalChange['type']) => void;
265
+ clearReviewFileExternalChange: (filePath: string) => void;
266
+ reloadReviewFileFromDisk: (filePath: string) => void;
267
+ invalidateChangeStats: (teamName: string) => void;
268
+
269
+ // Editable diff actions
270
+ updateEditedContent: (filePath: string, content: string) => void;
271
+ discardFileEdits: (filePath: string) => void;
272
+ discardAllEdits: () => void;
273
+ saveEditedFile: (filePath: string, projectPath?: string) => Promise<void>;
274
+
275
+ // Task change availability
276
+ checkTaskHasChanges: (
277
+ teamName: string,
278
+ taskId: string,
279
+ options: TaskChangeRequestOptions
280
+ ) => Promise<void>;
281
+ warmTaskChangeSummaries: (
282
+ requests: { teamName: string; taskId: string; options: TaskChangeRequestOptions }[]
283
+ ) => Promise<void>;
284
+ invalidateTaskChangePresence: (cacheKeys: string[]) => void;
285
+ }
286
+
287
+ /**
288
+ * Map a current CM chunk index to its original index, accounting for chunks
289
+ * that have been accepted/rejected (removed from CM view, causing index shifts).
290
+ *
291
+ * When chunk 0 is accepted, CM removes it — old chunk 1 becomes new chunk 0.
292
+ * This function reverses that shift so decisions are stored with stable indices.
293
+ */
294
+ function mapCurrentToOriginalIndex(
295
+ reviewKey: string,
296
+ currentIdx: number,
297
+ hunkDecisions: Record<string, HunkDecision>,
298
+ totalChunks: number
299
+ ): number {
300
+ const decided = new Set<number>();
301
+ for (let i = 0; i < totalChunks; i++) {
302
+ if (buildHunkDecisionKey(reviewKey, i) in hunkDecisions) {
303
+ decided.add(i);
304
+ }
305
+ }
306
+
307
+ // Walk original indices, skip already-decided, count undecided until currentIdx
308
+ let undecidedSeen = 0;
309
+ for (let orig = 0; orig < totalChunks; orig++) {
310
+ if (decided.has(orig)) continue;
311
+ if (undecidedSeen === currentIdx) return orig;
312
+ undecidedSeen++;
313
+ }
314
+
315
+ return currentIdx;
316
+ }
317
+
318
+ /** Get the hunk count for a file: prefer actual CM chunk count, fallback to snippet count */
319
+ export function getFileHunkCount(
320
+ filePath: string,
321
+ snippetsLength: number,
322
+ fileChunkCounts: Record<string, number>
323
+ ): number {
324
+ return fileChunkCounts[filePath] ?? snippetsLength;
325
+ }
326
+
327
+ function getMaxDecisionIndexForFile(
328
+ reviewKey: string,
329
+ hunkDecisions: Record<string, HunkDecision>
330
+ ): number {
331
+ let max = -1;
332
+ const prefix = `${reviewKey}:`;
333
+ for (const key of Object.keys(hunkDecisions)) {
334
+ if (!key.startsWith(prefix)) continue;
335
+ const raw = key.slice(prefix.length);
336
+ const idx = Number.parseInt(raw, 10);
337
+ if (!Number.isNaN(idx)) {
338
+ max = Math.max(max, idx);
339
+ }
340
+ }
341
+ return max;
342
+ }
343
+
344
+ function buildHunkContextHashesForFile(
345
+ original: string | null | undefined,
346
+ modified: string | null | undefined,
347
+ expectedHunkCount: number
348
+ ): Record<number, string> | undefined {
349
+ if (original === null || original === undefined) return undefined;
350
+ if (modified === null || modified === undefined) return undefined;
351
+
352
+ const patch = structuredPatch('file', 'file', original, modified);
353
+ const hunks = patch.hunks ?? [];
354
+ if (hunks.length === 0) return undefined;
355
+ if (hunks.length !== expectedHunkCount) return undefined;
356
+
357
+ const out: Record<number, string> = {};
358
+ for (let i = 0; i < hunks.length; i++) {
359
+ const hunk = hunks[i];
360
+ const oldSideContent = hunk.lines
361
+ .filter((l) => !l.startsWith('+'))
362
+ .map((l) => l.slice(1))
363
+ .join('\n');
364
+ const newSideContent = hunk.lines
365
+ .filter((l) => !l.startsWith('-'))
366
+ .map((l) => l.slice(1))
367
+ .join('\n');
368
+ out[i] = computeDiffContextHash(oldSideContent, newSideContent);
369
+ }
370
+ return out;
371
+ }
372
+
373
+ export const createChangeReviewSlice: StateCreator<AppState, [], [], ChangeReviewSlice> = (
374
+ set,
375
+ get
376
+ ) => {
377
+ const addMatchingReviewPathAliases = (
378
+ aliases: Set<string>,
379
+ filePath: string,
380
+ canonicalFilePath: string,
381
+ record: Record<string, unknown>
382
+ ): void => {
383
+ for (const key of Object.keys(record)) {
384
+ if (reviewPathsEqual(key, filePath) || reviewPathsEqual(key, canonicalFilePath)) {
385
+ aliases.add(key);
386
+ }
387
+ }
388
+ };
389
+
390
+ const buildResolvedFileInvalidation = (
391
+ s: ChangeReviewSlice,
392
+ filePath: string
393
+ ): Pick<
394
+ ChangeReviewSlice,
395
+ | 'fileChunkCounts'
396
+ | 'fileContents'
397
+ | 'fileContentsLoading'
398
+ | 'hunkContextHashesByFile'
399
+ | 'fileContentVersionByPath'
400
+ > => {
401
+ const existing = findReviewFileByPath(s.activeChangeSet?.files, filePath);
402
+ const canonicalFilePath = existing?.filePath ?? filePath;
403
+ const aliases = new Set([filePath, canonicalFilePath]);
404
+ addMatchingReviewPathAliases(aliases, filePath, canonicalFilePath, s.fileChunkCounts);
405
+ addMatchingReviewPathAliases(aliases, filePath, canonicalFilePath, s.fileContents);
406
+ addMatchingReviewPathAliases(aliases, filePath, canonicalFilePath, s.fileContentsLoading);
407
+ addMatchingReviewPathAliases(aliases, filePath, canonicalFilePath, s.fileContentVersionByPath);
408
+ const nextFileChunkCounts = { ...s.fileChunkCounts };
409
+ for (const alias of aliases) delete nextFileChunkCounts[alias];
410
+
411
+ const nextFileContents = { ...s.fileContents };
412
+ for (const alias of aliases) delete nextFileContents[alias];
413
+
414
+ const nextFileContentsLoading = { ...s.fileContentsLoading };
415
+ for (const alias of aliases) delete nextFileContentsLoading[alias];
416
+
417
+ const nextHunkContextHashesByFile = { ...s.hunkContextHashesByFile };
418
+ const reviewKey = getReviewKeyForFilePath(s.activeChangeSet?.files, filePath);
419
+ delete nextHunkContextHashesByFile[reviewKey];
420
+ for (const alias of aliases) delete nextHunkContextHashesByFile[alias];
421
+
422
+ const nextFileContentVersionByPath = { ...s.fileContentVersionByPath };
423
+ for (const alias of aliases) {
424
+ nextFileContentVersionByPath[alias] = (s.fileContentVersionByPath[alias] ?? 0) + 1;
425
+ }
426
+
427
+ return {
428
+ fileChunkCounts: nextFileChunkCounts,
429
+ fileContents: nextFileContents,
430
+ fileContentsLoading: nextFileContentsLoading,
431
+ hunkContextHashesByFile: nextHunkContextHashesByFile,
432
+ fileContentVersionByPath: nextFileContentVersionByPath,
433
+ };
434
+ };
435
+
436
+ const installActiveChangeSetForLoad = (
437
+ data: ReviewChangeSetLike,
438
+ extraState?: Partial<ChangeReviewSlice>
439
+ ): void => {
440
+ set((s) => ({
441
+ activeChangeSet: data,
442
+ changeSetLoading: false,
443
+ selectedReviewFilePath: data.files[0]?.filePath ?? null,
444
+ hunkDecisions: {},
445
+ fileDecisions: {},
446
+ fileContents: {},
447
+ fileContentsLoading: {},
448
+ fileChunkCounts: {},
449
+ reviewUndoStack: [],
450
+ hunkContextHashesByFile: {},
451
+ applyError: null,
452
+ editedContents: {},
453
+ changeSetEpoch: s.changeSetEpoch + 1,
454
+ fileContentVersionByPath: {},
455
+ reviewExternalChangesByFile: {},
456
+ ...extraState,
457
+ }));
458
+ };
459
+
460
+ const replaceActiveChangeSetAfterStaleRefresh = (
461
+ fresh: ReviewChangeSetLike,
462
+ applyError: string
463
+ ): void => {
464
+ set((s) => ({
465
+ activeChangeSet: fresh,
466
+ applying: false,
467
+ applyError,
468
+ selectedReviewFilePath: fresh.files[0]?.filePath ?? null,
469
+ hunkDecisions: {},
470
+ fileDecisions: {},
471
+ fileChunkCounts: {},
472
+ reviewUndoStack: [],
473
+ hunkContextHashesByFile: {},
474
+ fileContents: {},
475
+ fileContentsLoading: {},
476
+ editedContents: {},
477
+ changeSetEpoch: s.changeSetEpoch + 1,
478
+ fileContentVersionByPath: {},
479
+ reviewExternalChangesByFile: {},
480
+ }));
481
+ };
482
+
483
+ const revalidateTaskChangePresence = async (
484
+ teamName: string,
485
+ taskId: string,
486
+ options: TaskChangeRequestOptions
487
+ ): Promise<void> => {
488
+ const cacheKey = buildTaskChangePresenceKey(teamName, taskId, options);
489
+ if (
490
+ !isTaskSummaryCacheableForOptions(options) ||
491
+ taskChangesPresenceRevalidationInFlight.has(cacheKey)
492
+ ) {
493
+ return;
494
+ }
495
+
496
+ taskChangesPresenceRevalidationInFlight.add(cacheKey);
497
+ try {
498
+ const data = await api.review.getTaskChanges(teamName, taskId, {
499
+ ...options,
500
+ summaryOnly: true,
501
+ forceFresh: true,
502
+ });
503
+ const nextPresence = resolveTaskChangePresenceFromResult(data);
504
+ set((state) => ({
505
+ taskChangePresenceByKey: applyTaskChangePresenceCacheUpdate(
506
+ state.taskChangePresenceByKey,
507
+ cacheKey,
508
+ nextPresence
509
+ ),
510
+ }));
511
+ syncTaskChangeNegativeCache(cacheKey, nextPresence);
512
+ get().setSelectedTeamTaskChangePresence(teamName, taskId, nextPresence ?? 'unknown');
513
+ } catch {
514
+ // Best-effort background revalidation; keep optimistic state on transient failure.
515
+ } finally {
516
+ taskChangesPresenceRevalidationInFlight.delete(cacheKey);
517
+ }
518
+ };
519
+
520
+ return {
521
+ // Phase 1 initial state
522
+ activeChangeSet: null,
523
+ activeTaskChangeRequestOptions: null,
524
+ changeSetLoading: false,
525
+ changeSetError: null,
526
+ selectedReviewFilePath: null,
527
+ changeStatsCache: {},
528
+
529
+ // Phase 2 initial state
530
+ hunkDecisions: {},
531
+ fileDecisions: {},
532
+ fileChunkCounts: {},
533
+ reviewUndoStack: [],
534
+ hunkContextHashesByFile: {},
535
+ fileContents: {},
536
+ fileContentsLoading: {},
537
+ changeSetEpoch: 0,
538
+ fileContentVersionByPath: {},
539
+ reviewExternalChangesByFile: {},
540
+ collapseUnchanged: true,
541
+ applyError: null,
542
+ applying: false,
543
+
544
+ // Editable diff initial state
545
+ editedContents: {},
546
+
547
+ taskChangePresenceByKey: {},
548
+
549
+ fetchAgentChanges: async (teamName: string, memberName: string) => {
550
+ const requestToken = ++latestAgentChangesRequestToken;
551
+ set({ changeSetLoading: true, changeSetError: null });
552
+ try {
553
+ const data = await api.review.getAgentChanges(teamName, memberName);
554
+ if (requestToken !== latestAgentChangesRequestToken) return;
555
+ installActiveChangeSetForLoad(data, { activeTaskChangeRequestOptions: null });
556
+ } catch (error) {
557
+ if (requestToken !== latestAgentChangesRequestToken) return;
558
+ const message = error instanceof Error ? error.message : 'Failed to fetch agent changes';
559
+ logger.error('fetchAgentChanges error:', message);
560
+ set({ changeSetError: message, changeSetLoading: false });
561
+ }
562
+ },
563
+
564
+ recordTaskChangePresence: (
565
+ teamName: string,
566
+ taskId: string,
567
+ options: TaskChangeRequestOptions,
568
+ presence: TaskChangePresenceState | null
569
+ ) => {
570
+ const cacheKey = buildTaskChangePresenceKey(teamName, taskId, options);
571
+ set((s) => {
572
+ return {
573
+ taskChangePresenceByKey: applyTaskChangePresenceCacheUpdate(
574
+ s.taskChangePresenceByKey,
575
+ cacheKey,
576
+ presence
577
+ ),
578
+ };
579
+ });
580
+ syncTaskChangeNegativeCache(cacheKey, presence);
581
+ },
582
+
583
+ fetchTaskChanges: async (
584
+ teamName: string,
585
+ taskId: string,
586
+ options: TaskChangeRequestOptions
587
+ ) => {
588
+ const requestToken = ++latestTaskChangesRequestToken;
589
+ set({ changeSetLoading: true, changeSetError: null });
590
+ try {
591
+ const data = await api.review.getTaskChanges(teamName, taskId, options);
592
+ if (requestToken !== latestTaskChangesRequestToken) return;
593
+ const cacheKey = buildTaskChangePresenceKey(teamName, taskId, options);
594
+ const nextPresence = resolveTaskChangePresenceFromResult(data);
595
+ installActiveChangeSetForLoad(data, {
596
+ activeTaskChangeRequestOptions: options,
597
+ taskChangePresenceByKey: applyTaskChangePresenceCacheUpdate(
598
+ get().taskChangePresenceByKey,
599
+ cacheKey,
600
+ nextPresence
601
+ ),
602
+ });
603
+ get().setSelectedTeamTaskChangePresence(teamName, taskId, nextPresence ?? 'unknown');
604
+ syncTaskChangeNegativeCache(cacheKey, nextPresence);
605
+ } catch (error) {
606
+ if (requestToken !== latestTaskChangesRequestToken) return;
607
+ const message = error instanceof Error ? error.message : 'Failed to fetch task changes';
608
+ logger.error('fetchTaskChanges error:', message);
609
+ set({ changeSetError: message, changeSetLoading: false });
610
+ }
611
+ },
612
+
613
+ selectReviewFile: (filePath: string | null) => {
614
+ set({ selectedReviewFilePath: filePath });
615
+ },
616
+
617
+ clearChangeReview: () => {
618
+ latestAgentChangesRequestToken++;
619
+ latestTaskChangesRequestToken++;
620
+ latestDecisionLoadRequestToken++;
621
+ clearAllPersistDecisionTimers();
622
+ set((s) => ({
623
+ activeChangeSet: null,
624
+ changeSetLoading: false,
625
+ changeSetError: null,
626
+ selectedReviewFilePath: null,
627
+ activeTaskChangeRequestOptions: null,
628
+ hunkDecisions: {},
629
+ fileDecisions: {},
630
+ fileChunkCounts: {},
631
+ reviewUndoStack: [],
632
+ hunkContextHashesByFile: {},
633
+ fileContents: {},
634
+ fileContentsLoading: {},
635
+ changeSetEpoch: s.changeSetEpoch + 1,
636
+ fileContentVersionByPath: {},
637
+ reviewExternalChangesByFile: {},
638
+ applyError: null,
639
+ applying: false,
640
+ editedContents: {},
641
+ }));
642
+ },
643
+
644
+ clearChangeReviewCache: () => {
645
+ latestAgentChangesRequestToken++;
646
+ latestTaskChangesRequestToken++;
647
+ latestDecisionLoadRequestToken++;
648
+ clearAllPersistDecisionTimers();
649
+ set((s) => ({
650
+ activeChangeSet: null,
651
+ changeSetLoading: false,
652
+ changeSetError: null,
653
+ selectedReviewFilePath: null,
654
+ activeTaskChangeRequestOptions: null,
655
+ hunkDecisions: {},
656
+ fileDecisions: {},
657
+ fileChunkCounts: {},
658
+ reviewUndoStack: [],
659
+ hunkContextHashesByFile: {},
660
+ fileContents: {},
661
+ fileContentsLoading: {},
662
+ changeSetEpoch: s.changeSetEpoch + 1,
663
+ fileContentVersionByPath: {},
664
+ reviewExternalChangesByFile: {},
665
+ applyError: null,
666
+ applying: false,
667
+ editedContents: {},
668
+ }));
669
+ },
670
+
671
+ resetAllReviewState: () => {
672
+ latestAgentChangesRequestToken++;
673
+ latestTaskChangesRequestToken++;
674
+ latestDecisionLoadRequestToken++;
675
+ clearAllPersistDecisionTimers();
676
+ set((s) => ({
677
+ activeChangeSet: null,
678
+ changeSetLoading: false,
679
+ changeSetError: null,
680
+ selectedReviewFilePath: null,
681
+ activeTaskChangeRequestOptions: null,
682
+ hunkDecisions: {},
683
+ fileDecisions: {},
684
+ fileChunkCounts: {},
685
+ reviewUndoStack: [],
686
+ hunkContextHashesByFile: {},
687
+ fileContents: {},
688
+ fileContentsLoading: {},
689
+ changeSetEpoch: s.changeSetEpoch + 1,
690
+ fileContentVersionByPath: {},
691
+ reviewExternalChangesByFile: {},
692
+ applyError: null,
693
+ applying: false,
694
+ editedContents: {},
695
+ }));
696
+ },
697
+
698
+ // ── Decision persistence ──
699
+
700
+ loadDecisionsFromDisk: async (teamName: string, scopeKey: string, scopeToken: string) => {
701
+ const requestToken = ++latestDecisionLoadRequestToken;
702
+ try {
703
+ const data = await api.review.loadDecisions(teamName, scopeKey, scopeToken);
704
+ if (requestToken !== latestDecisionLoadRequestToken) return;
705
+ const normalized = normalizePersistedReviewState(get().activeChangeSet?.files ?? [], {
706
+ hunkDecisions: data?.hunkDecisions,
707
+ fileDecisions: data?.fileDecisions,
708
+ hunkContextHashesByFile: data?.hunkContextHashesByFile,
709
+ });
710
+ // Always set decisions — even to empty if no saved file exists.
711
+ // This prevents stale decisions from a previous scope leaking through.
712
+ set({
713
+ hunkDecisions: normalized.hunkDecisions,
714
+ fileDecisions: normalized.fileDecisions,
715
+ hunkContextHashesByFile: normalized.hunkContextHashesByFile,
716
+ });
717
+ } catch (error) {
718
+ if (requestToken !== latestDecisionLoadRequestToken) return;
719
+ logger.error('loadDecisionsFromDisk error:', error);
720
+ set({
721
+ hunkDecisions: {},
722
+ fileDecisions: {},
723
+ hunkContextHashesByFile: {},
724
+ });
725
+ }
726
+ },
727
+
728
+ persistDecisions: (teamName: string, scopeKey: string, scopeToken: string) => {
729
+ const scopeStorageKey = buildPersistDecisionScopeKey(teamName, scopeKey, scopeToken);
730
+ clearPersistDecisionTimer(scopeStorageKey);
731
+
732
+ const {
733
+ hunkDecisions,
734
+ fileDecisions,
735
+ hunkContextHashesByFile,
736
+ activeChangeSet,
737
+ fileContents,
738
+ fileChunkCounts,
739
+ } = get();
740
+
741
+ const computed: Record<string, Record<number, string>> = {};
742
+ for (const file of activeChangeSet?.files ?? []) {
743
+ const fp = file.filePath;
744
+ const content = fileContents[fp];
745
+ if (!content) continue;
746
+ const expected = getFileHunkCount(fp, file.snippets.length, fileChunkCounts);
747
+ const hashes = buildHunkContextHashesForFile(
748
+ content.originalFullContent,
749
+ content.modifiedFullContent,
750
+ expected
751
+ );
752
+ if (hashes) computed[fp] = hashes;
753
+ }
754
+
755
+ const mergedHashes: Record<string, Record<number, string>> = {};
756
+ for (const file of activeChangeSet?.files ?? []) {
757
+ const fp = file.filePath;
758
+ const reviewKey = getFileReviewKey(file);
759
+ mergedHashes[reviewKey] =
760
+ computed[fp] ?? hunkContextHashesByFile[reviewKey] ?? hunkContextHashesByFile[fp] ?? {};
761
+ }
762
+ set({ hunkContextHashesByFile: mergedHashes });
763
+
764
+ const persistedHunkDecisions = { ...hunkDecisions };
765
+ const persistedFileDecisions = { ...fileDecisions };
766
+ const persistedHashes = { ...mergedHashes };
767
+
768
+ const timer = setTimeout(() => {
769
+ persistDebounceTimers.delete(scopeStorageKey);
770
+ void api.review.saveDecisions(
771
+ teamName,
772
+ scopeKey,
773
+ scopeToken,
774
+ persistedHunkDecisions,
775
+ persistedFileDecisions,
776
+ persistedHashes
777
+ );
778
+ }, PERSIST_DEBOUNCE_MS);
779
+
780
+ persistDebounceTimers.set(scopeStorageKey, timer);
781
+ },
782
+
783
+ clearDecisionsFromDisk: async (teamName: string, scopeKey: string, scopeToken?: string) => {
784
+ clearPersistDecisionTimer(buildPersistDecisionScopeKey(teamName, scopeKey, scopeToken));
785
+ try {
786
+ await api.review.clearDecisions(teamName, scopeKey, scopeToken);
787
+ } catch (error) {
788
+ logger.error('clearDecisionsFromDisk error:', error);
789
+ }
790
+ },
791
+
792
+ fetchChangeStats: async (teamName: string, memberName: string) => {
793
+ try {
794
+ const stats = await api.review.getChangeStats(teamName, memberName);
795
+ const key = `${teamName}:${memberName}`;
796
+ set((state) => ({
797
+ changeStatsCache: { ...state.changeStatsCache, [key]: stats },
798
+ }));
799
+ } catch (error) {
800
+ logger.error('fetchChangeStats error:', error);
801
+ }
802
+ },
803
+
804
+ // ── Phase 2 actions ──
805
+
806
+ setHunkDecision: (filePath: string, hunkIndex: number, decision: HunkDecision) => {
807
+ const state = get();
808
+ const totalChunks = state.fileChunkCounts[filePath] ?? 0;
809
+ const reviewKey = getReviewKeyForFilePath(state.activeChangeSet?.files, filePath);
810
+ // Map current chunk index to original: after accept/reject, chunks shift in CM.
811
+ // We need the original index to keep decisions stable across shifts.
812
+ const originalIndex =
813
+ totalChunks > 0
814
+ ? mapCurrentToOriginalIndex(reviewKey, hunkIndex, state.hunkDecisions, totalChunks)
815
+ : hunkIndex;
816
+ const key = buildHunkDecisionKey(reviewKey, originalIndex);
817
+ set((s) => ({
818
+ hunkDecisions: { ...s.hunkDecisions, [key]: decision },
819
+ }));
820
+ return originalIndex;
821
+ },
822
+
823
+ clearHunkDecisionByOriginalIndex: (filePath: string, originalIndex: number) => {
824
+ const key = buildHunkDecisionKey(
825
+ getReviewKeyForFilePath(get().activeChangeSet?.files, filePath),
826
+ originalIndex
827
+ );
828
+ set((s) => {
829
+ if (!(key in s.hunkDecisions)) return s;
830
+ const next = { ...s.hunkDecisions };
831
+ delete next[key];
832
+ return { hunkDecisions: next };
833
+ });
834
+ },
835
+
836
+ setFileDecision: (filePath: string, decision: HunkDecision) => {
837
+ const reviewKey = getReviewKeyForFilePath(get().activeChangeSet?.files, filePath);
838
+ set((state) => ({
839
+ fileDecisions: { ...state.fileDecisions, [reviewKey]: decision },
840
+ }));
841
+ },
842
+
843
+ setFileChunkCount: (filePath: string, count: number) => {
844
+ set((s) => ({
845
+ fileChunkCounts: { ...s.fileChunkCounts, [filePath]: count },
846
+ }));
847
+ },
848
+
849
+ pushReviewUndoSnapshot: () => {
850
+ const state = get();
851
+ const snapshot: DecisionSnapshot = {
852
+ hunkDecisions: { ...state.hunkDecisions },
853
+ fileDecisions: { ...state.fileDecisions },
854
+ };
855
+ const stack = [...state.reviewUndoStack, snapshot];
856
+ if (stack.length > MAX_REVIEW_UNDO_DEPTH) {
857
+ stack.shift();
858
+ }
859
+ set({ reviewUndoStack: stack });
860
+ },
861
+
862
+ undoBulkReview: () => {
863
+ const state = get();
864
+ if (state.reviewUndoStack.length === 0) return false;
865
+ const stack = [...state.reviewUndoStack];
866
+ const snapshot = stack.pop()!;
867
+ set({
868
+ hunkDecisions: snapshot.hunkDecisions,
869
+ fileDecisions: snapshot.fileDecisions,
870
+ reviewUndoStack: stack,
871
+ });
872
+ return true;
873
+ },
874
+
875
+ acceptAllFile: (filePath: string) => {
876
+ const state = get();
877
+ const file = findReviewFileByPath(state.activeChangeSet?.files, filePath);
878
+ if (!file) return;
879
+
880
+ const count = getFileHunkCount(file.filePath, file.snippets.length, state.fileChunkCounts);
881
+ const newHunkDecisions = { ...state.hunkDecisions };
882
+ const reviewKey = getFileReviewKey(file);
883
+ for (let i = 0; i < count; i++) {
884
+ newHunkDecisions[buildHunkDecisionKey(reviewKey, i)] = 'accepted';
885
+ }
886
+ set({
887
+ hunkDecisions: newHunkDecisions,
888
+ fileDecisions: { ...state.fileDecisions, [reviewKey]: 'accepted' },
889
+ });
890
+ },
891
+
892
+ rejectAllFile: (filePath: string) => {
893
+ const state = get();
894
+ const file = findReviewFileByPath(state.activeChangeSet?.files, filePath);
895
+ if (!file) return;
896
+
897
+ const count = getFileHunkCount(file.filePath, file.snippets.length, state.fileChunkCounts);
898
+ const newHunkDecisions = { ...state.hunkDecisions };
899
+ const reviewKey = getFileReviewKey(file);
900
+ for (let i = 0; i < count; i++) {
901
+ newHunkDecisions[buildHunkDecisionKey(reviewKey, i)] = 'rejected';
902
+ }
903
+ set({
904
+ hunkDecisions: newHunkDecisions,
905
+ fileDecisions: { ...state.fileDecisions, [reviewKey]: 'rejected' },
906
+ });
907
+ },
908
+
909
+ acceptAll: () => {
910
+ const state = get();
911
+ if (!state.activeChangeSet) return;
912
+
913
+ const newHunkDecisions: Record<string, HunkDecision> = {};
914
+ const newFileDecisions: Record<string, HunkDecision> = {};
915
+
916
+ for (const file of state.activeChangeSet.files) {
917
+ const reviewKey = getFileReviewKey(file);
918
+ newFileDecisions[reviewKey] = 'accepted';
919
+ const count = getFileHunkCount(file.filePath, file.snippets.length, state.fileChunkCounts);
920
+ for (let i = 0; i < count; i++) {
921
+ newHunkDecisions[buildHunkDecisionKey(reviewKey, i)] = 'accepted';
922
+ }
923
+ }
924
+ set({ hunkDecisions: newHunkDecisions, fileDecisions: newFileDecisions });
925
+ },
926
+
927
+ rejectAll: () => {
928
+ const state = get();
929
+ if (!state.activeChangeSet) return;
930
+
931
+ const newHunkDecisions: Record<string, HunkDecision> = {};
932
+ const newFileDecisions: Record<string, HunkDecision> = {};
933
+
934
+ for (const file of state.activeChangeSet.files) {
935
+ const reviewKey = getFileReviewKey(file);
936
+ newFileDecisions[reviewKey] = 'rejected';
937
+ const count = getFileHunkCount(file.filePath, file.snippets.length, state.fileChunkCounts);
938
+ for (let i = 0; i < count; i++) {
939
+ newHunkDecisions[buildHunkDecisionKey(reviewKey, i)] = 'rejected';
940
+ }
941
+ }
942
+ set({ hunkDecisions: newHunkDecisions, fileDecisions: newFileDecisions });
943
+ },
944
+
945
+ setCollapseUnchanged: (collapse: boolean) => {
946
+ set({ collapseUnchanged: collapse });
947
+ },
948
+
949
+ fetchFileContent: async (
950
+ teamName: string,
951
+ memberName: string | undefined,
952
+ filePath: string
953
+ ) => {
954
+ const state = get();
955
+ const fileEntry = findReviewFileByPath(state.activeChangeSet?.files, filePath);
956
+ const canonicalFilePath = fileEntry?.filePath ?? filePath;
957
+ // Skip if already loaded or loading
958
+ if (
959
+ state.fileContents[filePath] ||
960
+ state.fileContents[canonicalFilePath] ||
961
+ state.fileContentsLoading[filePath] ||
962
+ state.fileContentsLoading[canonicalFilePath]
963
+ )
964
+ return;
965
+ const changeSetEpoch = state.changeSetEpoch;
966
+ const fileVersion = state.fileContentVersionByPath[filePath] ?? 0;
967
+ const canonicalFileVersion = state.fileContentVersionByPath[canonicalFilePath] ?? 0;
968
+
969
+ set((s) => ({
970
+ fileContentsLoading: {
971
+ ...s.fileContentsLoading,
972
+ [filePath]: true,
973
+ [canonicalFilePath]: true,
974
+ },
975
+ }));
976
+
977
+ try {
978
+ const snippets = fileEntry?.snippets ?? [];
979
+
980
+ const content = await api.review.getFileContent(
981
+ teamName,
982
+ memberName,
983
+ canonicalFilePath,
984
+ snippets
985
+ );
986
+ const latest = get();
987
+ if (changeSetEpoch !== latest.changeSetEpoch) return;
988
+ if ((latest.fileContentVersionByPath[filePath] ?? 0) !== fileVersion) return;
989
+ if ((latest.fileContentVersionByPath[canonicalFilePath] ?? 0) !== canonicalFileVersion)
990
+ return;
991
+ set((s) => {
992
+ const nextFileContents = { ...s.fileContents, [canonicalFilePath]: content };
993
+ if (canonicalFilePath !== filePath) {
994
+ delete nextFileContents[filePath];
995
+ }
996
+ const result: Partial<ChangeReviewSlice> = {
997
+ fileContents: nextFileContents,
998
+ fileContentsLoading: {
999
+ ...s.fileContentsLoading,
1000
+ [filePath]: false,
1001
+ [canonicalFilePath]: false,
1002
+ },
1003
+ };
1004
+
1005
+ // Update activeChangeSet stats if original was successfully resolved
1006
+ if (
1007
+ content.contentSource !== 'unavailable' &&
1008
+ content.contentSource !== 'disk-current' &&
1009
+ s.activeChangeSet
1010
+ ) {
1011
+ const updatedFiles = s.activeChangeSet.files.map((f) =>
1012
+ reviewPathsEqual(f.filePath, canonicalFilePath)
1013
+ ? { ...f, linesAdded: content.linesAdded, linesRemoved: content.linesRemoved }
1014
+ : f
1015
+ );
1016
+ const totalLinesAdded = updatedFiles.reduce((sum, f) => sum + f.linesAdded, 0);
1017
+ const totalLinesRemoved = updatedFiles.reduce((sum, f) => sum + f.linesRemoved, 0);
1018
+ result.activeChangeSet = {
1019
+ ...s.activeChangeSet,
1020
+ files: updatedFiles,
1021
+ totalLinesAdded,
1022
+ totalLinesRemoved,
1023
+ };
1024
+ }
1025
+
1026
+ return result;
1027
+ });
1028
+ } catch (error) {
1029
+ const latest = get();
1030
+ if (changeSetEpoch !== latest.changeSetEpoch) return;
1031
+ if ((latest.fileContentVersionByPath[filePath] ?? 0) !== fileVersion) return;
1032
+ if ((latest.fileContentVersionByPath[canonicalFilePath] ?? 0) !== canonicalFileVersion)
1033
+ return;
1034
+ logger.error('fetchFileContent error:', error);
1035
+ set((s) => ({
1036
+ fileContentsLoading: {
1037
+ ...s.fileContentsLoading,
1038
+ [filePath]: false,
1039
+ [canonicalFilePath]: false,
1040
+ },
1041
+ }));
1042
+ }
1043
+ },
1044
+
1045
+ applyReview: async (teamName: string, taskId?: string, memberName?: string) => {
1046
+ set({ applying: true, applyError: null });
1047
+
1048
+ try {
1049
+ // Stale check: re-fetch changes and compare content fingerprint
1050
+ const state = get();
1051
+ const current = state.activeChangeSet;
1052
+ const currentFingerprint = getReviewChangeSetIdentityToken(current);
1053
+ const staleMessage =
1054
+ 'Changes have been updated since you started reviewing. Please re-review.';
1055
+
1056
+ if (memberName && current) {
1057
+ const fresh = await api.review.getAgentChanges(teamName, memberName);
1058
+ if (currentFingerprint !== getReviewChangeSetIdentityToken(fresh)) {
1059
+ replaceActiveChangeSetAfterStaleRefresh(fresh, staleMessage);
1060
+ return;
1061
+ }
1062
+ } else if (taskId && current) {
1063
+ const fresh = await api.review.getTaskChanges(teamName, taskId, {
1064
+ ...(state.activeTaskChangeRequestOptions ?? {}),
1065
+ forceFresh: true,
1066
+ });
1067
+ if (currentFingerprint !== getReviewChangeSetIdentityToken(fresh)) {
1068
+ replaceActiveChangeSetAfterStaleRefresh(fresh, staleMessage);
1069
+ return;
1070
+ }
1071
+ }
1072
+
1073
+ // Build FileReviewDecision[] from hunkDecisions/fileDecisions
1074
+ const { hunkDecisions, fileDecisions, fileChunkCounts, activeChangeSet, fileContents } =
1075
+ get();
1076
+ if (!activeChangeSet) {
1077
+ set({ applying: false });
1078
+ return;
1079
+ }
1080
+
1081
+ const decisions: FileReviewDecision[] = [];
1082
+
1083
+ for (const file of activeChangeSet.files) {
1084
+ const reviewKey = getFileReviewKey(file);
1085
+ const fileDecision = fileDecisions[reviewKey] ?? 'pending';
1086
+ const hunkDecs: Record<number, HunkDecision> = {};
1087
+
1088
+ const baseCount = getFileHunkCount(file.filePath, file.snippets.length, fileChunkCounts);
1089
+ const maxIdx = getMaxDecisionIndexForFile(reviewKey, hunkDecisions);
1090
+ const count = Math.max(baseCount, maxIdx + 1);
1091
+ for (let i = 0; i < count; i++) {
1092
+ const key = buildHunkDecisionKey(reviewKey, i);
1093
+ hunkDecs[i] = hunkDecisions[key] ?? 'pending';
1094
+ }
1095
+
1096
+ // Only include files that have at least one rejected hunk
1097
+ const hasRejected =
1098
+ fileDecision === 'rejected' || Object.values(hunkDecs).some((d) => d === 'rejected');
1099
+ if (hasRejected) {
1100
+ const content = fileContents[file.filePath];
1101
+ const hunkContextHashes =
1102
+ maxIdx < baseCount
1103
+ ? buildHunkContextHashesForFile(
1104
+ content?.originalFullContent,
1105
+ content?.modifiedFullContent,
1106
+ baseCount
1107
+ )
1108
+ : undefined;
1109
+ decisions.push({
1110
+ filePath: file.filePath,
1111
+ fileDecision,
1112
+ hunkDecisions: hunkDecs,
1113
+ hunkContextHashes,
1114
+ // Provide optional context so main can apply without re-resolving.
1115
+ // If full contents are missing (lazy not loaded yet), still pass snippets.
1116
+ snippets: content?.snippets ?? file.snippets,
1117
+ originalFullContent: content?.originalFullContent,
1118
+ modifiedFullContent: content?.modifiedFullContent,
1119
+ isNewFile: content?.isNewFile ?? file.isNewFile,
1120
+ });
1121
+ }
1122
+ }
1123
+
1124
+ if (decisions.length === 0) {
1125
+ set({ applying: false });
1126
+ return;
1127
+ }
1128
+
1129
+ const request: ApplyReviewRequest = {
1130
+ teamName,
1131
+ taskId,
1132
+ memberName,
1133
+ decisions,
1134
+ };
1135
+
1136
+ await api.review.applyDecisions(request);
1137
+
1138
+ set({ applying: false });
1139
+ } catch (error) {
1140
+ logger.error('applyReview error:', error);
1141
+ set({
1142
+ applying: false,
1143
+ applyError: mapReviewError(error),
1144
+ });
1145
+ }
1146
+ },
1147
+
1148
+ applySingleFileDecision: async (
1149
+ teamName: string,
1150
+ filePath: string,
1151
+ taskId?: string,
1152
+ memberName?: string
1153
+ ) => {
1154
+ const { hunkDecisions, fileDecisions, fileChunkCounts, activeChangeSet, fileContents } =
1155
+ get();
1156
+ if (!activeChangeSet) return null;
1157
+
1158
+ const file = findReviewFileByPath(activeChangeSet.files, filePath);
1159
+ if (!file) return null;
1160
+
1161
+ const reviewKey = getFileReviewKey(file);
1162
+ const fileDecision = fileDecisions[reviewKey] ?? 'pending';
1163
+ const hunkDecs: Record<number, HunkDecision> = {};
1164
+ const baseCount = getFileHunkCount(file.filePath, file.snippets.length, fileChunkCounts);
1165
+ const maxIdx = getMaxDecisionIndexForFile(reviewKey, hunkDecisions);
1166
+ const count = Math.max(baseCount, maxIdx + 1);
1167
+ for (let i = 0; i < count; i++) {
1168
+ hunkDecs[i] = hunkDecisions[buildHunkDecisionKey(reviewKey, i)] ?? 'pending';
1169
+ }
1170
+
1171
+ const hasRejected =
1172
+ fileDecision === 'rejected' || Object.values(hunkDecs).some((d) => d === 'rejected');
1173
+ if (!hasRejected) return null;
1174
+
1175
+ try {
1176
+ const content = fileContents[file.filePath] ?? fileContents[filePath];
1177
+ const innerBaseCount = getFileHunkCount(
1178
+ file.filePath,
1179
+ file.snippets.length,
1180
+ fileChunkCounts
1181
+ );
1182
+ const innerMaxIdx = getMaxDecisionIndexForFile(reviewKey, hunkDecisions);
1183
+ const hunkContextHashes =
1184
+ innerMaxIdx < innerBaseCount
1185
+ ? buildHunkContextHashesForFile(
1186
+ content?.originalFullContent,
1187
+ content?.modifiedFullContent,
1188
+ innerBaseCount
1189
+ )
1190
+ : undefined;
1191
+ const result = await api.review.applyDecisions({
1192
+ teamName,
1193
+ taskId,
1194
+ memberName,
1195
+ decisions: [
1196
+ {
1197
+ filePath: file.filePath,
1198
+ fileDecision,
1199
+ hunkDecisions: hunkDecs,
1200
+ hunkContextHashes,
1201
+ snippets: content?.snippets ?? file.snippets,
1202
+ originalFullContent: content?.originalFullContent,
1203
+ modifiedFullContent: content?.modifiedFullContent,
1204
+ isNewFile: content?.isNewFile ?? file.isNewFile,
1205
+ },
1206
+ ],
1207
+ });
1208
+ return result;
1209
+ } catch (error) {
1210
+ logger.error('applySingleFileDecision error:', error);
1211
+ set({ applyError: mapReviewError(error) });
1212
+ return null;
1213
+ }
1214
+ },
1215
+
1216
+ removeReviewFile: (filePath: string) => {
1217
+ set((s) => {
1218
+ if (!s.activeChangeSet) return s;
1219
+ const existing = findReviewFileByPath(s.activeChangeSet.files, filePath);
1220
+ if (!existing) return s;
1221
+
1222
+ const nextFiles = s.activeChangeSet.files.filter(
1223
+ (f) => !reviewPathsEqual(f.filePath, existing.filePath)
1224
+ );
1225
+ const totalLinesAdded = nextFiles.reduce((sum, f) => sum + f.linesAdded, 0);
1226
+ const totalLinesRemoved = nextFiles.reduce((sum, f) => sum + f.linesRemoved, 0);
1227
+
1228
+ const aliases = new Set([filePath, existing.filePath]);
1229
+ const addMatchingAliases = (record: Record<string, unknown>): void => {
1230
+ for (const key of Object.keys(record)) {
1231
+ if (reviewPathsEqual(key, filePath) || reviewPathsEqual(key, existing.filePath)) {
1232
+ aliases.add(key);
1233
+ }
1234
+ }
1235
+ };
1236
+ addMatchingAliases(s.fileChunkCounts);
1237
+ addMatchingAliases(s.fileContents);
1238
+ addMatchingAliases(s.fileContentsLoading);
1239
+ addMatchingAliases(s.editedContents);
1240
+ addMatchingAliases(s.reviewExternalChangesByFile);
1241
+ addMatchingAliases(s.fileContentVersionByPath);
1242
+
1243
+ const nextHunkDecisions = { ...s.hunkDecisions };
1244
+ const reviewKey = getReviewKeyForFilePath(s.activeChangeSet.files, filePath);
1245
+ const prefix = `${reviewKey}:`;
1246
+ for (const key of Object.keys(nextHunkDecisions)) {
1247
+ if (key.startsWith(prefix)) delete nextHunkDecisions[key];
1248
+ }
1249
+
1250
+ const nextFileDecisions = { ...s.fileDecisions };
1251
+ delete nextFileDecisions[reviewKey];
1252
+
1253
+ const nextFileChunkCounts = { ...s.fileChunkCounts };
1254
+ for (const alias of aliases) delete nextFileChunkCounts[alias];
1255
+
1256
+ const nextFileContents = { ...s.fileContents };
1257
+ for (const alias of aliases) delete nextFileContents[alias];
1258
+
1259
+ const nextFileContentsLoading = { ...s.fileContentsLoading };
1260
+ for (const alias of aliases) delete nextFileContentsLoading[alias];
1261
+
1262
+ const nextEditedContents = { ...s.editedContents };
1263
+ for (const alias of aliases) delete nextEditedContents[alias];
1264
+
1265
+ const nextHashes = { ...s.hunkContextHashesByFile };
1266
+ delete nextHashes[reviewKey];
1267
+ for (const alias of aliases) delete nextHashes[alias];
1268
+
1269
+ const nextReviewExternalChangesByFile = { ...s.reviewExternalChangesByFile };
1270
+ for (const alias of aliases) delete nextReviewExternalChangesByFile[alias];
1271
+
1272
+ const nextFileContentVersionByPath = { ...s.fileContentVersionByPath };
1273
+ for (const alias of aliases) {
1274
+ nextFileContentVersionByPath[alias] = (s.fileContentVersionByPath[alias] ?? 0) + 1;
1275
+ }
1276
+
1277
+ const nextSelected =
1278
+ s.selectedReviewFilePath && reviewPathsEqual(s.selectedReviewFilePath, existing.filePath)
1279
+ ? (nextFiles[0]?.filePath ?? null)
1280
+ : s.selectedReviewFilePath;
1281
+
1282
+ return {
1283
+ activeChangeSet: {
1284
+ ...s.activeChangeSet,
1285
+ files: nextFiles,
1286
+ totalFiles: nextFiles.length,
1287
+ totalLinesAdded,
1288
+ totalLinesRemoved,
1289
+ },
1290
+ selectedReviewFilePath: nextSelected,
1291
+ hunkDecisions: nextHunkDecisions,
1292
+ fileDecisions: nextFileDecisions,
1293
+ fileChunkCounts: nextFileChunkCounts,
1294
+ fileContents: nextFileContents,
1295
+ fileContentsLoading: nextFileContentsLoading,
1296
+ editedContents: nextEditedContents,
1297
+ hunkContextHashesByFile: nextHashes,
1298
+ fileContentVersionByPath: nextFileContentVersionByPath,
1299
+ reviewExternalChangesByFile: nextReviewExternalChangesByFile,
1300
+ };
1301
+ });
1302
+ },
1303
+
1304
+ addReviewFile: (
1305
+ file: FileChangeSummary,
1306
+ options?: { index?: number; content?: FileChangeWithContent }
1307
+ ) => {
1308
+ set((s) => {
1309
+ if (!s.activeChangeSet) return s;
1310
+ if (findReviewFileByPath(s.activeChangeSet.files, file.filePath)) return s;
1311
+
1312
+ const idxRaw = options?.index;
1313
+ const idx =
1314
+ typeof idxRaw === 'number' && Number.isFinite(idxRaw)
1315
+ ? Math.max(0, Math.min(idxRaw, s.activeChangeSet.files.length))
1316
+ : s.activeChangeSet.files.length;
1317
+
1318
+ const nextFiles = [...s.activeChangeSet.files];
1319
+ nextFiles.splice(idx, 0, file);
1320
+ const totalLinesAdded = nextFiles.reduce((sum, f) => sum + f.linesAdded, 0);
1321
+ const totalLinesRemoved = nextFiles.reduce((sum, f) => sum + f.linesRemoved, 0);
1322
+
1323
+ const nextFileContents = options?.content
1324
+ ? { ...s.fileContents, [file.filePath]: options.content }
1325
+ : s.fileContents;
1326
+
1327
+ const nextFileContentsLoading = options?.content
1328
+ ? { ...s.fileContentsLoading, [file.filePath]: false }
1329
+ : s.fileContentsLoading;
1330
+
1331
+ const nextFileContentVersionByPath = {
1332
+ ...s.fileContentVersionByPath,
1333
+ [file.filePath]: s.fileContentVersionByPath[file.filePath] ?? 0,
1334
+ };
1335
+
1336
+ const nextReviewExternalChangesByFile = { ...s.reviewExternalChangesByFile };
1337
+ delete nextReviewExternalChangesByFile[file.filePath];
1338
+
1339
+ return {
1340
+ activeChangeSet: {
1341
+ ...s.activeChangeSet,
1342
+ files: nextFiles,
1343
+ totalFiles: nextFiles.length,
1344
+ totalLinesAdded,
1345
+ totalLinesRemoved,
1346
+ },
1347
+ selectedReviewFilePath: s.selectedReviewFilePath ?? file.filePath,
1348
+ fileContents: nextFileContents,
1349
+ fileContentsLoading: nextFileContentsLoading,
1350
+ fileContentVersionByPath: nextFileContentVersionByPath,
1351
+ reviewExternalChangesByFile: nextReviewExternalChangesByFile,
1352
+ };
1353
+ });
1354
+ },
1355
+
1356
+ clearReviewStateForFile: (filePath: string) => {
1357
+ set((s) => {
1358
+ const nextHunkDecisions = { ...s.hunkDecisions };
1359
+ const reviewKey = getReviewKeyForFilePath(s.activeChangeSet?.files, filePath);
1360
+ const prefix = `${reviewKey}:`;
1361
+ for (const key of Object.keys(nextHunkDecisions)) {
1362
+ if (key.startsWith(prefix) && nextHunkDecisions[key] === 'rejected') {
1363
+ delete nextHunkDecisions[key];
1364
+ }
1365
+ }
1366
+
1367
+ const nextFileDecisions = { ...s.fileDecisions };
1368
+ if (nextFileDecisions[reviewKey] === 'rejected') {
1369
+ delete nextFileDecisions[reviewKey];
1370
+ }
1371
+
1372
+ const nextEditedContents = { ...s.editedContents };
1373
+ delete nextEditedContents[filePath];
1374
+ const nextReviewExternalChangesByFile = { ...s.reviewExternalChangesByFile };
1375
+ delete nextReviewExternalChangesByFile[filePath];
1376
+
1377
+ return {
1378
+ hunkDecisions: nextHunkDecisions,
1379
+ fileDecisions: nextFileDecisions,
1380
+ editedContents: nextEditedContents,
1381
+ reviewExternalChangesByFile: nextReviewExternalChangesByFile,
1382
+ ...buildResolvedFileInvalidation(s, filePath),
1383
+ };
1384
+ });
1385
+ },
1386
+
1387
+ invalidateResolvedFileContent: (filePath: string) => {
1388
+ set((s) => buildResolvedFileInvalidation(s, filePath));
1389
+ },
1390
+
1391
+ markReviewFileExternallyChanged: (filePath: string, type: ReviewExternalChange['type']) => {
1392
+ set((s) => ({
1393
+ reviewExternalChangesByFile: {
1394
+ ...s.reviewExternalChangesByFile,
1395
+ [filePath]: { type },
1396
+ },
1397
+ }));
1398
+ },
1399
+
1400
+ clearReviewFileExternalChange: (filePath: string) => {
1401
+ set((s) => {
1402
+ if (!(filePath in s.reviewExternalChangesByFile)) return s;
1403
+ const next = { ...s.reviewExternalChangesByFile };
1404
+ delete next[filePath];
1405
+ return { reviewExternalChangesByFile: next };
1406
+ });
1407
+ },
1408
+
1409
+ reloadReviewFileFromDisk: (filePath: string) => {
1410
+ set((s) => {
1411
+ const nextEditedContents = { ...s.editedContents };
1412
+ delete nextEditedContents[filePath];
1413
+ const nextReviewExternalChangesByFile = { ...s.reviewExternalChangesByFile };
1414
+ delete nextReviewExternalChangesByFile[filePath];
1415
+ return {
1416
+ editedContents: nextEditedContents,
1417
+ reviewExternalChangesByFile: nextReviewExternalChangesByFile,
1418
+ ...buildResolvedFileInvalidation(s, filePath),
1419
+ };
1420
+ });
1421
+ },
1422
+
1423
+ // ── Editable diff actions ──
1424
+
1425
+ updateEditedContent: (filePath: string, content: string) => {
1426
+ set((s) => ({
1427
+ editedContents: { ...s.editedContents, [filePath]: content },
1428
+ }));
1429
+ },
1430
+
1431
+ discardFileEdits: (filePath: string) => {
1432
+ set((s) => {
1433
+ const next = { ...s.editedContents };
1434
+ delete next[filePath];
1435
+ return { editedContents: next };
1436
+ });
1437
+ },
1438
+
1439
+ discardAllEdits: () => set({ editedContents: {} }),
1440
+
1441
+ saveEditedFile: async (filePath: string, projectPath?: string) => {
1442
+ const state = get();
1443
+ const fileEntry = findReviewFileByPath(state.activeChangeSet?.files, filePath);
1444
+ const canonicalFilePath = fileEntry?.filePath ?? filePath;
1445
+ const hasRequestedDraft = filePath in state.editedContents;
1446
+ const hasCanonicalDraft = canonicalFilePath in state.editedContents;
1447
+ const content = hasRequestedDraft
1448
+ ? state.editedContents[filePath]
1449
+ : state.editedContents[canonicalFilePath];
1450
+ if (!hasRequestedDraft && !hasCanonicalDraft) return;
1451
+ if (content === undefined) return;
1452
+ set((s) => ({
1453
+ fileContentsLoading: {
1454
+ ...s.fileContentsLoading,
1455
+ [filePath]: false,
1456
+ [canonicalFilePath]: false,
1457
+ },
1458
+ applying: true,
1459
+ applyError: null,
1460
+ fileContentVersionByPath: {
1461
+ ...s.fileContentVersionByPath,
1462
+ [filePath]: (s.fileContentVersionByPath[filePath] ?? 0) + 1,
1463
+ [canonicalFilePath]: (s.fileContentVersionByPath[canonicalFilePath] ?? 0) + 1,
1464
+ },
1465
+ }));
1466
+ try {
1467
+ await api.review.saveEditedFile(canonicalFilePath, content, projectPath);
1468
+ set((s) => {
1469
+ const aliases = new Set([filePath, canonicalFilePath]);
1470
+ addMatchingReviewPathAliases(aliases, filePath, canonicalFilePath, s.editedContents);
1471
+ addMatchingReviewPathAliases(aliases, filePath, canonicalFilePath, s.fileChunkCounts);
1472
+ addMatchingReviewPathAliases(
1473
+ aliases,
1474
+ filePath,
1475
+ canonicalFilePath,
1476
+ s.hunkContextHashesByFile
1477
+ );
1478
+ addMatchingReviewPathAliases(
1479
+ aliases,
1480
+ filePath,
1481
+ canonicalFilePath,
1482
+ s.reviewExternalChangesByFile
1483
+ );
1484
+ addMatchingReviewPathAliases(aliases, filePath, canonicalFilePath, s.fileContents);
1485
+
1486
+ const nextEdited = { ...s.editedContents };
1487
+ for (const alias of aliases) delete nextEdited[alias];
1488
+
1489
+ const nextFileChunkCounts = { ...s.fileChunkCounts };
1490
+ for (const alias of aliases) delete nextFileChunkCounts[alias];
1491
+
1492
+ const nextHunkContextHashesByFile = { ...s.hunkContextHashesByFile };
1493
+ const reviewKey = getReviewKeyForFilePath(s.activeChangeSet?.files, canonicalFilePath);
1494
+ delete nextHunkContextHashesByFile[reviewKey];
1495
+ for (const alias of aliases) delete nextHunkContextHashesByFile[alias];
1496
+
1497
+ const nextReviewExternalChangesByFile = { ...s.reviewExternalChangesByFile };
1498
+ for (const alias of aliases) delete nextReviewExternalChangesByFile[alias];
1499
+
1500
+ // Update cached content in-place to avoid skeleton flash.
1501
+ // Replace modifiedFullContent with saved version so CodeMirror
1502
+ // reflects the new baseline without a full re-fetch cycle.
1503
+ const nextContents = { ...s.fileContents };
1504
+ const existing = nextContents[canonicalFilePath] ?? nextContents[filePath];
1505
+ for (const alias of aliases) {
1506
+ if (alias !== canonicalFilePath) delete nextContents[alias];
1507
+ }
1508
+ if (existing) {
1509
+ nextContents[canonicalFilePath] = {
1510
+ ...existing,
1511
+ filePath: canonicalFilePath,
1512
+ modifiedFullContent: content,
1513
+ contentSource: 'disk-current',
1514
+ };
1515
+ }
1516
+ return {
1517
+ editedContents: nextEdited,
1518
+ fileChunkCounts: nextFileChunkCounts,
1519
+ hunkContextHashesByFile: nextHunkContextHashesByFile,
1520
+ fileContents: nextContents,
1521
+ reviewExternalChangesByFile: nextReviewExternalChangesByFile,
1522
+ applying: false,
1523
+ };
1524
+ });
1525
+ } catch (error) {
1526
+ set({ applying: false, applyError: mapReviewError(error) });
1527
+ }
1528
+ },
1529
+
1530
+ checkTaskHasChanges: async (
1531
+ teamName: string,
1532
+ taskId: string,
1533
+ options: TaskChangeRequestOptions
1534
+ ) => {
1535
+ const selectedTask =
1536
+ get().selectedTeamName === teamName
1537
+ ? get().selectedTeamData?.tasks.find((task) => task.id === taskId)
1538
+ : undefined;
1539
+ const cacheKey = buildTaskChangePresenceKey(teamName, taskId, options);
1540
+ const summaryCacheable = isTaskSummaryCacheableForOptions(options);
1541
+ const cachedPresence = get().taskChangePresenceByKey[cacheKey];
1542
+ if (
1543
+ summaryCacheable &&
1544
+ (cachedPresence === 'has_changes' || cachedPresence === 'needs_attention')
1545
+ ) {
1546
+ get().setSelectedTeamTaskChangePresence(teamName, taskId, cachedPresence);
1547
+ return;
1548
+ }
1549
+ if (taskChangesCheckInFlight.has(cacheKey)) return;
1550
+ const negativeTs = taskChangesNegativeCache.get(cacheKey);
1551
+ const hasUnknownPresence = selectedTask?.changePresence === 'unknown';
1552
+ if (negativeTs && Date.now() - negativeTs < NEGATIVE_CACHE_TTL && !hasUnknownPresence) return;
1553
+
1554
+ taskChangesCheckInFlight.add(cacheKey);
1555
+ try {
1556
+ const data = await api.review.getTaskChanges(teamName, taskId, {
1557
+ ...options,
1558
+ summaryOnly: true,
1559
+ });
1560
+ const nextPresence = resolveTaskChangePresenceFromResult(data);
1561
+ if (nextPresence === 'has_changes' || nextPresence === 'needs_attention') {
1562
+ set((s) => ({
1563
+ taskChangePresenceByKey: { ...s.taskChangePresenceByKey, [cacheKey]: nextPresence },
1564
+ }));
1565
+ taskChangesNegativeCache.delete(cacheKey);
1566
+ get().setSelectedTeamTaskChangePresence(teamName, taskId, nextPresence);
1567
+ if (shouldBackgroundRevalidateTaskPresence(data, CHANGE_REVIEW_SLICE_BOOT_TIME)) {
1568
+ void revalidateTaskChangePresence(teamName, taskId, options);
1569
+ }
1570
+ } else if (nextPresence === 'no_changes') {
1571
+ set((s) => ({
1572
+ taskChangePresenceByKey: { ...s.taskChangePresenceByKey, [cacheKey]: 'no_changes' },
1573
+ }));
1574
+ taskChangesNegativeCache.set(cacheKey, Date.now());
1575
+ get().setSelectedTeamTaskChangePresence(teamName, taskId, 'no_changes');
1576
+ } else {
1577
+ set((s) => {
1578
+ const nextTaskChangePresenceByKey = { ...s.taskChangePresenceByKey };
1579
+ delete nextTaskChangePresenceByKey[cacheKey];
1580
+ return { taskChangePresenceByKey: nextTaskChangePresenceByKey };
1581
+ });
1582
+ taskChangesNegativeCache.delete(cacheKey);
1583
+ if (selectedTask?.changePresence && selectedTask.changePresence !== 'unknown') {
1584
+ get().setSelectedTeamTaskChangePresence(teamName, taskId, 'unknown');
1585
+ }
1586
+ }
1587
+ } catch {
1588
+ // Allow immediate retry after transient failures (race, file lock, late logs).
1589
+ } finally {
1590
+ taskChangesCheckInFlight.delete(cacheKey);
1591
+ }
1592
+ },
1593
+
1594
+ warmTaskChangeSummaries: async (requests) => {
1595
+ const uniqueRequests = new Map<
1596
+ string,
1597
+ { teamName: string; taskId: string; options: TaskChangeRequestOptions }
1598
+ >();
1599
+ for (const request of requests) {
1600
+ if (!isTaskSummaryCacheableForOptions(request.options)) continue;
1601
+ const cacheKey = buildTaskChangePresenceKey(
1602
+ request.teamName,
1603
+ request.taskId,
1604
+ request.options
1605
+ );
1606
+ uniqueRequests.set(cacheKey, request);
1607
+ }
1608
+
1609
+ const entries = [...uniqueRequests.entries()];
1610
+ const runWarmRequest = async (
1611
+ cacheKey: string,
1612
+ request: { teamName: string; taskId: string; options: TaskChangeRequestOptions }
1613
+ ): Promise<void> => {
1614
+ const cachedPresence = get().taskChangePresenceByKey[cacheKey];
1615
+ if (
1616
+ cachedPresence === 'has_changes' ||
1617
+ cachedPresence === 'needs_attention' ||
1618
+ taskChangesCheckInFlight.has(cacheKey)
1619
+ ) {
1620
+ return;
1621
+ }
1622
+
1623
+ taskChangesCheckInFlight.add(cacheKey);
1624
+ try {
1625
+ const data = await api.review.getTaskChanges(request.teamName, request.taskId, {
1626
+ ...request.options,
1627
+ summaryOnly: true,
1628
+ });
1629
+ const nextPresence = resolveTaskChangePresenceFromResult(data);
1630
+ if (nextPresence) {
1631
+ set((s) => ({
1632
+ taskChangePresenceByKey: {
1633
+ ...s.taskChangePresenceByKey,
1634
+ [cacheKey]: nextPresence,
1635
+ },
1636
+ }));
1637
+ }
1638
+ if (nextPresence === 'has_changes' || nextPresence === 'needs_attention') {
1639
+ taskChangesNegativeCache.delete(cacheKey);
1640
+ if (shouldBackgroundRevalidateTaskPresence(data, CHANGE_REVIEW_SLICE_BOOT_TIME)) {
1641
+ void revalidateTaskChangePresence(request.teamName, request.taskId, request.options);
1642
+ }
1643
+ } else if (nextPresence === 'no_changes') {
1644
+ taskChangesNegativeCache.set(cacheKey, Date.now());
1645
+ } else {
1646
+ taskChangesNegativeCache.delete(cacheKey);
1647
+ }
1648
+ } catch {
1649
+ // Best-effort warm path.
1650
+ } finally {
1651
+ taskChangesCheckInFlight.delete(cacheKey);
1652
+ }
1653
+ };
1654
+
1655
+ for (let index = 0; index < entries.length; index += TASK_CHANGE_WARM_CONCURRENCY) {
1656
+ await Promise.all(
1657
+ entries
1658
+ .slice(index, index + TASK_CHANGE_WARM_CONCURRENCY)
1659
+ .map(([cacheKey, request]) => runWarmRequest(cacheKey, request))
1660
+ );
1661
+ }
1662
+ },
1663
+
1664
+ invalidateTaskChangePresence: (cacheKeys) => {
1665
+ if (cacheKeys.length === 0) return;
1666
+ const keySet = new Set(cacheKeys);
1667
+ set((state) => {
1668
+ const nextTaskChangePresenceByKey = { ...state.taskChangePresenceByKey };
1669
+ let changed = false;
1670
+ for (const key of keySet) {
1671
+ if (key in nextTaskChangePresenceByKey) {
1672
+ delete nextTaskChangePresenceByKey[key];
1673
+ changed = true;
1674
+ }
1675
+ taskChangesNegativeCache.delete(key);
1676
+ }
1677
+ return changed ? { taskChangePresenceByKey: nextTaskChangePresenceByKey } : {};
1678
+ });
1679
+ },
1680
+
1681
+ invalidateChangeStats: (teamName: string) => {
1682
+ set((state) => {
1683
+ const newCache = { ...state.changeStatsCache };
1684
+ // Remove all entries for this team
1685
+ for (const key of Object.keys(newCache)) {
1686
+ if (key.startsWith(`${teamName}:`)) {
1687
+ delete newCache[key];
1688
+ }
1689
+ }
1690
+ return { changeStatsCache: newCache };
1691
+ });
1692
+ },
1693
+ };
1694
+ };