zen-gitsync 2.17.25 → 2.17.27

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 (131) hide show
  1. package/README.md +3 -3
  2. package/package.json +1 -1
  3. package/scripts/release.js +8 -10
  4. package/src/cli/ai/tools.js +2 -1
  5. package/src/ui/public/assets/AgentEngineSelector-BUZYAFe-.js +2 -0
  6. package/src/ui/public/assets/AgentEngineSelector-DUf0TfaU.css +1 -0
  7. package/src/ui/public/assets/AgentView-BFSmHCq7.js +10 -0
  8. package/src/ui/public/assets/AgentView-BaYMwfC4.css +1 -0
  9. package/src/ui/public/assets/AppVersionBadge-BVZKGETC.js +2 -0
  10. package/src/ui/public/assets/{AppVersionBadge--5If494j.css → AppVersionBadge-DDYQqoy7.css} +1 -1
  11. package/src/ui/public/assets/BranchSelector-DHWPodUA.js +1 -0
  12. package/src/ui/public/assets/{BranchSelector-BTCMYwG0.css → BranchSelector-F4ibldMN.css} +1 -1
  13. package/src/ui/public/assets/CommitForm-4kI2csgs.css +1 -0
  14. package/src/ui/public/assets/CommitForm-OhIAOaq7.js +6 -0
  15. package/src/ui/public/assets/CommonDialog-BXR1wKBq.js +1 -0
  16. package/src/ui/public/assets/CommonDialog-w6EmBEXv.css +1 -0
  17. package/src/ui/public/assets/EditorView-8SLDYcaH.js +37 -0
  18. package/src/ui/public/assets/EditorView-CftLj8-x.css +1 -0
  19. package/src/ui/public/assets/FileDiffViewer-CfMdcN2j.js +15 -0
  20. package/src/ui/public/assets/FileDiffViewer-n5ea9qMe.css +1 -0
  21. package/src/ui/public/assets/FlowExecutionViewer-Dp3ZiatT.js +1 -0
  22. package/src/ui/public/assets/FlowExecutionViewer-jNHI5zdm.css +1 -0
  23. package/src/ui/public/assets/FlowOrchestrationWorkspace-D97DHgyy.js +21 -0
  24. package/src/ui/public/assets/FlowOrchestrationWorkspace-DwuSSxJ1.css +1 -0
  25. package/src/ui/public/assets/IconButton-BburHRSR.js +1 -0
  26. package/src/ui/public/assets/IconButton-RlXqKOeu.css +1 -0
  27. package/src/ui/public/assets/LogList-XspUhx-C.css +1 -0
  28. package/src/ui/public/assets/LogList-gFCt4Zlu.js +7 -0
  29. package/src/ui/public/assets/MindmapView-Diq2Cu4O.css +1 -0
  30. package/src/ui/public/assets/MindmapView-N67x62Qk.js +2 -0
  31. package/src/ui/public/assets/MonitorView-BQr56ZSC.js +1 -0
  32. package/src/ui/public/assets/MonitorView-DwUwJXnY.css +1 -0
  33. package/src/ui/public/assets/ProjectStartupButton-CB8IOg1E.css +1 -0
  34. package/src/ui/public/assets/ProjectStartupButton-DuWf9Z0m.js +1 -0
  35. package/src/ui/public/assets/RecentDirectoriesChat-6OG0eOqz.js +3 -0
  36. package/src/ui/public/assets/RecentDirectoriesChat-BMkc7fS1.css +1 -0
  37. package/src/ui/public/assets/RemoteManagerDialog-D4WphMtO.css +1 -0
  38. package/src/ui/public/assets/RemoteManagerDialog-DrR79IRj.js +4 -0
  39. package/src/ui/public/assets/RemoteRepoCard-BU6qOfoG.js +7 -0
  40. package/src/ui/public/assets/RemoteRepoCard-o01IrbVz.css +1 -0
  41. package/src/ui/public/assets/SourceMapView-Ba73NVs8.js +3 -0
  42. package/src/ui/public/assets/SourceMapView-CfDydtsu.css +1 -0
  43. package/src/ui/public/assets/SvgIcon-22vrNtgL.js +1 -0
  44. package/src/ui/public/assets/{SvgIcon-BQ6bR7l0.css → SvgIcon-CDNsk4-Q.css} +1 -1
  45. package/src/ui/public/assets/TemplateManager-C9CzSc6Y.js +1 -0
  46. package/src/ui/public/assets/TemplateManager-DO7EPfj8.css +1 -0
  47. package/src/ui/public/assets/UserInputNode-4m8zpMv9.js +1 -0
  48. package/src/ui/public/assets/UserInputNode-DDNSarag.css +1 -0
  49. package/src/ui/public/assets/{WorkbenchView-CJCxYK-K.css → WorkbenchView-B4F_TeL5.css} +1 -1
  50. package/src/ui/public/assets/WorkbenchView-CcqEZ_GQ.js +142 -0
  51. package/src/ui/public/assets/_plugin-vue_export-helper-DRfZJqP7.js +19 -0
  52. package/src/ui/public/assets/agentConversations-17Pw5IQJ.js +6 -0
  53. package/src/ui/public/assets/{configStore-C-htWfWm.js → configStore-Gzz89_z_.js} +1 -1
  54. package/src/ui/public/assets/element-plus-AaVXaSqv.js +48 -0
  55. package/src/ui/public/assets/{flow-mindmap-OPE_D4da.js → flow-mindmap-C22NSZc1.js} +34 -34
  56. package/src/ui/public/assets/{gitStore-DYpd8vZK.js → gitStore--fzrTf_y.js} +1 -1
  57. package/src/ui/public/assets/index-C7Ad5BGO.css +1 -0
  58. package/src/ui/public/assets/index-EJrJOx7s.js +142 -0
  59. package/src/ui/public/assets/office-docx-CJ6JSCs6.js +32 -0
  60. package/src/ui/public/assets/{office-excel-DMRySOdK.js → office-excel-BOjWK-1x.js} +1 -1
  61. package/src/ui/public/assets/office-pptx-CF43ZwXE.js +74 -0
  62. package/src/ui/public/assets/previewSandbox-BOIpC6hW.js +9 -0
  63. package/src/ui/public/assets/{previewSandbox-SqNQvuHv.css → previewSandbox-PYnM6Paz.css} +1 -1
  64. package/src/ui/public/assets/socket-io-BFqQXqAp.js +1 -0
  65. package/src/ui/public/assets/{toolsStore-DFu4Alwg.js → toolsStore-B3bFcRXP.js} +1 -1
  66. package/src/ui/public/assets/{useThemeObserver-DYxFoHh1.js → useThemeObserver-CiUpR4zj.js} +1 -1
  67. package/src/ui/public/assets/vendor-Bwl4dT-P.css +1 -0
  68. package/src/ui/public/assets/{vendor-BjomZKSS.js → vendor-viKaN257.js} +180 -173
  69. package/src/ui/public/assets/vue-flow-DwYB6Myr.js +39 -0
  70. package/src/ui/public/index.html +25 -25
  71. package/src/ui/server/routes/workbench/dispatchInstruction.js +8 -6
  72. package/src/ui/server/routes/workbench/dispatchInstruction.test.js +29 -2
  73. package/src/ui/server/routes/workbench/index.js +22 -15
  74. package/src/ui/server/routes/workbench/orchestratorPrompt.test.js +47 -2
  75. package/src/ui/server/routes/workbench/orchestratorStore.js +29 -1
  76. package/src/ui/server/routes/workbench/shared.js +28 -2
  77. package/src/utils/selfUpdatePolicy.js +26 -4
  78. package/src/utils/selfUpdatePolicy.test.js +21 -2
  79. package/src/ui/public/assets/AgentEngineSelector-BArUIELz.js +0 -1
  80. package/src/ui/public/assets/AgentEngineSelector-DR3r5eQE.css +0 -1
  81. package/src/ui/public/assets/AgentView-B3LhhRsy.css +0 -1
  82. package/src/ui/public/assets/AgentView-LpT8A9Fo.js +0 -1
  83. package/src/ui/public/assets/AppVersionBadge-DNXQm_Si.js +0 -2
  84. package/src/ui/public/assets/BranchSelector-DZPTz5Ql.js +0 -1
  85. package/src/ui/public/assets/CommitForm-6avyu1Kv.js +0 -6
  86. package/src/ui/public/assets/CommitForm-C-cL8_vz.css +0 -1
  87. package/src/ui/public/assets/CommonDialog-Bzi5ISqT.css +0 -1
  88. package/src/ui/public/assets/CommonDialog-CRXyMlp5.js +0 -1
  89. package/src/ui/public/assets/EditorView-BIHL8I9g.js +0 -1
  90. package/src/ui/public/assets/EditorView-DqxBkn6c.css +0 -1
  91. package/src/ui/public/assets/FileDiffViewer-B6zy6NiA.css +0 -1
  92. package/src/ui/public/assets/FileDiffViewer-BEYcCYs5.js +0 -14
  93. package/src/ui/public/assets/FlowExecutionViewer-B5kh7VxS.css +0 -1
  94. package/src/ui/public/assets/FlowExecutionViewer-DKRHDqbG.js +0 -1
  95. package/src/ui/public/assets/FlowOrchestrationWorkspace-BnCsF4z4.js +0 -21
  96. package/src/ui/public/assets/FlowOrchestrationWorkspace-CM2_9TW3.css +0 -1
  97. package/src/ui/public/assets/IconButton-D4df4t9A.js +0 -1
  98. package/src/ui/public/assets/IconButton-W2X09OKH.css +0 -1
  99. package/src/ui/public/assets/LogList-C7q0Y4J_.css +0 -1
  100. package/src/ui/public/assets/LogList-DXqB4JJv.js +0 -7
  101. package/src/ui/public/assets/MindmapView-C4xg3TwX.css +0 -1
  102. package/src/ui/public/assets/MindmapView-Ddc55wzp.js +0 -1
  103. package/src/ui/public/assets/MonitorView-BWOQfPtI.css +0 -1
  104. package/src/ui/public/assets/MonitorView-DeERavq-.js +0 -1
  105. package/src/ui/public/assets/ProjectStartupButton-DaLU4iS4.js +0 -1
  106. package/src/ui/public/assets/ProjectStartupButton-VoQqFee4.css +0 -1
  107. package/src/ui/public/assets/RecentDirectoriesChat-DVtsf7lW.js +0 -1
  108. package/src/ui/public/assets/RecentDirectoriesChat-o4xK1Koc.css +0 -1
  109. package/src/ui/public/assets/RemoteManagerDialog-BSOph2h2.css +0 -1
  110. package/src/ui/public/assets/RemoteManagerDialog-C3cfDsZi.js +0 -1
  111. package/src/ui/public/assets/RemoteRepoCard-CG9WPcA_.css +0 -1
  112. package/src/ui/public/assets/RemoteRepoCard-D1Hso5Fq.js +0 -1
  113. package/src/ui/public/assets/SourceMapView-CO_BZJKu.css +0 -1
  114. package/src/ui/public/assets/SourceMapView-Dwn5wy1o.js +0 -3
  115. package/src/ui/public/assets/SvgIcon-diKLLWK9.js +0 -1
  116. package/src/ui/public/assets/TemplateManager-D9gyZbHP.js +0 -1
  117. package/src/ui/public/assets/TemplateManager-DiM3vrbf.css +0 -1
  118. package/src/ui/public/assets/UserInputNode-JNtVWcbE.js +0 -1
  119. package/src/ui/public/assets/UserInputNode-d-oRXM_J.css +0 -1
  120. package/src/ui/public/assets/WorkbenchView-DxaAjZAA.js +0 -6
  121. package/src/ui/public/assets/_plugin-vue_export-helper-C7IvbEC3.js +0 -19
  122. package/src/ui/public/assets/agentConversations-nN-yDz8q.js +0 -5
  123. package/src/ui/public/assets/element-plus-Bx3j6vzG.js +0 -28
  124. package/src/ui/public/assets/index-BZFKDSGv.css +0 -1
  125. package/src/ui/public/assets/index-eq1OUcdk.js +0 -29
  126. package/src/ui/public/assets/office-docx-B0sJQnr-.js +0 -31
  127. package/src/ui/public/assets/office-pptx-Do2blxao.js +0 -60
  128. package/src/ui/public/assets/previewSandbox-Z707CZD9.js +0 -9
  129. package/src/ui/public/assets/socket-io-TBtw0msS.js +0 -1
  130. package/src/ui/public/assets/vendor-BY5ZDlW1.css +0 -1
  131. package/src/ui/public/assets/vue-flow-fVVGZspa.js +0 -39
@@ -10,38 +10,38 @@
10
10
  <meta name="theme-color" content="#141820" media="(prefers-color-scheme: dark)" />
11
11
  <title>Zen GitSync</title>
12
12
  <!-- 字体改为自托管(@font-face 见 src/main.css),不再依赖 Google Fonts 外链 -->
13
- <script type="module" crossorigin src="/assets/index-eq1OUcdk.js"></script>
13
+ <script type="module" crossorigin src="/assets/index-EJrJOx7s.js"></script>
14
14
  <link rel="modulepreload" crossorigin href="/assets/rolldown-runtime-C0FnF6B9.js">
15
- <link rel="modulepreload" crossorigin href="/assets/element-plus-Bx3j6vzG.js">
15
+ <link rel="modulepreload" crossorigin href="/assets/element-plus-AaVXaSqv.js">
16
16
  <link rel="modulepreload" crossorigin href="/assets/monaco-DkJuNcCr.js">
17
- <link rel="modulepreload" crossorigin href="/assets/vendor-BjomZKSS.js">
18
- <link rel="modulepreload" crossorigin href="/assets/_plugin-vue_export-helper-C7IvbEC3.js">
19
- <link rel="modulepreload" crossorigin href="/assets/SvgIcon-diKLLWK9.js">
20
- <link rel="modulepreload" crossorigin href="/assets/IconButton-D4df4t9A.js">
21
- <link rel="modulepreload" crossorigin href="/assets/configStore-C-htWfWm.js">
22
- <link rel="modulepreload" crossorigin href="/assets/socket-io-TBtw0msS.js">
23
- <link rel="modulepreload" crossorigin href="/assets/gitStore-DYpd8vZK.js">
17
+ <link rel="modulepreload" crossorigin href="/assets/vendor-viKaN257.js">
18
+ <link rel="modulepreload" crossorigin href="/assets/_plugin-vue_export-helper-DRfZJqP7.js">
19
+ <link rel="modulepreload" crossorigin href="/assets/SvgIcon-22vrNtgL.js">
20
+ <link rel="modulepreload" crossorigin href="/assets/IconButton-BburHRSR.js">
21
+ <link rel="modulepreload" crossorigin href="/assets/configStore-Gzz89_z_.js">
22
+ <link rel="modulepreload" crossorigin href="/assets/socket-io-BFqQXqAp.js">
23
+ <link rel="modulepreload" crossorigin href="/assets/gitStore--fzrTf_y.js">
24
24
  <link rel="modulepreload" crossorigin href="/assets/fileIcon-B5p5pGN9.js">
25
- <link rel="modulepreload" crossorigin href="/assets/flow-mindmap-OPE_D4da.js">
26
- <link rel="modulepreload" crossorigin href="/assets/previewSandbox-Z707CZD9.js">
27
- <link rel="modulepreload" crossorigin href="/assets/useThemeObserver-DYxFoHh1.js">
28
- <link rel="modulepreload" crossorigin href="/assets/CommonDialog-CRXyMlp5.js">
29
- <link rel="modulepreload" crossorigin href="/assets/FileDiffViewer-BEYcCYs5.js">
30
- <link rel="modulepreload" crossorigin href="/assets/toolsStore-DFu4Alwg.js">
31
- <link rel="modulepreload" crossorigin href="/assets/TemplateManager-D9gyZbHP.js">
32
- <link rel="modulepreload" crossorigin href="/assets/vue-flow-fVVGZspa.js">
25
+ <link rel="modulepreload" crossorigin href="/assets/flow-mindmap-C22NSZc1.js">
26
+ <link rel="modulepreload" crossorigin href="/assets/previewSandbox-BOIpC6hW.js">
27
+ <link rel="modulepreload" crossorigin href="/assets/useThemeObserver-CiUpR4zj.js">
28
+ <link rel="modulepreload" crossorigin href="/assets/CommonDialog-BXR1wKBq.js">
29
+ <link rel="modulepreload" crossorigin href="/assets/FileDiffViewer-CfMdcN2j.js">
30
+ <link rel="modulepreload" crossorigin href="/assets/toolsStore-B3bFcRXP.js">
31
+ <link rel="modulepreload" crossorigin href="/assets/TemplateManager-C9CzSc6Y.js">
32
+ <link rel="modulepreload" crossorigin href="/assets/vue-flow-DwYB6Myr.js">
33
33
  <link rel="stylesheet" crossorigin href="/assets/element-plus-Qexo5hiM.css">
34
34
  <link rel="stylesheet" crossorigin href="/assets/monaco-B2lPvkrc.css">
35
- <link rel="stylesheet" crossorigin href="/assets/vendor-BY5ZDlW1.css">
36
- <link rel="stylesheet" crossorigin href="/assets/SvgIcon-BQ6bR7l0.css">
37
- <link rel="stylesheet" crossorigin href="/assets/IconButton-W2X09OKH.css">
35
+ <link rel="stylesheet" crossorigin href="/assets/vendor-Bwl4dT-P.css">
36
+ <link rel="stylesheet" crossorigin href="/assets/SvgIcon-CDNsk4-Q.css">
37
+ <link rel="stylesheet" crossorigin href="/assets/IconButton-RlXqKOeu.css">
38
38
  <link rel="stylesheet" crossorigin href="/assets/flow-mindmap-BMpmHdqh.css">
39
- <link rel="stylesheet" crossorigin href="/assets/previewSandbox-SqNQvuHv.css">
40
- <link rel="stylesheet" crossorigin href="/assets/CommonDialog-Bzi5ISqT.css">
41
- <link rel="stylesheet" crossorigin href="/assets/FileDiffViewer-B6zy6NiA.css">
42
- <link rel="stylesheet" crossorigin href="/assets/TemplateManager-DiM3vrbf.css">
39
+ <link rel="stylesheet" crossorigin href="/assets/previewSandbox-PYnM6Paz.css">
40
+ <link rel="stylesheet" crossorigin href="/assets/CommonDialog-w6EmBEXv.css">
41
+ <link rel="stylesheet" crossorigin href="/assets/FileDiffViewer-n5ea9qMe.css">
42
+ <link rel="stylesheet" crossorigin href="/assets/TemplateManager-DO7EPfj8.css">
43
43
  <link rel="stylesheet" crossorigin href="/assets/vue-flow-CavUTEzp.css">
44
- <link rel="stylesheet" crossorigin href="/assets/index-BZFKDSGv.css">
44
+ <link rel="stylesheet" crossorigin href="/assets/index-C7Ad5BGO.css">
45
45
  </head>
46
46
  <body>
47
47
  <div id="app"></div>
@@ -38,7 +38,7 @@ import {
38
38
  genId,
39
39
  TASKS_FILE,
40
40
  IMAGES_DIR,
41
- MAX_ATTACHMENTS_PER_TASK,
41
+ MAX_INSTRUCTION_CHARS,
42
42
  } from './shared.js';
43
43
  import { stagingPath, mimeForExt } from './attachmentUtils.js';
44
44
  import { readOrchestrator, resolveDispatchPrompt, appendInstruction } from './orchestratorStore.js';
@@ -109,7 +109,11 @@ export function createDispatcher({ configManager, getCurrentProjectPath, runTask
109
109
  async function dispatchInstruction(payload = {}) {
110
110
  const text = typeof payload.text === 'string' ? payload.text.trim() : '';
111
111
  if (!text) throw new HttpError(400, '指令内容不能为空');
112
- if (text.length > 4000) throw new HttpError(400, '指令过长(上限 4000 字)');
112
+ // 上限来自 shared.js,不在这里写死数字:写死过一次(4000),前端提示、
113
+ // 工具描述、这条校验各说各的,改的时候漏一处就变成"界面说能发、后端说不能"。
114
+ if (text.length > MAX_INSTRUCTION_CHARS) {
115
+ throw new HttpError(400, `指令过长(上限 ${MAX_INSTRUCTION_CHARS} 字)`);
116
+ }
113
117
 
114
118
  const fallbackPath = typeof getCurrentProjectPath === 'function' ? getCurrentProjectPath() : '';
115
119
  const bodyPath = typeof payload.projectPath === 'string' ? payload.projectPath.trim() : '';
@@ -182,11 +186,9 @@ export function createDispatcher({ configManager, getCurrentProjectPath, runTask
182
186
  // ── 附件:调用方只回传 { id, ext, originalName },服务端按 id 回暂存区找文件 ──
183
187
  // 路径完全由服务端拼(stagingPath 会同时校验 id 形状与 ext 白名单),
184
188
  // 所以不存在"调用方指定任意路径"这回事 —— 比"信任 absolutePath 再校验前缀"干净。
185
- // 数量与文件存在性在这里统一校验:上传时服务端是无状态的,压根不知道攒了几个。
189
+ // 这里只校验**每个附件自己**(形状 + 文件是否还在);数量不设上限,
190
+ // 真正的卡点只有下游读得动读不动。
186
191
  const rawAttachments = Array.isArray(payload.attachments) ? payload.attachments : [];
187
- if (rawAttachments.length > MAX_ATTACHMENTS_PER_TASK) {
188
- throw new HttpError(400, `附件最多 ${MAX_ATTACHMENTS_PER_TASK} 个`);
189
- }
190
192
  const staged = [];
191
193
  for (const item of rawAttachments) {
192
194
  const attId = typeof item?.id === 'string' ? item.id : '';
@@ -55,7 +55,7 @@ delete process.env.HOMEPATH;
55
55
  const load = p => import(pathToFileURL(path.join(projectRoot, p)).href);
56
56
  const { createDispatcher, resolveExecutor } = await load('src/ui/server/routes/workbench/dispatchInstruction.js');
57
57
  const { readOrchestrator, setOrchestratorActive, setDefaultPrompt } = await load('src/ui/server/routes/workbench/orchestratorStore.js');
58
- const { readJson, TASKS_FILE } = await load('src/ui/server/routes/workbench/shared.js');
58
+ const { readJson, TASKS_FILE, MAX_INSTRUCTION_CHARS } = await load('src/ui/server/routes/workbench/shared.js');
59
59
 
60
60
  // 两个项目目录:proj 是"选中/当前"的落点,other 用来证明显式指定优先于默认
61
61
  const proj = path.join(fakeHome, 'proj-alpha');
@@ -201,10 +201,37 @@ test('空指令 / 超长指令一律 400,不留下任务', async () => {
201
201
  const { dispatchInstruction } = makeDispatcher();
202
202
  const before = (await readTasks()).length;
203
203
  await assert.rejects(() => dispatchInstruction({ text: ' ' }), err => err.statusCode === 400 && /指令内容不能为空/.test(err.message));
204
- await assert.rejects(() => dispatchInstruction({ text: 'x'.repeat(4001) }), err => err.statusCode === 400 && /指令过长/.test(err.message));
204
+ // 用常量而不是写死数字:上限放宽过(4000 → 100000),钉死的用例会在放宽那天
205
+ // 悄悄变成"检查 4001 字"——它照样通过,却再也不测真正那道闸门了。
206
+ await assert.rejects(
207
+ () => dispatchInstruction({ text: 'x'.repeat(MAX_INSTRUCTION_CHARS + 1) }),
208
+ err => err.statusCode === 400 && /指令过长/.test(err.message),
209
+ );
205
210
  assert.equal((await readTasks()).length, before);
206
211
  });
207
212
 
213
+ test('刚好卡在上限的指令放行(闸门是 >,不是 >=)', async () => {
214
+ const { dispatchInstruction } = makeDispatcher();
215
+ const result = await dispatchInstruction({
216
+ text: 'x'.repeat(MAX_INSTRUCTION_CHARS),
217
+ autoRun: false,
218
+ });
219
+ // 整段正文原样落进任务:截断发生在"下发预览"那一层,不在派发这一层
220
+ assert.equal(result.task.desc.length, MAX_INSTRUCTION_CHARS);
221
+ });
222
+
223
+ test('指令正文原样进任务,不被悄悄截断', async () => {
224
+ const { dispatchInstruction } = makeDispatcher();
225
+ // 2026-09-29:用户粘一整段报错日志被 400 挡回来。除了放宽上限,还要钉住
226
+ // "整段进 desc" —— 因为 dispatchInstruction 是唯一拿得到原文的地方,
227
+ // 一旦这里截了,任务执行时模型看到的就是半截需求,而界面毫无异样。
228
+ const long = Array.from({ length: 3000 }, (_, i) => `第 ${i} 行:原始需求内容`).join('\n');
229
+ assert.ok(long.length > 20000 && long.length < MAX_INSTRUCTION_CHARS);
230
+ const result = await dispatchInstruction({ text: long, autoRun: false });
231
+ assert.equal(result.task.desc, long);
232
+ assert.equal(result.instruction.text, long, '指令流水那份也存完整原文(截断只发生在 5s 轮询下发时)');
233
+ });
234
+
208
235
  test('目标目录不存在:记一条 rejected 流水再抛 400', async () => {
209
236
  const { dispatchInstruction } = makeDispatcher();
210
237
  const missing = path.join(fakeHome, 'not-created-yet');
@@ -47,8 +47,9 @@ import {
47
47
  TRUTH_FILES,
48
48
  IMAGES_DIR,
49
49
  MAX_IMAGE_BYTES,
50
- MAX_ATTACHMENTS_PER_TASK,
51
50
  MAX_DEFAULT_PROMPT_CHARS,
51
+ MAX_INSTRUCTION_CHARS,
52
+ INSTRUCTION_PREVIEW_CHARS,
52
53
  readJson,
53
54
  writeJson,
54
55
  nowIso,
@@ -125,6 +126,7 @@ import {
125
126
  claimReportSlot,
126
127
  readReports,
127
128
  appendReport,
129
+ withInstructionPreview,
128
130
  } from './orchestratorStore.js';
129
131
  import {
130
132
  buildRunningFacts,
@@ -967,7 +969,12 @@ ${subSummaries.map((s, i) => `\n### [${i + 1}] ${s.name} (${s.root})\n${s.summar
967
969
  }
968
970
 
969
971
  // 共享 helper:写入新附件
970
- async function writeAttachmentTo({ req, target, maxCount }) {
972
+ //
973
+ // 只负责把字节落盘 + 造出附件记录,**不碰 target.attachments** —— 由调用方决定挂到哪。
974
+ // 之前这里既 push 了一次、调用方又 push 一次,而两处指向同一个数组(target 是浅拷贝),
975
+ // 于是每次上传都在 tasks.json 里留下一条 id 完全相同的幽灵附件:附件数翻倍、
976
+ // 缩略图列表出现重复项,删一条另一条还在。
977
+ async function writeAttachmentTo({ req, target }) {
971
978
  if (!req.body || !(req.body instanceof Buffer) || req.body.length === 0) {
972
979
  throw new HttpError(400, '请求体为空');
973
980
  }
@@ -980,10 +987,6 @@ ${subSummaries.map((s, i) => `\n### [${i + 1}] ${s.name} (${s.root})\n${s.summar
980
987
  if (!ext) {
981
988
  throw new HttpError(400, `不支持的文件类型(仅允许 ${[...ALLOWED_EXTS].join(', ')})`);
982
989
  }
983
- if (!Array.isArray(target.attachments)) target.attachments = [];
984
- if (target.attachments.length >= maxCount) {
985
- throw new HttpError(400, `附件已达上限 ${maxCount} 个`);
986
- }
987
990
 
988
991
  const attId = genId();
989
992
  await fsp.mkdir(target.storageDir, { recursive: true });
@@ -991,7 +994,7 @@ ${subSummaries.map((s, i) => `\n### [${i + 1}] ${s.name} (${s.root})\n${s.summar
991
994
  const storedPath = path.join(target.storageDir, storedName);
992
995
  await fsp.writeFile(storedPath, req.body);
993
996
 
994
- const attachment = {
997
+ return {
995
998
  id: attId,
996
999
  originalName,
997
1000
  mimeType,
@@ -1001,9 +1004,6 @@ ${subSummaries.map((s, i) => `\n### [${i + 1}] ${s.name} (${s.root})\n${s.summar
1001
1004
  absolutePath: storedPath,
1002
1005
  createdAt: nowIso()
1003
1006
  };
1004
- target.attachments.push(attachment);
1005
- target.updatedAt = nowIso();
1006
- return attachment;
1007
1007
  }
1008
1008
 
1009
1009
  // 子任务附件
@@ -1014,7 +1014,7 @@ ${subSummaries.map((s, i) => `\n### [${i + 1}] ${s.name} (${s.root})\n${s.summar
1014
1014
  const task = (data.tasks || []).find(t => t.id === taskId);
1015
1015
  if (!task) throw new HttpError(404, '任务不存在');
1016
1016
  const target = { ...task, storageDir: path.join(IMAGES_DIR, '_task-' + taskId) };
1017
- const att = await writeAttachmentTo({ req, target, maxCount: MAX_ATTACHMENTS_PER_TASK });
1017
+ const att = await writeAttachmentTo({ req, target });
1018
1018
  task.attachments = Array.isArray(task.attachments) ? task.attachments : [];
1019
1019
  task.attachments.push(att);
1020
1020
  task.updatedAt = nowIso();
@@ -1232,7 +1232,14 @@ ${subSummaries.map((s, i) => `\n### [${i + 1}] ${s.name} (${s.root})\n${s.summar
1232
1232
  success: true,
1233
1233
  active: state.active,
1234
1234
  updatedAt: state.updatedAt,
1235
- instructions: state.instructions,
1235
+ // ⚠️ 这个接口是 5s 轮询的。指令正文在**下发这一份**里被截到
1236
+ // INSTRUCTION_PREVIEW_CHARS(落盘那份不截,见 shared.js 的注释)。
1237
+ // 指令上限放宽到十万字之后不截就是 200 × 100000 = 20MB 一轮。
1238
+ instructions: state.instructions.map(withInstructionPreview),
1239
+ // 指令正文上限跟着状态一起下发:前端要显示"还剩多少字"就得知道这个数,
1240
+ // 而它归服务端定(派发校验也在这儿)。前端不再自己写一份数字 ——
1241
+ // 之前 maxlength 那种"界面说能发、后端 400"的分叉就是这么来的。
1242
+ maxInstructionChars: MAX_INSTRUCTION_CHARS,
1236
1243
  // 默认提示词跟着这份状态一起下发:控制台要拿它显示"当前会附带什么",
1237
1244
  // 设置弹窗打开时也不必再单独取一次(单条上限 8000 字,体积可控)
1238
1245
  defaultPrompt: state.defaultPrompt,
@@ -1480,12 +1487,12 @@ ${subSummaries.map((s, i) => `\n### [${i + 1}] ${s.name} (${s.root})\n${s.summar
1480
1487
  * 为什么需要这一层:§17 的上传端点都要求 taskId / subId 当挂载点,而派发这一刻
1481
1488
  * 任务还不存在。所以先写进 `_dispatch/`,**不写任何 JSON** —— 附件记录回给前端持有,
1482
1489
  * 派发时再按 id 搬进任务目录。
1483
- * 代价是服务端在这里无状态:它不知道前端一共攒了几个,附件数上限只能在派发时统一卡。
1490
+ * 代价是服务端在这里无状态:它不知道前端一共攒了几个(数量本来也不设上限,
1491
+ * 所以这份无状态不再有代价 —— 真要卡也只能卡在派发那一刻,而那时已经晚了)。
1484
1492
  */
1485
1493
  app.post('/api/workbench/orchestrator/attachments', rawAttachment, asyncRoute(async (req, res) => {
1486
1494
  await fsp.mkdir(DISPATCH_STAGING_DIR, { recursive: true });
1487
- const target = { attachments: [], storageDir: DISPATCH_STAGING_DIR };
1488
- const att = await writeAttachmentTo({ req, target, maxCount: MAX_ATTACHMENTS_PER_TASK });
1495
+ const att = await writeAttachmentTo({ req, target: { storageDir: DISPATCH_STAGING_DIR } });
1489
1496
  res.json({ success: true, attachment: att });
1490
1497
  }));
1491
1498
 
@@ -24,8 +24,8 @@
24
24
  import { test } from 'node:test';
25
25
  import assert from 'node:assert/strict';
26
26
 
27
- import { resolveDispatchPrompt, normalizeOrchestrator } from './orchestratorStore.js';
28
- import { MAX_DEFAULT_PROMPT_CHARS } from './shared.js';
27
+ import { resolveDispatchPrompt, normalizeOrchestrator, withInstructionPreview } from './orchestratorStore.js';
28
+ import { MAX_DEFAULT_PROMPT_CHARS, INSTRUCTION_PREVIEW_CHARS } from './shared.js';
29
29
 
30
30
  /** 造一份最小可用的编排状态(字段与 normalizeOrchestrator 的输出对齐) */
31
31
  function stateWith({ defaultPrompt = '', projectPrompts = {} } = {}) {
@@ -157,3 +157,48 @@ test('normalizeOrchestrator:指令的 promptSource 只认白名单,脏值归
157
157
  });
158
158
  assert.deepEqual(s.instructions.map(i => i.promptSource), ['both', 'global', '', '']);
159
159
  });
160
+
161
+ // ── 指令流水的「下发预览」截断(2026-09-29)────────────────────────────
162
+ //
163
+ // 指令上限从 4000 放宽到 100000 之后,唯一会先崩的是**5s 轮询**:
164
+ // /api/workbench/orchestrator 每轮下发 state.instructions 整份(200 条上限),
165
+ // 不截就是 200 × 100000 = 20MB 一轮。
166
+ //
167
+ // 关键在"截哪一份":落盘那份(orchestrator.json)与任务正文(task.desc)必须完整 ——
168
+ // 指令流水的意义就是"我说过什么"能被翻到,而 Agent 侧的真相源清单指着那个文件。
169
+ // 所以这里守的是"只截下发、且如实标出被截过"。
170
+
171
+ test('withInstructionPreview:超长正文截到预览上限并标出已截断', () => {
172
+ const long = 'x'.repeat(INSTRUCTION_PREVIEW_CHARS + 500);
173
+ const r = withInstructionPreview({ id: 'a', text: long, status: 'accepted' });
174
+ assert.equal(r.text.length, INSTRUCTION_PREVIEW_CHARS);
175
+ assert.equal(r.textTruncated, true);
176
+ // 其余字段一个都不能丢:前端要靠 status / projectPath / targetSource 渲染这行
177
+ assert.equal(r.id, 'a');
178
+ assert.equal(r.status, 'accepted');
179
+ });
180
+
181
+ test('withInstructionPreview:没超长时原样返回,且不误标截断', () => {
182
+ const short = '一句话指令';
183
+ const r = withInstructionPreview({ id: 'a', text: short });
184
+ assert.equal(r.text, short);
185
+ assert.equal(r.textTruncated, false);
186
+ // 恰好等于上限不算超(截断口是 >)
187
+ const atLimit = withInstructionPreview({ id: 'b', text: 'y'.repeat(INSTRUCTION_PREVIEW_CHARS) });
188
+ assert.equal(atLimit.textTruncated, false);
189
+ assert.equal(atLimit.text.length, INSTRUCTION_PREVIEW_CHARS);
190
+ });
191
+
192
+ test('withInstructionPreview:不改传入的记录(落盘那份必须还是完整的)', () => {
193
+ const long = 'z'.repeat(INSTRUCTION_PREVIEW_CHARS + 10);
194
+ const original = { id: 'a', text: long };
195
+ withInstructionPreview(original);
196
+ assert.equal(original.text.length, INSTRUCTION_PREVIEW_CHARS + 10, '截断只发生在返回的新对象上');
197
+ });
198
+
199
+ test('withInstructionPreview:脏输入不炸(缺 text / null / 非对象)', () => {
200
+ assert.deepEqual(withInstructionPreview({ id: 'a' }), { id: 'a', text: '', textTruncated: false });
201
+ assert.equal(withInstructionPreview(null).text, '');
202
+ assert.equal(withInstructionPreview(undefined).text, '');
203
+ assert.equal(withInstructionPreview({ text: 123 }).text, '');
204
+ });
@@ -39,6 +39,7 @@ import {
39
39
  ORCHESTRATOR_REPORTS_FILE,
40
40
  MAX_ORCHESTRATOR_INSTRUCTIONS,
41
41
  MAX_DEFAULT_PROMPT_CHARS,
42
+ INSTRUCTION_PREVIEW_CHARS,
42
43
  MAX_PROGRESS_REPORTS,
43
44
  PROGRESS_REPORT_INTERVALS_MS,
44
45
  DEFAULT_PROGRESS_REPORT_INTERVAL_MS,
@@ -245,6 +246,27 @@ export function resolveDispatchPrompt(state, projectPath) {
245
246
  return { text, source };
246
247
  }
247
248
 
249
+ /**
250
+ * 把一条指令流水压成「可以每 5 秒下发」的形状:正文只留开头 INSTRUCTION_PREVIEW_CHARS 字。
251
+ *
252
+ * 为什么需要它:指令正文上限已放宽到十万字(MAX_INSTRUCTION_CHARS),而
253
+ * /api/workbench/orchestrator 是 5s 轮询、下发 state.instructions 整份(200 条)。
254
+ * 不截就是 200 × 100000 = 20MB 一轮。
255
+ *
256
+ * 只压**下发**:落盘那份(orchestrator.json)与任务正文(task.desc)都保持完整,
257
+ * 指令流水的意义就是"我说过什么"能被翻到。附 textTruncated 让前端能如实说"已截断",
258
+ * 而不是让用户以为当初就只写了这么多。
259
+ *
260
+ * @param {object} it orchestratorStore 的指令记录
261
+ * @returns {object} 新对象(不改原记录 —— 落盘那份必须还是完整的)
262
+ */
263
+ export function withInstructionPreview(it) {
264
+ const src = it && typeof it === 'object' ? it : {};
265
+ const text = typeof src.text === 'string' ? src.text : '';
266
+ const truncated = text.length > INSTRUCTION_PREVIEW_CHARS;
267
+ return { ...src, text: truncated ? text.slice(0, INSTRUCTION_PREVIEW_CHARS) : text, textTruncated: truncated };
268
+ }
269
+
248
270
  /** 追加一条人类干预指令记录,返回写入后的那条记录 */
249
271
  export async function appendInstruction(entry) {
250
272
  return serialize(async () => {
@@ -508,12 +530,18 @@ export function buildActivityFeed({ jobs = [], tasks = [], instructions = [] } =
508
530
 
509
531
  for (const it of Array.isArray(instructions) ? instructions : []) {
510
532
  if (!it || !it.id) continue;
533
+ // 正文只带**开头一段**进活动流:这条流跟着 5s 轮询下发(截断口径见
534
+ // withInstructionPreview)。落盘那份不截 —— 完整原文在 orchestrator.json 与
535
+ // task.desc 里,要全文的人去那两个地方拿。
536
+ const preview = withInstructionPreview(it);
511
537
  rows.push({
512
538
  id: `ins:${it.id}`,
513
539
  kind: 'user',
514
540
  at: it.at,
515
541
  instructionId: it.id,
516
- text: it.text || '',
542
+ text: preview.text,
543
+ // 前端据此显示"已截断",而不是让人以为指令本来就那么短
544
+ textTruncated: preview.textTruncated,
517
545
  taskId: it.taskId || null,
518
546
  instructionStatus: it.status || 'created',
519
547
  reason: it.reason || '',
@@ -102,6 +102,31 @@ export const TRUTH_FILES = {
102
102
  // 就不会出现"派发时写进去了、回头在编辑器里一保存又被悄悄截掉"。
103
103
  export const MAX_DEFAULT_PROMPT_CHARS = 4000;
104
104
 
105
+ // 派发指令正文长度上限。
106
+ //
107
+ // 2026-09-29 从 4000 放宽到 100000:执行侧走的是 claude / opencode / codex,
108
+ // 上下文窗口早已是百万级,而 prompt 是**经 stdin 喂进去**的(见 taskRunner 的
109
+ // launchXxxRun),不受 Windows 命令行 32K 上限约束。4000 是模型上下文还小的时候
110
+ // 定的,现在它卡住的不是模型,是"粘一份完整报错日志 / 一整段需求"这种最普通的用法。
111
+ //
112
+ // 十万字不是"随便多大都行",它同时要落在三份存储里(见下方各自的注释):
113
+ // · tasks.json 的 task.desc —— 一次执行真正用的正文
114
+ // · jobs.json 的 job.prompt —— 保留策略 500 条 / 256MB 兜底
115
+ // · orchestrator.json 的指令流水 —— 200 条上限
116
+ // 真正会让界面卡住的不是这三份,而是**轮询下发**(INSTRUCTION_PREVIEW_CHARS 那道口子)。
117
+ export const MAX_INSTRUCTION_CHARS = 100000;
118
+
119
+ // 指令流水进 5s 轮询 / 活动流时的正文上限。
120
+ //
121
+ // 为什么单独一道:/api/workbench/orchestrator 每 5 秒把 state.instructions 与活动流
122
+ // 整份下发(MAX_ORCHESTRATOR_INSTRUCTIONS = 200 条)。指令放宽到十万字之后,
123
+ // 不截就是 200 × 100000 = 20MB 一轮 —— 界面先卡住,而"卡住"和"上限"毫无关系。
124
+ //
125
+ // 只截**下发**这一份,不截落盘:流水存在的意义就是"我说过什么"能被翻到,
126
+ // 落盘那份必须完整(Agent 侧的真相源清单也指着 orchestrator.json)。
127
+ // 任务正文本身更不截 —— 完整正文在 task.desc 里,流水这条只是同一句话的旁证。
128
+ export const INSTRUCTION_PREVIEW_CHARS = 2000;
129
+
105
130
  // 子项目识别 / 文件扫描时需要跳过的目录
106
131
  export const SKIP_DIRS = new Set([
107
132
  'node_modules', 'dist', 'build', '.next', '.nuxt', '__pycache__',
@@ -117,12 +142,13 @@ export const MANIFEST_FILES = [
117
142
  'Gemfile', 'pubspec.yaml'
118
143
  ];
119
144
 
120
- // 附件大小 / 数量限制
145
+ // 附件大小限制(**数量不限**)
121
146
  // 单文件 20MB:4K 屏截图(PNG)5–15MB 是常态,卡在 5MB 会让"随手截一张就超"。
122
147
  // 真正需要小体积的是图片,由前端上传前压缩保证(见 useWorkbenchAttachments.ts),
123
148
  // 这里只做最后一道兜底,避免超大 body 把内存吃光。
149
+ // 数量曾经按 9 个封顶,2026-09-29 按需求放开:一次要交十几张截图 / 一堆日志是常态,
150
+ // 封顶只会逼人分批建任务。数量放开后单文件上限成了唯一的内存关口,所以它必须留着。
124
151
  export const MAX_IMAGE_BYTES = 20 * 1024 * 1024; // 单个附件最大 20MB
125
- export const MAX_ATTACHMENTS_PER_TASK = 9; // 一个任务最多挂 9 个附件
126
152
 
127
153
  // ── 时间 / ID 工具 ─────────────────────────────────────────
128
154
  export function nowIso() {
@@ -105,19 +105,41 @@ export function nextPollIntervalMs({ elapsedMs, fastWindowMs, fastIntervalMs, sl
105
105
  * 与"EPERM(文件被占)"分不清 —— 两者的处理方式完全相反(一个只能等,一个要杀实例)。
106
106
  * 汇总成 `E404 ×8 / ETARGET ×2` 这样一行,下一次事故就不用再靠猜。
107
107
  *
108
+ * v2.17.25 实测教训(优先级为什么长这样):registry 还没就绪(E404)的每一轮
109
+ * 输出里都附带 `npm warn cleanup Failed to remove ... EPERM` —— 那是 npm 删旧
110
+ * 全局目录没删干净的动作噪声,不是失败根因。按旧的"EPERM 子串优先"判法被
111
+ * 误判成文件占用,导致每轮反复停实例 + 强删全局目录。所以:
112
+ * ① npm 自己报了 `npm error code <CODE>` 行 → 以它为最终裁决;
113
+ * ② 没有 code 行 → 只在 **error 行**里找文件占用类(warn 行永远不算);
114
+ * ③ 还没有 → 才对全文兜底(兼容只截到 warn cleanup 的残缺输出)。
115
+ *
108
116
  * @param {string} output - npm install 的 stdout+stderr
109
- * @returns {string} E404 | ETARGET | EPERM | EBUSY | ENOTFOUND | ETIMEDOUT | ENETUNREACH | '其他'
117
+ * @returns {string} E404 | ETARGET | EPERM | ENOTFOUND | ETIMEDOUT | ENETUNREACH | 其他 | npm 原生未识别短码
110
118
  */
111
119
  export function classifyInstallError(output) {
112
120
  const s = String(output || '')
113
121
  if (!s) return '其他'
114
- // 顺序有讲究:E404 / ETARGET 是"registry 还没就绪",EPERM / EBUSY 是"文件被占",
115
- // 两类混在一起时优先报前者(它是自更新这一段的主题),但两者都会各自计数。
116
- if (/EPERM|EBUSY|operation not permitted|Failed to remove/i.test(s)) return 'EPERM'
122
+ // ① npm 的裁决码行,如 `npm error code E404`
123
+ const codeLine = s.match(/^\s*npm (?:error|ERR!) code ([A-Z0-9_]+)\b/m)
124
+ if (codeLine) return normalizeErrCode(codeLine[1])
125
+ // ② error 行里的文件占用类(warn 行不算 —— 那是清理噪声)
126
+ const errorLines = s.split(/\r?\n/).filter((l) => /^\s*npm (?:error|ERR!)/.test(l))
127
+ if (errorLines.some((l) => /EPERM|EBUSY|operation not permitted|Failed to remove/i.test(l))) return 'EPERM'
128
+ // ③ 全文兜底:registry 类优先(它是自更新这一段的主题),文件占用类殿后
117
129
  if (/\bETARGET\b/.test(s)) return 'ETARGET'
118
130
  if (/\bE404\b|404 Not Found/i.test(s)) return 'E404'
119
131
  if (/\bENOTFOUND\b/.test(s)) return 'ENOTFOUND'
120
132
  if (/\bETIMEDOUT\b|timed out/i.test(s)) return 'ETIMEDOUT'
121
133
  if (/\bENETUNREACH\b|ECONNRESET|ECONNREFUSED/i.test(s)) return 'ENETUNREACH'
134
+ if (/EPERM|EBUSY|operation not permitted|Failed to remove/i.test(s)) return 'EPERM'
122
135
  return '其他'
123
136
  }
137
+
138
+ // npm 原生错误码 → 本脚本的对账短码。EBUSY 与 EPERM 同属"文件被占"一类
139
+ // (处置完全相同:杀实例 + 清目录),合并进同一个桶方便 errorTally 汇总;
140
+ // 没见过的码原样返回,别硬塞 —— 归错桶比归不出来更误导。
141
+ function normalizeErrCode(code) {
142
+ if (code === 'EBUSY') return 'EPERM'
143
+ if (code === 'ECONNRESET' || code === 'ECONNREFUSED') return 'ENETUNREACH'
144
+ return code
145
+ }
@@ -189,6 +189,24 @@ test('classifyInstallError: EPERM / EBUSY → 文件被占(和"还没就绪"完
189
189
  assert.equal(classifyInstallError('npm error EBUSY: resource busy or locked'), 'EPERM')
190
190
  })
191
191
 
192
+ test('classifyInstallError: npm 的 code 行是最终裁决(v2.17.25 实测回归)', () => {
193
+ // v2.17.25 真实形态:registry 还没就绪(E404)的每一轮都附带 npm warn cleanup EPERM
194
+ // (删旧全局目录的噪声)。旧判法按"EPERM 子串优先"误判成文件占用,
195
+ // 导致每轮反复停实例 + 强删全局目录。有 code 行时必须以它为准。
196
+ const notReady = [
197
+ 'npm warn cleanup Failed to remove some directories: EPERM',
198
+ 'npm error code E404',
199
+ 'npm error 404 Not Found - GET https://registry.npmjs.org/zen-gitsync/-/zen-gitsync-2.17.25.tgz - Not found',
200
+ ].join('\n')
201
+ assert.equal(classifyInstallError(notReady), 'E404')
202
+ // npm 自己裁决成文件占用 → 才允许走"杀实例 + 清目录"
203
+ assert.equal(classifyInstallError('npm error code EPERM\nnpm error rename EPERM: operation not permitted'), 'EPERM')
204
+ // EBUSY 与 EPERM 同属"文件被占",归进同一个桶
205
+ assert.equal(classifyInstallError('npm error code EBUSY\nnpm error resource busy or locked'), 'EPERM')
206
+ // 没见过的码原样返回,不硬塞进既有短码
207
+ assert.equal(classifyInstallError('npm error code EAI_AGAIN\nnpm error getaddrinfo EAI_AGAIN'), 'EAI_AGAIN')
208
+ })
209
+
192
210
  test('classifyInstallError: 网络类与空输入不炸,各有短码', () => {
193
211
  assert.equal(classifyInstallError('npm error code ENOTFOUND registry.npmjs.org'), 'ENOTFOUND')
194
212
  assert.equal(classifyInstallError('npm error code ETIMEDOUT'), 'ETIMEDOUT')
@@ -198,7 +216,8 @@ test('classifyInstallError: 网络类与空输入不炸,各有短码', () => {
198
216
  assert.equal(classifyInstallError('some unknown failure'), '其他')
199
217
  })
200
218
 
201
- test('classifyInstallError: 两类同时出现时优先报文件占用(它是唯一要"动手"的那类)', () => {
202
- const mixed = 'npm error code E404\nnpm error EPERM: operation not permitted'
219
+ test('classifyInstallError: 无 code 行时,error 行里的文件占用类仍优先于 registry 类', () => {
220
+ // 两条都是 error 行、又没有裁决 code 行 → 文件占用是唯一要"动手"的那类,优先报
221
+ const mixed = 'npm error E404 Not Found - GET https://...\nnpm error EPERM: operation not permitted'
203
222
  assert.equal(classifyInstallError(mixed), 'EPERM')
204
223
  })
@@ -1 +0,0 @@
1
- import"./rolldown-runtime-C0FnF6B9.js";import{$ as e,C as t,Cn as n,Dn as r,En as i,Er as a,Jn as o,K as s,Kn as c,Mn as l,Pn as u,Pr as d,Qn as f,Qr as p,Rn as m,S as h,Tn as g,Un as _,Xn as v,br as y,cr as b,pr as x,q as S,qr as C,w,wn as T,yn as E}from"./element-plus-Bx3j6vzG.js";import{n as D,t as O}from"./_plugin-vue_export-helper-C7IvbEC3.js";import{t as k}from"./SvgIcon-diKLLWK9.js";import{t as A}from"./toolsStore-DFu4Alwg.js";import{a as j,m as M}from"./index-eq1OUcdk.js";import{c as N,d as P,l as F,u as I}from"./agentConversations-nN-yDz8q.js";m(),x();function L(e,t=680){let r=y(0),i=null;function a(){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 o(()=>{a()||_(()=>a())}),c(()=>{i?.disconnect(),i=null}),{width:r,narrow:n(()=>r.value>0&&r.value<t)}}m(),x(),d();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`},W=O(u({__name:`AgentEngineSelector`,props:{engine:{},locked:{type:Boolean}},emits:[`select`],setup(o,{emit:c}){let u=o,d=c,m=A(),_=n(()=>I(u.engine)),x=n(()=>({gai:!0,claude:m.claudeAvailable,opencode:m.opencodeAvailable,codex:m.codexAvailable})),O=y(!1),L=y(null);function W(e){if(!u.locked){if(!x.value[e]){if(!P(e))return;L.value=e,O.value=!0;return}d(`select`,e)}}return(n,c)=>(v(),r(`div`,R,[l(a(h),{trigger:`click`,onCommand:W},{dropdown:b(()=>[l(a(w),null,{default:b(()=>[(v(!0),r(E,null,f(a(N),n=>(v(),g(a(t),{key:n.id,command:n.id},{default:b(()=>[T(`span`,V,[a(P)(n.id)?(v(),g(j,{key:0,executor:n.id,class:`agent-engine__item-icon`},null,8,[`executor`])):(v(),g(k,{key:1,"icon-class":a(F),class:`agent-engine__item-icon`},null,8,[`icon-class`])),T(`span`,H,p(n.name),1),o.engine===n.id?(v(),g(a(s),{key:2,class:`agent-engine__item-check`},{default:b(()=>[l(a(e))]),_:1})):x.value[n.id]?i(``,!0):(v(),r(`span`,U,p(a(D)(`@42BB9:未安装`)),1))])]),_:2},1032,[`command`]))),128))]),_:1})]),default:b(()=>[T(`button`,{type:`button`,class:C([`agent-engine__btn`,{"is-locked":o.locked}]),disabled:o.locked,title:o.locked?a(D)(`@AGENT:本会话的引擎已锁定;要换引擎请新建会话`):a(D)(`@AGENT:新建会话使用的引擎`),"aria-label":a(D)(`@AGENT:智能体引擎`)},[a(P)(o.engine)?(v(),g(j,{key:0,executor:o.engine,class:`agent-engine__icon`},null,8,[`executor`])):(v(),g(k,{key:1,"icon-class":a(F),class:`agent-engine__icon`},null,8,[`icon-class`])),T(`span`,B,p(_.value),1),l(a(s),{class:`agent-engine__caret`},{default:b(()=>[l(a(S))]),_:1})],10,z)]),_:1}),l(M,{modelValue:O.value,"onUpdate:modelValue":c[0]||=e=>O.value=e,tool:L.value},null,8,[`modelValue`,`tool`])]))}}),[[`__scopeId`,`data-v-81dfa007`]]);export{L as n,W as t};
@@ -1 +0,0 @@
1
- .agent-engine[data-v-81dfa007]{vertical-align:middle}.agent-engine__btn[data-v-81dfa007]{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-81dfa007]:hover:not(:disabled){border-color:var(--color-primary);color:var(--text-primary)}.agent-engine__btn[data-v-81dfa007]:disabled{cursor:not-allowed;color:var(--text-tertiary)}.agent-engine__icon[data-v-81dfa007],.agent-engine__item-icon[data-v-81dfa007]{flex:none;font-size:14px}.agent-engine__caret[data-v-81dfa007]{color:var(--text-tertiary);font-size:12px}.agent-engine__item[data-v-81dfa007]{align-items:center;gap:8px;min-width:148px;display:inline-flex}.agent-engine__item-name[data-v-81dfa007]{flex:1}.agent-engine__item-check[data-v-81dfa007]{color:var(--color-primary);font-size:13px}.agent-engine__item-missing[data-v-81dfa007]{font-size:var(--font-size-sm);color:var(--text-tertiary)}
@@ -1 +0,0 @@
1
- .marketplace-panel[data-v-6788a7b1]{background:var(--bg-container);flex-direction:column;height:100%;min-height:0;display:flex}.mp-toolbar[data-v-6788a7b1]{flex-shrink:0;align-items:center;gap:12px;padding:12px 16px 8px;display:flex}.mp-search[data-v-6788a7b1]{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-6788a7b1]:focus-within{border-color:var(--color-primary)}.mp-search .mp-search-icon[data-v-6788a7b1]{color:var(--text-tertiary);flex-shrink:0}.mp-search .mp-search-input[data-v-6788a7b1]{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-6788a7b1]::placeholder{color:var(--text-tertiary)}.mp-target[data-v-6788a7b1]{flex-shrink:0}.mp-refresh[data-v-6788a7b1]{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-6788a7b1]:hover{color:var(--color-primary);border-color:var(--color-primary)}.mp-refresh.spinning svg[data-v-6788a7b1]{animation:.9s linear infinite mkt-spin-6788a7b1}@keyframes mkt-spin-6788a7b1{to{transform:rotate(360deg)}}.mp-toolbar-sub[data-v-6788a7b1]{flex-wrap:wrap;flex-shrink:0;align-items:center;gap:12px;padding:0 16px 10px;display:flex}.mp-target-hint[data-v-6788a7b1]{font-size:var(--font-size-sm);color:var(--text-tertiary)}.mp-target-hint b[data-v-6788a7b1]{color:var(--color-primary);font-weight:600}.mp-source-filter[data-v-6788a7b1]{flex-wrap:wrap;align-items:center;gap:6px;display:flex}.mp-chip[data-v-6788a7b1]{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-6788a7b1]:hover{color:var(--text-secondary);border-color:var(--text-tertiary)}.mp-chip.active[data-v-6788a7b1]{color:var(--color-primary);border-color:var(--color-primary);background:color-mix(in srgb, var(--color-primary) 8%, transparent)}.mp-chip.clear[data-v-6788a7b1]{color:var(--text-tertiary);border-style:dashed}.mp-body[data-v-6788a7b1]{flex:1;min-height:0;padding:0 16px 20px;overflow-y:auto}.mp-group[data-v-6788a7b1]{margin-bottom:20px}.mp-group.installed[data-v-6788a7b1]{border-top:1px dashed var(--border-color);padding-top:14px}.mp-group-header[data-v-6788a7b1]{align-items:center;gap:8px;margin-bottom:10px;display:flex}.mp-group-title[data-v-6788a7b1]{font-size:var(--font-size-mid);color:var(--text-primary);margin:0;font-weight:600}.mp-group-count[data-v-6788a7b1]{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-6788a7b1]{font-size:var(--font-size-sm);color:var(--color-warning);align-items:center;gap:4px;display:inline-flex}.mp-group-link[data-v-6788a7b1]{color:var(--text-tertiary);transition:color var(--transition-fast) ease;display:inline-flex}.mp-group-link[data-v-6788a7b1]:hover{color:var(--color-primary)}.mp-grid[data-v-6788a7b1]{grid-template-columns:repeat(auto-fill,minmax(280px,1fr));gap:10px;display:grid}.mp-card[data-v-6788a7b1]{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-6788a7b1]:hover{border-color:color-mix(in srgb, var(--color-primary) 40%, var(--border-color));box-shadow:var(--shadow-sm)}.mp-card-head[data-v-6788a7b1]{align-items:center;gap:8px;display:flex}.mp-card-name[data-v-6788a7b1]{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-6788a7b1]{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-6788a7b1]{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-6788a7b1]{flex-wrap:wrap;align-items:center;gap:6px;min-height:18px;display:flex}.mp-meta-pkg[data-v-6788a7b1],.mp-meta-repo[data-v-6788a7b1]{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-6788a7b1]{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-6788a7b1]{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-6788a7b1]{background:color-mix(in srgb, var(--color-warning) 14%, transparent);color:var(--color-warning)}.mp-card-foot[data-v-6788a7b1]{align-items:center;gap:8px;margin-top:2px;display:flex}.mp-card-link[data-v-6788a7b1]{font-size:var(--font-size-xs);color:var(--text-tertiary);text-decoration:none}.mp-card-link[data-v-6788a7b1]:hover{color:var(--color-primary);text-decoration:underline}.mp-foot-spacer[data-v-6788a7b1]{flex:1}.mp-install-btn[data-v-6788a7b1]{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-6788a7b1]:hover:not(:disabled){background:var(--color-primary);color:#fff}.mp-install-btn[data-v-6788a7b1]:disabled{opacity:.5;cursor:not-allowed}.mp-installed-chip[data-v-6788a7b1]{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-6788a7b1]{font-size:var(--font-size-xs);color:var(--text-tertiary)}.mp-empty[data-v-6788a7b1]{font-size:var(--font-size-sm);color:var(--text-tertiary);padding:16px 0}.mp-empty.all[data-v-6788a7b1]{text-align:center;padding:48px 0}.mp-installed-block[data-v-6788a7b1]{margin-bottom:12px}.mp-installed-target[data-v-6788a7b1]{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-6788a7b1]{flex-direction:column;gap:4px;display:flex}.mp-installed-row[data-v-6788a7b1]{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-6788a7b1]{font-size:var(--font-size-sm);color:var(--text-primary);white-space:nowrap;text-overflow:ellipsis;font-weight:500;overflow:hidden}.mp-remove-btn[data-v-6788a7b1]{font-size:var(--font-size-xs);border:1px solid var(--border-color);border-radius:var(--radius-xs);color:var(--text-tertiary);cursor:pointer;transition:var(--transition-ui-fast);background:0 0;align-items:center;gap:4px;padding:3px 10px;font-family:inherit;display:inline-flex}.mp-remove-btn[data-v-6788a7b1]:hover:not(:disabled){color:var(--color-danger);border-color:var(--color-danger)}.mp-remove-btn[data-v-6788a7b1]:disabled{opacity:.5;cursor:not-allowed}@media (prefers-reduced-motion:reduce){.mp-refresh.spinning svg[data-v-6788a7b1]{animation:none}}.mkt-form[data-v-6788a7b1]{flex-direction:column;gap:12px;display:flex}.mkt-form-name[data-v-6788a7b1]{font-size:var(--font-size-mid);color:var(--text-primary);margin:0;font-weight:600}.mkt-field[data-v-6788a7b1]{flex-direction:column;gap:4px;display:flex}.mkt-field label[data-v-6788a7b1]{font-size:var(--font-size-sm);color:var(--text-secondary);font-weight:600}.mkt-field input[data-v-6788a7b1]{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-6788a7b1]:focus{border-color:var(--color-primary);outline:none}.mkt-hint[data-v-6788a7b1]{font-size:var(--font-size-xs);color:var(--text-tertiary);margin:0}.mkt-cancel[data-v-6788a7b1],.mkt-confirm[data-v-6788a7b1]{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-6788a7b1]{border:1px solid var(--border-color);color:var(--text-secondary);background:0 0;margin-right:8px}.mkt-cancel[data-v-6788a7b1]:hover{color:var(--text-primary);border-color:var(--text-tertiary)}.mkt-confirm[data-v-6788a7b1]{border:1px solid var(--color-primary);background:var(--color-primary);color:#fff}.mkt-confirm[data-v-6788a7b1]:hover{opacity:.9}.agent-view[data-v-05972cec]{background:var(--bg-container);height:100%;min-height:0;display:flex}.agent-sidebar[data-v-05972cec]{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-05972cec]{flex:1;min-height:0;padding:10px 6px 8px 12px}.sidebar-resizer[data-v-05972cec]{cursor:col-resize;width:4px;transition:background var(--transition-fast) ease;background:0 0;flex-shrink:0}.sidebar-resizer[data-v-05972cec]:hover,.sidebar-resizer.active[data-v-05972cec]{background:var(--color-primary);opacity:.3}.agent-chat-area[data-v-05972cec]{flex-direction:column;flex:1;min-width:0;display:flex;position:relative;overflow:hidden}.agent-engine[data-v-05972cec]{margin-bottom:7px;margin-left:auto}.agent-tabs[data-v-05972cec]{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-05972cec]{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-05972cec]:hover{color:var(--text-secondary);background:var(--bg-hover)}.agent-tab.active[data-v-05972cec]{color:var(--color-primary);border-bottom-color:var(--color-primary);font-weight:600}.agent-tab-body[data-v-05972cec]{flex-direction:column;flex:1;min-height:0;display:flex;overflow:hidden}.agent-chat-pane[data-v-05972cec]{flex-direction:column;flex:1;min-height:0;display:flex;position:relative;overflow:hidden}.agent-tab-body[data-v-05972cec]>.marketplace-panel{flex:1;min-height:0}.chat-loading[data-v-05972cec]{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-05972cec]{background:var(--bg-panel);flex-direction:column;flex:1;min-height:0;display:flex;overflow:hidden}.agent-list-page .agent-conversations[data-v-05972cec]{padding:12px 12px 10px 14px}.agent-page-bar[data-v-05972cec]{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-05972cec]{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-05972cec]:hover{background:var(--bg-hover);color:var(--text-secondary)}.agent-page-title[data-v-05972cec]{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-05972cec]{flex-direction:column;flex:1;min-height:0;display:flex;overflow:hidden}.agent-chat-host[data-v-05972cec] .acu-avatar--left{background:0 0}.agent-chat-host[data-v-05972cec] .acu-welcome-logo{background:var(--welcome-avatar) center/46px 46px no-repeat;width:56px;height:56px;box-shadow:none}.agent-chat-host[data-v-05972cec] .acu-welcome-logo>svg{display:none}.agent-chat-host[data-v-05972cec] .acu-welcome-grid{flex-wrap:wrap;display:flex}.agent-chat-host[data-v-05972cec] .acu-welcome-grid>.acu-preset-q{flex:260px;min-width:0}[data-theme=dark]{background:var(--bg-panel)}
@@ -1 +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,Er as c,H as l,K as u,Mn as d,Ot as f,Pn as p,Pr as m,Q as h,Qn as g,Qr as _,Rn as v,Tn as y,Un as b,Ut as x,Xn as S,Yr as C,a as w,at as T,br as E,cr as D,ft as O,hn as k,i as A,j as ee,jn as j,ln as M,lr as N,o as P,or as F,pr as I,qr as L,qt as te,rn as ne,st as re,tr as R,vr as ie,wn as z,yn as B}from"./element-plus-Bx3j6vzG.js";import{c as V,t as H}from"./vendor-BjomZKSS.js";import{n as U,t as W}from"./_plugin-vue_export-helper-C7IvbEC3.js";import{t as ae}from"./configStore-C-htWfWm.js";import{t as oe}from"./CommonDialog-CRXyMlp5.js";import{a as se,i as G,n as K,o as q,r as ce,s as le,t as J}from"./agentConversations-nN-yDz8q.js";import{n as ue,t as de}from"./AgentEngineSelector-BArUIELz.js";v(),I(),M(),m();var fe={class:`marketplace-panel`},Y={class:`mp-toolbar`},pe={class:`mp-search`},me=[`placeholder`],he={class:`mp-toolbar-sub`},ge={class:`mp-target-hint`},_e={class:`mp-source-filter`},X=[`onClick`],ve={class:`mp-body`},ye={class:`mp-group-header`},be={class:`mp-group-title`},xe={key:0,class:`mp-group-count`},Se={key:1,class:`mp-group-error`},Ce=[`href`,`title`],we={key:0,class:`mp-grid`},Te={class:`mp-card-head`},Z=[`title`],Ee={key:0,class:`mp-card-metric`},De=[`title`],Q={class:`mp-card-meta`},$=[`title`],Oe=[`title`],ke={key:2,class:`mp-tag remote`},Ae={class:`mp-card-foot`},je=[`href`],Me={key:1,class:`mp-installed-chip`},Ne=[`disabled`,`onClick`],Pe={key:3,class:`mp-not-installable`},Fe={key:1,class:`mp-empty`},Ie={key:0,class:`mp-group installed`},Le={class:`mp-group-header`},Re={class:`mp-group-title`},ze={class:`mp-group-count`},Be={key:0,class:`mp-installed-block`},Ve={class:`mp-installed-target`},He={class:`mp-installed-list`},Ue={class:`mp-installed-name`},We={key:0,class:`mp-meta-pkg`},Ge={key:1,class:`mp-tag warn`},Ke=[`disabled`,`onClick`],qe={key:1,class:`mp-empty all`},Je={key:0,class:`mkt-form`},Ye={class:`mkt-form-name`},Xe=[`onUpdate:modelValue`,`placeholder`],Ze={key:0,class:`mkt-field`},Qe={class:`mkt-hint`},$e=W(p({__name:`MarketplacePanel`,props:{type:{}},setup(i){let f=i,p=ae(),m=E([]),h=E([]),v=E(!1),b=E(``),C=E(``),T=E([]),M=E(`project`),I=E(new Set),R=E(new Set),V=r(()=>p.currentDirectory||``),H=null;async function W(){if(!v.value){v.value=!0;try{let e=new URLSearchParams({type:f.type,cwd:V.value});C.value&&e.set(`q`,C.value),T.value.length&&e.set(`sources`,T.value.join(`,`));let t=await(await fetch(`/api/agent/marketplace/catalog?${e}`)).json();t?.success?(m.value=t.groups||[],h.value=t.installed||[]):w.error(t?.error||U(`@MKT:加载失败`))}catch{w.error(U(`@MKT:加载失败`))}finally{v.value=!1}}}F(b,e=>{H&&clearTimeout(H),H=setTimeout(()=>{C.value=e.trim(),W()},400)}),F(T,()=>W()),F(()=>f.type,()=>{T.value=[],C.value=``,b.value=``,W()}),F(V,()=>W()),W();let se=r(()=>{let e=V.value?V.value.split(/[\\/]/).filter(Boolean).pop():``;return M.value===`project`?e||U(`@MKT:当前项目`):U(`@MKT:g ai 智能体`)});function G(e){return e.stars?`${K(e.stars)} ★`:e.downloads?`${K(e.downloads)} ${U(`@MKT:周下载`)}`:e.uses?`${K(e.uses)} ${U(`@MKT:次使用`)}`:``}function K(e){return e>=1e3?`${(e/1e3).toFixed(e>=1e4?0:1)}k`:String(e)}function q(e){return`${e.id}`}function ce(e){return I.value.has(q(e))}let le=r(()=>{let e={project:[],global:[]};for(let t of h.value)(e[t.target]||=[]).push(t);return e}),J=ie({visible:!1,item:null,env:{},args:``});function ue(e){return f.type===`mcp`&&(!!e.envKeys?.length||!!e.args?.length)}function de(e){J.item=e,J.env=Object.fromEntries((e.envKeys||[]).map(e=>[e,``])),J.args=(e.args||[]).join(` `),J.visible=!0}function $e(){let e=J.item;if(J.visible=!1,!e)return;let t=Object.fromEntries(Object.entries(J.env).filter(([,e])=>e.trim()!==``)),n=J.args.trim()?J.args.trim().match(/(?:[^\s"]+|"[^"]*")+/g)?.map(e=>e.replace(/^"|"$/g,``))??void 0:void 0;tt(e,{env:t,...n?{args:n}:{}})}async function et(e){if(!e.installed||await nt(e)){if(ue(e)){de(e);return}await tt(e,{})}}async function tt(e,t){let n=q(e);I.value.add(n);try{let n=await fetch(`/api/agent/marketplace/install`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({type:f.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||U(`@MKT:安装失败`));return}r.item?.warning&&w.warning(r.item.warning),r.item?.envKeys?.length?w.warning(`${U(`@MKT:还缺环境变量`)}: ${r.item.envKeys.join(`, `)}`):w.success(U(`@MKT:安装成功`)),await W()}catch{w.error(U(`@MKT:安装失败`))}finally{I.value.delete(n)}}async function nt(e){try{return await A.confirm(`${e.name} ${U(`@MKT:已安装,要重新安装吗?`)}`,U(`@MKT:重新安装`),{confirmButtonText:U(`@MKT:重装`),cancelButtonText:U(`@MKT:取消`),type:`warning`}),!0}catch{return!1}}async function rt(e){try{await A.confirm(`${U(`@MKT:确定卸载`)} ${e.name}?`,U(`@MKT:卸载`),{confirmButtonText:U(`@MKT:卸载`),cancelButtonText:U(`@MKT:取消`),type:`warning`})}catch{return}let t=`${e.target}:${e.id}`;R.value.add(t);try{let t=new URLSearchParams({target:e.target,cwd:V.value}),n=await fetch(`/api/agent/marketplace/item/${f.type}/${encodeURIComponent(e.id)}?${t}`,{method:`DELETE`}),r=await n.json().catch(()=>null);if(!n.ok||!r?.success){w.error(r?.error||U(`@MKT:卸载失败`));return}w.success(U(`@MKT:已卸载`)),await W()}catch{w.error(U(`@MKT:卸载失败`))}finally{R.value.delete(t)}}return(r,f)=>{let p=u,C=t,w=ee,E=l,A=P;return S(),a(`div`,fe,[z(`div`,Y,[z(`div`,pe,[d(p,{class:`mp-search-icon`},{default:D(()=>[d(c(x))]),_:1}),N(z(`input`,{"onUpdate:modelValue":f[0]||=e=>b.value=e,class:`mp-search-input`,type:`text`,placeholder:i.type===`skill`?c(U)(`@MKT:搜索 Skill...`):c(U)(`@MKT:搜索 MCP 服务...`)},null,8,me),[[k,b.value]])]),d(w,{modelValue:M.value,"onUpdate:modelValue":f[1]||=e=>M.value=e,size:`small`,class:`mp-target`},{default:D(()=>[d(C,{value:`project`},{default:D(()=>[j(_(c(U)(`@MKT:当前项目`)),1)]),_:1}),d(C,{value:`global`},{default:D(()=>[j(_(c(U)(`@MKT:g ai 智能体`)),1)]),_:1})]),_:1},8,[`modelValue`]),d(E,{content:c(U)(`@MKT:刷新`),placement:`top`,"show-after":300},{default:D(()=>[z(`button`,{class:L([`mp-refresh`,{spinning:v.value}]),onClick:W},[d(p,null,{default:D(()=>[d(c(n))]),_:1})],2)]),_:1},8,[`content`])]),z(`div`,he,[z(`span`,ge,[j(_(c(U)(`@MKT:安装到`))+` `,1),z(`b`,null,_(se.value),1)]),z(`div`,_e,[(S(!0),a(B,null,g(m.value,e=>(S(),a(`button`,{key:e.id,class:L([`mp-chip`,{active:T.value.includes(e.id)}]),onClick:()=>{let t=T.value.indexOf(e.id);t>=0?T.value.splice(t,1):T.value.push(e.id)}},_(e.label),11,X))),128)),T.value.length?(S(),a(`button`,{key:0,class:`mp-chip clear`,onClick:f[2]||=e=>T.value=[]},_(c(U)(`@MKT:全部来源`)),1)):s(``,!0)])]),N((S(),a(`div`,ve,[(S(!0),a(B,null,g(m.value,t=>(S(),a(`section`,{key:t.id,class:`mp-group`},[z(`header`,ye,[z(`h3`,be,_(t.label),1),t.status===`ok`?(S(),a(`span`,xe,_(t.items.length),1)):(S(),a(`span`,Se,[d(p,null,{default:D(()=>[d(c(ne))]),_:1}),j(` `+_(t.error||c(U)(`@MKT:来源不可用`)),1)])),t.homepage?(S(),a(`a`,{key:2,class:`mp-group-link`,href:t.homepage,target:`_blank`,rel:`noopener noreferrer`,title:t.homepage},[d(p,null,{default:D(()=>[d(c(o))]),_:1})],8,Ce)):s(``,!0)]),t.status===`ok`&&t.items.length?(S(),a(`div`,we,[(S(!0),a(B,null,g(t.items,t=>(S(),a(`article`,{key:q(t),class:`mp-card`},[z(`div`,Te,[z(`span`,{class:`mp-card-name`,title:t.name},_(t.name),9,Z),G(t)?(S(),a(`span`,Ee,[d(p,null,{default:D(()=>[t.stars?(S(),y(c(te),{key:0})):(S(),y(c(O),{key:1}))]),_:2},1024),j(` `+_(G(t)),1)])):s(``,!0)]),z(`p`,{class:`mp-card-desc`,title:t.description},_(t.description||c(U)(`@MKT:暂无描述`)),9,De),z(`div`,Q,[t.package?(S(),a(`code`,{key:0,class:`mp-meta-pkg`,title:t.package},_(t.package),9,$)):t.repository?(S(),a(`span`,{key:1,class:`mp-meta-repo`,title:t.repository+(t.subpath?` · `+t.subpath:``)},[j(_(t.repository),1),t.subpath?(S(),a(B,{key:0},[j(` · `+_(t.subpath),1)],64)):s(``,!0)],8,Oe)):s(``,!0),t.transport===`remote`?(S(),a(`span`,ke,_(c(U)(`@MKT:远程服务`)),1)):s(``,!0),(S(!0),a(B,null,g((t.tags||[]).slice(0,2),e=>(S(),a(`span`,{key:e,class:`mp-tag`},_(e),1))),128))]),z(`div`,Ae,[t.upstream?(S(),a(`a`,{key:0,class:`mp-card-link`,href:t.upstream,target:`_blank`,rel:`noopener noreferrer`},_(c(U)(`@MKT:查看来源`)),9,je)):s(``,!0),f[6]||=z(`span`,{class:`mp-foot-spacer`},null,-1),t.installed?(S(),a(`span`,Me,[d(p,null,{default:D(()=>[d(c(e))]),_:1}),j(_(c(U)(`@MKT:已安装`)),1)])):t.installable===!1?(S(),a(`span`,Pe,_(c(U)(`@MKT:暂不支持一键安装`)),1)):(S(),a(`button`,{key:2,class:`mp-install-btn`,disabled:ce(t),onClick:e=>et(t)},_(ce(t)?c(U)(`@MKT:安装中...`):c(U)(`@MKT:安装`)),9,Ne))])]))),128))])):t.status===`ok`?(S(),a(`div`,Fe,_(c(U)(`@MKT:该来源暂无结果`)),1)):s(``,!0)]))),128)),h.value.length?(S(),a(`section`,Ie,[z(`header`,Le,[z(`h3`,Re,_(c(U)(`@MKT:已安装`)),1),z(`span`,ze,_(h.value.length),1)]),(S(),a(B,null,g([`project`,`global`],e=>z(`div`,{key:e},[le.value[e]?.length?(S(),a(`div`,Be,[z(`div`,Ve,_(e===`project`?c(U)(`@MKT:当前项目`):c(U)(`@MKT:g ai 智能体`)),1),z(`div`,He,[(S(!0),a(B,null,g(le.value[e],t=>(S(),a(`div`,{key:e+t.id,class:`mp-installed-row`},[z(`span`,Ue,_(t.name),1),t.package?(S(),a(`code`,We,_(t.package),1)):s(``,!0),t.requiredEnv?.length?(S(),a(`span`,Ge,_(c(U)(`@MKT:待配置`))+`: `+_(t.requiredEnv.join(`, `)),1)):s(``,!0),f[7]||=z(`span`,{class:`mp-foot-spacer`},null,-1),z(`button`,{class:`mp-remove-btn`,disabled:R.value.has(e+t.id),onClick:e=>rt(t)},[d(p,null,{default:D(()=>[d(c(re))]),_:1}),j(` `+_(R.value.has(e+t.id)?c(U)(`@MKT:卸载中...`):c(U)(`@MKT:卸载`)),1)],8,Ke)]))),128))])])):s(``,!0)])),64))])):s(``,!0),!v.value&&!m.value.length?(S(),a(`div`,qe,_(c(U)(`@MKT:加载失败`)),1)):s(``,!0)])),[[A,v.value]]),d(oe,{modelValue:J.visible,"onUpdate:modelValue":f[5]||=e=>J.visible=e,title:c(U)(`@MKT:配置 MCP`),width:`460px`,type:`flex`,"close-on-click-modal":!1,"append-to-body":``},{footer:D(()=>[z(`button`,{class:`mkt-cancel`,onClick:f[4]||=e=>J.visible=!1},_(c(U)(`@MKT:取消`)),1),z(`button`,{class:`mkt-confirm`,onClick:$e},_(c(U)(`@MKT:安装`)),1)]),default:D(()=>[J.item?(S(),a(`div`,Je,[z(`p`,Ye,_(J.item.name),1),(S(!0),a(B,null,g(Object.keys(J.env),e=>(S(),a(`div`,{key:e,class:`mkt-field`},[z(`label`,null,_(e),1),N(z(`input`,{"onUpdate:modelValue":t=>J.env[e]=t,type:`text`,autocomplete:`off`,placeholder:c(U)(`@MKT:环境变量值`),spellcheck:`false`},null,8,Xe),[[k,J.env[e]]])]))),128)),J.item.args?.length?(S(),a(`div`,Ze,[z(`label`,null,_(c(U)(`@MKT:启动参数`)),1),N(z(`input`,{"onUpdate:modelValue":f[3]||=e=>J.args=e,type:`text`,autocomplete:`off`,spellcheck:`false`},null,512),[[k,J.args]]),z(`p`,Qe,_(c(U)(`@MKT:参数提示`)),1)])):s(``,!0)])):s(``,!0)]),_:1},8,[`modelValue`,`title`])])}}}),[[`__scopeId`,`data-v-6788a7b1`]]);v(),I(),m();var et={class:`agent-chat-area`},tt={class:`agent-tabs`,role:`tablist`},nt=[`aria-selected`,`onClick`],rt={class:`agent-tab-body`},it={key:0,class:`agent-chat-pane`},at={key:0,class:`agent-list-page`},ot={key:0,class:`agent-page-bar`},st=[`title`,`aria-label`],ct={class:`agent-page-title`},lt={key:0,class:`chat-loading`},ut=W(p({__name:`AgentView`,setup(e){let t=ae(),n=E(null),{narrow:o}=ue(n),l=E(`chat`),p=E(`chat`),m=r(()=>p.value===`mcp`?`mcp`:`skill`),v=r(()=>[{id:`chat`,label:U(`@AGENT:对话`),icon:h},{id:`skill`,label:U(`@AGENT:Skill 广场`),icon:i},{id:`mcp`,label:U(`@AGENT:MCP 广场`),icon:T}]),{sessions:x,sessionsLoading:w,currentSessionId:O,messages:k,isStreaming:A,sessionLoading:ee,loadSessions:j,loadSession:M,deleteSession:N,renameSession:P,newSession:I,sendMessage:te,pendingQuestion:ne,answeringQuestion:re,answerQuestion:ie,isSessionGenerating:W,currentEngine:oe,pendingEngine:fe,isEngineLocked:Y,pickEngine:pe,stop:me}=le(),he=r(()=>Y.value?oe.value:fe.value);function ge(e){Y.value||pe(e)}let _e=r(()=>{let e=t.theme;return e===`dark`||e===`auto`&&window.matchMedia(`(prefers-color-scheme: dark)`).matches?`dark`:`light`}),X=r(()=>se(x.value,W)),ve=ce(),ye=r(()=>X.value.find(e=>e.id===O.value)?.title||U(`@AGENT:对话`)),be={"--welcome-avatar":`url("${J}")`},xe=r(()=>[{id:`p1`,label:U(`@AGENT:查看项目结构`),prompt:U(`@AGENT:prompt_p1`)},{id:`p2`,label:U(`@AGENT:分析代码质量`),prompt:U(`@AGENT:prompt_p2`)},{id:`p3`,label:U(`@AGENT:帮我写测试`),prompt:U(`@AGENT:prompt_p3`)},{id:`p4`,label:U(`@AGENT:Git 状态检查`),prompt:U(`@AGENT:prompt_p4`)},{id:`p5`,label:U(`@AGENT:帮我启动项目`),prompt:U(`@AGENT:prompt_p5`)}]);async function Se(e){await te(e.text,e.files),await b(),Z()}async function Ce(e){await te(e.prompt),await b(),Z()}let we=G(),Te=E(null);function Z(e=!0){Te.value?.scrollToBottom(e)}F(()=>k.value.length,()=>{b(()=>Z(!1))});function Ee(e){M(e),l.value=`chat`}function De(){I(),l.value=`chat`}let Q=E(280),$=E(!1);function Oe(e){$.value=!0;let t=e.clientX,n=Q.value,r=e=>{let r=e.clientX-t,i=Math.max(200,Math.min(500,n+r));Q.value=i},i=()=>{$.value=!1,document.removeEventListener(`mousemove`,r),document.removeEventListener(`mouseup`,i)};document.addEventListener(`mousemove`,r),document.addEventListener(`mouseup`,i)}return F(o,e=>{e||(l.value=`chat`)}),F(()=>[t.currentDirectory,A.value],async([e,t],n)=>{let r=!n||e!==n[0],i=!!n&&n[1]===!0&&!t;(r||i)&&await j(),O.value&&!A.value&&!x.value.some(e=>e.sessionId===O.value)&&I()},{immediate:!0}),(e,t)=>(S(),a(`div`,{ref_key:`rootRef`,ref:n,class:`agent-view`},[p.value===`chat`&&!c(o)?(S(),a(B,{key:0},[z(`aside`,{class:`agent-sidebar`,style:C({width:Q.value+`px`})},[d(c(H),{class:`agent-conversations`,items:X.value,"active-id":c(O),loading:c(w),labels:c(ve),onSelect:Ee,onNew:De,onRename:c(P),onDelete:c(N)},null,8,[`items`,`active-id`,`loading`,`labels`,`onRename`,`onDelete`])],4),z(`div`,{class:L([`sidebar-resizer`,{active:$.value}]),onMousedown:Oe},null,34)],64)):s(``,!0),z(`main`,et,[z(`nav`,tt,[(S(!0),a(B,null,g(v.value,e=>(S(),a(`button`,{key:e.id,type:`button`,class:L([`agent-tab`,{active:p.value===e.id}]),role:`tab`,"aria-selected":p.value===e.id,onClick:t=>p.value=e.id},[d(c(u),null,{default:D(()=>[(S(),y(R(e.icon)))]),_:2},1024),z(`span`,null,_(e.label),1)],10,nt))),128)),p.value===`chat`?(S(),y(de,{key:0,class:`agent-engine`,engine:he.value,locked:c(Y),onSelect:ge},null,8,[`engine`,`locked`])):s(``,!0)]),z(`div`,rt,[p.value===`chat`?(S(),a(`div`,it,[c(o)&&l.value===`list`?(S(),a(`div`,at,[d(c(H),{class:`agent-conversations`,items:X.value,"active-id":c(O),loading:c(w),labels:c(ve),onSelect:Ee,onNew:De,onRename:c(P),onDelete:c(N)},null,8,[`items`,`active-id`,`loading`,`labels`,`onRename`,`onDelete`])])):(S(),a(B,{key:1},[c(o)?(S(),a(`div`,ot,[z(`button`,{type:`button`,class:`agent-page-back`,title:c(U)(`@AGENT:返回会话列表`),"aria-label":c(U)(`@AGENT:返回会话列表`),onClick:t[0]||=e=>l.value=`list`},[...t[1]||=[z(`svg`,{viewBox:`0 0 24 24`,width:`14`,height:`14`,fill:`none`,stroke:`currentColor`,"stroke-width":`2`,"stroke-linecap":`round`,"stroke-linejoin":`round`},[z(`line`,{x1:`19`,y1:`12`,x2:`5`,y2:`12`}),z(`polyline`,{points:`12 19 5 12 12 5`})],-1)]],8,st),z(`span`,ct,_(ye.value),1)])):s(``,!0),z(`div`,{class:`agent-chat-host`,style:be},[c(ee)?(S(),a(`div`,lt,[d(c(u),{class:`is-loading`,size:32},{default:D(()=>[d(c(f))]),_:1}),z(`span`,null,_(c(U)(`@AGENT:加载会话中...`)),1)])):(S(),y(c(V),{key:1,ref_key:`chatContainerRef`,ref:Te,messages:c(k),"preset-questions":xe.value,"welcome-title":c(U)(`@AGENT:智能体助手`),"welcome-description":c(U)(`@AGENT:我可以帮你阅读代码、执行命令、修改文件。选择下方话题或直接输入你的问题。`),"assistant-name":c(K),"assistant-avatar":c(J),theme:_e.value,disabled:c(A),generating:c(A),"upload-config":{accept:c(q)},placeholder:c(A)?c(U)(`@AGENT:正在生成中...`):c(U)(`@AGENT:输入消息,Enter 发送`),question:c(ne),"question-submitting":c(re),"question-labels":c(we),"plan-config":{labels:{title:c(U)(`@AGENT:计划`),raw:c(U)(`@AGENT:原始参数`)}},onSend:Se,onSelect:Ce,onStop:c(me),onAnswer:c(ie)},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`]))])],64))])):(S(),y($e,{key:1,type:m.value},null,8,[`type`]))])])],512))}}),[[`__scopeId`,`data-v-05972cec`]]);export{ut as default};
@@ -1,2 +0,0 @@
1
- import"./rolldown-runtime-C0FnF6B9.js";import{Cn as e,Dn as t,En as n,Er as r,H as i,Jn as a,K as o,Kn as s,L as c,Mn as l,Ot as u,Pn as d,Pr as f,Qr as p,Rn as m,Tn as h,Un as g,Xn as _,a as v,br as y,cr as b,et as x,i as S,jn as C,or as w,pr as T,qr as E,tt as D,wn as O}from"./element-plus-Bx3j6vzG.js";import{n as k,t as A}from"./_plugin-vue_export-helper-C7IvbEC3.js";import{t as j}from"./CommonDialog-CRXyMlp5.js";async function M(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 N(){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 P(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 M(t,t=>{try{e(JSON.parse(t))}catch{}})}async function F(){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())};M(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(),T(),f();var I={key:3},L={key:4},R={key:5},z={key:0,class:`upgrade-hint`},B={key:0,class:`upgrade-countdown`},V={key:1,class:`upgrade-manual`},H={class:`upgrade-manual__label`},U=`npm install -g zen-gitsync --registry https://registry.npmjs.org/`,W=A(d({__name:`UpgradeDialog`,props:{modelValue:{type:Boolean},logs:{},status:{},countdown:{default:0}},emits:[`update:modelValue`,`retry`,`restart`,`cancel`],setup(e,{emit:i}){let a=e,s=i,d=y(null);w(()=>a.logs,async()=>{await g(),d.value&&(d.value.scrollTop=d.value.scrollHeight)});function f(){a.status!==`running`&&(a.status===`success`&&a.countdown>0&&s(`cancel`),s(`update:modelValue`,!1))}function m(){s(`retry`)}function S(){s(`restart`)}function T(){s(`cancel`),s(`update:modelValue`,!1)}async function A(){try{await navigator.clipboard.writeText(U),v.success(k(`@F13B4:已复制升级命令`))}catch{v.warning(k(`@F13B4:复制失败,请手动复制`))}}return(i,a)=>{let g=o,v=c;return _(),h(j,{"model-value":e.modelValue,"onUpdate:modelValue":a[0]||=e=>s(`update:modelValue`,e),title:r(k)(`@F13B4:升级`),width:`680px`,type:`flex`,"close-on-click-modal":!1,"close-on-press-escape":!1,"show-close":e.status!==`running`,"append-to-body":``,onClose:f},{footer:b(()=>[e.status===`failed`?(_(),h(v,{key:0,type:`primary`,onClick:m},{default:b(()=>[C(p(r(k)(`@F13B4:重试`)),1)]),_:1})):n(``,!0),e.status===`failed`?(_(),h(v,{key:1,onClick:A},{default:b(()=>[C(p(r(k)(`@F13B4:复制升级命令`)),1)]),_:1})):n(``,!0),e.status===`success`?(_(),h(v,{key:2,type:`primary`,onClick:S},{default:b(()=>[C(p(r(k)(`@F13B4:立即重启并刷新`)),1)]),_:1})):n(``,!0),e.status===`success`?(_(),h(v,{key:3,onClick:T},{default:b(()=>[C(p(r(k)(`@F13B4:稍后手动重启`)),1)]),_:1})):n(``,!0),e.status===`running`?(_(),h(v,{key:4,disabled:``},{default:b(()=>[C(p(r(k)(`@F13B4:升级中`))+`... `,1)]),_:1})):n(``,!0)]),default:b(()=>[O(`div`,{class:E([`upgrade-status`,`is-${e.status}`])},[e.status===`running`?(_(),h(g,{key:0,class:`is-loading`},{default:b(()=>[l(r(u))]),_:1})):e.status===`success`?(_(),h(g,{key:1,color:`var(--color-success)`},{default:b(()=>[l(r(x))]),_:1})):(_(),h(g,{key:2,color:`var(--color-danger)`},{default:b(()=>[l(r(D))]),_:1})),e.status===`running`?(_(),t(`span`,I,p(r(k)(`@F13B4:升级中`)),1)):e.status===`success`?(_(),t(`span`,L,p(r(k)(`@F13B4:升级完成`)),1)):(_(),t(`span`,R,p(r(k)(`@F13B4:升级失败`)),1))],2),e.status===`success`?(_(),t(`p`,z,[C(p(r(k)(`@F13B4:新版本已全局安装,需要重启服务才能生效`))+` `,1),e.countdown>0?(_(),t(`span`,B,p(r(k)(`@F13B4:{seconds} 秒后自动刷新`,{seconds:e.countdown})),1)):n(``,!0)])):n(``,!0),e.status===`failed`?(_(),t(`p`,V,[O(`span`,H,p(r(k)(`@F13B4:手动升级命令`)),1),O(`code`,{class:`upgrade-manual__cmd`},p(U))])):n(``,!0),O(`pre`,{ref_key:`logEl`,ref:d,class:`upgrade-log`},p(e.logs||r(k)(`@F13B4:等待日志输出`)),513)]),_:1},8,[`model-value`,`title`,`show-close`])}}}),[[`__scopeId`,`data-v-5c9f402d`]]);m(),T(),f();var G={class:`app-version-badge`},K=[`aria-label`],q=[`aria-label`],J=[`title`,`aria-label`],Y={class:`upgrade-btn__label`},X={class:`upgrade-btn__version`},Z=`https://github.com/xz333221/zen-gitsync/releases`,Q=5,$=`app-version-check`,ee=36e5,te=A(d({__name:`AppVersionBadge`,setup(o){let c=`2.17.25`,u=y(c),d=e(()=>u.value||c),f=y(null),m=y(!1),h=y(``),g=y(`running`),x=y(0),C=null,w=y(!1);function T(){try{let e=sessionStorage.getItem($);if(!e)return null;let{at:t,data:n}=JSON.parse(e);return Date.now()-t>ee?null:n}catch{return null}}function E(e){try{sessionStorage.setItem($,JSON.stringify({at:Date.now(),data:e}))}catch{}}async function D(e=!1){let t=T();t&&(f.value=t);try{let t=await N();f.value=t,E(t),t.current&&(u.value=t.current),!e&&t.success&&t.hasUpdate&&v({type:`success`,message:k(`@F13B4:发现新版本 {version}`,{version:t.latest??``}),duration:4e3})}catch(t){e||v({type:`warning`,message:k(`@F13B4:检查更新失败 {error}`,{error:t?.message||``})})}}async function A(){if(f.value?.hasUpdate){try{await S.confirm(k(`@F13B4:确定要从 {current} 升级到 {latest} 吗?`,{current:f.value.current,latest:f.value.latest}),k(`@F13B4:升级`),{confirmButtonText:k(`@F13B4:确定升级`),cancelButtonText:k(`@F13B4:取消`),type:`info`})}catch{return}h.value=``,g.value=`running`,m.value=!0,await P(e=>{if(e.type===`done`){if(g.value=e.code===0?`success`:`failed`,e.code===0){try{sessionStorage.removeItem($)}catch{}f.value?.latest&&(u.value=f.value.latest),j()}}else e.message&&(h.value+=e.message)})}}function j(){M(),x.value=Q,C=setInterval(()=>{--x.value,x.value<=0&&(C&&=(clearInterval(C),null),L())},1e3)}function M(){C&&=(clearInterval(C),null),x.value=0}function I(){M()}async function L(){if(M(),!w.value){w.value=!0;try{let e=await F(),{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];v({type:`warning`,message:n?k(n):k(`@F13B4:重启失败 {error}`,{error:t})}),w.value=!1}}}return s(()=>{M()}),a(()=>{D(!0)}),(e,a)=>{let o=i;return _(),t(`div`,G,[l(o,{content:r(k)(`@F13B4:在 GitHub 上查看源码`),placement:`top`,effect:`dark`,"show-after":300},{default:b(()=>[O(`a`,{href:`https://github.com/xz333221/zen-gitsync`,target:`_blank`,rel:`noopener noreferrer`,class:`badge-icon-link`,"aria-label":r(k)(`@F13B4:在 GitHub 上查看源码`)},[...a[1]||=[O(`svg`,{viewBox:`0 0 24 24`,width:`15`,height:`15`,"aria-hidden":`true`,fill:`currentColor`,xmlns:`http://www.w3.org/2000/svg`},[O(`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,K)]),_:1},8,[`content`]),l(o,{content:r(k)(`@F13B4:在 npm 上查看包`),placement:`top`,effect:`dark`,"show-after":300},{default:b(()=>[O(`a`,{href:`https://www.npmjs.com/package/zen-gitsync`,target:`_blank`,rel:`noopener noreferrer`,class:`badge-icon-link badge-icon-link--npm`,"aria-label":r(k)(`@F13B4:在 npm 上查看包`)},[...a[2]||=[O(`svg`,{viewBox:`0 0 24 24`,width:`15`,height:`15`,"aria-hidden":`true`,fill:`currentColor`,xmlns:`http://www.w3.org/2000/svg`},[O(`path`,{d:`M2 2v20h20V2H2zm17.167 17.167H12.5v-10h-3.333v10H4.833V4.833h14.334v14.334z`})],-1)]],8,q)]),_:1},8,[`content`]),l(o,{content:r(k)(`@F13B4:查看更新`),placement:`top`,effect:`dark`,"show-after":300},{default:b(()=>[O(`a`,{href:Z,target:`_blank`,rel:`noopener noreferrer`,class:`version-link`},`v`+p(d.value),1)]),_:1},8,[`content`]),f.value?.hasUpdate?(_(),t(`button`,{key:0,type:`button`,class:`upgrade-btn`,title:r(k)(`@F13B4:发现新版本 {version},点击升级`,{version:f.value.latest??``}),"aria-label":r(k)(`@F13B4:发现新版本 {version},点击升级`,{version:f.value.latest??``}),onClick:A},[a[3]||=O(`span`,{class:`upgrade-btn__dot`,"aria-hidden":`true`},null,-1),O(`span`,Y,p(r(k)(`@F13B4:升级`)),1),O(`span`,X,`v`+p(f.value.latest),1)],8,J)):n(``,!0),l(W,{modelValue:m.value,"onUpdate:modelValue":a[0]||=e=>m.value=e,logs:h.value,status:g.value,countdown:x.value,onRetry:A,onRestart:L,onCancel:I},null,8,[`modelValue`,`logs`,`status`,`countdown`])])}}}),[[`__scopeId`,`data-v-4084e4be`]]);export{te as default};