zen-gitsync 2.17.29 → 2.17.30

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 (135) hide show
  1. package/README.md +38 -6
  2. package/package.json +5 -2
  3. package/scripts/release.js +175 -32
  4. package/src/cli/ai/agent.js +1 -1
  5. package/src/cli/ai/extensions.js +1 -1
  6. package/src/cli/ai/mcp.js +6 -5
  7. package/src/cli/ai/mcp.test.js +14 -2
  8. package/src/cli/ai/skills.js +8 -5
  9. package/src/cli/ai/skills.test.js +14 -2
  10. package/src/paths.js +26 -7
  11. package/src/ui/public/assets/AgentEngineSelector-DMqqMLhx.js +1 -0
  12. package/src/ui/public/assets/AgentEngineSelector-DR3r5eQE.css +1 -0
  13. package/src/ui/public/assets/AgentView-B0wVzLSW.js +1 -0
  14. package/src/ui/public/assets/AgentView-D87GB-H9.css +1 -0
  15. package/src/ui/public/assets/{AppVersionBadge-DDYQqoy7.css → AppVersionBadge--5If494j.css} +1 -1
  16. package/src/ui/public/assets/AppVersionBadge-Cc1kJ0D9.js +2 -0
  17. package/src/ui/public/assets/{BranchSelector-F4ibldMN.css → BranchSelector-BTCMYwG0.css} +1 -1
  18. package/src/ui/public/assets/BranchSelector-CLWTSyiv.js +1 -0
  19. package/src/ui/public/assets/CommitForm-C-cL8_vz.css +1 -0
  20. package/src/ui/public/assets/CommitForm-xhy_saX5.js +6 -0
  21. package/src/ui/public/assets/CommonDialog-Bzi5ISqT.css +1 -0
  22. package/src/ui/public/assets/CommonDialog-DeSPG5C-.js +1 -0
  23. package/src/ui/public/assets/EditorView-BsuQfcO4.css +1 -0
  24. package/src/ui/public/assets/EditorView-DOBl6sqW.js +1 -0
  25. package/src/ui/public/assets/FileDiffViewer-B6zy6NiA.css +1 -0
  26. package/src/ui/public/assets/FileDiffViewer-YaOsJZZI.js +14 -0
  27. package/src/ui/public/assets/FlowExecutionViewer-B5kh7VxS.css +1 -0
  28. package/src/ui/public/assets/FlowExecutionViewer-BkkeKggj.js +1 -0
  29. package/src/ui/public/assets/FlowOrchestrationWorkspace-B95bYCEf.js +21 -0
  30. package/src/ui/public/assets/FlowOrchestrationWorkspace-CM2_9TW3.css +1 -0
  31. package/src/ui/public/assets/IconButton-DI4O8Y_p.js +1 -0
  32. package/src/ui/public/assets/IconButton-W2X09OKH.css +1 -0
  33. package/src/ui/public/assets/LogList-DLxxk9NX.css +1 -0
  34. package/src/ui/public/assets/LogList-DiIWwVCn.js +7 -0
  35. package/src/ui/public/assets/MindmapView-BFK6XzVU.js +1 -0
  36. package/src/ui/public/assets/MindmapView-C4xg3TwX.css +1 -0
  37. package/src/ui/public/assets/MonitorView-BWOQfPtI.css +1 -0
  38. package/src/ui/public/assets/MonitorView-Blsb4ZtW.js +1 -0
  39. package/src/ui/public/assets/ProjectStartupButton-DiN1xs-u.js +1 -0
  40. package/src/ui/public/assets/ProjectStartupButton-VoQqFee4.css +1 -0
  41. package/src/ui/public/assets/RecentDirectoriesChat-DD-qyYN0.js +1 -0
  42. package/src/ui/public/assets/RecentDirectoriesChat-o4xK1Koc.css +1 -0
  43. package/src/ui/public/assets/RemoteManagerDialog-BSOph2h2.css +1 -0
  44. package/src/ui/public/assets/RemoteManagerDialog-D_rJHy9p.js +1 -0
  45. package/src/ui/public/assets/RemoteRepoCard-B98ZUYrp.js +1 -0
  46. package/src/ui/public/assets/RemoteRepoCard-CG9WPcA_.css +1 -0
  47. package/src/ui/public/assets/SourceMapView-CO_BZJKu.css +1 -0
  48. package/src/ui/public/assets/SourceMapView-DCLg5IHN.js +3 -0
  49. package/src/ui/public/assets/{SvgIcon-CDNsk4-Q.css → SvgIcon-BQ6bR7l0.css} +1 -1
  50. package/src/ui/public/assets/SvgIcon-Dg1c0l0q.js +1 -0
  51. package/src/ui/public/assets/TemplateManager-ClQb9yY3.js +1 -0
  52. package/src/ui/public/assets/TemplateManager-DiM3vrbf.css +1 -0
  53. package/src/ui/public/assets/UserInputNode-d-oRXM_J.css +1 -0
  54. package/src/ui/public/assets/UserInputNode-oLCCf_Bn.js +1 -0
  55. package/src/ui/public/assets/WorkbenchView-BljYNp6b.css +1 -0
  56. package/src/ui/public/assets/WorkbenchView-C-yJWhrO.js +20 -0
  57. package/src/ui/public/assets/{_plugin-vue_export-helper-BKeZSroN.js → _plugin-vue_export-helper-OGa67zj-.js} +3 -3
  58. package/src/ui/public/assets/{agentConversations-ChTc-LkQ.js → agentConversations-DaJcYEPn.js} +1 -1
  59. package/src/ui/public/assets/{configStore-fJaic3kE.js → configStore-BrdhjW5U.js} +1 -1
  60. package/src/ui/public/assets/element-plus-Cfu0ZUp9.js +28 -0
  61. package/src/ui/public/assets/flow-mindmap-C_fqj8ZL.js +257 -0
  62. package/src/ui/public/assets/{gitStore-D9oF5nof.js → gitStore-BgvCpppG.js} +1 -1
  63. package/src/ui/public/assets/index-BgO3-PhT.js +29 -0
  64. package/src/ui/public/assets/{index-DVpFh9xE.css → index-BrIl1iaG.css} +1 -1
  65. package/src/ui/public/assets/office-docx-rxiawIim.js +31 -0
  66. package/src/ui/public/assets/{office-excel-CXD9VwgB.js → office-excel-IkgTJC0h.js} +1 -1
  67. package/src/ui/public/assets/office-pptx-CyxidPZ2.js +60 -0
  68. package/src/ui/public/assets/previewSandbox-NWN_GKkA.js +9 -0
  69. package/src/ui/public/assets/{previewSandbox-PYnM6Paz.css → previewSandbox-SqNQvuHv.css} +1 -1
  70. package/src/ui/public/assets/socket-io-TBtw0msS.js +1 -0
  71. package/src/ui/public/assets/{toolsStore-C89takm3.js → toolsStore-DrTCC32p.js} +1 -1
  72. package/src/ui/public/assets/{useThemeObserver-BvSA7RDh.js → useThemeObserver-qsxO0WJ7.js} +1 -1
  73. package/src/ui/public/assets/{vendor-CCrjQw5C.js → vendor-sxw_ye8l.js} +173 -180
  74. package/src/ui/public/assets/vue-flow-Cjr_ADp5.js +39 -0
  75. package/src/ui/public/index.html +24 -24
  76. package/src/ui/server/routes/workbench/agentMarketplace.js +12 -10
  77. package/src/ui/server/routes/workbench/agentMarketplace.test.js +18 -9
  78. package/src/ui/server/routes/workbench/envContext.js +16 -0
  79. package/src/ui/server/routes/workbench/envContext.test.js +17 -0
  80. package/src/ui/server/routes/workbench/index.js +84 -0
  81. package/src/ui/server/routes/workbench/jobImage.js +141 -0
  82. package/src/ui/server/routes/workbench/jobImage.test.js +153 -0
  83. package/src/utils/desktopNotify.js +217 -0
  84. package/src/utils/desktopNotify.test.js +122 -0
  85. package/src/ui/public/assets/AgentEngineSelector-DUf0TfaU.css +0 -1
  86. package/src/ui/public/assets/AgentEngineSelector-Wl7dKYKL.js +0 -2
  87. package/src/ui/public/assets/AgentView-Bebv5xk6.css +0 -1
  88. package/src/ui/public/assets/AgentView-CebQ3545.js +0 -11
  89. package/src/ui/public/assets/AppVersionBadge-jm8ejuZ-.js +0 -2
  90. package/src/ui/public/assets/BranchSelector-CcNILIbg.js +0 -1
  91. package/src/ui/public/assets/CommitForm-1ocxo8Cb.js +0 -6
  92. package/src/ui/public/assets/CommitForm-4kI2csgs.css +0 -1
  93. package/src/ui/public/assets/CommonDialog-DpFTO5cg.js +0 -1
  94. package/src/ui/public/assets/CommonDialog-w6EmBEXv.css +0 -1
  95. package/src/ui/public/assets/EditorView-CftLj8-x.css +0 -1
  96. package/src/ui/public/assets/EditorView-CtRHV1Sv.js +0 -37
  97. package/src/ui/public/assets/FileDiffViewer-DXJO-G55.js +0 -15
  98. package/src/ui/public/assets/FileDiffViewer-n5ea9qMe.css +0 -1
  99. package/src/ui/public/assets/FlowExecutionViewer-d4O5Y9Ca.js +0 -1
  100. package/src/ui/public/assets/FlowExecutionViewer-jNHI5zdm.css +0 -1
  101. package/src/ui/public/assets/FlowOrchestrationWorkspace-CKLRMeaL.js +0 -21
  102. package/src/ui/public/assets/FlowOrchestrationWorkspace-DwuSSxJ1.css +0 -1
  103. package/src/ui/public/assets/IconButton-Dl1cdP_x.js +0 -1
  104. package/src/ui/public/assets/IconButton-RlXqKOeu.css +0 -1
  105. package/src/ui/public/assets/LogList-CdDRC_CO.js +0 -7
  106. package/src/ui/public/assets/LogList-XspUhx-C.css +0 -1
  107. package/src/ui/public/assets/MindmapView-Diq2Cu4O.css +0 -1
  108. package/src/ui/public/assets/MindmapView-U84MWPHc.js +0 -2
  109. package/src/ui/public/assets/MonitorView-DwUwJXnY.css +0 -1
  110. package/src/ui/public/assets/MonitorView-spOV0cP7.js +0 -1
  111. package/src/ui/public/assets/ProjectStartupButton-BbAbfFn2.js +0 -1
  112. package/src/ui/public/assets/ProjectStartupButton-CB8IOg1E.css +0 -1
  113. package/src/ui/public/assets/RecentDirectoriesChat-BMkc7fS1.css +0 -1
  114. package/src/ui/public/assets/RecentDirectoriesChat-CxNjjgVG.js +0 -3
  115. package/src/ui/public/assets/RemoteManagerDialog-BVm2-Hn4.js +0 -4
  116. package/src/ui/public/assets/RemoteManagerDialog-D4WphMtO.css +0 -1
  117. package/src/ui/public/assets/RemoteRepoCard-UHK_hrDS.js +0 -7
  118. package/src/ui/public/assets/RemoteRepoCard-o01IrbVz.css +0 -1
  119. package/src/ui/public/assets/SourceMapView-CfDydtsu.css +0 -1
  120. package/src/ui/public/assets/SourceMapView-PHMr0wt3.js +0 -3
  121. package/src/ui/public/assets/SvgIcon-Dk7WnZLB.js +0 -1
  122. package/src/ui/public/assets/TemplateManager-B3ZNTXnP.js +0 -1
  123. package/src/ui/public/assets/TemplateManager-DO7EPfj8.css +0 -1
  124. package/src/ui/public/assets/UserInputNode-DDNSarag.css +0 -1
  125. package/src/ui/public/assets/UserInputNode-DeAD7H0z.js +0 -1
  126. package/src/ui/public/assets/WorkbenchView-DsZ2ONpW.js +0 -143
  127. package/src/ui/public/assets/WorkbenchView-kZ8DoH2l.css +0 -1
  128. package/src/ui/public/assets/element-plus-D0R8BMj4.js +0 -48
  129. package/src/ui/public/assets/flow-mindmap-Doi70WWn.js +0 -257
  130. package/src/ui/public/assets/index-CKxygqdx.js +0 -148
  131. package/src/ui/public/assets/office-docx-O0YXk5gA.js +0 -32
  132. package/src/ui/public/assets/office-pptx-CiF2OlTq.js +0 -74
  133. package/src/ui/public/assets/previewSandbox-21uStCdd.js +0 -9
  134. package/src/ui/public/assets/socket-io-BFqQXqAp.js +0 -1
  135. package/src/ui/public/assets/vue-flow-5Aq2bwoG.js +0 -39
@@ -0,0 +1,153 @@
1
+ // Copyright 2026 xz333221
2
+ //
3
+ // Licensed under the Apache License, Version 2.0 (the "License");
4
+ // you may not use this file except in compliance with the License.
5
+ // You may obtain a copy of the License at
6
+ //
7
+ // http://www.apache.org/licenses/LICENSE-2.0
8
+ //
9
+ // Unless required by applicable law or agreed to in writing, software
10
+ // distributed under the License is distributed on an "AS IS" BASIS,
11
+ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ // See the License for the specific language governing permissions and
13
+ // limitations under the License.
14
+ //
15
+ // 正文内嵌图片的路径判定单测(jobImage.js)。
16
+ //
17
+ // 这个模块是 `/api/workbench/jobs/:id/image` 的**权限边界**:判错一条
18
+ // (比如 `..` 没挡住、或把兄弟目录当成了子目录)就等于把整台机器的任意文件
19
+ // 开了个读取口子。而它又是纯字符串/路径运算 —— 正是那种"看着对、边界一塌糊涂"
20
+ // 的地方,所以边界用例逐条钉在这里。
21
+ //
22
+ // 路径夹具一律用 `path.resolve` 造,不写死 `C:\…`:这份测试在 Windows 和
23
+ // Linux 上都要跑得通(盘符相关的用例单独按 process.platform 分支)。
24
+
25
+ import { test } from 'node:test';
26
+ import assert from 'node:assert/strict';
27
+ import path from 'path';
28
+
29
+ import {
30
+ normalizeImagePath,
31
+ imageExtOf,
32
+ isInsideRoot,
33
+ resolveJobImagePath,
34
+ IMAGE_PATH_ERRORS,
35
+ IMAGE_PATH_STATUS,
36
+ } from './jobImage.js';
37
+
38
+ const ROOT = path.resolve(path.join('/tmp', 'ws', 'repo'));
39
+ const inside = (...seg) => path.join(ROOT, ...seg);
40
+
41
+ test('相对路径按仓库根解析(模型常写 docs/screenshots/a.png)', () => {
42
+ const r = resolveJobImagePath({ root: ROOT, requested: 'docs/screenshots/a.png' });
43
+ assert.equal(r.ok, true);
44
+ assert.equal(r.absPath, inside('docs', 'screenshots', 'a.png'));
45
+ assert.equal(r.ext, 'png');
46
+ assert.equal(r.mime, 'image/png');
47
+ });
48
+
49
+ test('仓库内的绝对路径原样通过', () => {
50
+ const abs = inside('docs', 'b.JPEG');
51
+ const r = resolveJobImagePath({ root: ROOT, requested: abs });
52
+ assert.equal(r.ok, true);
53
+ assert.equal(r.absPath, abs);
54
+ // 后缀判定不区分大小写,否则 `A.PNG` 会被当成"不是图片"
55
+ assert.equal(r.ext, 'jpeg');
56
+ assert.equal(r.mime, 'image/jpeg');
57
+ });
58
+
59
+ test('`..` 穿越一律挡掉(相对与绝对两种写法)', () => {
60
+ for (const bad of [
61
+ '../secret.png',
62
+ path.join('docs', '..', '..', 'secret.png'),
63
+ path.resolve(ROOT, '..', 'secret.png'),
64
+ path.resolve(ROOT, '..', '..', 'windows', 'system32', 'x.png'),
65
+ ]) {
66
+ const r = resolveJobImagePath({ root: ROOT, requested: bad });
67
+ assert.equal(r.ok, false, `应当拒绝: ${bad}`);
68
+ assert.equal(r.reason, 'outside-root');
69
+ }
70
+ });
71
+
72
+ test('兄弟目录不是子目录(前缀相同也不行)', () => {
73
+ // 用 startsWith(root) 判权限的经典漏洞:D:\ws\repo-old 会被当成 D:\ws\repo 内部
74
+ const sibling = `${ROOT}-old${path.sep}a.png`;
75
+ assert.equal(isInsideRoot(ROOT, sibling), false);
76
+ const r = resolveJobImagePath({ root: ROOT, requested: sibling });
77
+ assert.equal(r.ok, false);
78
+ assert.equal(r.reason, 'outside-root');
79
+ });
80
+
81
+ test('仓库根自身不是文件', () => {
82
+ // 目录不是文件 —— realpath 那一步也会拦,但这一层先判掉,省一次读盘
83
+ assert.equal(isInsideRoot(ROOT, ROOT), false);
84
+ // 后缀为空的路径在更前面就被 bad-ext 拦了,走不到这里(两种拒绝都算正常)
85
+ assert.equal(resolveJobImagePath({ root: ROOT, requested: '.' }).ok, false);
86
+ });
87
+
88
+ test('只认图片后缀,其余一律 415', () => {
89
+ for (const bad of ['a.txt', 'a.md', 'a.exe', 'a', 'docs/.env', 'a.png.txt', 'a.png/../b.txt']) {
90
+ const r = resolveJobImagePath({ root: ROOT, requested: bad });
91
+ assert.equal(r.ok, false, `应当拒绝: ${bad}`);
92
+ assert.ok(r.reason === 'bad-ext' || r.reason === 'outside-root', `实际 reason=${r.reason}`);
93
+ }
94
+ // 明确挑一条纯后缀问题,别让上面那条断言把两种情况混着放过
95
+ assert.equal(resolveJobImagePath({ root: ROOT, requested: 'docs/a.txt' }).reason, 'bad-ext');
96
+ assert.equal(IMAGE_PATH_STATUS['bad-ext'], 415);
97
+ });
98
+
99
+ test('空值 / 空白 / NUL 字节不往下走', () => {
100
+ for (const bad of ['', ' ', null, undefined, 123, {}, 'a\u0000.png']) {
101
+ assert.equal(resolveJobImagePath({ root: ROOT, requested: bad }).reason, 'empty');
102
+ }
103
+ });
104
+
105
+ test('仓库路径缺失时不猜、直接拒', () => {
106
+ for (const root of ['', ' ', null, undefined]) {
107
+ const r = resolveJobImagePath({ root, requested: inside('a.png') });
108
+ assert.equal(r.ok, false);
109
+ assert.equal(r.reason, 'no-root');
110
+ }
111
+ assert.equal(IMAGE_PATH_STATUS['no-root'], 400);
112
+ });
113
+
114
+ test('模型给的包裹写法要认(尖括号 / 反引号 / 引号 / file://)', () => {
115
+ assert.equal(normalizeImagePath(' <docs/a.png> '), 'docs/a.png');
116
+ assert.equal(normalizeImagePath('`docs/a.png`'), 'docs/a.png');
117
+ assert.equal(normalizeImagePath('"docs/a.png"'), 'docs/a.png');
118
+ assert.equal(normalizeImagePath("'docs/a.png'"), 'docs/a.png');
119
+ // 只脱**成对**的包裹:单个尖括号是模型打漏了,脱掉反而会拼出一个它没写过的路径
120
+ assert.equal(normalizeImagePath('<docs/a.png'), '<docs/a.png');
121
+
122
+ const viaFileUrl = normalizeImagePath(`file://${ROOT.replace(/\\/g, '/')}/a.png`);
123
+ const r = resolveJobImagePath({ root: ROOT, requested: `file://${ROOT.replace(/\\/g, '/')}/a.png` });
124
+ assert.equal(r.ok, true, `file:// 形式应当能解析,实际: ${JSON.stringify({ viaFileUrl, r })}`);
125
+ assert.equal(r.absPath, inside('a.png'));
126
+ });
127
+
128
+ test('win32 的盘符形式 file:///C:/… 不能把盘符当成相对目录', { skip: process.platform !== 'win32' }, () => {
129
+ // file:///C:/a.png → 去掉协议后是 /C:/a.png,多那个斜杠会让 resolve 把它当相对路径
130
+ assert.equal(normalizeImagePath('file:///C:/ws/a.png'), 'C:/ws/a.png');
131
+ assert.equal(normalizeImagePath('file:///c:\\ws\\a.png'), 'c:\\ws\\a.png');
132
+ });
133
+
134
+ test('跨盘符(win32)判为不在仓库内', { skip: process.platform !== 'win32' }, () => {
135
+ const r = resolveJobImagePath({ root: ROOT, requested: 'D:\\other\\a.png' });
136
+ assert.equal(r.ok, false);
137
+ assert.equal(r.reason, 'outside-root');
138
+ });
139
+
140
+ test('后缀提取:取最后一段,不把目录名当后缀', () => {
141
+ assert.equal(imageExtOf('a/b/c.PNG'), 'png');
142
+ assert.equal(imageExtOf('a.png/b'), '');
143
+ assert.equal(imageExtOf('a.'), '');
144
+ assert.equal(imageExtOf(''), '');
145
+ });
146
+
147
+ test('错误码与文案一一对应(路由按 reason 直接取,不用再猜)', () => {
148
+ for (const reason of Object.keys(IMAGE_PATH_ERRORS)) {
149
+ assert.ok(IMAGE_PATH_STATUS[reason], `reason「${reason}」缺状态码`);
150
+ }
151
+ // 越权是 403、后缀不对是 415:别都糊成 400,排查时看不出是"路径不合法"还是"没权限"
152
+ assert.equal(IMAGE_PATH_STATUS['outside-root'], 403);
153
+ });
@@ -0,0 +1,217 @@
1
+ // Copyright 2026 xz333221
2
+ //
3
+ // Licensed under the Apache License, Version 2.0 (the "License");
4
+ // you may not use this file except in compliance with the License.
5
+ // You may obtain a copy of the License at
6
+ //
7
+ // http://www.apache.org/licenses/LICENSE-2.0
8
+ //
9
+ // Unless required by applicable law or agreed to in writing, software
10
+ // distributed under the License is distributed on an "AS IS" BASIS,
11
+ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ // See the License for the specific language governing permissions and
13
+ // limitations under the License.
14
+ //
15
+ // 长任务结束后的"人不在终端前也能收到"的提醒:系统通知 + 提示音 + 终端标题。
16
+ //
17
+ // 为什么需要它:release 的"装全局"阶段实测能磨十几分钟(registry 元数据/对象就绪慢,
18
+ // 见 scripts/release.js 顶部那一长串注释),用户不可能一直盯着终端。终端里再醒目的一行
19
+ // "🎉 发布完成",对已经走去干别的事的人等于没打 —— 提醒必须走**终端之外**的信道。
20
+ //
21
+ // 三条信道各自独立、各自 best-effort,一条挂掉不影响另外两条:
22
+ // ① 系统通知(Windows toast / macOS osascript / Linux notify-send);
23
+ // ② 提示音(成功与失败**不同**的音 —— 人不在屏幕前时,响成什么样是唯一能分辨结果的信号);
24
+ // ③ 终端标题(任务栏上那行字变成结果,回头扫一眼就知道)。
25
+ //
26
+ // 硬约束(踩过的坑都在这三条里):
27
+ // - **绝不抛异常、绝不阻塞**:提醒是附加品,不许把一次成功的发布变成失败退出,
28
+ // 也不许让父进程等着它。派生进程一律 unref(),`main()` 该返回就返回。
29
+ // - spawn 一个不存在的命令(比如 Linux 上没装 notify-send)是走 `'error'` 事件而不是
30
+ // 抛异常 —— 不挂这个监听就是**未捕获异常**,会直接打挂发布流程。
31
+ // - 文案里带用户可控内容(版本号 / 报错原文 / 任务标题)时,**按目标信道各自转义**:
32
+ // toast 正文进的是 XML(要转义 `& < >`),PowerShell 走单引号字符串(内部单引号翻倍)。
33
+ // 漏了这两处不会报错,只会"什么都没弹出来",排查起来毫无线索。
34
+ import { spawn } from 'node:child_process'
35
+
36
+ // ---------------------------------------------------------------------------
37
+ // 转义
38
+ // ---------------------------------------------------------------------------
39
+
40
+ /**
41
+ * PowerShell 单引号字符串字面量。
42
+ *
43
+ * 单引号是 PS 里唯一"什么都不解释"的字符串形式(双引号里 `$` 和反引号会被展开),
44
+ * 所以文案一律走这里;内部单引号翻倍即可,换行 / 中文 / 反斜杠都不用额外处理。
45
+ */
46
+ export function psQuote(value) {
47
+ return `'${String(value ?? '').replace(/'/g, "''")}'`
48
+ }
49
+
50
+ /**
51
+ * XML 文本节点转义(toast 正文专用)。
52
+ *
53
+ * toast 正文是塞进 `XmlDocument.LoadXml()` 的 —— 报错原文里只要出现一个 `<`
54
+ * (例如 `Unexpected token < in JSON at position 0`),整份 XML 就解析失败,
55
+ * 结果是一条 toast 都弹不出来,而且 PowerShell 的错误还被我们吞掉了。
56
+ */
57
+ export function xmlEscape(value) {
58
+ const map = { '&': '&amp;', '<': '&lt;', '>': '&gt;' }
59
+ return String(value ?? '').replace(/[&<>]/g, (c) => map[c])
60
+ }
61
+
62
+ /**
63
+ * AppleScript 字符串字面量(osascript 用)。双引号字符串,转义 `\` 与 `"`。
64
+ */
65
+ export function appleScriptQuote(value) {
66
+ return `"${String(value ?? '').replace(/\\/g, '\\\\').replace(/"/g, '\\"')}"`
67
+ }
68
+
69
+ // ---------------------------------------------------------------------------
70
+ // Windows
71
+ // ---------------------------------------------------------------------------
72
+
73
+ // 用 PowerShell 自己的 AUMID 注册 toast —— 它是系统里**已经有快捷方式**的那一个,
74
+ // toast 因此能弹出来(归属显示为 Windows PowerShell)。自己造一个 AUMID 需要先往开始
75
+ // 菜单写 .lnk,发布脚本不该干那种事。
76
+ const POWERSHELL_AUMID =
77
+ '{1AC14E77-02E7-4E5D-B744-2EB1AE5198B7}\\WindowsPowerShell\\v1.0\\powershell.exe'
78
+
79
+ // 提示音:wav 名以 `%WINDIR%\Media` 下的文件名为准(zh-CN 的 Windows 11 上同样是
80
+ // 这几个英文名)。逐个试,一个都没有才退回 SystemSounds 的默认音。
81
+ const WIN_WAVS = {
82
+ ok: ['Windows Notify System Generic.wav', 'notify.wav', 'chimes.wav'],
83
+ fail: ['Windows Critical Stop.wav', 'Windows Battery Critical.wav', 'Alarm01.wav'],
84
+ }
85
+ const WIN_SYSTEM_SOUND = { ok: 'Asterisk', fail: 'Hand' }
86
+
87
+ function winSoundScript(ok) {
88
+ const kind = ok ? 'ok' : 'fail'
89
+ const candidates = WIN_WAVS[kind].map((f) => `(Join-Path $env:WINDIR 'Media\\${f}')`).join(', ')
90
+ return [
91
+ '$played = $false',
92
+ `foreach ($w in @(${candidates})) {`
93
+ + ' if (-not $played -and (Test-Path $w)) {'
94
+ + ' try { (New-Object System.Media.SoundPlayer $w).PlaySync(); $played = $true } catch { }'
95
+ + ' } }',
96
+ // 兜底:Play() 是异步的,不放这个 Start-Sleep 的话 PowerShell 会先于声音退出
97
+ `if (-not $played) { [System.Media.SystemSounds]::${WIN_SYSTEM_SOUND[kind]}.Play(); Start-Sleep -Milliseconds 700 }`,
98
+ ].join('; ')
99
+ }
100
+
101
+ function winToastScript(title, message) {
102
+ const xml = '<toast><visual><binding template="ToastGeneric">'
103
+ + `<text>${xmlEscape(title)}</text><text>${xmlEscape(message)}</text>`
104
+ + '</binding></visual></toast>'
105
+ return [
106
+ '[Windows.UI.Notifications.ToastNotificationManager, Windows.UI.Notifications, ContentType = WindowsRuntime] | Out-Null',
107
+ '[Windows.Data.Xml.Dom.XmlDocument, Windows.Data.Xml.Dom.XmlDocument, ContentType = WindowsRuntime] | Out-Null',
108
+ '$doc = New-Object Windows.Data.Xml.Dom.XmlDocument',
109
+ `$doc.LoadXml(${psQuote(xml)})`,
110
+ '$toast = [Windows.UI.Notifications.ToastNotification]::new($doc)',
111
+ `[Windows.UI.Notifications.ToastNotificationManager]::CreateToastNotifier(${psQuote(POWERSHELL_AUMID)}).Show($toast)`,
112
+ ].join('; ')
113
+ }
114
+
115
+ // ---------------------------------------------------------------------------
116
+ // 计划(纯函数 —— 单测只断言这里)
117
+ // ---------------------------------------------------------------------------
118
+
119
+ /**
120
+ * 按平台把"提醒一次"翻译成要跑的进程列表。
121
+ *
122
+ * 纯函数,不碰进程也不碰文件系统,便于单测;真正 spawn 的是 notifyDesktop()。
123
+ * 返回 `{ title, cmds: [{ cmd, args }] }`。
124
+ */
125
+ export function buildNotifyPlan({ platform = process.platform, title, message, ok = true } = {}) {
126
+ const safeTitle = String(title ?? '')
127
+ const safeMessage = String(message ?? '')
128
+
129
+ if (platform === 'win32') {
130
+ const ps = ['-NoProfile', '-NonInteractive', '-Command']
131
+ return {
132
+ title: safeTitle,
133
+ cmds: [
134
+ { cmd: 'powershell.exe', args: [...ps, winToastScript(safeTitle, safeMessage)] },
135
+ { cmd: 'powershell.exe', args: [...ps, winSoundScript(ok)] },
136
+ ],
137
+ }
138
+ }
139
+
140
+ if (platform === 'darwin') {
141
+ const script = `display notification ${appleScriptQuote(safeMessage)}`
142
+ + ` with title ${appleScriptQuote(safeTitle)}`
143
+ + ` sound name ${appleScriptQuote(ok ? 'Glass' : 'Basso')}`
144
+ return { title: safeTitle, cmds: [{ cmd: 'osascript', args: ['-e', script] }] }
145
+ }
146
+
147
+ // Linux / 其他:notify-send 是事实标准,声音文件各发行版路径不一,拿不到就是没声音
148
+ return {
149
+ title: safeTitle,
150
+ cmds: [
151
+ {
152
+ cmd: 'notify-send',
153
+ args: ['-a', 'zen-gitsync', '-u', ok ? 'normal' : 'critical', safeTitle, safeMessage],
154
+ },
155
+ {
156
+ cmd: 'paplay',
157
+ args: [
158
+ ok
159
+ ? '/usr/share/sounds/freedesktop/stereo/complete.oga'
160
+ : '/usr/share/sounds/freedesktop/stereo/dialog-error.oga',
161
+ ],
162
+ },
163
+ ],
164
+ }
165
+ }
166
+
167
+ /**
168
+ * 发一次提醒。返回成功派生的进程数(拿不到具体结果 —— 提醒本身没有失败可言)。
169
+ *
170
+ * 不 await、不阻塞:所有子进程 unref(),调用方紧接着 process.exit() 也不会把它们掐掉。
171
+ */
172
+ export function notifyDesktop({ title, message, ok = true, platform = process.platform } = {}) {
173
+ let plan
174
+ try {
175
+ plan = buildNotifyPlan({ platform, title, message, ok })
176
+ } catch {
177
+ return 0
178
+ }
179
+
180
+ let launched = 0
181
+ for (const { cmd, args } of plan.cmds) {
182
+ try {
183
+ const child = spawn(cmd, args, { stdio: 'ignore' })
184
+ // 命令不存在只会触发 'error' 事件;没有这个监听就是未捕获异常
185
+ child.on('error', () => {})
186
+ child.unref()
187
+ launched += 1
188
+ } catch {
189
+ /* 参数非法等同步异常:吞掉,提醒失败不反噬主线 */
190
+ }
191
+ }
192
+ return launched
193
+ }
194
+
195
+ /**
196
+ * 改终端标题。
197
+ *
198
+ * Windows 走 `process.title`(Node 内部是 SetConsoleTitle,实测 cmd 窗口标题会变);
199
+ * 其他平台 process.title 只改进程名,所以额外写一次 OSC 0 转义 —— 只在 TTY 上写,
200
+ * 重定向到文件时写这个等于往文件里灌乱码。
201
+ */
202
+ export function setTerminalTitle(text) {
203
+ const title = String(text ?? '')
204
+ try {
205
+ process.title = title
206
+ } catch {
207
+ /* 个别平台 process.title 只读 */
208
+ }
209
+ if (process.platform !== 'win32' && process.stdout?.isTTY) {
210
+ try {
211
+ process.stdout.write(`\u001b]0;${title}\u0007`)
212
+ } catch {
213
+ /* 忽略 */
214
+ }
215
+ }
216
+ return title
217
+ }
@@ -0,0 +1,122 @@
1
+ // Copyright 2026 xz333221
2
+ //
3
+ // Licensed under the Apache License, Version 2.0 (the "License");
4
+ // you may not use this file except in compliance with the License.
5
+ // You may obtain a copy of the License at
6
+ //
7
+ // http://www.apache.org/licenses/LICENSE-2.0
8
+ //
9
+ // Unless required by applicable law or agreed to in writing, software
10
+ // distributed under the License is distributed on an "AS IS" BASIS,
11
+ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ // See the License for the specific language governing permissions and
13
+ // limitations under the License.
14
+ //
15
+ // src/utils/desktopNotify.js 里"纯函数 + 转义"部分的单元测试(node:test 内置)。
16
+ //
17
+ // 只测到 buildNotifyPlan 为止,**不真的 spawn** —— 单测里弹 toast / 响一声会打扰跑测试的人。
18
+ // 这里要守住的两件事,都是"错了不会报错、只会静默失效"的类型:
19
+ // ① 文案里的 `<` / `&` 没转义 → toast 的 XML 解析失败 → 一条都不弹,且错误被吞;
20
+ // ② 文案里的单引号没翻倍 → PowerShell 语法错 → 静默什么都不做。
21
+ import { test } from 'node:test'
22
+ import assert from 'node:assert/strict'
23
+ import {
24
+ buildNotifyPlan,
25
+ psQuote,
26
+ xmlEscape,
27
+ appleScriptQuote,
28
+ } from './desktopNotify.js'
29
+
30
+ // ========== 转义 ==========
31
+
32
+ test('psQuote: 单引号翻倍,其余原样(反斜杠 / 中文不处理)', () => {
33
+ assert.equal(psQuote("it's"), "'it''s'")
34
+ assert.equal(psQuote('C:\\a\\b'), "'C:\\a\\b'")
35
+ assert.equal(psQuote('发布完成'), "'发布完成'")
36
+ assert.equal(psQuote(null), "''")
37
+ })
38
+
39
+ test('xmlEscape: 只转 & < >,引号在文本节点里合法不用转', () => {
40
+ assert.equal(xmlEscape('a & b < c > d'), 'a &amp; b &lt; c &gt; d')
41
+ assert.equal(xmlEscape('Unexpected token < in JSON'), 'Unexpected token &lt; in JSON')
42
+ assert.equal(xmlEscape('"quoted"'), '"quoted"')
43
+ })
44
+
45
+ test('appleScriptQuote: 先转反斜杠再转双引号(顺序反了会把 \\" 二次转义)', () => {
46
+ assert.equal(appleScriptQuote('say "hi"'), '"say \\"hi\\""')
47
+ assert.equal(appleScriptQuote('C:\\tmp'), '"C:\\\\tmp"')
48
+ })
49
+
50
+ // ========== Windows ==========
51
+
52
+ test('win32 plan: 两个进程 —— toast 一个、提示音一个', () => {
53
+ const plan = buildNotifyPlan({ platform: 'win32', title: 't', message: 'm', ok: true })
54
+ assert.equal(plan.cmds.length, 2)
55
+ assert.ok(plan.cmds.every((c) => c.cmd === 'powershell.exe'))
56
+ assert.ok(plan.cmds.every((c) => c.args.includes('-NoProfile') && c.args.includes('-NonInteractive')))
57
+ })
58
+
59
+ test('win32 toast: 正文走 XML 转义,且整段脚本是单引号字面量(带引号也不炸)', () => {
60
+ const plan = buildNotifyPlan({
61
+ platform: 'win32',
62
+ title: "it's done",
63
+ message: 'Unexpected token < in JSON & "x"',
64
+ ok: true,
65
+ })
66
+ const script = plan.cmds[0].args.at(-1)
67
+ assert.ok(script.includes('Unexpected token &lt; in JSON &amp; "x"'), 'XML 未转义')
68
+ assert.ok(script.includes("it''s done"), 'PS 单引号未翻倍')
69
+ // 转义后的正文不能再出现裸的 <text> 之外的尖括号
70
+ assert.ok(!script.includes('< in JSON'), '原始 < 漏进了 XML')
71
+ })
72
+
73
+ test('win32 提示音:成功与失败是不同的音源', () => {
74
+ const okScript = buildNotifyPlan({ platform: 'win32', title: 't', message: 'm', ok: true }).cmds[1].args.at(-1)
75
+ const failScript = buildNotifyPlan({ platform: 'win32', title: 't', message: 'm', ok: false }).cmds[1].args.at(-1)
76
+ assert.notEqual(okScript, failScript)
77
+ assert.ok(okScript.includes('Windows Notify System Generic.wav'))
78
+ assert.ok(failScript.includes('Windows Critical Stop.wav'))
79
+ // 各自的兜底音也要分开
80
+ assert.ok(okScript.includes('SystemSounds]::Asterisk.Play()'))
81
+ assert.ok(failScript.includes('SystemSounds]::Hand.Play()'))
82
+ })
83
+
84
+ test('win32 提示音:先 PlaySync 播 wav,播到了就不放兜底音', () => {
85
+ const script = buildNotifyPlan({ platform: 'win32', title: 't', message: 'm' }).cmds[1].args.at(-1)
86
+ assert.ok(script.includes('PlaySync()'))
87
+ assert.ok(script.includes('if (-not $played)'), '兜底音没被 $played 拦住 → 会响两遍')
88
+ })
89
+
90
+ // ========== macOS / Linux ==========
91
+
92
+ test('darwin plan: 一条 osascript,成功失败用不同的 sound name', () => {
93
+ const okPlan = buildNotifyPlan({ platform: 'darwin', title: 't', message: 'm', ok: true })
94
+ const failPlan = buildNotifyPlan({ platform: 'darwin', title: 't', message: 'm', ok: false })
95
+ assert.equal(okPlan.cmds.length, 1)
96
+ assert.equal(okPlan.cmds[0].cmd, 'osascript')
97
+ assert.ok(okPlan.cmds[0].args[1].includes('sound name "Glass"'))
98
+ assert.ok(failPlan.cmds[0].args[1].includes('sound name "Basso"'))
99
+ assert.ok(failPlan.cmds[0].args[1].includes('with title "t"'))
100
+ })
101
+
102
+ test('darwin plan: 文案里的双引号转义后再进 AppleScript', () => {
103
+ const plan = buildNotifyPlan({ platform: 'darwin', title: 'he said "hi"', message: 'm' })
104
+ assert.ok(plan.cmds[0].args[1].includes('with title "he said \\"hi\\""'))
105
+ })
106
+
107
+ test('linux plan: 失败用 critical 优先级,标题与正文原样落参数(不走 shell,无需转义)', () => {
108
+ const plan = buildNotifyPlan({ platform: 'linux', title: "it's <ok>", message: 'm & m', ok: false })
109
+ const notify = plan.cmds.find((c) => c.cmd === 'notify-send')
110
+ assert.deepEqual(notify.args, ['-a', 'zen-gitsync', '-u', 'critical', "it's <ok>", 'm & m'])
111
+ })
112
+
113
+ test('未知平台按 linux 兜底(不抛异常)', () => {
114
+ const plan = buildNotifyPlan({ platform: 'freebsd', title: 't', message: 'm' })
115
+ assert.ok(plan.cmds.some((c) => c.cmd === 'notify-send'))
116
+ })
117
+
118
+ test('缺省参数不炸:title / message 全空也能生成计划', () => {
119
+ const plan = buildNotifyPlan({ platform: 'win32' })
120
+ assert.equal(plan.title, '')
121
+ assert.equal(plan.cmds.length, 2)
122
+ })
@@ -1 +0,0 @@
1
- .agent-engine[data-v-c67a4d14]{vertical-align:middle}.agent-engine__btn[data-v-c67a4d14]{border:1px solid var(--border-color);border-radius:var(--radius-base);color:var(--text-secondary);font-size:var(--font-size-sm);cursor:pointer;transition:border-color var(--transition-fast) ease, color var(--transition-fast) ease;background:0 0;align-items:center;gap:6px;padding:4px 8px;font-family:inherit;line-height:1.5;display:inline-flex}.agent-engine__btn[data-v-c67a4d14]:hover:not(:disabled){border-color:var(--color-primary);color:var(--text-primary)}.agent-engine__btn[data-v-c67a4d14]:disabled{cursor:not-allowed;color:var(--text-tertiary)}.agent-engine__icon[data-v-c67a4d14],.agent-engine__item-icon[data-v-c67a4d14]{flex:none;font-size:14px}.agent-engine__caret[data-v-c67a4d14]{color:var(--text-tertiary);font-size:12px}.agent-engine__item[data-v-c67a4d14]{align-items:center;gap:8px;min-width:148px;display:inline-flex}.agent-engine__item-name[data-v-c67a4d14]{flex:1}.agent-engine__item-check[data-v-c67a4d14]{color:var(--color-primary);font-size:13px}.agent-engine__item-missing[data-v-c67a4d14]{font-size:var(--font-size-sm);color:var(--text-tertiary)}
@@ -1,2 +0,0 @@
1
- import"./rolldown-runtime-C0FnF6B9.js";import{$ as e,C as t,Cn as n,Dn as r,En as i,Gn as a,Hn as o,K as s,Kr as c,Mn as l,Nr as u,Pn as d,Qr as f,Rn as p,S as m,Tn as h,Yn as g,Zn as _,dr as v,or as y,q as b,qn as x,vr as S,w as C,wn as w,yn as T}from"./element-plus-D0R8BMj4.js";import{n as E,t as D}from"./_plugin-vue_export-helper-BKeZSroN.js";import{t as O}from"./SvgIcon-Dk7WnZLB.js";import{t as k}from"./toolsStore-C89takm3.js";import{s as A,v as j}from"./index-CKxygqdx.js";import{c as M,d as N,l as P,u as F}from"./agentConversations-ChTc-LkQ.js";p(),v();function I(e,t=680){let r=S(0),i=null;function s(){let t=e.value;return t?(r.value=t.clientWidth,typeof ResizeObserver>`u`||(i=new ResizeObserver(e=>{let t=e[0]?.contentRect?.width;typeof t==`number`&&(r.value=t)}),i.observe(t),!0)):!1}return x(()=>{s()||o(()=>s())}),a(()=>{i?.disconnect(),i=null}),{width:r,narrow:n(()=>r.value>0&&r.value<t)}}p(),v();var L=d({__name:`AgentEngineSelector`,props:{engine:{type:String,required:!0},locked:{type:Boolean,required:!1}},emits:[`select`],setup(r,{expose:i,emit:a}){i();let o=r,c=a,l=k(),u=n(()=>F(o.engine)),d=n(()=>({gai:!0,claude:l.claudeAvailable,opencode:l.opencodeAvailable,codex:l.codexAvailable})),f=S(!1),p=S(null);function h(e){if(!o.locked){if(!d.value[e]){if(!N(e))return;p.value=e,f.value=!0;return}c(`select`,e)}}let g={props:o,emit:c,toolsStore:l,engineName:u,engineAvailability:d,installDialogVisible:f,installTool:p,onCommand:h,get $t(){return E},get ElIcon(){return s},get ElDropdown(){return m},get ElDropdownMenu(){return C},get ElDropdownItem(){return t},get ArrowDown(){return b},get Check(){return e},get AGENT_ENGINE_OPTIONS(){return M},get BUILTIN_ENGINE_ICON(){return P},get isExternalAgentEngine(){return N},SvgIcon:O,TaskExecutorIcon:A,ToolInstallDialog:j};return Object.defineProperty(g,"__isScriptSetup",{enumerable:!1,value:!0}),g}});p(),u();var R={class:`agent-engine`},z=[`disabled`,`title`,`aria-label`],B={class:`agent-engine__name`},V={class:`agent-engine__item`},H={class:`agent-engine__item-name`},U={key:3,class:`agent-engine__item-missing`};function W(e,t,n,a,o,s){return g(),r(T,null,[i(` 单根包一层:父级传下来的 class(Tab 行的 margin-left:auto、面板的 flex:none)
2
- 才能可靠落到这里 —— 多根组件会丢掉 fallthrough class。 `),w(`div`,R,[l(a.ElDropdown,{trigger:`click`,onCommand:a.onCommand},{dropdown:y(()=>[l(a.ElDropdownMenu,null,{default:y(()=>[(g(!0),r(T,null,_(a.AGENT_ENGINE_OPTIONS,e=>(g(),h(a.ElDropdownItem,{key:e.id,command:e.id},{default:y(()=>[w(`span`,V,[a.isExternalAgentEngine(e.id)?(g(),h(a.TaskExecutorIcon,{key:0,executor:e.id,class:`agent-engine__item-icon`},null,8,[`executor`])):(g(),h(a.SvgIcon,{key:1,"icon-class":a.BUILTIN_ENGINE_ICON,class:`agent-engine__item-icon`},null,8,[`icon-class`])),w(`span`,H,f(e.name),1),n.engine===e.id?(g(),h(a.ElIcon,{key:2,class:`agent-engine__item-check`},{default:y(()=>[l(a.Check)]),_:1})):a.engineAvailability[e.id]?i(`v-if`,!0):(g(),r(`span`,U,f(a.$t(`@42BB9:未安装`)),1))])]),_:2},1032,[`command`]))),128))]),_:1})]),default:y(()=>[w(`button`,{type:`button`,class:c([`agent-engine__btn`,{"is-locked":n.locked}]),disabled:n.locked,title:n.locked?a.$t(`@AGENT:本会话的引擎已锁定;要换引擎请新建会话`):a.$t(`@AGENT:新建会话使用的引擎`),"aria-label":a.$t(`@AGENT:智能体引擎`)},[a.isExternalAgentEngine(n.engine)?(g(),h(a.TaskExecutorIcon,{key:0,executor:n.engine,class:`agent-engine__icon`},null,8,[`executor`])):(g(),h(a.SvgIcon,{key:1,"icon-class":a.BUILTIN_ENGINE_ICON,class:`agent-engine__icon`},null,8,[`icon-class`])),w(`span`,B,f(a.engineName),1),l(a.ElIcon,{class:`agent-engine__caret`},{default:y(()=>[l(a.ArrowDown)]),_:1})],10,z)]),_:1}),l(a.ToolInstallDialog,{modelValue:a.installDialogVisible,"onUpdate:modelValue":t[0]||=e=>a.installDialogVisible=e,tool:a.installTool},null,8,[`modelValue`,`tool`])])],2112)}var G=D(L,[[`render`,W],[`__scopeId`,`data-v-c67a4d14`],[`__file`,`C:/workspace/github_workspace/xz333221/zen-gitsync/src/ui/client/src/components/AgentEngineSelector.vue`]]);export{I as n,G as t};
@@ -1 +0,0 @@
1
- .marketplace-panel[data-v-e9f9e851]{background:var(--bg-container);flex-direction:column;height:100%;min-height:0;display:flex}.mp-toolbar[data-v-e9f9e851]{flex-shrink:0;align-items:center;gap:12px;padding:12px 16px 8px;display:flex}.mp-search[data-v-e9f9e851]{border:1px solid var(--border-color);border-radius:var(--radius-md);background:var(--bg-panel);max-width:420px;height:32px;transition:border-color var(--transition-fast) ease;flex:1;align-items:center;gap:6px;padding:0 10px;display:flex}.mp-search[data-v-e9f9e851]:focus-within{border-color:var(--color-primary)}.mp-search .mp-search-icon[data-v-e9f9e851]{color:var(--text-tertiary);flex-shrink:0}.mp-search .mp-search-input[data-v-e9f9e851]{color:var(--text-primary);font-size:var(--font-size-mid);background:0 0;border:none;outline:none;flex:1;font-family:inherit}.mp-search .mp-search-input[data-v-e9f9e851]::placeholder{color:var(--text-tertiary)}.mp-target[data-v-e9f9e851]{flex-shrink:0}.mp-refresh[data-v-e9f9e851]{border:1px solid var(--border-color);border-radius:var(--radius-md);background:var(--bg-panel);width:30px;height:30px;color:var(--text-secondary);cursor:pointer;transition:var(--transition-ui-fast);justify-content:center;align-items:center;display:flex}.mp-refresh[data-v-e9f9e851]:hover{color:var(--color-primary);border-color:var(--color-primary)}.mp-refresh.spinning svg[data-v-e9f9e851]{animation:.9s linear infinite mkt-spin-e9f9e851}@keyframes mkt-spin-e9f9e851{to{transform:rotate(360deg)}}.mp-toolbar-sub[data-v-e9f9e851]{flex-wrap:wrap;flex-shrink:0;align-items:center;gap:12px;padding:0 16px 10px;display:flex}.mp-target-hint[data-v-e9f9e851]{font-size:var(--font-size-sm);color:var(--text-tertiary)}.mp-target-hint b[data-v-e9f9e851]{color:var(--color-primary);font-weight:600}.mp-source-filter[data-v-e9f9e851]{flex-wrap:wrap;align-items:center;gap:6px;display:flex}.mp-chip[data-v-e9f9e851]{font-size:var(--font-size-sm);border:1px solid var(--border-color);border-radius:var(--radius-xl);color:var(--text-tertiary);cursor:pointer;transition:var(--transition-ui-fast);background:0 0;padding:2px 10px;font-family:inherit}.mp-chip[data-v-e9f9e851]:hover{color:var(--text-secondary);border-color:var(--text-tertiary)}.mp-chip.active[data-v-e9f9e851]{color:var(--color-primary);border-color:var(--color-primary);background:color-mix(in srgb, var(--color-primary) 8%, transparent)}.mp-chip.clear[data-v-e9f9e851]{color:var(--text-tertiary);border-style:dashed}.mp-body[data-v-e9f9e851]{flex:1;min-height:0;padding:0 16px 20px;overflow-y:auto}.mp-group[data-v-e9f9e851]{margin-bottom:20px}.mp-group.installed[data-v-e9f9e851]{border-top:1px dashed var(--border-color);padding-top:14px}.mp-group-header[data-v-e9f9e851]{align-items:center;gap:8px;margin-bottom:10px;display:flex}.mp-group-title[data-v-e9f9e851]{font-size:var(--font-size-mid);color:var(--text-primary);margin:0;font-weight:600}.mp-group-count[data-v-e9f9e851]{min-width:20px;height:18px;font-size:var(--font-size-xs);border-radius:var(--radius-lg);background:var(--bg-hover);color:var(--text-tertiary);justify-content:center;align-items:center;padding:0 6px;display:inline-flex}.mp-group-error[data-v-e9f9e851]{font-size:var(--font-size-sm);color:var(--color-warning);align-items:center;gap:4px;display:inline-flex}.mp-group-link[data-v-e9f9e851]{color:var(--text-tertiary);transition:color var(--transition-fast) ease;display:inline-flex}.mp-group-link[data-v-e9f9e851]:hover{color:var(--color-primary)}.mp-grid[data-v-e9f9e851]{grid-template-columns:repeat(auto-fill,minmax(280px,1fr));gap:10px;display:grid}.mp-card[data-v-e9f9e851]{border:1px solid var(--border-color);border-radius:var(--radius-md);background:var(--bg-panel);transition:border-color var(--transition-fast) ease, box-shadow var(--transition-fast) ease;flex-direction:column;gap:6px;padding:12px;display:flex}.mp-card[data-v-e9f9e851]:hover{border-color:color-mix(in srgb, var(--color-primary) 40%, var(--border-color));box-shadow:var(--shadow-sm)}.mp-card-head[data-v-e9f9e851]{align-items:center;gap:8px;display:flex}.mp-card-name[data-v-e9f9e851]{min-width:0;font-size:var(--font-size-mid);color:var(--text-primary);white-space:nowrap;text-overflow:ellipsis;flex:1;font-weight:600;overflow:hidden}.mp-card-metric[data-v-e9f9e851]{font-size:var(--font-size-xs);color:var(--text-tertiary);flex-shrink:0;align-items:center;gap:3px;display:inline-flex}.mp-card-desc[data-v-e9f9e851]{font-size:var(--font-size-sm);color:var(--text-secondary);-webkit-line-clamp:2;-webkit-box-orient:vertical;min-height:36px;margin:0;line-height:1.5;display:-webkit-box;overflow:hidden}.mp-card-meta[data-v-e9f9e851]{flex-wrap:wrap;align-items:center;gap:6px;min-height:18px;display:flex}.mp-meta-pkg[data-v-e9f9e851],.mp-meta-repo[data-v-e9f9e851]{font-size:var(--font-size-xs);color:var(--text-tertiary);white-space:nowrap;text-overflow:ellipsis;max-width:220px;overflow:hidden}.mp-tag[data-v-e9f9e851]{font-size:var(--font-size-xs);border-radius:var(--radius-lg);background:var(--bg-hover);color:var(--text-tertiary);flex-shrink:0;padding:1px 6px}.mp-tag.remote[data-v-e9f9e851]{background:color-mix(in srgb, var(--color-info,var(--action-teal)) 14%, transparent);color:var(--color-info,var(--action-teal))}.mp-tag.warn[data-v-e9f9e851]{background:color-mix(in srgb, var(--color-warning) 14%, transparent);color:var(--color-warning)}.mp-card-foot[data-v-e9f9e851]{align-items:center;gap:8px;margin-top:2px;display:flex}.mp-card-link[data-v-e9f9e851]{font-size:var(--font-size-xs);color:var(--text-tertiary);text-decoration:none}.mp-card-link[data-v-e9f9e851]:hover{color:var(--color-primary);text-decoration:underline}.mp-foot-spacer[data-v-e9f9e851]{flex:1}.mp-install-btn[data-v-e9f9e851]{font-size:var(--font-size-sm);border:1px solid var(--color-primary);border-radius:var(--radius-base);color:var(--color-primary);cursor:pointer;transition:var(--transition-ui-fast);background:0 0;padding:4px 14px;font-family:inherit}.mp-install-btn[data-v-e9f9e851]:hover:not(:disabled){background:var(--color-primary);color:#fff}.mp-install-btn[data-v-e9f9e851]:disabled{opacity:.5;cursor:not-allowed}.mp-installed-chip[data-v-e9f9e851]{font-size:var(--font-size-xs);color:var(--color-success,var(--action-emerald));align-items:center;gap:3px;display:inline-flex}.mp-not-installable[data-v-e9f9e851]{font-size:var(--font-size-xs);color:var(--text-tertiary)}.mp-empty[data-v-e9f9e851]{font-size:var(--font-size-sm);color:var(--text-tertiary);padding:16px 0}.mp-empty.all[data-v-e9f9e851]{text-align:center;padding:48px 0}.mp-installed-block[data-v-e9f9e851]{margin-bottom:12px}.mp-installed-target[data-v-e9f9e851]{font-size:var(--font-size-xs);color:var(--text-tertiary);text-transform:uppercase;letter-spacing:.4px;margin-bottom:6px;font-weight:600}.mp-installed-list[data-v-e9f9e851]{flex-direction:column;gap:4px;display:flex}.mp-installed-row[data-v-e9f9e851]{border:1px solid var(--border-color);border-radius:var(--radius-base);background:var(--bg-panel);align-items:center;gap:10px;padding:6px 10px;display:flex}.mp-installed-name[data-v-e9f9e851]{font-size:var(--font-size-sm);color:var(--text-primary);white-space:nowrap;text-overflow:ellipsis;min-width:0;font-weight:500;overflow:hidden}.mp-installed-id[data-v-e9f9e851]{font-size:var(--font-size-xs);font-family:var(--font-mono);color:var(--text-tertiary);white-space:nowrap;text-overflow:ellipsis;max-width:40%;overflow:hidden}.mp-reveal-btn[data-v-e9f9e851],.mp-remove-btn[data-v-e9f9e851]{font-size:var(--font-size-xs);border:1px solid var(--border-color);border-radius:var(--radius-xs);color:var(--text-tertiary);cursor:pointer;white-space:nowrap;transition:var(--transition-ui-fast);background:0 0;align-items:center;gap:4px;padding:3px 10px;font-family:inherit;display:inline-flex}.mp-reveal-btn[data-v-e9f9e851]:disabled,.mp-remove-btn[data-v-e9f9e851]:disabled{opacity:.5;cursor:not-allowed}.mp-reveal-btn[data-v-e9f9e851]:hover:not(:disabled){color:var(--text-primary);border-color:var(--text-tertiary)}.mp-remove-btn[data-v-e9f9e851]:hover:not(:disabled){color:var(--color-danger);border-color:var(--color-danger)}@media (prefers-reduced-motion:reduce){.mp-refresh.spinning svg[data-v-e9f9e851]{animation:none}}.mkt-form[data-v-e9f9e851]{flex-direction:column;gap:12px;display:flex}.mkt-form-name[data-v-e9f9e851]{font-size:var(--font-size-mid);color:var(--text-primary);margin:0;font-weight:600}.mkt-field[data-v-e9f9e851]{flex-direction:column;gap:4px;display:flex}.mkt-field label[data-v-e9f9e851]{font-size:var(--font-size-sm);color:var(--text-secondary);font-weight:600}.mkt-field input[data-v-e9f9e851]{border:1px solid var(--border-color);border-radius:var(--radius-md);background:var(--bg-container);height:30px;color:var(--text-primary);font-size:var(--font-size-mid);transition:border-color var(--transition-fast) ease;padding:0 8px;font-family:inherit}.mkt-field input[data-v-e9f9e851]:focus{border-color:var(--color-primary);outline:none}.mkt-hint[data-v-e9f9e851]{font-size:var(--font-size-xs);color:var(--text-tertiary);margin:0}.mkt-cancel[data-v-e9f9e851],.mkt-confirm[data-v-e9f9e851]{font-size:var(--font-size-mid);border-radius:var(--radius-base);cursor:pointer;transition:var(--transition-ui-fast);padding:6px 16px;font-family:inherit}.mkt-cancel[data-v-e9f9e851]{border:1px solid var(--border-color);color:var(--text-secondary);background:0 0;margin-right:8px}.mkt-cancel[data-v-e9f9e851]:hover{color:var(--text-primary);border-color:var(--text-tertiary)}.mkt-confirm[data-v-e9f9e851]{border:1px solid var(--color-primary);background:var(--color-primary);color:#fff}.mkt-confirm[data-v-e9f9e851]:hover{opacity:.9}.agent-view[data-v-bd681ddd]{background:var(--bg-container);height:100%;min-height:0;display:flex}.agent-sidebar[data-v-bd681ddd]{background:var(--bg-panel);border-right:1px solid var(--border-color);flex-direction:column;flex-shrink:0;display:flex;overflow:hidden}.agent-conversations[data-v-bd681ddd]{flex:1;min-height:0;padding:10px 6px 8px 12px}.sidebar-resizer[data-v-bd681ddd]{cursor:col-resize;width:4px;transition:background var(--transition-fast) ease;background:0 0;flex-shrink:0}.sidebar-resizer[data-v-bd681ddd]:hover,.sidebar-resizer.active[data-v-bd681ddd]{background:var(--color-primary);opacity:.3}.agent-chat-area[data-v-bd681ddd]{flex-direction:column;flex:1;min-width:0;display:flex;position:relative;overflow:hidden}.agent-engine[data-v-bd681ddd]{margin-bottom:7px;margin-left:auto}.agent-tabs[data-v-bd681ddd]{border-bottom:1px solid var(--border-color);background:var(--bg-panel);flex-shrink:0;align-items:center;gap:4px;padding:8px 12px 0;display:flex}.agent-tab[data-v-bd681ddd]{color:var(--text-tertiary);font-size:var(--font-size-mid);cursor:pointer;transition:color var(--transition-fast) ease, border-color var(--transition-fast) ease, background var(--transition-fast) ease;border-radius:var(--radius-base) var(--radius-base) 0 0;background:0 0;border:none;border-bottom:2px solid #0000;align-items:center;gap:6px;padding:7px 14px 9px;font-family:inherit;display:inline-flex}.agent-tab[data-v-bd681ddd]:hover{color:var(--text-secondary);background:var(--bg-hover)}.agent-tab.active[data-v-bd681ddd]{color:var(--color-primary);border-bottom-color:var(--color-primary);font-weight:600}.agent-tab-body[data-v-bd681ddd]{flex-direction:column;flex:1;min-height:0;display:flex;overflow:hidden}.agent-chat-pane[data-v-bd681ddd]{flex-direction:column;flex:1;min-height:0;display:flex;position:relative;overflow:hidden}.agent-tab-body[data-v-bd681ddd]>.marketplace-panel{flex:1;min-height:0}.chat-loading[data-v-bd681ddd]{color:var(--text-tertiary);font-size:var(--font-size-base);flex-direction:column;flex:1;justify-content:center;align-items:center;gap:12px;display:flex}.agent-list-page[data-v-bd681ddd]{background:var(--bg-panel);flex-direction:column;flex:1;min-height:0;display:flex;overflow:hidden}.agent-list-page .agent-conversations[data-v-bd681ddd]{padding:12px 12px 10px 14px}.agent-page-bar[data-v-bd681ddd]{border-bottom:1px solid var(--border-color);background:var(--bg-panel);flex-shrink:0;align-items:center;gap:6px;height:34px;padding:0 12px 0 6px;display:flex}.agent-page-back[data-v-bd681ddd]{border-radius:var(--radius-xs);width:24px;height:24px;color:var(--text-tertiary);cursor:pointer;transition:var(--transition-ui-fast);background:0 0;border:none;flex-shrink:0;justify-content:center;align-items:center;padding:0;display:flex}.agent-page-back[data-v-bd681ddd]:hover{background:var(--bg-hover);color:var(--text-secondary)}.agent-page-title[data-v-bd681ddd]{min-width:0;font-size:var(--font-size-mid);color:var(--text-secondary);white-space:nowrap;text-overflow:ellipsis;flex:1;overflow:hidden}.agent-chat-host[data-v-bd681ddd]{flex-direction:column;flex:1;min-height:0;display:flex;overflow:hidden}.agent-chat-host[data-v-bd681ddd] .acu-avatar--left{background:0 0}.agent-chat-host[data-v-bd681ddd] .acu-welcome-logo{background:var(--welcome-avatar) center/46px 46px no-repeat;width:56px;height:56px;box-shadow:none}.agent-chat-host[data-v-bd681ddd] .acu-welcome-logo>svg{display:none}.agent-chat-host[data-v-bd681ddd] .acu-welcome-grid{flex-wrap:wrap;display:flex}.agent-chat-host[data-v-bd681ddd] .acu-welcome-grid>.acu-preset-q{flex:260px;min-width:0}[data-theme=dark]{background:var(--bg-panel)}
@@ -1,11 +0,0 @@
1
- import"./rolldown-runtime-C0FnF6B9.js";import{$ as e,A as t,Bt as n,Cn as r,Ct as i,Dn as a,Dt as o,En as s,H as c,Hn as l,Jr as u,K as d,Kr as f,Mn as p,Nr as m,Ot as h,Pn as g,Q as _,Qr as v,Rn as y,Tn as b,Ut as x,Yn as S,Zn as C,a as w,at as T,dr as E,er as D,ft as O,gr as k,hn as A,i as j,ir as M,j as N,jn as P,ln as F,o as I,or as L,qt as ee,rn as te,sr as R,st as z,vr as B,wn as V,xt as H,yn as U}from"./element-plus-D0R8BMj4.js";import{o as W,s as G}from"./vendor-CCrjQw5C.js";import{n as K,t as q}from"./_plugin-vue_export-helper-BKeZSroN.js";import{t as J}from"./configStore-fJaic3kE.js";import{t as ne}from"./CommonDialog-DpFTO5cg.js";import{a as re,i as ie,n as ae,o as oe,r as se,s as ce,t as le}from"./agentConversations-ChTc-LkQ.js";import{n as ue,t as de}from"./AgentEngineSelector-Wl7dKYKL.js";y(),E();var fe=g({__name:`MarketplacePanel`,props:{type:{type:String,required:!0}},setup(t,{expose:i}){i();let a=t,s=J(),c=B([]),l=B([]),u=B(!1),d=B(``),f=B(``),p=B([]),m=B(`project`),h=B(new Set),g=B(new Set),_=B(new Set),v=r(()=>s.currentDirectory||``),y=null;async function b(){if(!u.value){u.value=!0;try{let e=new URLSearchParams({type:a.type,cwd:v.value});f.value&&e.set(`q`,f.value),p.value.length&&e.set(`sources`,p.value.join(`,`));let t=await(await fetch(`/api/agent/marketplace/catalog?${e}`)).json();t?.success?(c.value=t.groups||[],l.value=t.installed||[]):w.error(t?.error||K(`@MKT:加载失败`))}catch{w.error(K(`@MKT:加载失败`))}finally{u.value=!1}}}M(d,e=>{y&&clearTimeout(y),y=setTimeout(()=>{f.value=e.trim(),b()},400)}),M(p,()=>b()),M(()=>a.type,()=>{p.value=[],f.value=``,d.value=``,b()}),M(v,()=>b()),b();let S=r(()=>{let e=v.value?v.value.split(/[\\/]/).filter(Boolean).pop():``;return m.value===`project`?e||K(`@MKT:当前项目`):K(`@MKT:g ai 智能体`)});function C(e){return e.stars?`${T(e.stars)} ★`:e.downloads?`${T(e.downloads)} ${K(`@MKT:周下载`)}`:e.uses?`${T(e.uses)} ${K(`@MKT:次使用`)}`:``}function T(e){return e>=1e3?`${(e/1e3).toFixed(e>=1e4?0:1)}k`:String(e)}function E(e){return`${e.id}`}function D(e){return h.value.has(E(e))}let A=r(()=>{let e={project:[],global:[]};for(let t of l.value)(e[t.target]||=[]).push(t);return e}),N=k({visible:!1,item:null,env:{},args:``});function P(e){return a.type===`mcp`&&(!!e.envKeys?.length||!!e.args?.length)}function F(e){N.item=e,N.env=Object.fromEntries((e.envKeys||[]).map(e=>[e,``])),N.args=(e.args||[]).join(` `),N.visible=!0}function I(){let e=N.item;if(N.visible=!1,!e)return;let t=Object.fromEntries(Object.entries(N.env).filter(([,e])=>e.trim()!==``)),n=N.args.trim()?N.args.trim().match(/(?:[^\s"]+|"[^"]*")+/g)?.map(e=>e.replace(/^"|"$/g,``))??void 0:void 0;R(e,{env:t,...n?{args:n}:{}})}async function L(e){if(!e.installed||await V(e)){if(P(e)){F(e);return}await R(e,{})}}async function R(e,t){let n=E(e);h.value.add(n);try{let n=await fetch(`/api/agent/marketplace/install`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({type:a.type,target:m.value,cwd:v.value,item:{...e,...t}})}),r=await n.json().catch(()=>null);if(!n.ok||!r?.success){w.error(r?.error||K(`@MKT:安装失败`));return}r.item?.warning&&w.warning(r.item.warning),r.item?.envKeys?.length?w.warning(`${K(`@MKT:还缺环境变量`)}: ${r.item.envKeys.join(`, `)}`):w.success(K(`@MKT:安装成功`)),await b()}catch{w.error(K(`@MKT:安装失败`))}finally{h.value.delete(n)}}async function V(e){try{return await j.confirm(`${e.name} ${K(`@MKT:已安装,要重新安装吗?`)}`,K(`@MKT:重新安装`),{confirmButtonText:K(`@MKT:重装`),cancelButtonText:K(`@MKT:取消`),type:`warning`}),!0}catch{return!1}}async function U(e){let t=`${e.target}:${e.id}`;_.value.add(t);try{let t=await fetch(`/api/agent/marketplace/reveal`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({type:a.type,target:e.target,cwd:v.value,id:e.id})}),n=await t.json().catch(()=>null);(!t.ok||!n?.success)&&w.error(n?.error||K(`@MKT:打开失败`))}catch{w.error(K(`@MKT:打开失败`))}finally{_.value.delete(t)}}async function W(e){try{await j.confirm(`${K(`@MKT:确定卸载`)} ${e.name}?`,K(`@MKT:卸载`),{confirmButtonText:K(`@MKT:卸载`),cancelButtonText:K(`@MKT:取消`),type:`warning`})}catch{return}let t=`${e.target}:${e.id}`;g.value.add(t);try{let t=new URLSearchParams({target:e.target,cwd:v.value}),n=await fetch(`/api/agent/marketplace/item/${a.type}/${encodeURIComponent(e.id)}?${t}`,{method:`DELETE`}),r=await n.json().catch(()=>null);if(!n.ok||!r?.success){w.error(r?.error||K(`@MKT:卸载失败`));return}w.success(K(`@MKT:已卸载`)),await b()}catch{w.error(K(`@MKT:卸载失败`))}finally{g.value.delete(t)}}let G={props:a,configStore:s,groups:c,installed:l,loading:u,searchInput:d,query:f,selectedSources:p,installTarget:m,installing:h,removing:g,revealing:_,cwd:v,get searchTimer(){return y},set searchTimer(e){y=e},loadCatalog:b,targetLabel:S,metricText:C,formatCount:T,cardKey:E,isBusy:D,installedByTarget:A,configDialog:N,needsConfig:P,openConfigDialog:F,confirmConfig:I,onInstall:L,doInstall:R,confirmReinstall:V,onReveal:U,onRemove:W,get $t(){return K},get Search(){return x},get Refresh(){return n},get Delete(){return z},get Link(){return o},get Check(){return e},get Star(){return ee},get Download(){return O},get Warning(){return te},get FolderOpened(){return H},CommonDialog:ne};return Object.defineProperty(G,"__isScriptSetup",{enumerable:!1,value:!0}),G}});y(),F(),m();var pe={class:`marketplace-panel`},Y={class:`mp-toolbar`},X={class:`mp-search`},me=[`placeholder`],he={class:`mp-toolbar-sub`},Z={class:`mp-target-hint`},Q={class:`mp-source-filter`},ge=[`onClick`],$={class:`mp-body`},_e={class:`mp-group-header`},ve={class:`mp-group-title`},ye={key:0,class:`mp-group-count`},be={key:1,class:`mp-group-error`},xe=[`href`,`title`],Se={key:0,class:`mp-grid`},Ce={class:`mp-card-head`},we=[`title`],Te={key:0,class:`mp-card-metric`},Ee=[`title`],De={class:`mp-card-meta`},Oe=[`title`],ke=[`title`],Ae={key:2,class:`mp-tag remote`},je={class:`mp-card-foot`},Me=[`href`],Ne={key:1,class:`mp-installed-chip`},Pe=[`disabled`,`onClick`],Fe={key:3,class:`mp-not-installable`},Ie={key:1,class:`mp-empty`},Le={key:0,class:`mp-group installed`},Re={class:`mp-group-header`},ze={class:`mp-group-title`},Be={class:`mp-group-count`},Ve={key:0,class:`mp-installed-block`},He={class:`mp-installed-target`},Ue={class:`mp-installed-list`},We=[`title`],Ge=[`title`],Ke={key:1,class:`mp-meta-pkg`},qe={key:2,class:`mp-tag warn`},Je=[`disabled`,`title`,`onClick`],Ye=[`disabled`,`onClick`],Xe={key:1,class:`mp-empty all`},Ze={key:0,class:`mkt-form`},Qe={class:`mkt-form-name`},$e=[`onUpdate:modelValue`,`placeholder`],et={key:0,class:`mkt-field`},tt={class:`mkt-hint`};function nt(e,n,r,i,o,l){let u=d,m=t,h=N,g=c,_=I;return S(),a(`div`,pe,[s(` ── 工具栏 ───────────────────────────────── `),V(`div`,Y,[V(`div`,X,[p(u,{class:`mp-search-icon`},{default:L(()=>[p(i.Search)]),_:1}),R(V(`input`,{"onUpdate:modelValue":n[0]||=e=>i.searchInput=e,class:`mp-search-input`,type:`text`,placeholder:r.type===`skill`?i.$t(`@MKT:搜索 Skill...`):i.$t(`@MKT:搜索 MCP 服务...`)},null,8,me),[[A,i.searchInput]])]),p(h,{modelValue:i.installTarget,"onUpdate:modelValue":n[1]||=e=>i.installTarget=e,size:`small`,class:`mp-target`},{default:L(()=>[p(m,{value:`project`},{default:L(()=>[P(v(i.$t(`@MKT:当前项目`)),1)]),_:1}),p(m,{value:`global`},{default:L(()=>[P(v(i.$t(`@MKT:g ai 智能体`)),1)]),_:1})]),_:1},8,[`modelValue`]),p(g,{content:i.$t(`@MKT:刷新`),placement:`top`,"show-after":300},{default:L(()=>[V(`button`,{class:f([`mp-refresh`,{spinning:i.loading}]),onClick:i.loadCatalog},[p(u,null,{default:L(()=>[p(i.Refresh)]),_:1})],2)]),_:1},8,[`content`])]),V(`div`,he,[V(`span`,Z,[P(v(i.$t(`@MKT:安装到`))+` `,1),V(`b`,null,v(i.targetLabel),1)]),V(`div`,Q,[(S(!0),a(U,null,C(i.groups,e=>(S(),a(`button`,{key:e.id,class:f([`mp-chip`,{active:i.selectedSources.includes(e.id)}]),onClick:()=>{let t=i.selectedSources.indexOf(e.id);t>=0?i.selectedSources.splice(t,1):i.selectedSources.push(e.id)}},v(e.label),11,ge))),128)),i.selectedSources.length?(S(),a(`button`,{key:0,class:`mp-chip clear`,onClick:n[2]||=e=>i.selectedSources=[]},v(i.$t(`@MKT:全部来源`)),1)):s(`v-if`,!0)])]),s(` ── 内容 ─────────────────────────────────── `),R((S(),a(`div`,$,[(S(!0),a(U,null,C(i.groups,e=>(S(),a(`section`,{key:e.id,class:`mp-group`},[V(`header`,_e,[V(`h3`,ve,v(e.label),1),e.status===`ok`?(S(),a(`span`,ye,v(e.items.length),1)):(S(),a(`span`,be,[p(u,null,{default:L(()=>[p(i.Warning)]),_:1}),P(` `+v(e.error||i.$t(`@MKT:来源不可用`)),1)])),e.homepage?(S(),a(`a`,{key:2,class:`mp-group-link`,href:e.homepage,target:`_blank`,rel:`noopener noreferrer`,title:e.homepage},[p(u,null,{default:L(()=>[p(i.Link)]),_:1})],8,xe)):s(`v-if`,!0)]),e.status===`ok`&&e.items.length?(S(),a(`div`,Se,[(S(!0),a(U,null,C(e.items,e=>(S(),a(`article`,{key:i.cardKey(e),class:`mp-card`},[V(`div`,Ce,[V(`span`,{class:`mp-card-name`,title:e.name},v(e.name),9,we),i.metricText(e)?(S(),a(`span`,Te,[p(u,null,{default:L(()=>[e.stars?(S(),b(i.Star,{key:0})):(S(),b(i.Download,{key:1}))]),_:2},1024),P(` `+v(i.metricText(e)),1)])):s(`v-if`,!0)]),V(`p`,{class:`mp-card-desc`,title:e.description},v(e.description||i.$t(`@MKT:暂无描述`)),9,Ee),V(`div`,De,[e.package?(S(),a(`code`,{key:0,class:`mp-meta-pkg`,title:e.package},v(e.package),9,Oe)):e.repository?(S(),a(`span`,{key:1,class:`mp-meta-repo`,title:e.repository+(e.subpath?` · `+e.subpath:``)},[P(v(e.repository),1),e.subpath?(S(),a(U,{key:0},[P(` · `+v(e.subpath),1)],64)):s(`v-if`,!0)],8,ke)):s(`v-if`,!0),e.transport===`remote`?(S(),a(`span`,Ae,v(i.$t(`@MKT:远程服务`)),1)):s(`v-if`,!0),(S(!0),a(U,null,C((e.tags||[]).slice(0,2),e=>(S(),a(`span`,{key:e,class:`mp-tag`},v(e),1))),128))]),V(`div`,je,[e.upstream?(S(),a(`a`,{key:0,class:`mp-card-link`,href:e.upstream,target:`_blank`,rel:`noopener noreferrer`},v(i.$t(`@MKT:查看来源`)),9,Me)):s(`v-if`,!0),n[6]||=V(`span`,{class:`mp-foot-spacer`},null,-1),e.installed?(S(),a(`span`,Ne,[p(u,null,{default:L(()=>[p(i.Check)]),_:1}),P(v(i.$t(`@MKT:已安装`)),1)])):e.installable===!1?(S(),a(`span`,Fe,v(i.$t(`@MKT:暂不支持一键安装`)),1)):(S(),a(`button`,{key:2,class:`mp-install-btn`,disabled:i.isBusy(e),onClick:t=>i.onInstall(e)},v(i.isBusy(e)?i.$t(`@MKT:安装中...`):i.$t(`@MKT:安装`)),9,Pe))])]))),128))])):e.status===`ok`?(S(),a(`div`,Ie,v(i.$t(`@MKT:该来源暂无结果`)),1)):s(`v-if`,!0)]))),128)),s(` ── 已安装 ─────────────────────────────── `),i.installed.length?(S(),a(`section`,Le,[V(`header`,Re,[V(`h3`,ze,v(i.$t(`@MKT:已安装`)),1),V(`span`,Be,v(i.installed.length),1)]),(S(),a(U,null,C([`project`,`global`],e=>V(`div`,{key:e},[i.installedByTarget[e]?.length?(S(),a(`div`,Ve,[V(`div`,He,v(e===`project`?i.$t(`@MKT:当前项目`):i.$t(`@MKT:g ai 智能体`)),1),V(`div`,Ue,[(S(!0),a(U,null,C(i.installedByTarget[e],t=>(S(),a(`div`,{key:e+t.id,class:`mp-installed-row`},[V(`span`,{class:`mp-installed-name`,title:t.name},v(t.name),9,We),s(` 实际落盘的标识:SKILL.md 里的 name 常与仓库名不同(如仓库 taste-skill
2
- 里的 skill 自称 brandkit),不显示这个用户就对不上广场卡片上的名字 `),t.id&&t.id!==t.name?(S(),a(`code`,{key:0,class:`mp-installed-id`,title:t.dir||t.file||t.id},v(t.id),9,Ge)):s(`v-if`,!0),t.package?(S(),a(`code`,Ke,v(t.package),1)):s(`v-if`,!0),t.requiredEnv?.length?(S(),a(`span`,qe,v(i.$t(`@MKT:待配置`))+`: `+v(t.requiredEnv.join(`, `)),1)):s(`v-if`,!0),n[7]||=V(`span`,{class:`mp-foot-spacer`},null,-1),V(`button`,{class:`mp-reveal-btn`,disabled:i.revealing.has(e+t.id),title:t.dir||t.file||``,onClick:e=>i.onReveal(t)},[p(u,null,{default:L(()=>[p(i.FolderOpened)]),_:1}),P(` `+v(i.$t(`@MKT:打开文件夹`)),1)],8,Je),V(`button`,{class:`mp-remove-btn`,disabled:i.removing.has(e+t.id),onClick:e=>i.onRemove(t)},[p(u,null,{default:L(()=>[p(i.Delete)]),_:1}),P(` `+v(i.removing.has(e+t.id)?i.$t(`@MKT:卸载中...`):i.$t(`@MKT:卸载`)),1)],8,Ye)]))),128))])])):s(`v-if`,!0)])),64))])):s(`v-if`,!0),!i.loading&&!i.groups.length?(S(),a(`div`,Xe,v(i.$t(`@MKT:加载失败`)),1)):s(`v-if`,!0)])),[[_,i.loading]]),s(` ── MCP 安装前配置 ───────────────────────── `),p(i.CommonDialog,{modelValue:i.configDialog.visible,"onUpdate:modelValue":n[5]||=e=>i.configDialog.visible=e,title:i.$t(`@MKT:配置 MCP`),width:`460px`,type:`flex`,"close-on-click-modal":!1,"append-to-body":``},{footer:L(()=>[V(`button`,{class:`mkt-cancel`,onClick:n[4]||=e=>i.configDialog.visible=!1},v(i.$t(`@MKT:取消`)),1),V(`button`,{class:`mkt-confirm`,onClick:i.confirmConfig},v(i.$t(`@MKT:安装`)),1)]),default:L(()=>[i.configDialog.item?(S(),a(`div`,Ze,[V(`p`,Qe,v(i.configDialog.item.name),1),(S(!0),a(U,null,C(Object.keys(i.configDialog.env),e=>(S(),a(`div`,{key:e,class:`mkt-field`},[V(`label`,null,v(e),1),R(V(`input`,{"onUpdate:modelValue":t=>i.configDialog.env[e]=t,type:`text`,autocomplete:`off`,placeholder:i.$t(`@MKT:环境变量值`),spellcheck:`false`},null,8,$e),[[A,i.configDialog.env[e]]])]))),128)),i.configDialog.item.args?.length?(S(),a(`div`,et,[V(`label`,null,v(i.$t(`@MKT:启动参数`)),1),R(V(`input`,{"onUpdate:modelValue":n[3]||=e=>i.configDialog.args=e,type:`text`,autocomplete:`off`,spellcheck:`false`},null,512),[[A,i.configDialog.args]]),V(`p`,tt,v(i.$t(`@MKT:参数提示`)),1)])):s(`v-if`,!0)])):s(`v-if`,!0)]),_:1},8,[`modelValue`,`title`])])}var rt=q(fe,[[`render`,nt],[`__scopeId`,`data-v-e9f9e851`],[`__file`,`C:/workspace/github_workspace/xz333221/zen-gitsync/src/ui/client/src/components/MarketplacePanel.vue`]]);y(),E();var it=g({__name:`AgentView`,setup(e,{expose:t}){t();let n=J(),a=B(null),{narrow:o}=ue(a),s=B(`chat`),c=B(`chat`),u=r(()=>c.value===`mcp`?`mcp`:`skill`),f=r(()=>[{id:`chat`,label:K(`@AGENT:对话`),icon:_},{id:`skill`,label:K(`@AGENT:Skill 广场`),icon:i},{id:`mcp`,label:K(`@AGENT:MCP 广场`),icon:T}]),{sessions:p,sessionsLoading:m,currentSessionId:g,messages:v,isStreaming:y,sessionLoading:b,loadSessions:x,loadSession:S,deleteSession:C,renameSession:w,newSession:E,sendMessage:D,pendingQuestion:O,answeringQuestion:k,answerQuestion:A,isSessionGenerating:j,currentEngine:N,pendingEngine:P,isEngineLocked:F,pickEngine:I,stop:L}=ce(),ee=r(()=>F.value?N.value:P.value);function te(e){F.value||I(e)}let R=r(()=>{let e=n.theme;return e===`dark`||e===`auto`&&window.matchMedia(`(prefers-color-scheme: dark)`).matches?`dark`:`light`}),z=r(()=>re(p.value,j)),V=se(),H=r(()=>z.value.find(e=>e.id===g.value)?.title||K(`@AGENT:对话`)),U={"--welcome-avatar":`url("${le}")`},q=r(()=>[{id:`p1`,label:K(`@AGENT:查看项目结构`),prompt:K(`@AGENT:prompt_p1`)},{id:`p2`,label:K(`@AGENT:分析代码质量`),prompt:K(`@AGENT:prompt_p2`)},{id:`p3`,label:K(`@AGENT:帮我写测试`),prompt:K(`@AGENT:prompt_p3`)},{id:`p4`,label:K(`@AGENT:Git 状态检查`),prompt:K(`@AGENT:prompt_p4`)},{id:`p5`,label:K(`@AGENT:帮我启动项目`),prompt:K(`@AGENT:prompt_p5`)}]);async function ne(e){await D(e.text,e.files),await l(),X()}async function fe(e){await D(e.prompt),await l(),X()}let pe=ie(),Y=B(null);function X(e=!0){Y.value?.scrollToBottom(e)}M(()=>v.value.length,()=>{l(()=>X(!1))});function me(e){S(e),s.value=`chat`}function he(){E(),s.value=`chat`}let Z=B(280),Q=B(!1);function ge(e){Q.value=!0;let t=e.clientX,n=Z.value,r=e=>{let r=e.clientX-t,i=Math.max(200,Math.min(500,n+r));Z.value=i},i=()=>{Q.value=!1,document.removeEventListener(`mousemove`,r),document.removeEventListener(`mouseup`,i)};document.addEventListener(`mousemove`,r),document.addEventListener(`mouseup`,i)}M(o,e=>{e||(s.value=`chat`)}),M(()=>[n.currentDirectory,y.value],async([e,t],n)=>{let r=!n||e!==n[0],i=!!n&&n[1]===!0&&!t;(r||i)&&await x(),g.value&&!y.value&&!p.value.some(e=>e.sessionId===g.value)&&E()},{immediate:!0});let $={configStore:n,rootRef:a,narrow:o,chatPage:s,activeTab:c,marketplaceType:u,tabs:f,sessions:p,sessionsLoading:m,currentSessionId:g,messages:v,isStreaming:y,sessionLoading:b,loadSessions:x,loadSession:S,deleteSession:C,renameSession:w,newSession:E,sendMessage:D,pendingQuestion:O,answeringQuestion:k,answerQuestion:A,isSessionGenerating:j,currentEngine:N,pendingEngine:P,isEngineLocked:F,pickEngine:I,stop:L,displayEngine:ee,handleEngineSelect:te,chatTheme:R,conversationItems:z,conversationLabels:V,currentSessionTitle:H,welcomeAvatarStyle:U,presetQuestions:q,onSend:ne,onSelectPreset:fe,questionLabels:pe,chatContainerRef:Y,scrollToBottom:X,selectSession:me,handleNewSession:he,sidebarWidth:Z,isResizing:Q,startResize:ge,get $t(){return K},get ElIcon(){return d},get Loading(){return h},get ChatContainer(){return G},get ConversationList(){return W},get AGENT_UPLOAD_ACCEPT(){return oe},get AGENT_ASSISTANT_NAME(){return ae},get AGENT_ASSISTANT_AVATAR(){return le},MarketplacePanel:rt,AgentEngineSelector:de};return Object.defineProperty($,"__isScriptSetup",{enumerable:!1,value:!0}),$}});y(),m();var at={ref:`rootRef`,class:`agent-view`},ot={class:`agent-chat-area`},st={class:`agent-tabs`,role:`tablist`},ct=[`aria-selected`,`onClick`],lt={class:`agent-tab-body`},ut={key:0,class:`agent-chat-pane`},dt={key:0,class:`agent-list-page`},ft={key:0,class:`agent-page-bar`},pt=[`title`,`aria-label`],mt={class:`agent-page-title`},ht={key:0,class:`chat-loading`};function gt(e,t,n,r,i,o){return S(),a(`div`,at,[s(` ═══ 宽屏:左侧会话列表(只在「对话」Tab 显示)═══
3
- 窄屏时这一整块不渲染,会话列表改成 body 里的一个整页(见 .agent-list-page) `),r.activeTab===`chat`&&!r.narrow?(S(),a(U,{key:0},[V(`aside`,{class:`agent-sidebar`,style:u({width:r.sidebarWidth+`px`})},[s(` 会话列表:UI 与交互都在组件库的 ConversationList 里 `),p(r.ConversationList,{class:`agent-conversations`,items:r.conversationItems,"active-id":r.currentSessionId,loading:r.sessionsLoading,labels:r.conversationLabels,onSelect:r.selectSession,onNew:r.handleNewSession,onRename:r.renameSession,onDelete:r.deleteSession},null,8,[`items`,`active-id`,`loading`,`labels`,`onRename`,`onDelete`])],4),s(` 拖拽分隔条 `),V(`div`,{class:f([`sidebar-resizer`,{active:r.isResizing}]),onMousedown:r.startResize},null,34)],64)):s(`v-if`,!0),s(` ═══ 右侧:Tab 栏 + 内容区 ═══ `),V(`main`,ot,[V(`nav`,st,[(S(!0),a(U,null,C(r.tabs,e=>(S(),a(`button`,{key:e.id,type:`button`,class:f([`agent-tab`,{active:r.activeTab===e.id}]),role:`tab`,"aria-selected":r.activeTab===e.id,onClick:t=>r.activeTab=e.id},[p(r.ElIcon,null,{default:L(()=>[(S(),b(D(e.icon)))]),_:2},1024),V(`span`,null,v(e.label),1)],10,ct))),128)),s(` ═══ 引擎选择器 ═══
4
- 与工作台执行器下拉同一套交互(点击展开、未安装标出来、点了直接开安装弹窗)。
5
- 下拉本体收口在 AgentEngineSelector,文件空间 g ai 面板复用同一份实现。
6
- 会话一旦落盘就置灰:引擎在会话建立时锁定(三家续聊标识互不通用),要换请新建。 `),r.activeTab===`chat`?(S(),b(r.AgentEngineSelector,{key:0,class:`agent-engine`,engine:r.displayEngine,locked:r.isEngineLocked,onSelect:r.handleEngineSelect},null,8,[`engine`,`locked`])):s(`v-if`,!0)]),V(`div`,lt,[s(` ── 对话 ── `),r.activeTab===`chat`?(S(),a(`div`,ut,[s(` 窄屏 · 会话列表页:列表独占整页 `),r.narrow&&r.chatPage===`list`?(S(),a(`div`,dt,[p(r.ConversationList,{class:`agent-conversations`,items:r.conversationItems,"active-id":r.currentSessionId,loading:r.sessionsLoading,labels:r.conversationLabels,onSelect:r.selectSession,onNew:r.handleNewSession,onRename:r.renameSession,onDelete:r.deleteSession},null,8,[`items`,`active-id`,`loading`,`labels`,`onRename`,`onDelete`])])):(S(),a(U,{key:1},[s(` 对话页(窄屏时顶上多一条返回条;宽屏左侧就摆着列表,不需要) `),r.narrow?(S(),a(`div`,ft,[V(`button`,{type:`button`,class:`agent-page-back`,title:r.$t(`@AGENT:返回会话列表`),"aria-label":r.$t(`@AGENT:返回会话列表`),onClick:t[0]||=e=>r.chatPage=`list`},[...t[1]||=[V(`svg`,{viewBox:`0 0 24 24`,width:`14`,height:`14`,fill:`none`,stroke:`currentColor`,"stroke-width":`2`,"stroke-linecap":`round`,"stroke-linejoin":`round`},[V(`line`,{x1:`19`,y1:`12`,x2:`5`,y2:`12`}),V(`polyline`,{points:`12 19 5 12 12 5`})],-1)]],8,pt),V(`span`,mt,v(r.currentSessionTitle),1)])):s(`v-if`,!0),s(`
7
- 对话宿主:单独开一层裁剪边界。
8
- 欢迎页在矮/窄窗口里会比可用高度还高,而它是垂直居中的 —— 溢出的部分
9
- 会**向上**跑出 .acu-chat,盖到刚加的返回条上(把返回条的点击也一起吃掉,
10
- e2e 实测就是这个症状)。宿主的 overflow:hidden 把它裁在自己这一格里。
11
- `),V(`div`,{class:`agent-chat-host`,style:r.welcomeAvatarStyle},[s(` 加载中 `),r.sessionLoading?(S(),a(`div`,ht,[p(r.ElIcon,{class:`is-loading`,size:32},{default:L(()=>[p(r.Loading)]),_:1}),V(`span`,null,v(r.$t(`@AGENT:加载会话中...`)),1)])):(S(),a(U,{key:1},[s(` ChatContainer `),p(r.ChatContainer,{ref:`chatContainerRef`,messages:r.messages,"preset-questions":r.presetQuestions,"welcome-title":r.$t(`@AGENT:智能体助手`),"welcome-description":r.$t(`@AGENT:我可以帮你阅读代码、执行命令、修改文件。选择下方话题或直接输入你的问题。`),"assistant-name":r.AGENT_ASSISTANT_NAME,"assistant-avatar":r.AGENT_ASSISTANT_AVATAR,theme:r.chatTheme,disabled:r.isStreaming,generating:r.isStreaming,"upload-config":{accept:r.AGENT_UPLOAD_ACCEPT},placeholder:r.isStreaming?r.$t(`@AGENT:正在生成中...`):r.$t(`@AGENT:输入消息,Enter 发送`),question:r.pendingQuestion,"question-submitting":r.answeringQuestion,"question-labels":r.questionLabels,"plan-config":{labels:{title:r.$t(`@AGENT:计划`),raw:r.$t(`@AGENT:原始参数`)}},onSend:r.onSend,onSelect:r.onSelectPreset,onStop:r.stop,onAnswer:r.answerQuestion},null,8,[`messages`,`preset-questions`,`welcome-title`,`welcome-description`,`assistant-name`,`assistant-avatar`,`theme`,`disabled`,`generating`,`upload-config`,`placeholder`,`question`,`question-submitting`,`question-labels`,`plan-config`,`onStop`,`onAnswer`])],2112))])],64))])):(S(),a(U,{key:1},[s(` ── Skill 广场 / MCP 广场 ── `),p(r.MarketplacePanel,{type:r.marketplaceType},null,8,[`type`])],2112))])])],512)}var _t=q(it,[[`render`,gt],[`__scopeId`,`data-v-bd681ddd`],[`__file`,`C:/workspace/github_workspace/xz333221/zen-gitsync/src/ui/client/src/views/AgentView.vue`]]);export{_t as default};
@@ -1,2 +0,0 @@
1
- import"./rolldown-runtime-C0FnF6B9.js";import{Cn as e,Dn as t,En as n,Gn as r,H as i,Hn as a,K as o,Kr as s,L as c,Mn as l,Nr as u,Ot as d,Pn as f,Qr as p,Rn as m,Tn as h,Yn as g,a as _,dr as v,et as y,i as b,ir as x,jn as S,or as C,qn as w,tt as T,vr as E,wn as D}from"./element-plus-D0R8BMj4.js";import{n as O,t as k}from"./_plugin-vue_export-helper-BKeZSroN.js";import{t as A}from"./CommonDialog-DpFTO5cg.js";async function j(e,t){let n=e.body.getReader(),r=new TextDecoder(`utf-8`),i=``;for(;;){let{done:e,value:a}=await n.read();if(e)break;i+=r.decode(a,{stream:!0});let o=i.split(`
2
- `);i=o.pop()??``;for(let e of o)e.trim()&&t(e)}i.trim()&&t(i)}async function M(){let e=await fetch(`/api/app-version`);return e.ok?e.json():{success:!1,current:``,latest:null,hasUpdate:!1,error:`HTTP ${e.status}`}}async function N(e){let t=await fetch(`/api/app-upgrade`,{method:`POST`});if(!t.ok){let n=`HTTP ${t.status}`;try{let e=await t.json();e?.error&&(n=e.error)}catch{}e({type:`error`,message:n}),e({type:`done`,code:t.status});return}if(!t.body){e({type:`error`,message:`响应无 body`}),e({type:`done`,code:-1});return}await j(t,t=>{try{e(JSON.parse(t))}catch{}})}async function P(){let e=await fetch(`/api/app-restart`,{method:`POST`});if(!e.ok){let t=`HTTP ${e.status}`;try{let n=await e.json();n?.error&&(t=n.error)}catch{}throw Error(t)}if(!e.body)throw Error(`响应无 body`);return new Promise((t,n)=>{let r=!1,i=e=>{r||(r=!0,e())};j(e,e=>{try{let r=JSON.parse(e);r.type===`ready`&&typeof r.port==`number`&&r.port>0?i(()=>t(r.port)):r.type===`timeout`?i(()=>n(Error(`restart.timeout`))):r.type===`error`&&i(()=>n(Error(r.message||`restart.failed`)))}catch{}}).then(()=>{i(()=>n(Error(`restart.stream-closed`)))}).catch(()=>{})})}m(),v();var F=`npm install -g zen-gitsync --registry https://registry.npmjs.org/`,I=f({__name:`UpgradeDialog`,props:{modelValue:{type:Boolean,required:!0},logs:{type:String,required:!0},status:{type:String,required:!0},countdown:{type:Number,required:!1,default:0}},emits:[`update:modelValue`,`retry`,`restart`,`cancel`],setup(e,{expose:t,emit:n}){t();let r=e,i=n,o=E(null);x(()=>r.logs,async()=>{await a(),o.value&&(o.value.scrollTop=o.value.scrollHeight)});function s(){r.status!==`running`&&(r.status===`success`&&r.countdown>0&&i(`cancel`),i(`update:modelValue`,!1))}function c(){i(`retry`)}function l(){i(`restart`)}function u(){i(`cancel`),i(`update:modelValue`,!1)}async function f(){try{await navigator.clipboard.writeText(F),_.success(O(`@F13B4:已复制升级命令`))}catch{_.warning(O(`@F13B4:复制失败,请手动复制`))}}let p={MANUAL_COMMAND:F,props:r,emit:i,logEl:o,close:s,onRetry:c,onRestart:l,onLater:u,onCopyCommand:f,get Loading(){return d},get CircleCheck(){return y},get CircleClose(){return T},get $t(){return O},CommonDialog:A};return Object.defineProperty(p,"__isScriptSetup",{enumerable:!1,value:!0}),p}});m(),u();var L={key:3},R={key:4},z={key:5},B={key:0,class:`upgrade-hint`},V={key:0,class:`upgrade-countdown`},H={key:1,class:`upgrade-manual`},U={class:`upgrade-manual__label`};function W(e,r,i,a,u,d){let f=o,m=c;return g(),h(a.CommonDialog,{"model-value":i.modelValue,"onUpdate:modelValue":r[0]||=e=>a.emit(`update:modelValue`,e),title:a.$t(`@F13B4:升级`),width:`680px`,type:`flex`,"close-on-click-modal":!1,"close-on-press-escape":!1,"show-close":i.status!==`running`,"append-to-body":``,onClose:a.close},{footer:C(()=>[i.status===`failed`?(g(),h(m,{key:0,type:`primary`,onClick:a.onRetry},{default:C(()=>[S(p(a.$t(`@F13B4:重试`)),1)]),_:1})):n(`v-if`,!0),i.status===`failed`?(g(),h(m,{key:1,onClick:a.onCopyCommand},{default:C(()=>[S(p(a.$t(`@F13B4:复制升级命令`)),1)]),_:1})):n(`v-if`,!0),i.status===`success`?(g(),h(m,{key:2,type:`primary`,onClick:a.onRestart},{default:C(()=>[S(p(a.$t(`@F13B4:立即重启并刷新`)),1)]),_:1})):n(`v-if`,!0),i.status===`success`?(g(),h(m,{key:3,onClick:a.onLater},{default:C(()=>[S(p(a.$t(`@F13B4:稍后手动重启`)),1)]),_:1})):n(`v-if`,!0),i.status===`running`?(g(),h(m,{key:4,disabled:``},{default:C(()=>[S(p(a.$t(`@F13B4:升级中`))+`... `,1)]),_:1})):n(`v-if`,!0)]),default:C(()=>[D(`div`,{class:s([`upgrade-status`,`is-${i.status}`])},[i.status===`running`?(g(),h(f,{key:0,class:`is-loading`},{default:C(()=>[l(a.Loading)]),_:1})):i.status===`success`?(g(),h(f,{key:1,color:`var(--color-success)`},{default:C(()=>[l(a.CircleCheck)]),_:1})):(g(),h(f,{key:2,color:`var(--color-danger)`},{default:C(()=>[l(a.CircleClose)]),_:1})),i.status===`running`?(g(),t(`span`,L,p(a.$t(`@F13B4:升级中`)),1)):i.status===`success`?(g(),t(`span`,R,p(a.$t(`@F13B4:升级完成`)),1)):(g(),t(`span`,z,p(a.$t(`@F13B4:升级失败`)),1))],2),i.status===`success`?(g(),t(`p`,B,[S(p(a.$t(`@F13B4:新版本已全局安装,需要重启服务才能生效`))+` `,1),i.countdown>0?(g(),t(`span`,V,p(a.$t(`@F13B4:{seconds} 秒后自动刷新`,{seconds:i.countdown})),1)):n(`v-if`,!0)])):n(`v-if`,!0),i.status===`failed`?(g(),t(`p`,H,[D(`span`,U,p(a.$t(`@F13B4:手动升级命令`)),1),D(`code`,{class:`upgrade-manual__cmd`},p(a.MANUAL_COMMAND))])):n(`v-if`,!0),D(`pre`,{ref:`logEl`,class:`upgrade-log`},p(i.logs||a.$t(`@F13B4:等待日志输出`)),513)]),_:1},8,[`model-value`,`title`,`show-close`])}var G=k(I,[[`render`,W],[`__scopeId`,`data-v-bc566bba`],[`__file`,`C:/workspace/github_workspace/xz333221/zen-gitsync/src/ui/client/src/components/UpgradeDialog.vue`]]);m(),v();var K=`https://github.com/xz333221/zen-gitsync/releases`,q=5,J=`app-version-check`,Y=36e5,X=f({__name:`AppVersionBadge`,setup(t,{expose:n}){n();let i=`2.17.29`,a=E(i),o=e(()=>a.value||i),s=E(null),c=E(!1),l=E(``),u=E(`running`),d=E(0),f=null,p=E(!1);function m(){try{let e=sessionStorage.getItem(J);if(!e)return null;let{at:t,data:n}=JSON.parse(e);return Date.now()-t>Y?null:n}catch{return null}}function h(e){try{sessionStorage.setItem(J,JSON.stringify({at:Date.now(),data:e}))}catch{}}async function g(e=!1){let t=m();t&&(s.value=t);try{let t=await M();s.value=t,h(t),t.current&&(a.value=t.current),!e&&t.success&&t.hasUpdate&&_({type:`success`,message:O(`@F13B4:发现新版本 {version}`,{version:t.latest??``}),duration:4e3})}catch(t){e||_({type:`warning`,message:O(`@F13B4:检查更新失败 {error}`,{error:t?.message||``})})}}async function v(){if(s.value?.hasUpdate){try{await b.confirm(O(`@F13B4:确定要从 {current} 升级到 {latest} 吗?`,{current:s.value.current,latest:s.value.latest}),O(`@F13B4:升级`),{confirmButtonText:O(`@F13B4:确定升级`),cancelButtonText:O(`@F13B4:取消`),type:`info`})}catch{return}l.value=``,u.value=`running`,c.value=!0,await N(e=>{if(e.type===`done`){if(u.value=e.code===0?`success`:`failed`,e.code===0){try{sessionStorage.removeItem(J)}catch{}s.value?.latest&&(a.value=s.value.latest),y()}}else e.message&&(l.value+=e.message)})}}function y(){x(),d.value=q,f=setInterval(()=>{--d.value,d.value<=0&&(f&&=(clearInterval(f),null),C())},1e3)}function x(){f&&=(clearInterval(f),null),d.value=0}function S(){x()}async function C(){if(x(),!p.value){p.value=!0;try{let e=await P(),{pathname:t,search:n,hash:r}=window.location;window.location.href=`http://localhost:${e}${t}${n}${r}`}catch(e){let t=e?.message||``,n={"restart.timeout":`@F13B4:重启超时`,"restart.stream-closed":`@F13B4:重启失败,请刷新页面重试`,"restart.failed":`@F13B4:重启失败`}[t];_({type:`warning`,message:n?O(n):O(`@F13B4:重启失败 {error}`,{error:t})}),p.value=!1}}}r(()=>{x()}),w(()=>{g(!0)});let T={buildVersion:i,releasesUrl:K,runtimeVersion:a,displayVersion:o,latestInfo:s,upgradeDialogVisible:c,upgradeLogs:l,upgradeStatus:u,AUTO_REFRESH_SECONDS:q,countdown:d,get countdownTimer(){return f},set countdownTimer(e){f=e},restartInFlight:p,CACHE_KEY:J,CACHE_TTL_MS:Y,readCache:m,writeCache:h,checkUpdate:g,handleUpgrade:v,startAutoRefreshCountdown:y,cancelAutoRefreshCountdown:x,handleCancelAutoRefresh:S,handleRestart:C,get $t(){return O},UpgradeDialog:G};return Object.defineProperty(T,"__isScriptSetup",{enumerable:!1,value:!0}),T}});m(),u();var Z={class:`app-version-badge`},Q=[`aria-label`],$=[`aria-label`],ee=[`title`,`aria-label`],te={class:`upgrade-btn__label`},ne={class:`upgrade-btn__version`};function re(e,r,a,o,s,c){let u=i;return g(),t(`div`,Z,[n(` GitHub 链接 `),l(u,{content:o.$t(`@F13B4:在 GitHub 上查看源码`),placement:`top`,effect:`dark`,"show-after":300},{default:C(()=>[D(`a`,{href:`https://github.com/xz333221/zen-gitsync`,target:`_blank`,rel:`noopener noreferrer`,class:`badge-icon-link`,"aria-label":o.$t(`@F13B4:在 GitHub 上查看源码`)},[...r[1]||=[D(`svg`,{viewBox:`0 0 24 24`,width:`15`,height:`15`,"aria-hidden":`true`,fill:`currentColor`,xmlns:`http://www.w3.org/2000/svg`},[D(`path`,{d:`M12 2C6.477 2 2 6.484 2 12.017c0 4.425 2.865 8.18 6.839 9.504.5.092.682-.217.682-.483 0-.237-.008-.868-.013-1.703-2.782.605-3.369-1.343-3.369-1.343-.454-1.158-1.11-1.466-1.11-1.466-.908-.62.069-.608.069-.608 1.003.07 1.531 1.032 1.531 1.032.892 1.53 2.341 1.088 2.91.832.092-.647.35-1.088.636-1.338-2.22-.253-4.555-1.113-4.555-4.951 0-1.093.39-1.988 1.029-2.688-.103-.253-.446-1.272.098-2.65 0 0 .84-.27 2.75 1.026A9.564 9.564 0 0 1 12 6.844a9.59 9.59 0 0 1 2.504.337c1.909-1.296 2.747-1.027 2.747-1.027.546 1.379.202 2.398.1 2.651.64.7 1.028 1.595 1.028 2.688 0 3.848-2.339 4.695-4.566 4.943.359.309.678.92.678 1.855 0 1.338-.012 2.419-.012 2.747 0 .268.18.58.688.482A10.02 10.02 0 0 0 22 12.017C22 6.484 17.522 2 12 2z`})],-1)]],8,Q)]),_:1},8,[`content`]),n(` NPM 链接 `),l(u,{content:o.$t(`@F13B4:在 npm 上查看包`),placement:`top`,effect:`dark`,"show-after":300},{default:C(()=>[D(`a`,{href:`https://www.npmjs.com/package/zen-gitsync`,target:`_blank`,rel:`noopener noreferrer`,class:`badge-icon-link badge-icon-link--npm`,"aria-label":o.$t(`@F13B4:在 npm 上查看包`)},[...r[2]||=[D(`svg`,{viewBox:`0 0 24 24`,width:`15`,height:`15`,"aria-hidden":`true`,fill:`currentColor`,xmlns:`http://www.w3.org/2000/svg`},[D(`path`,{d:`M2 2v20h20V2H2zm17.167 17.167H12.5v-10h-3.333v10H4.833V4.833h14.334v14.334z`})],-1)]],8,$)]),_:1},8,[`content`]),n(` 版本号 `),l(u,{content:o.$t(`@F13B4:查看更新`),placement:`top`,effect:`dark`,"show-after":300},{default:C(()=>[D(`a`,{href:o.releasesUrl,target:`_blank`,rel:`noopener noreferrer`,class:`version-link`},`v`+p(o.displayVersion),1)]),_:1},8,[`content`]),o.latestInfo?.hasUpdate?(g(),t(`button`,{key:0,type:`button`,class:`upgrade-btn`,title:o.$t(`@F13B4:发现新版本 {version},点击升级`,{version:o.latestInfo.latest??``}),"aria-label":o.$t(`@F13B4:发现新版本 {version},点击升级`,{version:o.latestInfo.latest??``}),onClick:o.handleUpgrade},[r[3]||=D(`span`,{class:`upgrade-btn__dot`,"aria-hidden":`true`},null,-1),D(`span`,te,p(o.$t(`@F13B4:升级`)),1),D(`span`,ne,`v`+p(o.latestInfo.latest),1)],8,ee)):n(`v-if`,!0),l(o.UpgradeDialog,{modelValue:o.upgradeDialogVisible,"onUpdate:modelValue":r[0]||=e=>o.upgradeDialogVisible=e,logs:o.upgradeLogs,status:o.upgradeStatus,countdown:o.countdown,onRetry:o.handleUpgrade,onRestart:o.handleRestart,onCancel:o.handleCancelAutoRefresh},null,8,[`modelValue`,`logs`,`status`,`countdown`])])}var ie=k(X,[[`render`,re],[`__scopeId`,`data-v-7e0300ec`],[`__file`,`C:/workspace/github_workspace/xz333221/zen-gitsync/src/ui/client/src/components/AppVersionBadge.vue`]]);export{ie as default};