cc-viewer 1.6.348 → 1.7.1

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 (222) hide show
  1. package/README.md +13 -1
  2. package/cli.js +116 -37
  3. package/dist/assets/App-gUSmU6Ny.js +2 -0
  4. package/dist/assets/{MdxEditorPanel-ualoRFOL.js → MdxEditorPanel-DAR39A3S.js} +1 -1
  5. package/dist/assets/Mobile-qfRsRth0.js +1 -0
  6. package/dist/assets/{_baseUniq-CG81mgN_.js → _baseUniq-D9_E5ZDp.js} +1 -1
  7. package/dist/assets/{arc-C161J4f2.js → arc-D4iFrlhb.js} +1 -1
  8. package/dist/assets/{architectureDiagram-Q4EWVU46-BmbDMdn9.js → architectureDiagram-Q4EWVU46-f1neo7hg.js} +1 -1
  9. package/dist/assets/{blockDiagram-DXYQGD6D-DXpMvdxd.js → blockDiagram-DXYQGD6D-DWi9e9tG.js} +1 -1
  10. package/dist/assets/{c4Diagram-AHTNJAMY-8Mypg0IU.js → c4Diagram-AHTNJAMY-DAdj49A2.js} +1 -1
  11. package/dist/assets/{channel-B2khdmvc.js → channel-B3VthT0m.js} +1 -1
  12. package/dist/assets/{chunk-4BX2VUAB-I61r1g6z.js → chunk-4BX2VUAB-BrPcAHV3.js} +1 -1
  13. package/dist/assets/{chunk-4TB4RGXK-VFPlRE5E.js → chunk-4TB4RGXK-Bvopc1V9.js} +1 -1
  14. package/dist/assets/{chunk-55IACEB6-DvCamg_6.js → chunk-55IACEB6-Juw-7Z3i.js} +1 -1
  15. package/dist/assets/{chunk-EDXVE4YY-Dj4Q8RwR.js → chunk-EDXVE4YY-DVYP9tpB.js} +1 -1
  16. package/dist/assets/{chunk-FMBD7UC4-_bgQuuJh.js → chunk-FMBD7UC4-VQ7VOXl3.js} +1 -1
  17. package/dist/assets/{chunk-OYMX7WX6-3to38LyN.js → chunk-OYMX7WX6-F8Rj4ZUZ.js} +1 -1
  18. package/dist/assets/{chunk-QZHKN3VN-Zl26wKKz.js → chunk-QZHKN3VN-Dn7K3yi9.js} +1 -1
  19. package/dist/assets/{chunk-YZCP3GAM-RLYPrlh0.js → chunk-YZCP3GAM-UjO-KTsn.js} +1 -1
  20. package/dist/assets/classDiagram-6PBFFD2Q-DmSozxoO.js +1 -0
  21. package/dist/assets/classDiagram-v2-HSJHXN6E-DmSozxoO.js +1 -0
  22. package/dist/assets/clone-BQtvy9Ae.js +1 -0
  23. package/dist/assets/{cose-bilkent-S5V4N54A-CsYOjJ0V.js → cose-bilkent-S5V4N54A-BDhNj8rq.js} +1 -1
  24. package/dist/assets/{dagre-KV5264BT-C3YXXaFU.js → dagre-KV5264BT-BzdcDuyt.js} +1 -1
  25. package/dist/assets/{diagram-5BDNPKRD-DUCOg1ld.js → diagram-5BDNPKRD-DP_SX65N.js} +1 -1
  26. package/dist/assets/{diagram-G4DWMVQ6-BtlLl_zX.js → diagram-G4DWMVQ6-B-pBFw8Q.js} +1 -1
  27. package/dist/assets/{diagram-MMDJMWI5-1Qp9xfOk.js → diagram-MMDJMWI5-6XSfN1eN.js} +1 -1
  28. package/dist/assets/{diagram-TYMM5635-Cj7VMp3L.js → diagram-TYMM5635-DJvGxvpE.js} +1 -1
  29. package/dist/assets/{erDiagram-SMLLAGMA-CVZVn541.js → erDiagram-SMLLAGMA-CpqdpGYj.js} +1 -1
  30. package/dist/assets/{flowDiagram-DWJPFMVM-CiU9Fkkv.js → flowDiagram-DWJPFMVM-C9m7wjwI.js} +1 -1
  31. package/dist/assets/{ganttDiagram-T4ZO3ILL-aRaG97ID.js → ganttDiagram-T4ZO3ILL-CU0XOfY3.js} +1 -1
  32. package/dist/assets/{gitGraphDiagram-UUTBAWPF-Cr2LJlvX.js → gitGraphDiagram-UUTBAWPF-ipyGULnP.js} +1 -1
  33. package/dist/assets/{graph-B59lDyMn.js → graph-BR2KmES9.js} +1 -1
  34. package/dist/assets/{index-C1ptnCpq.js → index--wgRW_OB.js} +1 -1
  35. package/dist/assets/index-B51Ci0GL.js +2 -0
  36. package/dist/assets/{index-CRAWA8PC.js → index-C-be29ey.js} +1 -1
  37. package/dist/assets/{index-CSM1Kw8l.js → index-C16fiVhv.js} +1 -1
  38. package/dist/assets/{index-Ddk-kg22.js → index-D5YruOKD.js} +1 -1
  39. package/dist/assets/{index-BnhNIjbj.js → index-D5tSeMU7.js} +1 -1
  40. package/dist/assets/{index-vFEQq-NB.js → index-DuZQ6FqR.js} +1 -1
  41. package/dist/assets/{index-CO2hZ1s4.js → index-OWzSX2T8.js} +1 -1
  42. package/dist/assets/{infoDiagram-42DDH7IO-CnO22bw8.js → infoDiagram-42DDH7IO-BQWQjfFX.js} +1 -1
  43. package/dist/assets/{ishikawaDiagram-UXIWVN3A-ByNzpScH.js → ishikawaDiagram-UXIWVN3A-Df-KH_Hz.js} +1 -1
  44. package/dist/assets/{journeyDiagram-VCZTEJTY-yaVaCWPq.js → journeyDiagram-VCZTEJTY-CMbB_4G9.js} +1 -1
  45. package/dist/assets/{jszip.min-BdVFP3OW.js → jszip.min-B6k2t9Ch.js} +1 -1
  46. package/dist/assets/{kanban-definition-6JOO6SKY-RIPR_RQ3.js → kanban-definition-6JOO6SKY-CImdlKN3.js} +1 -1
  47. package/dist/assets/{layout-0rzel0v_.js → layout-DQl4aw09.js} +1 -1
  48. package/dist/assets/{linear-sktN0tB6.js → linear-CLD3aPlK.js} +1 -1
  49. package/dist/assets/mermaid.core-CN35dvKJ.js +7 -0
  50. package/dist/assets/{min-rspGOkz_.js → min-_D86henJ.js} +1 -1
  51. package/dist/assets/{mindmap-definition-QFDTVHPH-BsVzZzTz.js → mindmap-definition-QFDTVHPH-2JVcu1nk.js} +1 -1
  52. package/dist/assets/{pieDiagram-DEJITSTG-CsoWsw4f.js → pieDiagram-DEJITSTG-Dik-m5u7.js} +1 -1
  53. package/dist/assets/{quadrantDiagram-34T5L4WZ-CTMKuCAI.js → quadrantDiagram-34T5L4WZ-Bm5l40Jq.js} +1 -1
  54. package/dist/assets/{requirementDiagram-MS252O5E-C67256te.js → requirementDiagram-MS252O5E-Ck9w5rTj.js} +1 -1
  55. package/dist/assets/{sankeyDiagram-XADWPNL6-X8Y7KnW0.js → sankeyDiagram-XADWPNL6-BbGVCFEf.js} +1 -1
  56. package/dist/assets/{seqResourceLoaders-B5Zy_CC_.css → seqResourceLoaders-DFrHmgnK.css} +1 -1
  57. package/dist/assets/seqResourceLoaders-DZJxMMaR.js +2 -0
  58. package/dist/assets/{sequenceDiagram-FGHM5R23-Cils0TAp.js → sequenceDiagram-FGHM5R23-vjvEdksk.js} +1 -1
  59. package/dist/assets/{stateDiagram-FHFEXIEX-DFnohvpG.js → stateDiagram-FHFEXIEX-DTqMoiB2.js} +1 -1
  60. package/dist/assets/{stateDiagram-v2-QKLJ7IA2-BTX_4o81.js → stateDiagram-v2-QKLJ7IA2-CNbAwnhu.js} +1 -1
  61. package/dist/assets/{timeline-definition-GMOUNBTQ-DrIYFrzZ.js → timeline-definition-GMOUNBTQ-Cw8O-52d.js} +1 -1
  62. package/dist/assets/{vendor-antd-D5HmSDSg.js → vendor-antd-DI7JL-mE.js} +1 -1
  63. package/dist/assets/{vendor-codemirror-C5yuj7ze.js → vendor-codemirror-B9c49dtM.js} +1 -1
  64. package/dist/assets/{vendor-mdxeditor-BcEooTvb.js → vendor-mdxeditor-B6cpBtIE.js} +2 -2
  65. package/dist/assets/{vendor-qrcode-d9LCn0-9.js → vendor-qrcode-Dn90-s-u.js} +1 -1
  66. package/dist/assets/{vendor-virtuoso-K9m4MN6a.js → vendor-virtuoso-CpdSKD_6.js} +1 -1
  67. package/dist/assets/{vennDiagram-DHZGUBPP-lRqBYTr9.js → vennDiagram-DHZGUBPP-DCuqymb7.js} +1 -1
  68. package/dist/assets/{wardley-RL74JXVD-e8YfxpOe.js → wardley-RL74JXVD-BJ20wJUU.js} +1 -1
  69. package/dist/assets/{wardleyDiagram-NUSXRM2D-Bxrg774h.js → wardleyDiagram-NUSXRM2D-Vz6US3qt.js} +1 -1
  70. package/dist/assets/{xychartDiagram-5P7HB3ND-DWhZqhq8.js → xychartDiagram-5P7HB3ND-CzkLKrmi.js} +1 -1
  71. package/dist/index.html +4 -4
  72. package/findcc.js +4 -3
  73. package/package.json +3 -1
  74. package/server/i18n.js +21 -141
  75. package/server/interceptor.js +211 -403
  76. package/server/lib/async-write-queue.js +12 -1
  77. package/server/lib/delta-reconstructor.js +3 -3
  78. package/server/lib/error-report.js +45 -0
  79. package/server/lib/im-log-watcher.js +68 -8
  80. package/server/lib/interceptor-core.js +19 -174
  81. package/server/lib/log-management.js +214 -247
  82. package/server/lib/log-stream.js +59 -108
  83. package/server/lib/log-watcher.js +62 -274
  84. package/server/lib/log-zip.js +148 -0
  85. package/server/lib/session-boundary.js +266 -0
  86. package/server/lib/session-pin-store.js +10 -19
  87. package/server/lib/stats-worker.js +138 -267
  88. package/server/lib/user-prompt-extract.js +250 -0
  89. package/server/lib/v2/adapter.js +1124 -0
  90. package/server/lib/v2/blob-store.js +44 -0
  91. package/server/lib/v2/conversation-store.js +217 -0
  92. package/server/lib/v2/convert-manager.js +79 -0
  93. package/server/lib/v2/convert-worker.js +42 -0
  94. package/server/lib/v2/convert.js +409 -0
  95. package/server/lib/v2/identity.js +204 -0
  96. package/server/lib/v2/journal.js +62 -0
  97. package/server/lib/v2/layout.js +167 -0
  98. package/server/lib/v2/live-feed.js +558 -0
  99. package/server/lib/v2/meta-rows.js +360 -0
  100. package/server/lib/v2/migrate-prompt.js +54 -0
  101. package/server/lib/v2/replay.js +191 -0
  102. package/server/lib/v2/session-select.js +222 -0
  103. package/server/lib/v2/singleflight.js +104 -0
  104. package/server/lib/v2/v2-writer.js +528 -0
  105. package/server/lib/v2/verify.js +233 -0
  106. package/server/lib/wire-compress.js +167 -0
  107. package/server/routes/events.js +120 -150
  108. package/server/routes/files-fs.js +4 -2
  109. package/server/routes/im.js +11 -2
  110. package/server/routes/logs.js +209 -164
  111. package/server/routes/preferences.js +0 -3
  112. package/server/routes/project-meta.js +2 -3
  113. package/server/routes/session-pin.js +7 -8
  114. package/server/routes/v2.js +60 -0
  115. package/server/routes/workspaces.js +50 -14
  116. package/server/server.js +70 -66
  117. package/server/workspace-registry.js +24 -4
  118. package/src/utils/apiUrl.js +26 -0
  119. package/src/utils/askFallback.js +21 -0
  120. package/src/utils/askOptionDesc.js +55 -0
  121. package/src/utils/askPortalMatcher.js +54 -0
  122. package/src/utils/autoApproveOptions.js +27 -0
  123. package/src/utils/avatarAnimationPostPass.js +49 -0
  124. package/src/utils/builtinPresets.js +18 -0
  125. package/src/utils/clearCheckpoint.js +7 -0
  126. package/src/utils/commandValidator.js +45 -0
  127. package/src/utils/contentFilter.js +620 -0
  128. package/src/utils/contextRaw.js +18 -0
  129. package/src/utils/contextSidebarNavigation.js +16 -0
  130. package/src/utils/contextTurns.js +117 -0
  131. package/src/utils/displayScaleHelper.js +47 -0
  132. package/src/utils/effectiveModel.js +13 -0
  133. package/src/utils/entry-slim.js +563 -0
  134. package/src/utils/entryCache.js +144 -0
  135. package/src/utils/errorReport.js +6 -0
  136. package/src/utils/fileExpandedPathsStorage.js +70 -0
  137. package/src/utils/fileIcons.jsx +36 -0
  138. package/src/utils/fileOpen.js +37 -0
  139. package/src/utils/formatters.js +48 -0
  140. package/src/utils/gitApi.js +50 -0
  141. package/src/utils/gitTreeBuilder.js +19 -0
  142. package/src/utils/helpers.js +846 -0
  143. package/src/utils/identityHeal.js +99 -0
  144. package/src/utils/imConnState.js +59 -0
  145. package/src/utils/imOrigin.js +24 -0
  146. package/src/utils/imTr.js +7 -0
  147. package/src/utils/imageCompress.js +83 -0
  148. package/src/utils/imageDownscale.js +28 -0
  149. package/src/utils/imageResize.js +112 -0
  150. package/src/utils/ingestPipeline.js +47 -0
  151. package/src/utils/lazyWithReload.js +53 -0
  152. package/src/utils/markdown.js +30 -0
  153. package/src/utils/markdownIncremental.js +39 -0
  154. package/src/utils/markdownProfiler.js +113 -0
  155. package/src/utils/mdExtensionDetect.js +64 -0
  156. package/src/utils/memoryLinkParser.js +44 -0
  157. package/src/utils/modalMask.js +15 -0
  158. package/src/utils/monotime.js +6 -0
  159. package/src/utils/pinnedMenu.js +55 -0
  160. package/src/utils/presetShortcuts.js +14 -0
  161. package/src/utils/projectAlias.js +135 -0
  162. package/src/utils/promptClassifier.js +112 -0
  163. package/src/utils/promptDetect.js +196 -0
  164. package/src/utils/promptNav.js +60 -0
  165. package/src/utils/ptyChunkBuilder.js +251 -0
  166. package/src/utils/quickMenuHoverIntent.js +35 -0
  167. package/src/utils/rateLimitParser.js +119 -0
  168. package/src/utils/readResultPool.js +106 -0
  169. package/src/utils/refreshCachedItemProp.js +30 -0
  170. package/src/utils/requestType.js +208 -0
  171. package/src/utils/resizeCalc.js +21 -0
  172. package/src/utils/resolveLocalized.js +17 -0
  173. package/src/utils/searchApi.js +47 -0
  174. package/src/utils/searchReplace.js +51 -0
  175. package/src/utils/seqResourceLoaders.js +79 -0
  176. package/src/utils/sessionManager.js +471 -0
  177. package/src/utils/sessionMerge.js +180 -0
  178. package/src/utils/skillModalController.js +111 -0
  179. package/src/utils/skillsParser.js +116 -0
  180. package/src/utils/slashCommandLabels.js +80 -0
  181. package/src/utils/splitDragCalc.js +73 -0
  182. package/src/utils/stickyBottomController.js +643 -0
  183. package/src/utils/svgSanitize.js +48 -0
  184. package/src/utils/systemTags.js +47 -0
  185. package/src/utils/tClaude.js +20 -0
  186. package/src/utils/teamModalBuilder.js +351 -0
  187. package/src/utils/teamSessionParser.js +190 -0
  188. package/src/utils/teammateAvatars.js +194 -0
  189. package/src/utils/teammateDetector.js +121 -0
  190. package/src/utils/termDiag.js +114 -0
  191. package/src/utils/terminalClipboard.js +117 -0
  192. package/src/utils/terminalWriteQueue.js +398 -0
  193. package/src/utils/thinkingMerge.js +49 -0
  194. package/src/utils/toolCatalog.js +29 -0
  195. package/src/utils/toolResultBuilder.js +393 -0
  196. package/src/utils/toolResultClassifier.js +16 -0
  197. package/src/utils/toolResultCore.js +167 -0
  198. package/src/utils/toolsDiff.js +44 -0
  199. package/src/utils/toolsXmlFormatter.js +3 -0
  200. package/src/utils/ultraplanController.js +109 -0
  201. package/src/utils/ultraplanExperts.js +86 -0
  202. package/src/utils/ultraplanTemplates.js +154 -0
  203. package/src/utils/userImageRefs.js +49 -0
  204. package/src/utils/v3Assembler.js +112 -0
  205. package/src/utils/v3Rows.js +48 -0
  206. package/src/utils/voicePackPlayer.js +233 -0
  207. package/src/utils/webSearchGrouping.js +111 -0
  208. package/src/utils/workflowFormat.js +52 -0
  209. package/src/utils/workflowRuns.js +118 -0
  210. package/src/utils/workflowStore.js +94 -0
  211. package/dist/assets/App-VTo4NVdo.js +0 -2
  212. package/dist/assets/Mobile-CcmYvMm_.js +0 -1
  213. package/dist/assets/classDiagram-6PBFFD2Q-BXnyUgsj.js +0 -1
  214. package/dist/assets/classDiagram-v2-HSJHXN6E-BXnyUgsj.js +0 -1
  215. package/dist/assets/clone-md-Mc1-T.js +0 -1
  216. package/dist/assets/index-DCo650PG.js +0 -2
  217. package/dist/assets/mermaid.core-C2Parj1q.js +0 -7
  218. package/dist/assets/seqResourceLoaders-BVozq68i.js +0 -2
  219. package/server/lib/instance-registry.js +0 -57
  220. package/server/lib/jsonl-archive.js +0 -180
  221. package/server/lib/teammate-detect.js +0 -32
  222. package/server/lib/zip-safety.js +0 -68
@@ -0,0 +1,111 @@
1
+ // Skill 管理弹窗的「开关 / 永久删除」共享控制器。
2
+ // AppHeader 与 Mobile 是同形态的类组件(同样的 state._skillsModal / state._fsSkills、
3
+ // this.setState、this.reloadFsSkills()),原本各持一份逐字相同的 handler,极易漂移
4
+ // (评审已发现 Mobile 漏了一条镜像注释)。这里抽成以组件实例为 host 的纯函数,两端只留一行委派。
5
+ // ImSkillsModal 是函数组件 + IM 端点 + _tr 文案,状态形态不同,刻意不并入。
6
+ import { message } from 'antd';
7
+ import { apiUrl } from './apiUrl';
8
+ import { t } from '../i18n';
9
+ import { skillKey, skillOrderKey } from './skillsParser';
10
+
11
+ // 把 reload 的权威结果按当前显示顺序(skillOrderKey 保位)合并回 _skillsModal.skills。
12
+ // skillOrderKey 只取 source+name(开关/删除都不改它),所以行原地不动;modal 没见过的新条目排末尾。
13
+ export function mergePreservingOrder(prevSkills, resultSkills) {
14
+ const orderMap = new Map(prevSkills.map((s, i) => [skillOrderKey(s), i]));
15
+ return [...resultSkills].sort((a, b) => {
16
+ const ai = orderMap.get(skillOrderKey(a));
17
+ const bi = orderMap.get(skillOrderKey(b));
18
+ if (ai === undefined && bi === undefined) return 0;
19
+ if (ai === undefined) return 1;
20
+ if (bi === undefined) return -1;
21
+ return ai - bi;
22
+ });
23
+ }
24
+
25
+ // 切换 skill 启用状态:乐观翻 Switch + 失败回滚 + reload 保位对齐。
26
+ export async function handleSkillToggle(host, skill) {
27
+ const key = skillKey(skill);
28
+ if (host.state._skillsModal?.toggling?.has(key)) return;
29
+ const enable = !skill.enabled;
30
+ // 用 skillKey(含 path)匹配——同名同 source 的重复两份路径不同,只翻被点那行,不牵连孪生。
31
+ const flipEnabled = (target) => (s) => (skillKey(s) === key) ? { ...s, enabled: target } : s;
32
+ host.setState(prev => {
33
+ const next = new Set(prev._skillsModal.toggling); next.add(key);
34
+ return { _skillsModal: { ...prev._skillsModal, toggling: next, skills: prev._skillsModal.skills.map(flipEnabled(enable)) } };
35
+ });
36
+ try {
37
+ const r = await fetch(apiUrl('/api/skills/toggle'), {
38
+ method: 'POST',
39
+ headers: { 'Content-Type': 'application/json' },
40
+ body: JSON.stringify({ source: skill.source, name: skill.name, enable }),
41
+ });
42
+ const data = await r.json();
43
+ if (!r.ok) {
44
+ host.setState(prev => ({ _skillsModal: { ...prev._skillsModal, skills: prev._skillsModal.skills.map(flipEnabled(!enable)) } }));
45
+ if (data.code === 'DUPLICATE') message.error(t('ui.skillToggleDuplicate', { name: skill.name }));
46
+ else message.error(t('ui.skillToggleFailed', { reason: data.error || 'unknown' }));
47
+ return;
48
+ }
49
+ // 乐观翻 _fsSkills(popover chip 源)里这条的 enabled——reload 失败也能立即反映用户动作;
50
+ // reload 成功会用权威数据覆盖。
51
+ host.setState(prev => ({
52
+ _fsSkills: Array.isArray(prev._fsSkills)
53
+ ? prev._fsSkills.map(s => (skillKey(s) === key) ? { ...s, enabled: enable } : s)
54
+ : prev._fsSkills,
55
+ }));
56
+ const result = await host.reloadFsSkills();
57
+ if (result.ok) {
58
+ host.setState(prev => ({ _skillsModal: { ...prev._skillsModal, skills: mergePreservingOrder(prev._skillsModal.skills, result.skills) } }));
59
+ }
60
+ } catch (e) {
61
+ host.setState(prev => ({ _skillsModal: { ...prev._skillsModal, skills: prev._skillsModal.skills.map(flipEnabled(!enable)) } }));
62
+ message.error(t('ui.skillToggleFailed', { reason: e.message }));
63
+ } finally {
64
+ host.setState(prev => {
65
+ const next = new Set(prev._skillsModal.toggling); next.delete(key);
66
+ return { _skillsModal: { ...prev._skillsModal, toggling: next } };
67
+ });
68
+ }
69
+ }
70
+
71
+ // 永久删除单个 skill:成功后乐观移除该行 + reload 保位对齐。
72
+ // reload 合并回 _skillsModal.skills 很关键:否则删掉「重复对」其中一份后,存活那行仍带旧
73
+ // duplicate ⚠ 徽标,要等关弹窗重开才消失(listSkills 重算后该标记自然清掉)。
74
+ export async function handleSkillDelete(host, skill) {
75
+ const key = skillKey(skill);
76
+ // 复用 toggling Set 作「该行操作进行中」标记:防二次确认后的连点重入,
77
+ // 并让 SkillsManagerModal 在删除期间给删除按钮转圈 + 禁用同行 Switch(与 toggle 一致的忙态反馈)。
78
+ if (host.state._skillsModal?.toggling?.has(key)) return;
79
+ host.setState(prev => {
80
+ const next = new Set(prev._skillsModal.toggling); next.add(key);
81
+ return { _skillsModal: { ...prev._skillsModal, toggling: next } };
82
+ });
83
+ try {
84
+ const r = await fetch(apiUrl('/api/skills/delete'), {
85
+ method: 'POST',
86
+ headers: { 'Content-Type': 'application/json' },
87
+ body: JSON.stringify({ source: skill.source, name: skill.name, enabled: skill.enabled }),
88
+ });
89
+ const data = await r.json().catch(() => ({}));
90
+ if (!r.ok) {
91
+ message.error(t('ui.skillDeleteFailed', { reason: data.error || 'unknown' }));
92
+ return;
93
+ }
94
+ message.success(t('ui.skillDeleteSuccess', { name: skill.name }));
95
+ host.setState(prev => ({
96
+ _skillsModal: { ...prev._skillsModal, skills: (prev._skillsModal.skills || []).filter(s => skillKey(s) !== key) },
97
+ _fsSkills: Array.isArray(prev._fsSkills) ? prev._fsSkills.filter(s => skillKey(s) !== key) : prev._fsSkills,
98
+ }));
99
+ const result = await host.reloadFsSkills();
100
+ if (result.ok) {
101
+ host.setState(prev => ({ _skillsModal: { ...prev._skillsModal, skills: mergePreservingOrder(prev._skillsModal.skills, result.skills) } }));
102
+ }
103
+ } catch (e) {
104
+ message.error(t('ui.skillDeleteFailed', { reason: e.message }));
105
+ } finally {
106
+ host.setState(prev => {
107
+ const next = new Set(prev._skillsModal.toggling); next.delete(key);
108
+ return { _skillsModal: { ...prev._skillsModal, toggling: next } };
109
+ });
110
+ }
111
+ }
@@ -0,0 +1,116 @@
1
+ // Claude Code 硬编码的 builtin skill 名单(本地无文件、不可禁用)。
2
+ // 与 server/lib/skills-api.js 的 BUILTIN_NAMES 保持一致;前端用于 popover chip 过滤。
3
+ export const BUILTIN_SKILL_NAMES = new Set([
4
+ 'update-config', 'keybindings-help', 'simplify', 'fewer-permission-prompts',
5
+ 'loop', 'schedule', 'claude-api', 'init', 'review', 'security-review',
6
+ ]);
7
+
8
+ // 把 /api/skills 返回的 skill 对象映射到 Claude Code system-reminder 里的显示名。
9
+ // 插件 skill: `<pluginShort>:<skillDir>`;其它源用裸名。pluginName 形如 `short@marketplace`。
10
+ export function skillToDisplayName(s) {
11
+ if (!s || typeof s.name !== 'string') return '';
12
+ if (s.source === 'plugin' && typeof s.pluginName === 'string' && s.pluginName.includes('@')) {
13
+ const short = s.pluginName.split('@')[0];
14
+ if (short) return `${short}:${s.name}`;
15
+ }
16
+ return s.name;
17
+ }
18
+
19
+ // Skill 的稳定唯一标识——用于开关弹窗的 toggling Set key、乐观更新匹配、reload 排序 orderMap、
20
+ // 以及 React 渲染 key。可切换 skill(user/project)用其磁盘 path:同名两份分别住在 skills/ 与
21
+ // skills-skip/,路径天然不同,避免「同名同 source」共用 key 导致两行串台(一起转圈/一起翻转)。
22
+ // builtin(path:null)回落 source-name(名字唯一),仍稳定,供只读 chip 的排序兜底。
23
+ export function skillKey(s) {
24
+ return (s && s.path) ? s.path : `${s?.source}-${s?.name}`;
25
+ }
26
+
27
+ // 跨「开关」稳定的标识——只用 source+name(开关把 skill 在 skills/ ↔ skills-skip/ 间搬,path 会变,
28
+ // 但 source/name 不变)。专用于「toggle 后保位」:开关一个 skill 后它在列表里原地不动,不会因为 path
29
+ // 变了而被 orderMap 当成新条目甩到末尾。与 skillKey(含 path、用于重复态去重)刻意不同。
30
+ export function skillOrderKey(s) {
31
+ return `${s?.source}-${s?.name}`;
32
+ }
33
+
34
+ // 弹窗「默认排序」——仅在打开面板时套用(开关期间不重排,避免抖动让用户找不到 skill)。
35
+ // 规则:项目级(project) 排在用户级(user) 之前,其余源其后;同源按名字 localeCompare 保证确定性
36
+ // (彻底消除每次刷新 readdir 顺序漂移导致的乱序)。返回新数组,不改入参。
37
+ const SKILL_SOURCE_RANK = { project: 0, user: 1, plugin: 2, builtin: 3 };
38
+ export function sortSkillsDefault(skills) {
39
+ if (!Array.isArray(skills)) return [];
40
+ return [...skills].sort((a, b) => {
41
+ const ra = SKILL_SOURCE_RANK[a?.source] ?? 9;
42
+ const rb = SKILL_SOURCE_RANK[b?.source] ?? 9;
43
+ if (ra !== rb) return ra - rb;
44
+ return String(a?.name || '').localeCompare(String(b?.name || ''));
45
+ });
46
+ }
47
+
48
+ // 把 /api/skills 的权威结果与历史 system-reminder 解析结果合并为「当前在用」chip 列表。
49
+ // - fsSkills=null → 返回 null,由调用方回退到历史解析
50
+ // - 只保留 enabled=true 且非 builtin 的条目
51
+ // - description 优先 fs(SKILL.md frontmatter),缺失时回退历史 system-reminder 的 desc,再缺则空串
52
+ // - 按显示名去重(user/project 同名只保留最后一条;plugin 因为带前缀天然不冲突)
53
+ export function mergeActiveSkills(fsSkills, historicalSkills) {
54
+ if (!Array.isArray(fsSkills)) return null;
55
+ const descMap = new Map((historicalSkills || []).map(s => [s.name, s.description]));
56
+ const out = new Map();
57
+ for (const s of fsSkills) {
58
+ if (!s || !s.enabled || s.source === 'builtin') continue;
59
+ const name = skillToDisplayName(s);
60
+ if (!name || BUILTIN_SKILL_NAMES.has(name)) continue;
61
+ out.set(name, { name, description: s.description || descMap.get(name) || '' });
62
+ }
63
+ return [...out.values()];
64
+ }
65
+
66
+ // 从 <system-reminder> 内部文本里抽出 skills 列表。
67
+ // 识别 header 句 "skills are available for use with the Skill tool" 后,按行扫描
68
+ // `- <name>: <desc>` 格式;name 内允许冒号(plugin:foo / skill-creator:skill-creator
69
+ // 都按原样保留),按首个 ': '(冒号+空格)切分。description 可跨多行,遇空行 / 下一个 `- `
70
+ // flush;遇非 dash 非续行非空文本视为列表结束。
71
+ //
72
+ // 纯函数,无传递依赖 —— 可直接被 node --test 导入(避免 src/utils/contentFilter.js
73
+ // 里缺 .js 后缀的 import 引起的 Node ESM resolver 失败)。
74
+ export function parseLoadedSkills(innerText) {
75
+ if (typeof innerText !== 'string') return [];
76
+ const headerIdx = innerText.indexOf('skills are available for use with the Skill tool');
77
+ if (headerIdx < 0) return [];
78
+ const lineStart = innerText.indexOf('\n', headerIdx);
79
+ if (lineStart < 0) return [];
80
+ const tail = innerText.slice(lineStart + 1);
81
+ const lines = tail.split(/\r?\n/);
82
+ const skills = [];
83
+ let current = null;
84
+ const flush = () => {
85
+ if (current && current.name) skills.push(current);
86
+ current = null;
87
+ };
88
+ for (const line of lines) {
89
+ if (/^-\s+/.test(line)) {
90
+ flush();
91
+ const rest = line.replace(/^-\s+/, '');
92
+ const colonSpaceIdx = rest.indexOf(': ');
93
+ if (colonSpaceIdx > 0) {
94
+ current = {
95
+ name: rest.slice(0, colonSpaceIdx).trim(),
96
+ description: rest.slice(colonSpaceIdx + 2).trim(),
97
+ };
98
+ } else {
99
+ const name = rest.replace(/:\s*$/, '').trim();
100
+ current = { name, description: '' };
101
+ }
102
+ } else if (current && line.trim()) {
103
+ // 续行:保留换行符。弹窗用 white-space: pre-wrap 渲染,
104
+ // 让多段落 description(如 test-spec-designer 的触发场景/关键词)保持可读的结构。
105
+ current.description = current.description
106
+ ? current.description + '\n' + line.trim()
107
+ : line.trim();
108
+ } else if (!line.trim()) {
109
+ flush();
110
+ } else {
111
+ break;
112
+ }
113
+ }
114
+ flush();
115
+ return skills;
116
+ }
@@ -0,0 +1,80 @@
1
+ import { t } from '../i18n.js';
2
+
3
+ // 命令清单与 Claude Code CLI upstream 公开 slash 命令对齐;upstream 增删命令时
4
+ // 需手动同步本表 + src/i18n.js 的 "ui.slashCommand.*" 翻译块。未在表中的命令
5
+ // (含用户自定义 ~/.claude/commands/*.md)→ 返回 null → 调用方回落到原文渲染。
6
+ // 命令大小写敏感(与 CLI 保持一致),exact-match 不做大小写规约。
7
+ // 翻译歧义点见 src/i18n.js "ui.slashCommand.*" 块顶部注释(单一信息源)。
8
+ const COMMAND_KEYS = Object.freeze({
9
+ '/clear': 'ui.slashCommand.clear',
10
+ '/compact': 'ui.slashCommand.compact',
11
+ '/theme': 'ui.slashCommand.theme',
12
+ '/cost': 'ui.slashCommand.cost',
13
+ '/usage': 'ui.slashCommand.usage',
14
+ '/context': 'ui.slashCommand.context',
15
+ '/model': 'ui.slashCommand.model',
16
+ '/effort': 'ui.slashCommand.effort',
17
+ '/login': 'ui.slashCommand.login',
18
+ '/logout': 'ui.slashCommand.logout',
19
+ '/status': 'ui.slashCommand.status',
20
+ '/help': 'ui.slashCommand.help',
21
+ '/init': 'ui.slashCommand.init',
22
+ '/agents': 'ui.slashCommand.agents',
23
+ '/config': 'ui.slashCommand.config',
24
+ '/memory': 'ui.slashCommand.memory',
25
+ '/permissions': 'ui.slashCommand.permissions',
26
+ '/hooks': 'ui.slashCommand.hooks',
27
+ '/plugins': 'ui.slashCommand.plugins',
28
+ '/release-notes': 'ui.slashCommand.releaseNotes',
29
+ '/upgrade': 'ui.slashCommand.upgrade',
30
+ '/bug': 'ui.slashCommand.bug',
31
+ '/doctor': 'ui.slashCommand.doctor',
32
+ '/mcp': 'ui.slashCommand.mcp',
33
+ '/vim': 'ui.slashCommand.vim',
34
+ '/export': 'ui.slashCommand.export',
35
+ '/pr-comments': 'ui.slashCommand.prComments',
36
+ '/review': 'ui.slashCommand.review',
37
+ '/security-review': 'ui.slashCommand.securityReview',
38
+ '/ide': 'ui.slashCommand.ide',
39
+ '/resume': 'ui.slashCommand.resume',
40
+ '/terminal-setup': 'ui.slashCommand.terminalSetup',
41
+ '/migrate-installer': 'ui.slashCommand.migrateInstaller',
42
+ });
43
+
44
+ // 裸命令名(如 "/model")+ 可选参数(如 "/model opus")。
45
+ // 参数前只允许 ASCII 空格/制表符 — 避免 U+2028/U+2029 等异常空白被
46
+ // 当成参数分隔符绕过多行守卫。
47
+ const COMMAND_PATTERN = /^(\/[a-z][a-z0-9-]*)([ \t]+.*)?$/;
48
+ // 拒任意 Unicode 换行(LF / CR / U+2028 LINE SEP / U+2029 PARAGRAPH SEP)。
49
+ const ANY_LINEBREAK = new RegExp('[\\n\\r\\u2028\\u2029]');
50
+ // bidi-control(U+202A LRE / U+202B RLE / U+202C PDF / U+202D LRO / U+202E RLO /
51
+ // U+2066 LRI / U+2067 RLI / U+2068 FSI / U+2069 PDI):进入气泡文本会翻转剩余
52
+ // 字符视觉方向,撕裂气泡/相邻消息布局。在拼接 rest 前 strip 掉。
53
+ const BIDI_CONTROL = new RegExp('[\\u202A-\\u202E\\u2066-\\u2069]', 'g');
54
+
55
+ // 内部 matcher:返回 { cmd, rest } 或 null。两个 export 共享同一套校验逻辑,
56
+ // 不引入第二份 regex 路径。
57
+ function matchCommand(text) {
58
+ if (typeof text !== 'string') return null;
59
+ if (ANY_LINEBREAK.test(text)) return null;
60
+ const trimmed = text.trim();
61
+ const match = COMMAND_PATTERN.exec(trimmed);
62
+ if (!match) return null;
63
+ const cmd = match[1];
64
+ if (!COMMAND_KEYS[cmd]) return null;
65
+ return { cmd, rest: (match[2] || '').replace(BIDI_CONTROL, '') };
66
+ }
67
+
68
+ export function getSlashCommandLabel(text) {
69
+ const m = matchCommand(text);
70
+ if (!m) return null;
71
+ const label = t(COMMAND_KEYS[m.cmd]);
72
+ return m.rest ? `${label}${m.rest}` : label;
73
+ }
74
+
75
+ // 返回裸命令(如 "/model"),用于 Tooltip 等次要展示位,避免参数(可能含
76
+ // /login <token> 等敏感数据)在 hover/title 处被泄漏。
77
+ export function getSlashCommandTooltip(text) {
78
+ const m = matchCommand(text);
79
+ return m ? m.cmd : null;
80
+ }
@@ -0,0 +1,73 @@
1
+ /**
2
+ * Pure geometry for the ChatView terminal/sidebar drag-resize with snap lines.
3
+ * Extracted from ChatView's handleSplitMouseDown/handleSidebarMouseDown so the
4
+ * math is unit-testable; the drag lifecycle (document listeners, body cursor)
5
+ * lives in chat/controllers/splitDragController.js.
6
+ *
7
+ * Snap-line object shape is shared by both axes and consumed by
8
+ * SnapLineOverlay: { cols, terminalPx, linePosition }. For the sidebar axis
9
+ * cols/terminalPx/linePosition are all the same pixel width (legacy shape).
10
+ */
11
+
12
+ // Terminal font is 13px Menlo/Monaco; a character cell is ~7.8px wide.
13
+ export const TERMINAL_CHAR_WIDTH = 7.8;
14
+ export const TERMINAL_SNAP_COLS = [60, 80, 100, 120];
15
+ export const SIDEBAR_SNAP_WIDTHS = [180, 240, 300, 360];
16
+ export const SNAP_THRESHOLD_PX = 60;
17
+ export const RESIZER_WIDTH_PX = 5;
18
+
19
+ /**
20
+ * Snap lines for the terminal split, positioned so the terminal lands on a
21
+ * standard column count. Lines outside the usable band (terminal wider than
22
+ * 75% or narrower than 15% of the container) are dropped.
23
+ */
24
+ export function computeTerminalSnapLines(containerWidth, {
25
+ charWidth = TERMINAL_CHAR_WIDTH,
26
+ cols = TERMINAL_SNAP_COLS,
27
+ resizerWidth = RESIZER_WIDTH_PX,
28
+ } = {}) {
29
+ if (!containerWidth || containerWidth <= 0) return [];
30
+ return cols.map(c => {
31
+ const terminalPx = c * charWidth;
32
+ const totalTerminalWidth = terminalPx + resizerWidth;
33
+ if (totalTerminalWidth > containerWidth * 0.75 || totalTerminalWidth < containerWidth * 0.15) return null;
34
+ // Line position = container width - terminal pixel width - resizer width.
35
+ return { cols: c, terminalPx, linePosition: containerWidth - terminalPx - resizerWidth };
36
+ }).filter(snap => snap !== null);
37
+ }
38
+
39
+ /** Sidebar snap lines; widths at or beyond 40% of the container are dropped. */
40
+ export function computeSidebarSnapLines(containerWidth, widths = SIDEBAR_SNAP_WIDTHS) {
41
+ if (!containerWidth || containerWidth <= 0) return [];
42
+ return widths
43
+ .filter(w => w < containerWidth * 0.4)
44
+ .map(w => ({ cols: w, terminalPx: w, linePosition: w }));
45
+ }
46
+
47
+ /** Terminal width from a drag position: right-edge anchored, 200px..75% band. */
48
+ export function clampTerminalWidth(rectRight, clientX, containerWidth) {
49
+ return Math.max(200, Math.min(containerWidth * 0.75, rectRight - clientX));
50
+ }
51
+
52
+ /** Sidebar width from a drag position: left-edge anchored, 160px..40% band. */
53
+ export function clampSidebarWidth(clientX, rectLeft, containerWidth) {
54
+ return Math.max(160, Math.min(containerWidth * 0.4, clientX - rectLeft));
55
+ }
56
+
57
+ /**
58
+ * Nearest snap line to `position` (already in the same coordinate space as
59
+ * linePosition), or null when none is within the threshold.
60
+ */
61
+ export function findActiveSnapLine(position, snapLines, threshold = SNAP_THRESHOLD_PX) {
62
+ if (!snapLines || snapLines.length === 0) return null;
63
+ let minDistance = Infinity;
64
+ let closestSnap = null;
65
+ for (const snap of snapLines) {
66
+ const distance = Math.abs(position - snap.linePosition);
67
+ if (distance < minDistance) {
68
+ minDistance = distance;
69
+ closestSnap = snap;
70
+ }
71
+ }
72
+ return (closestSnap && minDistance < threshold) ? closestSnap : null;
73
+ }