zen-gitsync 2.15.34 → 2.16.2

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 (92) hide show
  1. package/README.md +37 -3
  2. package/package.json +1 -1
  3. package/src/cli/ai/agent.js +493 -83
  4. package/src/cli/ai/agent.test.js +190 -0
  5. package/src/cli/ai/images.js +211 -0
  6. package/src/cli/ai/images.test.js +98 -0
  7. package/src/cli/ai/modelSetup.js +594 -0
  8. package/src/cli/ai/modelSetup.test.js +496 -0
  9. package/src/cli/ai/platformGuard.js +200 -0
  10. package/src/cli/ai/platformGuard.test.js +253 -0
  11. package/src/cli/ai/streamFilter.js +82 -0
  12. package/src/cli/ai/streamFilter.test.js +88 -0
  13. package/src/cli/ai/termui.js +453 -0
  14. package/src/cli/ai/termui.test.js +253 -0
  15. package/src/cli/ai/tools.js +54 -18
  16. package/src/cli/ai/tools.test.js +28 -0
  17. package/src/ui/public/assets/AppVersionBadge-BTznOVSJ.js +2 -0
  18. package/src/ui/public/assets/AppVersionBadge-CS7Trvp-.css +1 -0
  19. package/src/ui/public/assets/BranchSelector-B0eLkAdv.css +1 -0
  20. package/src/ui/public/assets/BranchSelector-BISqgXoZ.js +1 -0
  21. package/src/ui/public/assets/CommitForm-C7FmI0Sl.js +6 -0
  22. package/src/ui/public/assets/CommitForm-DdK8dymc.css +1 -0
  23. package/src/ui/public/assets/CommonDialog-BA4xg5l3.js +1 -0
  24. package/src/ui/public/assets/CommonDialog-ChSKsEz2.css +1 -0
  25. package/src/ui/public/assets/EditorView-BVod-rjn.css +1 -0
  26. package/src/ui/public/assets/EditorView-CSWy4q7G.js +0 -0
  27. package/src/ui/public/assets/FlowExecutionViewer-CZdF1f1e.js +1 -0
  28. package/src/ui/public/assets/FlowExecutionViewer-jNHI5zdm.css +1 -0
  29. package/src/ui/public/assets/FlowOrchestrationWorkspace-BECFOAcQ.js +21 -0
  30. package/src/ui/public/assets/FlowOrchestrationWorkspace-CYGp-PqS.css +1 -0
  31. package/src/ui/public/assets/LogList-CeFFytjB.css +1 -0
  32. package/src/ui/public/assets/LogList-DYoGtOou.js +15 -0
  33. package/src/ui/public/assets/MindmapView-3rMfOoNm.css +1 -0
  34. package/src/ui/public/assets/MindmapView-CU3tXgbR.js +1 -0
  35. package/src/ui/public/assets/MonitorView-CSAqU7oV.js +1 -0
  36. package/src/ui/public/assets/MonitorView-TaS4kL0Y.css +1 -0
  37. package/src/ui/public/assets/ProjectStartupButton-2By0-fuq.js +1 -0
  38. package/src/ui/public/assets/ProjectStartupButton-QLPltCK5.css +1 -0
  39. package/src/ui/public/assets/RemoteRepoCard-BRGNlbeO.css +1 -0
  40. package/src/ui/public/assets/RemoteRepoCard-DHsJwTEX.js +5 -0
  41. package/src/ui/public/assets/SourceMapView-BNRjvgzH.css +1 -0
  42. package/src/ui/public/assets/SourceMapView-DskfUia5.js +3 -0
  43. package/src/ui/public/assets/SvgIcon-BqWp2WS8.js +1 -0
  44. package/src/ui/public/assets/{SvgIcon-BQ6bR7l0.css → SvgIcon-CDNsk4-Q.css} +1 -1
  45. package/src/ui/public/assets/UserInputNode-BVgQkB2l.css +1 -0
  46. package/src/ui/public/assets/UserInputNode-Ccnh0e-0.js +1 -0
  47. package/src/ui/public/assets/WorkbenchView-1q0xaqyV.js +28 -0
  48. package/src/ui/public/assets/{WorkbenchView-CQfjF0Gr.css → WorkbenchView-DbF4hH5s.css} +1 -1
  49. package/src/ui/public/assets/{_plugin-vue_export-helper-B7s-K_Aj.js → _plugin-vue_export-helper-BQRl7WAH.js} +1 -1
  50. package/src/ui/public/assets/{configStore-DPGao81A.js → configStore-BkxDLXGa.js} +1 -1
  51. package/src/ui/public/assets/element-plus-DNnePjco.js +32 -0
  52. package/src/ui/public/assets/{flow-mindmap-5SDymYbY.js → flow-mindmap-DRmzYE4F.js} +1 -1
  53. package/src/ui/public/assets/index-Br2VZQBT.js +68 -0
  54. package/src/ui/public/assets/{index-CWoujklW.css → index-CaIu73UM.css} +1 -1
  55. package/src/ui/public/assets/socket-io-pmu5tRPo.js +1 -0
  56. package/src/ui/public/assets/{vendor-BteTDIoK.js → vendor-CcK8n8T4.js} +44 -37
  57. package/src/ui/public/assets/vue-flow-pAz--378.js +39 -0
  58. package/src/ui/public/index.html +12 -12
  59. package/src/ui/public/assets/AppVersionBadge-DL0Y81fU.js +0 -2
  60. package/src/ui/public/assets/AppVersionBadge-Dj6H729N.css +0 -1
  61. package/src/ui/public/assets/BranchSelector-B_VMrSKv.js +0 -1
  62. package/src/ui/public/assets/BranchSelector-T_rJm-JY.css +0 -1
  63. package/src/ui/public/assets/CommitForm-BNuwp3ha.js +0 -6
  64. package/src/ui/public/assets/CommitForm-CUvl0VcF.css +0 -1
  65. package/src/ui/public/assets/CommonDialog-C2dbNSrw.js +0 -1
  66. package/src/ui/public/assets/CommonDialog-Cyh3xu0p.css +0 -1
  67. package/src/ui/public/assets/EditorView-BdTQsQw7.js +0 -0
  68. package/src/ui/public/assets/EditorView-CRYGVw_r.css +0 -1
  69. package/src/ui/public/assets/FlowExecutionViewer-B5kh7VxS.css +0 -1
  70. package/src/ui/public/assets/FlowExecutionViewer-D5DBcy8S.js +0 -1
  71. package/src/ui/public/assets/FlowOrchestrationWorkspace-BAQDU9Jd.css +0 -1
  72. package/src/ui/public/assets/FlowOrchestrationWorkspace-omHN8HaT.js +0 -21
  73. package/src/ui/public/assets/LogList-DJYpDUTZ.css +0 -1
  74. package/src/ui/public/assets/LogList-Dc1RMEFq.js +0 -7
  75. package/src/ui/public/assets/MindmapView-BPTh-XAf.js +0 -1
  76. package/src/ui/public/assets/MindmapView-CWoqfZRL.css +0 -1
  77. package/src/ui/public/assets/MonitorView-DxFogQIt.css +0 -1
  78. package/src/ui/public/assets/MonitorView-RTbnLZlO.js +0 -1
  79. package/src/ui/public/assets/ProjectStartupButton-B7zvqRj_.css +0 -1
  80. package/src/ui/public/assets/ProjectStartupButton-CcQ8SaPb.js +0 -1
  81. package/src/ui/public/assets/RemoteRepoCard-Cr1yQ4pF.js +0 -1
  82. package/src/ui/public/assets/RemoteRepoCard-d0klonVb.css +0 -1
  83. package/src/ui/public/assets/SourceMapView-BbiYmCHB.js +0 -3
  84. package/src/ui/public/assets/SourceMapView-DG_a_076.css +0 -1
  85. package/src/ui/public/assets/SvgIcon-D3jL_b3c.js +0 -1
  86. package/src/ui/public/assets/UserInputNode-DCk_cvaT.js +0 -1
  87. package/src/ui/public/assets/UserInputNode-DDtvv9Iq.css +0 -1
  88. package/src/ui/public/assets/WorkbenchView-KX3nKGZc.js +0 -12
  89. package/src/ui/public/assets/element-plus-BAcS68uS.js +0 -24
  90. package/src/ui/public/assets/index-B8oXZO4w.js +0 -38
  91. package/src/ui/public/assets/socket-io-BC2cap_-.js +0 -1
  92. package/src/ui/public/assets/vue-flow-CtZ_ZWKZ.js +0 -39
@@ -0,0 +1,253 @@
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/cli/ai/termui.js 单元测试:
16
+ // - 渲染结构用注入 write 的方式收集输出,stripAnsi 后断言纯文本结构
17
+ // - ANSI 着色本身不在断言范围(非 TTY 下 chalk 自动降级,测了也没意义)
18
+ import { test } from 'node:test'
19
+ import assert from 'node:assert/strict'
20
+ import {
21
+ stripAnsi, truncateDisplay, summarizeToolArgs,
22
+ createAssistantWriter, printToolHeader, printToolResult,
23
+ filterSlashCommands, renderSlashHintBody, SLASH_COMMANDS,
24
+ } from './termui.js'
25
+
26
+ // 收集 write 输出的辅助:返回 {text(), lines()}
27
+ function collect() {
28
+ let buf = ''
29
+ return {
30
+ write: (s) => { buf += s },
31
+ text: () => stripAnsi(buf),
32
+ lines: () => stripAnsi(buf).split('\n'),
33
+ }
34
+ }
35
+
36
+ // ── truncateDisplay(回归基础,主测试在 agent.test.js)──
37
+ test('truncateDisplay: 短文本原样,长文本头尾保留', () => {
38
+ assert.equal(truncateDisplay('abc'), 'abc')
39
+ const long = Array.from({ length: 40 }, (_, i) => `row${i}-` + 'x'.repeat(40)).join('\n')
40
+ const r = truncateDisplay(long, 600)
41
+ assert.match(r, /回显省略/)
42
+ assert.ok(r.startsWith('row0'))
43
+ assert.ok(r.trimEnd().endsWith('x'.repeat(40)))
44
+ })
45
+
46
+ // ── summarizeToolArgs ──
47
+ test('summarizeToolArgs: run_command 带 $ 前缀', () => {
48
+ assert.equal(summarizeToolArgs('run_command', { command: 'npm test' }), '$ npm test')
49
+ })
50
+
51
+ test('summarizeToolArgs: read_file 行范围与非范围', () => {
52
+ assert.equal(summarizeToolArgs('read_file', { path: 'a.js' }), 'a.js')
53
+ assert.equal(
54
+ summarizeToolArgs('read_file', { path: 'a.js', offset: 10, limit: 50 }),
55
+ 'a.js L10-59',
56
+ )
57
+ })
58
+
59
+ test('summarizeToolArgs: write_file 显示字符数,单位可本地化', () => {
60
+ assert.equal(summarizeToolArgs('write_file', { path: 'a.js', content: 'abcd' }), 'a.js (4 字符)')
61
+ assert.equal(summarizeToolArgs('write_file', { path: 'a.js', content: 'abcd' }, { chars: 'chars' }), 'a.js (4 chars)')
62
+ })
63
+
64
+ test('summarizeToolArgs: 其余工具的摘要格式', () => {
65
+ assert.equal(summarizeToolArgs('edit_file', { path: 'a.js' }), 'a.js')
66
+ assert.equal(summarizeToolArgs('list_files', {}), '. depth=2')
67
+ assert.equal(summarizeToolArgs('search_text', { pattern: 'foo', path: 'src' }), '/foo/ src')
68
+ })
69
+
70
+ test('summarizeToolArgs: 超长命令截断到 120 字符', () => {
71
+ const s = summarizeToolArgs('run_command', { command: 'x'.repeat(200) })
72
+ assert.ok(s.length <= 123) // '$ ' 前缀 + 120 字符 + '…'
73
+ assert.ok(s.endsWith('…'))
74
+ })
75
+
76
+ test('summarizeToolArgs: 未知工具回退 JSON,缺参数不炸', () => {
77
+ assert.equal(summarizeToolArgs('whatever', { a: 1 }), '{"a":1}')
78
+ assert.equal(summarizeToolArgs('run_command', undefined), '$ ')
79
+ })
80
+
81
+ // ── createAssistantWriter ──
82
+ test('writer: 思考段头部只打印一次,内容直写', () => {
83
+ const c = collect()
84
+ const w = createAssistantWriter({ thinkingHeader: '✻ 思考', write: c.write })
85
+ w.writeThinking('第一段')
86
+ w.writeThinking('第二段')
87
+ w.finish()
88
+ const text = c.text()
89
+ assert.equal(text.match(/✻ 思考/g).length, 1)
90
+ assert.ok(text.includes('第一段第二段'))
91
+ })
92
+
93
+ test('writer: showThinking=false 时丢弃思考段', () => {
94
+ const c = collect()
95
+ const w = createAssistantWriter({ showThinking: false, thinkingHeader: '✻ 思考', write: c.write })
96
+ w.writeThinking('看不见')
97
+ w.writeContent('正文\n')
98
+ w.finish()
99
+ const text = c.text()
100
+ assert.ok(!text.includes('看不见'))
101
+ assert.ok(!text.includes('✻ 思考'))
102
+ assert.ok(text.includes('正文'))
103
+ })
104
+
105
+ test('writer: 正文首行 ⏺ 子弹头,后续行缩进对齐', () => {
106
+ const c = collect()
107
+ const w = createAssistantWriter({ write: c.write })
108
+ w.writeContent('第一行\n第二行\n第三行')
109
+ w.finish()
110
+ const lines = c.lines()
111
+ assert.ok(lines.some(l => l.startsWith('⏺ 第一行')))
112
+ assert.ok(lines.some(l => l.startsWith(' 第二行')))
113
+ assert.ok(lines.some(l => l.startsWith(' 第三行')))
114
+ })
115
+
116
+ test('writer: 行内 markdown — bold 标记被消费,code 反引号被消费', () => {
117
+ const c = collect()
118
+ const w = createAssistantWriter({ write: c.write })
119
+ w.writeContent('这是 **加粗** 和 `代码` 混排\n')
120
+ w.finish()
121
+ const text = c.text()
122
+ assert.ok(text.includes('加粗'))
123
+ assert.ok(text.includes('代码'))
124
+ assert.ok(!text.includes('**'), '不应残留 ** 标记')
125
+ assert.ok(!text.includes('`'), '不应残留反引号')
126
+ })
127
+
128
+ test('writer: 标题与列表渲染', () => {
129
+ const c = collect()
130
+ const w = createAssistantWriter({ write: c.write })
131
+ w.writeContent('## 标题文字\n- 列表项\n')
132
+ w.finish()
133
+ const text = c.text()
134
+ assert.ok(text.includes('标题文字'))
135
+ assert.ok(!text.includes('##'), '标题井号应被消费')
136
+ assert.ok(text.includes('- 列表项'))
137
+ })
138
+
139
+ test('writer: 代码围栏内容带槽线,围栏标记不原样显示', () => {
140
+ const c = collect()
141
+ const w = createAssistantWriter({ write: c.write })
142
+ w.writeContent('```js\nconst x = 1\n```\n围栏外\n')
143
+ w.finish()
144
+ const lines = c.lines()
145
+ assert.ok(lines.some(l => l.includes('│ const x = 1')), '代码行应有 │ 槽线')
146
+ assert.ok(!lines.some(l => l.trim() === '```js' || l.trim() === '```'), '裸围栏标记不应出现')
147
+ assert.ok(lines.some(l => l.includes('围栏外')))
148
+ })
149
+
150
+ test('writer: ** 标记跨 chunk 也能正确渲染(行缓冲)', () => {
151
+ const c = collect()
152
+ const w = createAssistantWriter({ write: c.write })
153
+ // ** 被流式分片切开:buf 里暂不渲染,等行尾到了一次性渲染
154
+ w.writeContent('这是 *')
155
+ w.writeContent('*加粗** 文本\n')
156
+ w.finish()
157
+ const text = c.text()
158
+ assert.ok(text.includes('加粗'))
159
+ assert.ok(!text.includes('**'))
160
+ })
161
+
162
+ test('writer: 思考→正文切换插入换行分隔', () => {
163
+ const c = collect()
164
+ const w = createAssistantWriter({ thinkingHeader: '✻ 思考', write: c.write })
165
+ w.writeThinking('想了一下')
166
+ w.writeContent('正文\n')
167
+ w.finish()
168
+ const text = c.text()
169
+ assert.ok(/想了一下\n/.test(text), '思考与正文之间应有换行')
170
+ })
171
+
172
+ test('writer: 完全无输出时 finish 不产生空行', () => {
173
+ const c = collect()
174
+ const w = createAssistantWriter({ write: c.write })
175
+ w.finish()
176
+ assert.equal(c.text(), '')
177
+ })
178
+
179
+ test('writer: 头部空白行被吞掉,连续空白行合并(真实 MiniMax 输出形态)', () => {
180
+ const c = collect()
181
+ const w = createAssistantWriter({ write: c.write })
182
+ // 模拟模型先吐几个空行再出正文的场景(实测 minimax-m2.7 的流式输出)
183
+ w.writeContent('\n\n\n')
184
+ w.writeContent('答案\n\n\n\n下一段\n')
185
+ w.finish()
186
+ const lines = c.lines()
187
+ assert.equal(lines[0], '⏺ 答案', `首行应为 ⏺ 答案,实际: ${lines[0]}`)
188
+ // "答案"与"下一段"之间最多一个空行,且空行不带缩进
189
+ const midBlank = lines.slice(1, lines.indexOf(' 下一段')).filter(l => l === '').length
190
+ assert.ok(midBlank <= 1, `空行应被合并,实际行: ${JSON.stringify(lines)}`)
191
+ assert.ok(lines.includes(' 下一段'))
192
+ })
193
+
194
+ // ── 工具块 ──
195
+ test('printToolHeader: ⏺ + 名称 + 摘要', () => {
196
+ const c = collect()
197
+ printToolHeader('run_command', '$ npm test', c.write)
198
+ const text = c.text()
199
+ assert.ok(text.includes('⏺ run_command'))
200
+ assert.ok(text.includes('$ npm test'))
201
+ })
202
+
203
+ test('printToolResult: 首行 └─,后续行 │ 槽线', () => {
204
+ const c = collect()
205
+ printToolResult('$ ls\n(exit 0)\nfile1\nfile2', c.write)
206
+ const lines = c.lines()
207
+ assert.ok(lines[0].startsWith(' └─ '), `首行应为 └─ 槽线,实际: ${lines[0]}`)
208
+ assert.ok(lines.slice(1).every(l => l.startsWith(' │ ') || l === ''), '后续行应为 │ 槽线')
209
+ })
210
+
211
+ test('printToolResult: 超长结果被截断并含省略标记', () => {
212
+ const c = collect()
213
+ const long = Array.from({ length: 60 }, (_, i) => `line${i} ` + 'y'.repeat(40)).join('\n')
214
+ printToolResult(long, c.write)
215
+ assert.match(c.text(), /回显省略/)
216
+ })
217
+
218
+ // ── 斜杠命令即时提示 ──
219
+
220
+ test('filterSlashCommands: 单个 / 返回全部命令', () => {
221
+ const got = filterSlashCommands('/', 'zh').map(m => m.cmd)
222
+ assert.deepEqual(got, SLASH_COMMANDS.map(c => c.cmd))
223
+ })
224
+
225
+ test('filterSlashCommands: 前缀过滤,大小写不敏感', () => {
226
+ assert.deepEqual(filterSlashCommands('/m', 'zh').map(m => m.cmd), ['/model'])
227
+ assert.deepEqual(filterSlashCommands('/AD', 'zh').map(m => m.cmd), ['/addmodel'])
228
+ assert.deepEqual(filterSlashCommands('/exit', 'zh').map(m => m.cmd), ['/exit'])
229
+ })
230
+
231
+ test('filterSlashCommands: 已输入空格(进入参数)时不再提示', () => {
232
+ assert.deepEqual(filterSlashCommands('/model 2', 'zh'), [])
233
+ assert.deepEqual(filterSlashCommands('/cd ..', 'zh'), [])
234
+ })
235
+
236
+ test('filterSlashCommands: 非 slash 输入 / 无匹配返回空', () => {
237
+ assert.deepEqual(filterSlashCommands('hello', 'zh'), [])
238
+ assert.deepEqual(filterSlashCommands('', 'zh'), [])
239
+ assert.deepEqual(filterSlashCommands('/xyz', 'zh'), [])
240
+ })
241
+
242
+ test('filterSlashCommands: locale 决定说明语言', () => {
243
+ const zh = filterSlashCommands('/help', 'zh')[0]
244
+ const en = filterSlashCommands('/help', 'en')[0]
245
+ assert.equal(zh.desc, '显示帮助')
246
+ assert.equal(en.desc, 'Show help')
247
+ })
248
+
249
+ test('renderSlashHintBody: 每行含命令名与说明,空输入返回空串', () => {
250
+ assert.equal(renderSlashHintBody([]), '')
251
+ const body = stripAnsi(renderSlashHintBody(filterSlashCommands('/m', 'en')))
252
+ assert.match(body, /\/model\s+List \/ switch models/)
253
+ })
@@ -27,8 +27,10 @@
27
27
  import { exec } from 'node:child_process'
28
28
  import { promises as fs } from 'node:fs'
29
29
  import path from 'node:path'
30
+ import iconv from 'iconv-lite'
30
31
  import { trackChild } from '../cleanup.js'
31
32
  import { checkDangerousCommand } from './safety.js'
33
+ import { guardCommand } from './platformGuard.js'
32
34
 
33
35
  // ──────────────────────────────────────────────
34
36
  // 常量
@@ -145,12 +147,12 @@ export const TOOL_DEFINITIONS = [
145
147
  type: 'function',
146
148
  function: {
147
149
  name: 'search_text',
148
- description: '在目录内所有文本文件中搜索正则匹配的行,返回 文件:行号: 内容。用于定位符号、关键字、调用点。',
150
+ description: '搜索正则匹配的行,返回 文件:行号: 内容。path 可以是目录(递归搜全部文本文件)或单个文件。用于定位符号、关键字、调用点。',
149
151
  parameters: {
150
152
  type: 'object',
151
153
  properties: {
152
154
  pattern: { type: 'string', description: '正则表达式(JavaScript 语法)' },
153
- path: { type: 'string', description: '搜索目录,默认智能体启动目录' },
155
+ path: { type: 'string', description: '搜索目录或单个文件路径,默认智能体启动目录' },
154
156
  ext: { type: 'string', description: '限定扩展名过滤,如 "js" 或 "js,ts,vue";不传则搜全部文本文件' },
155
157
  ignore_case: { type: 'boolean', description: '忽略大小写,默认 false' },
156
158
  },
@@ -179,15 +181,29 @@ function truncateMiddle(text, budget) {
179
181
  return `${text.slice(0, head)}\n\n... [中间省略 ${omitted} 字符] ...\n\n${text.slice(text.length - tail)}`
180
182
  }
181
183
 
184
+ // 子进程输出解码:Windows cmd/PowerShell 的本地化消息(错误提示等)是
185
+ // GBK(CP936) 字节流,直接按 UTF-8 解会变成 '����' 乱码(实测踩坑)。
186
+ // 策略:先严格 UTF-8 解码(node/git 等现代工具的输出都能过),
187
+ // 失败(含非法字节序列,GBK 中文几乎必挂)再按 GBK 兜底。
188
+ function decodeOutput(buf) {
189
+ if (!buf || buf.length === 0) return ''
190
+ try {
191
+ return new TextDecoder('utf-8', { fatal: true }).decode(buf)
192
+ } catch {
193
+ return iconv.decode(buf, 'gbk')
194
+ }
195
+ }
196
+
182
197
  // 包装 exec 为 Promise,stdout/stderr 合并返回;出错(非零退出)也正常返回输出
198
+ // encoding:'buffer' 拿原始字节,由 decodeOutput 决定真实编码
183
199
  function execCommand(command, options) {
184
200
  return new Promise((resolve) => {
185
- const child = exec(command, options, (err, stdout, stderr) => {
201
+ const child = exec(command, { ...options, encoding: 'buffer' }, (err, stdout, stderr) => {
186
202
  resolve({
187
203
  code: err ? (typeof err.code === 'number' ? err.code : 1) : 0,
188
204
  killed: !!err?.killed,
189
- stdout: stdout || '',
190
- stderr: stderr || '',
205
+ stdout: decodeOutput(stdout),
206
+ stderr: decodeOutput(stderr),
191
207
  errorMessage: err && !('code' in err) ? err.message : null,
192
208
  })
193
209
  })
@@ -211,6 +227,13 @@ async function toolRunCommand(args, ctx) {
211
227
  return `已拒绝执行(安全守卫): ${danger.reason}\n命令: ${command}\n如果你确认需要类似效果,请换一种不破坏系统的方式,或明确告知用户需要他手动执行。`
212
228
  }
213
229
 
230
+ // 平台兼容性守卫 — 拦截"在当前 shell 里注定失败"的命令
231
+ // (如 Windows cmd 上跑 tail/grep/ls 等 Unix-only 命令)
232
+ const platform = await guardCommand(command)
233
+ if (platform.blocked) {
234
+ return `已拒绝执行(平台守卫): ${platform.reason}`
235
+ }
236
+
214
237
  const cwd = resolvePath(ctx, args.cwd)
215
238
  let timeoutSec = Number(args.timeout_seconds) || CMD_TIMEOUT_DEFAULT
216
239
  timeoutSec = Math.max(1, Math.min(CMD_TIMEOUT_MAX, timeoutSec))
@@ -377,6 +400,21 @@ async function toolSearchText(args, ctx) {
377
400
  let scanned = 0
378
401
  let overflow = false
379
402
 
403
+ // 在单个文件内匹配各行(path 直接指向文件时走这个分支 ——
404
+ // 模型经常拿文件路径来搜,兼容掉,别让它吃"不是目录"的错)
405
+ async function searchOneFile(full) {
406
+ let text
407
+ try { text = await fs.readFile(full, 'utf-8') } catch { return }
408
+ scanned++
409
+ const lines = text.split('\n')
410
+ for (let i = 0; i < lines.length && hits.length < MAX_SEARCH_HITS; i++) {
411
+ regex.lastIndex = 0
412
+ if (regex.test(lines[i])) {
413
+ hits.push(`${path.relative(ctx.cwd, full) || full}:${i + 1}: ${lines[i].trim().slice(0, 300)}`)
414
+ }
415
+ }
416
+ }
417
+
380
418
  async function walk(current, depth) {
381
419
  if (hits.length >= MAX_SEARCH_HITS || depth > 12) { if (depth > 12) return; overflow = true; return }
382
420
  let items
@@ -394,25 +432,23 @@ async function toolSearchText(args, ctx) {
394
432
  let st
395
433
  try { st = await fs.stat(full) } catch { continue }
396
434
  if (st.size > MAX_SEARCH_FILE_SIZE) continue
397
- let text
398
- try { text = await fs.readFile(full, 'utf-8') } catch { continue }
399
- scanned++
400
- const lines = text.split('\n')
401
- for (let i = 0; i < lines.length && hits.length < MAX_SEARCH_HITS; i++) {
402
- regex.lastIndex = 0
403
- if (regex.test(lines[i])) {
404
- hits.push(`${path.relative(ctx.cwd, full) || full}:${i + 1}: ${lines[i].trim().slice(0, 300)}`)
405
- }
406
- }
435
+ await searchOneFile(full)
407
436
  }
408
437
  }
409
438
  }
410
439
 
440
+ let st
411
441
  try {
412
- const st = await fs.stat(dir)
413
- if (!st.isDirectory()) return `错误: 不是目录: ${dir}`
442
+ st = await fs.stat(dir)
414
443
  } catch {
415
- return `错误: 目录不存在: ${dir}`
444
+ return `错误: 路径不存在: ${dir}`
445
+ }
446
+ // 文件:直接搜它自己(忽略 ext 过滤 — 用户/模型明确指名了这个文件)
447
+ if (!st.isDirectory()) {
448
+ if (st.size > MAX_SEARCH_FILE_SIZE) return `错误: 文件过大(${(st.size / 1024 / 1024).toFixed(1)}MB),请用 read_file 分段查看`
449
+ await searchOneFile(dir)
450
+ if (hits.length === 0) return `未找到匹配 "${args.pattern}" 的内容(已扫描 1 个文件)`
451
+ return hits.join('\n')
416
452
  }
417
453
 
418
454
  await walk(dir, 1)
@@ -160,3 +160,31 @@ test('未知工具返回错误字符串并列出可用工具', async () => {
160
160
  assert.match(r, /未知工具/)
161
161
  assert.match(r, /run_command/)
162
162
  })
163
+
164
+ // ========== search_text: 文件路径直接搜索 ==========
165
+
166
+ test('search_text 支持 path 直接指向单个文件', async () => {
167
+ const r = await executeTool('search_text', { pattern: 'LINE-2', path: 'src/deep/hello.txt' }, ctx)
168
+ assert.match(r, /hello\.txt:2:/)
169
+ const miss = await executeTool('search_text', { pattern: 'not-in-file-xyz', path: 'src/deep/hello.txt' }, ctx)
170
+ assert.match(miss, /未找到/)
171
+ })
172
+
173
+ // ========== run_command: 输出编码(GBK 乱码回归) ==========
174
+
175
+ test('run_command UTF-8 中文输出不乱码', async () => {
176
+ // node 自身 stdout 是 UTF-8,跨平台一致
177
+ const r = await executeTool('run_command', { command: 'node -e "console.log(\'中文输出测试\')"' }, ctx)
178
+ assert.match(r, /中文输出测试/)
179
+ })
180
+
181
+ test('run_command 本地化错误消息解码正确(Windows GBK 回归)', async () => {
182
+ const r = await executeTool('run_command', { command: 'nonexistent-cmd-xyz-123' }, ctx)
183
+ if (process.platform === 'win32') {
184
+ // cmd.exe 的"不是内部或外部命令"是 GBK 字节流,修复前会显示为 '����' 乱码
185
+ assert.match(r, /不是内部或外部命令/)
186
+ assert.doesNotMatch(r, /�/)
187
+ } else {
188
+ assert.match(r, /not found|未找到/)
189
+ }
190
+ })
@@ -0,0 +1,2 @@
1
+ import"./rolldown-runtime-DAXXjFlN.js";import{B as e,Cn as t,D as n,Et as r,Fn as i,G as a,Jn as o,Ln as s,Mn as c,Sn as l,Tn as u,Xn as d,Y as f,_n as p,_r as m,a as h,gn as g,hn as _,i as v,mn as y,mr as b,nt as x,rt as S,sr as C,vn as w,zn as T}from"./element-plus-DNnePjco.js";import{n as E,t as D}from"./_plugin-vue_export-helper-BQRl7WAH.js";async function O(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 k(){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 A(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 O(t,t=>{try{e(JSON.parse(t))}catch{}})}async function j(){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())};O(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(()=>{})})}var M=u({__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 i=e,a=n,s=C(null);o(()=>i.logs,async()=>{await c(),s.value&&(s.value.scrollTop=s.value.scrollHeight)});function l(){i.status!==`running`&&(i.status===`success`&&i.countdown>0&&a(`cancel`),a(`update:modelValue`,!1))}function u(){a(`retry`)}function d(){a(`restart`)}function f(){a(`cancel`),a(`update:modelValue`,!1)}let p={props:i,emit:a,logEl:s,close:l,onRetry:u,onRestart:d,onLater:f,get Loading(){return r},get CircleCheck(){return x},get CircleClose(){return S},get $t(){return E}};return Object.defineProperty(p,"__isScriptSetup",{enumerable:!1,value:!0}),p}}),N={key:3},P={key:4},F={key:5},I={key:0,class:`upgrade-hint`},L={key:0,class:`upgrade-countdown`};function R(r,i,a,o,s,c){let u=f,h=e,v=n;return T(),g(v,{"model-value":a.modelValue,"onUpdate:modelValue":i[0]||=e=>o.emit(`update:modelValue`,e),title:o.$t(`@F13B4:升级`),width:`680px`,"close-on-click-modal":!1,"close-on-press-escape":!1,"show-close":a.status!==`running`,"append-to-body":``,onClose:o.close},{footer:d(()=>[a.status===`failed`?(T(),g(h,{key:0,type:`primary`,onClick:o.onRetry},{default:d(()=>[l(m(o.$t(`@F13B4:重试`)),1)]),_:1})):p(`v-if`,!0),a.status===`success`?(T(),g(h,{key:1,type:`primary`,onClick:o.onRestart},{default:d(()=>[l(m(o.$t(`@F13B4:立即重启并刷新`)),1)]),_:1})):p(`v-if`,!0),a.status===`success`?(T(),g(h,{key:2,onClick:o.onLater},{default:d(()=>[l(m(o.$t(`@F13B4:稍后手动重启`)),1)]),_:1})):p(`v-if`,!0),a.status===`running`?(T(),g(h,{key:3,disabled:``},{default:d(()=>[l(m(o.$t(`@F13B4:升级中`))+`... `,1)]),_:1})):p(`v-if`,!0)]),default:d(()=>[_(`div`,{class:b([`upgrade-status`,`is-${a.status}`])},[a.status===`running`?(T(),g(u,{key:0,class:`is-loading`},{default:d(()=>[t(o.Loading)]),_:1})):a.status===`success`?(T(),g(u,{key:1,color:`#67c23a`},{default:d(()=>[t(o.CircleCheck)]),_:1})):(T(),g(u,{key:2,color:`#f56c6c`},{default:d(()=>[t(o.CircleClose)]),_:1})),a.status===`running`?(T(),w(`span`,N,m(o.$t(`@F13B4:升级中`)),1)):a.status===`success`?(T(),w(`span`,P,m(o.$t(`@F13B4:升级完成`)),1)):(T(),w(`span`,F,m(o.$t(`@F13B4:升级失败`)),1))],2),a.status===`success`?(T(),w(`p`,I,[l(m(o.$t(`@F13B4:新版本已全局安装,需要重启服务才能生效`))+` `,1),a.countdown>0?(T(),w(`span`,L,m(o.$t(`@F13B4:{seconds} 秒后自动刷新`,{seconds:a.countdown})),1)):p(`v-if`,!0)])):p(`v-if`,!0),_(`pre`,{ref:`logEl`,class:`upgrade-log`},m(a.logs||o.$t(`@F13B4:等待日志输出`)),513)]),_:1},8,[`model-value`,`title`,`show-close`])}var z=D(M,[[`render`,R],[`__scopeId`,`data-v-bc566bba`],[`__file`,`E:/workspace/github_workspace/zen-git/src/ui/client/src/components/UpgradeDialog.vue`]]),B=`https://github.com/xz333221/zen-gitsync/releases`,V=5,H=`app-version-check`,U=3600*1e3,W=u({__name:`AppVersionBadge`,setup(e,{expose:t}){t();let n=`2.16.2`,r=C(n),a=y(()=>r.value||n),o=C(null),c=C(!1),l=C(``),u=C(`running`),d=C(0),f=null,p=C(!1);function m(){try{let e=sessionStorage.getItem(H);if(!e)return null;let{at:t,data:n}=JSON.parse(e);return Date.now()-t>U?null:n}catch{return null}}function g(e){try{sessionStorage.setItem(H,JSON.stringify({at:Date.now(),data:e}))}catch{}}async function _(e=!1){let t=m();t&&(o.value=t);try{let t=await k();o.value=t,g(t),t.current&&(r.value=t.current),!e&&t.success&&t.hasUpdate&&h({type:`success`,message:E(`@F13B4:发现新版本 {version}`,{version:t.latest??``}),duration:4e3})}catch(t){e||h({type:`warning`,message:E(`@F13B4:检查更新失败 {error}`,{error:t?.message||``})})}}async function b(){if(o.value?.hasUpdate){try{await v.confirm(E(`@F13B4:确定要从 {current} 升级到 {latest} 吗?`,{current:o.value.current,latest:o.value.latest}),E(`@F13B4:升级`),{confirmButtonText:E(`@F13B4:确定升级`),cancelButtonText:E(`@F13B4:取消`),type:`info`})}catch{return}l.value=``,u.value=`running`,c.value=!0,await A(e=>{if(e.type===`done`){if(u.value=e.code===0?`success`:`failed`,e.code===0){try{sessionStorage.removeItem(H)}catch{}o.value?.latest&&(r.value=o.value.latest),x()}}else e.message&&(l.value+=e.message)})}}function x(){S(),d.value=V,f=setInterval(()=>{--d.value,d.value<=0&&(f&&=(clearInterval(f),null),T())},1e3)}function S(){f&&=(clearInterval(f),null),d.value=0}function w(){S()}async function T(){if(S(),!p.value){p.value=!0;try{let e=await j(),{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];h({type:`warning`,message:n?E(n):E(`@F13B4:重启失败 {error}`,{error:t})}),p.value=!1}}}i(()=>{S()}),s(()=>{_(!0)});let D={buildVersion:n,releasesUrl:B,runtimeVersion:r,displayVersion:a,latestInfo:o,upgradeDialogVisible:c,upgradeLogs:l,upgradeStatus:u,AUTO_REFRESH_SECONDS:V,countdown:d,get countdownTimer(){return f},set countdownTimer(e){f=e},restartInFlight:p,CACHE_KEY:H,CACHE_TTL_MS:U,readCache:m,writeCache:g,checkUpdate:_,handleUpgrade:b,startAutoRefreshCountdown:x,cancelAutoRefreshCountdown:S,handleCancelAutoRefresh:w,handleRestart:T,get $t(){return E},UpgradeDialog:z};return Object.defineProperty(D,"__isScriptSetup",{enumerable:!1,value:!0}),D}}),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`};function Z(e,n,r,i,o,s){let c=a;return T(),w(`div`,G,[p(` GitHub 链接 `),t(c,{content:i.$t(`@F13B4:在 GitHub 上查看源码`),placement:`top`,effect:`dark`,"show-after":300},{default:d(()=>[_(`a`,{href:`https://github.com/xz333221/zen-gitsync`,target:`_blank`,rel:`noopener noreferrer`,class:`badge-icon-link`,"aria-label":i.$t(`@F13B4:在 GitHub 上查看源码`)},[...n[1]||=[_(`svg`,{viewBox:`0 0 24 24`,width:`15`,height:`15`,"aria-hidden":`true`,fill:`currentColor`,xmlns:`http://www.w3.org/2000/svg`},[_(`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`]),p(` NPM 链接 `),t(c,{content:i.$t(`@F13B4:在 npm 上查看包`),placement:`top`,effect:`dark`,"show-after":300},{default: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":i.$t(`@F13B4:在 npm 上查看包`)},[...n[2]||=[_(`svg`,{viewBox:`0 0 24 24`,width:`15`,height:`15`,"aria-hidden":`true`,fill:`currentColor`,xmlns:`http://www.w3.org/2000/svg`},[_(`path`,{d:`M2 2v20h20V2H2zm17.167 17.167H12.5v-10h-3.333v10H4.833V4.833h14.334v14.334z`})],-1)]],8,q)]),_:1},8,[`content`]),p(` 版本号 `),t(c,{content:i.$t(`@F13B4:查看更新`),placement:`top`,effect:`dark`,"show-after":300},{default:d(()=>[_(`a`,{href:i.releasesUrl,target:`_blank`,rel:`noopener noreferrer`,class:`version-link`},`v`+m(i.displayVersion),1)]),_:1},8,[`content`]),i.latestInfo?.hasUpdate?(T(),w(`button`,{key:0,type:`button`,class:`upgrade-btn`,title:i.$t(`@F13B4:发现新版本 {version},点击升级`,{version:i.latestInfo.latest??``}),"aria-label":i.$t(`@F13B4:发现新版本 {version},点击升级`,{version:i.latestInfo.latest??``}),onClick:i.handleUpgrade},[n[3]||=_(`span`,{class:`upgrade-btn__dot`,"aria-hidden":`true`},null,-1),_(`span`,Y,m(i.$t(`@F13B4:升级`)),1),_(`span`,X,`v`+m(i.latestInfo.latest),1)],8,J)):p(`v-if`,!0),t(i.UpgradeDialog,{modelValue:i.upgradeDialogVisible,"onUpdate:modelValue":n[0]||=e=>i.upgradeDialogVisible=e,logs:i.upgradeLogs,status:i.upgradeStatus,countdown:i.countdown,onRetry:i.handleUpgrade,onRestart:i.handleRestart,onCancel:i.handleCancelAutoRefresh},null,8,[`modelValue`,`logs`,`status`,`countdown`])])}var Q=D(W,[[`render`,Z],[`__scopeId`,`data-v-7e0300ec`],[`__file`,`E:/workspace/github_workspace/zen-git/src/ui/client/src/components/AppVersionBadge.vue`]]);export{Q as default};
@@ -0,0 +1 @@
1
+ .upgrade-status[data-v-bc566bba]{align-items:center;gap:var(--spacing-base);margin-bottom:var(--spacing-md);font-size:14px;font-weight:500;display:flex}.upgrade-status.is-running[data-v-bc566bba]{color:var(--el-color-primary)}.upgrade-status.is-success[data-v-bc566bba]{color:var(--el-color-success)}.upgrade-status.is-failed[data-v-bc566bba]{color:var(--el-color-danger)}.upgrade-status .is-loading[data-v-bc566bba]{animation:2s linear infinite rotating-bc566bba}.upgrade-log[data-v-bc566bba]{color:#d4d4d4;white-space:pre-wrap;word-break:break-all;background:#1e1e1e;border-radius:4px;height:320px;margin:0;padding:12px;font-family:JetBrains Mono,Cascadia Code,Consolas,monospace;font-size:12px;line-height:1.6;overflow:auto}.upgrade-hint[data-v-bc566bba]{margin:0 0 var(--spacing-md) 0;border-left:3px solid var(--el-color-success);color:var(--el-color-success);background:#67c23a14;border-radius:4px;padding:8px 12px;font-size:13px}.upgrade-countdown[data-v-bc566bba]{background:var(--el-color-success);color:#fff;border-radius:10px;margin-left:8px;padding:1px 8px;font-size:12px;font-weight:600;animation:1s ease-in-out infinite pulse-bc566bba;display:inline-block}@keyframes pulse-bc566bba{0%,to{opacity:1}50%{opacity:.55}}@keyframes rotating-bc566bba{0%{transform:rotate(0)}to{transform:rotate(360deg)}}.app-version-badge[data-v-7e0300ec]{align-items:center;gap:var(--spacing-base);margin-left:auto;display:inline-flex}.badge-icon-link[data-v-7e0300ec]{width:22px;height:22px;color:var(--text-tertiary);opacity:.7;border-radius:4px;justify-content:center;align-items:center;text-decoration:none;transition:color .18s,opacity .18s,background .18s;display:inline-flex}.badge-icon-link[data-v-7e0300ec]:hover{opacity:1;color:var(--text-secondary);background:var(--bg-hover,#0000000f)}.badge-icon-link--npm[data-v-7e0300ec]:hover{color:var(--color-npm)}.badge-icon-link[data-v-7e0300ec]:first-child:hover{color:var(--text-primary)}.version-link[data-v-7e0300ec]{color:var(--text-secondary);border-radius:3px;padding:2px 6px;font-family:monospace;font-size:11px;text-decoration:none;transition:color .2s,background-color .2s}.version-link[data-v-7e0300ec]:hover{color:var(--color-primary);background-color:#0000000a}.upgrade-btn[data-v-7e0300ec]{appearance:none;cursor:pointer;color:#fff;background:linear-gradient(135deg, var(--color-primary) 0%, #3a8ee6 100%);height:22px;box-shadow:var(--shadow-sm);transition:transform var(--transition-fast) var(--ease-custom), box-shadow var(--transition-fast) var(--ease-custom), filter var(--transition-fast) var(--ease-custom);white-space:nowrap;-webkit-user-select:none;user-select:none;border:0;border-radius:11px;outline:none;align-items:center;gap:5px;margin:0;padding:0 10px 0 8px;font-family:inherit;font-size:11px;font-weight:600;line-height:1;display:inline-flex}.upgrade-btn[data-v-7e0300ec]:hover{filter:brightness(1.08);box-shadow:var(--shadow-md);transform:translateY(-1px)}.upgrade-btn[data-v-7e0300ec]:active{filter:brightness(.96);box-shadow:var(--shadow-sm);transform:translateY(0)}.upgrade-btn[data-v-7e0300ec]:focus-visible{outline:2px solid var(--color-primary);outline-offset:2px}.upgrade-btn__dot[data-v-7e0300ec]{background:#fff;border-radius:50%;flex-shrink:0;width:6px;height:6px;animation:1.6s ease-in-out infinite upgrade-pulse-7e0300ec;box-shadow:0 0 0 2px #ffffff59}.upgrade-btn__label[data-v-7e0300ec]{letter-spacing:.5px}.upgrade-btn__version[data-v-7e0300ec]{background:#ffffff38;border-radius:6px;padding:1px 5px;font-family:monospace;font-size:10px;font-weight:500;line-height:1.2}@keyframes upgrade-pulse-7e0300ec{0%,to{opacity:1;transform:scale(1)}50%{opacity:.55;transform:scale(.8)}}
@@ -0,0 +1 @@
1
+ .branch-wrapper[data-v-9548a76f]{align-items:center;gap:2px;display:inline-flex}.branch-btn[data-v-9548a76f]{cursor:pointer;height:22px;color:var(--color-text);font-family:var(--font-mono,monospace);white-space:nowrap;background:0 0;border:none;border-radius:3px;align-items:center;gap:4px;padding:0 6px;font-size:12px;transition:background .15s;display:inline-flex}.branch-btn[data-v-9548a76f]:hover{background:var(--bg-hover,#ffffff14)}.branch-btn[data-v-9548a76f]:active{background:var(--bg-active,#ffffff1f)}[data-v-9548a76f] .branch-btn-icon{flex-shrink:0;width:14px;height:14px}.branch-btn-name[data-v-9548a76f]{text-overflow:ellipsis;max-width:160px;overflow:hidden}.branch-btn-caret[data-v-9548a76f]{opacity:.6;align-items:center;font-size:10px;display:flex}.branch-popover-inner[data-v-9548a76f]{flex-direction:column;gap:0;display:flex}.branch-search[data-v-9548a76f]{padding-bottom:8px}.branch-list[data-v-9548a76f]{max-height:240px;margin:0 -16px;padding:0 4px;overflow-y:auto}.branch-list[data-v-9548a76f]::-webkit-scrollbar{width:4px}.branch-list[data-v-9548a76f]::-webkit-scrollbar-thumb{background:var(--border-color,#8080804d);border-radius:2px}.branch-item[data-v-9548a76f]{cursor:pointer;height:28px;color:var(--color-text);border-radius:3px;align-items:center;gap:6px;padding:0 12px;font-size:13px;transition:background .1s;display:flex}.branch-item[data-v-9548a76f]:hover{background:var(--bg-hover,#ffffff14)}.branch-item.is-active[data-v-9548a76f]{color:var(--color-primary)}.branch-item-check[data-v-9548a76f]{color:var(--color-primary);flex-shrink:0;font-size:12px}.branch-item-check-placeholder[data-v-9548a76f]{flex-shrink:0;width:12px;display:inline-block}[data-v-9548a76f] .branch-item-icon{opacity:.7;flex-shrink:0;width:13px;height:13px}.branch-item-name[data-v-9548a76f]{text-overflow:ellipsis;white-space:nowrap;flex:1;overflow:hidden}.branch-empty[data-v-9548a76f]{text-align:center;color:var(--color-text-secondary,#888);padding:12px;font-size:12px}.branch-popover-footer[data-v-9548a76f]{margin-top:4px;padding-top:6px}.branch-create-btn[data-v-9548a76f]{cursor:pointer;width:100%;height:28px;color:var(--color-text);text-align:left;background:0 0;border:none;border-radius:3px;align-items:center;gap:6px;padding:0 12px;font-size:13px;transition:background .1s;display:flex}.branch-create-btn[data-v-9548a76f]:hover{background:var(--bg-hover,#ffffff14)}.branch-info[data-v-9548a76f]{align-items:center;display:flex}.create-branch-content[data-v-9548a76f]{padding:var(--spacing-base) 0}.is-loading[data-v-9548a76f]{animation:2s linear infinite rotating-9548a76f}@keyframes rotating-9548a76f{0%{transform:rotate(0)}to{transform:rotate(360deg)}}.branch-popover{border-radius:4px!important;padding:12px 16px!important;box-shadow:0 4px 16px #0000002e!important}.create-branch-dialog .modern-select .el-select__wrapper{border-radius:var(--radius-lg);border:1px solid var(--border-input);box-shadow:var(--shadow-sm);padding:10px var(--spacing-md);background:var(--bg-container);transition:all .2s}.create-branch-dialog .modern-select .el-select__wrapper:hover{border-color:var(--color-gray-300);box-shadow:var(--shadow-md)}.create-branch-dialog .modern-select.is-focus .el-select__wrapper{border-color:#3498db;box-shadow:0 0 0 2px #3498db1a,0 2px 6px #00000014}
@@ -0,0 +1 @@
1
+ import"./rolldown-runtime-DAXXjFlN.js";import{Cn as e,K as t,L as n,Lt as r,Pt as i,R as a,Tn as o,Vn as s,W as c,Xn as l,Y as u,_n as d,_r as f,cn as p,gn as m,hn as h,kt as g,mn as _,mr as v,q as y,r as b,sr as x,tt as S,un as C,vn as w,zn as T}from"./element-plus-DNnePjco.js";import{n as E,t as D}from"./_plugin-vue_export-helper-BQRl7WAH.js";import{t as O}from"./SvgIcon-BqWp2WS8.js";import{n as k,t as A}from"./CommonDialog-BA4xg5l3.js";import{y as j}from"./index-Br2VZQBT.js";var M=o({__name:`BranchSelector`,emits:[`branchChanged`],setup(e,{expose:t,emit:n}){t();let a=j(),o=n,s=x(!1),c=x(``),l=_(()=>{let e=c.value.trim().toLowerCase();return e?a.allBranches.filter(t=>t.toLowerCase().includes(e)):a.allBranches});async function u(e){if(e===a.currentBranch){s.value=!1;return}s.value=!1,await a.changeBranch(e)&&(o(`branchChanged`),a.refreshLog())}let d=x(!1),f=x(``),p=x(``);function m(){s.value=!1,p.value=a.currentBranch,d.value=!0}async function h(){await a.createBranch(f.value,p.value)&&(d.value=!1,f.value=``,o(`branchChanged`))}let v=x(!1);async function y(){if(!v.value){v.value=!0;try{await Promise.all([a.getCurrentBranch(!0),a.getBranchStatus(!0),a.getAllBranches()]),o(`branchChanged`),a.refreshLog()}finally{v.value=!1}}}let b={gitStore:a,emit:o,branchPopoverVisible:s,branchFilter:c,filteredBranches:l,selectBranch:u,createBranchDialogVisible:d,newBranchName:f,selectedBaseBranch:p,openCreateBranchDialog:m,createNewBranch:h,isRefreshingBranch:v,refreshCurrentBranch:y,get $t(){return E},get Plus(){return i},get Menu(){return g},get Check(){return S},get Refresh(){return r},CommonDialog:A,IconButton:k,SvgIcon:O};return Object.defineProperty(b,"__isScriptSetup",{enumerable:!1,value:!0}),b}}),N={key:0,class:`branch-info`},P={class:`branch-wrapper`},F={class:`branch-btn`},I={class:`branch-btn-name`},L={class:`branch-btn-caret`},R={class:`branch-popover-inner`},z={class:`branch-search`},B={class:`branch-list`},V=[`onClick`],H={key:1,class:`branch-item-check-placeholder`},U={class:`branch-item-name`},W={key:0,class:`branch-empty`},G={class:`branch-popover-footer`},K={class:`create-branch-content`},q={class:`form-label`},J={class:`form-label`},Y={class:`dialog-footer`},X={class:`footer-actions`},Z=[`disabled`];function Q(r,i,o,g,_,x){let S=u,E=c,D=b,O=y,k=n,A=a,j=t;return g.gitStore.currentBranch?(T(),w(`div`,N,[h(`div`,P,[d(` VSCode 风格分支按钮 + 弹出列表 `),e(D,{visible:g.branchPopoverVisible,"onUpdate:visible":i[1]||=e=>g.branchPopoverVisible=e,placement:`top-start`,width:260,trigger:`click`,"popper-class":`branch-popover`,"show-arrow":!1,onShow:i[2]||=e=>g.branchFilter=``},{reference:l(()=>[h(`button`,F,[e(g.SvgIcon,{"icon-class":`git-branch`,"class-name":`branch-btn-icon`}),h(`span`,I,f(g.gitStore.currentBranch),1),h(`span`,L,[e(S,null,{default:l(()=>[...i[7]||=[h(`svg`,{viewBox:`0 0 1024 1024`,xmlns:`http://www.w3.org/2000/svg`},[h(`path`,{fill:`currentColor`,d:`M831.872 340.864 512 652.672 192.128 340.864a30.592 30.592 0 0 0-42.752 0 29.12 29.12 0 0 0 0 41.6L489.664 714.24a32 32 0 0 0 44.672 0l340.288-331.776a29.12 29.12 0 0 0 0-41.6 30.592 30.592 0 0 0-42.752 0z`})],-1)]]),_:1})])])]),default:l(()=>[h(`div`,R,[d(` 搜索框 `),h(`div`,z,[e(E,{modelValue:g.branchFilter,"onUpdate:modelValue":i[0]||=e=>g.branchFilter=e,placeholder:g.$t(`@F13B4:搜索或签出分支`),size:`small`,clearable:``,autofocus:``},null,8,[`modelValue`,`placeholder`])]),d(` 分支列表 `),h(`div`,B,[(T(!0),w(C,null,s(g.filteredBranches,t=>(T(),w(`div`,{key:t,class:v([`branch-item`,{"is-active":t===g.gitStore.currentBranch}]),onClick:e=>g.selectBranch(t)},[t===g.gitStore.currentBranch?(T(),m(S,{key:0,class:`branch-item-check`},{default:l(()=>[e(g.Check)]),_:1})):(T(),w(`span`,H)),e(g.SvgIcon,{"icon-class":`git-branch`,"class-name":`branch-item-icon`}),h(`span`,U,f(t),1)],10,V))),128)),g.filteredBranches.length===0?(T(),w(`div`,W,f(g.$t(`@F13B4:没有匹配的分支`)),1)):d(`v-if`,!0)]),d(` 底部:创建新分支 `),h(`div`,G,[h(`button`,{class:`branch-create-btn`,onClick:g.openCreateBranchDialog},[e(S,null,{default:l(()=>[e(g.Plus)]),_:1}),h(`span`,null,f(g.$t(`@F13B4:创建新分支`)),1)])])])]),_:1},8,[`visible`]),d(` 刷新按钮 `),e(g.IconButton,{tooltip:g.$t(`@CMDCON:刷新`),onClick:g.refreshCurrentBranch},{default:l(()=>[e(S,{class:v({"is-loading":g.isRefreshingBranch})},{default:l(()=>[e(g.Refresh)]),_:1},8,[`class`])]),_:1},8,[`tooltip`])]),d(` 创建分支对话框 `),e(g.CommonDialog,{modelValue:g.createBranchDialogVisible,"onUpdate:modelValue":i[6]||=e=>g.createBranchDialogVisible=e,title:g.$t(`@F13B4:创建新分支`),size:`small`,"destroy-on-close":!0,"append-to-body":!0,"custom-class":`create-branch-dialog`},{footer:l(()=>[h(`div`,Y,[h(`div`,X,[h(`button`,{type:`button`,class:`dialog-cancel-btn`,onClick:i[5]||=e=>g.createBranchDialogVisible=!1},f(g.$t(`@F13B4:取消`)),1),h(`button`,{type:`button`,class:`dialog-confirm-btn`,onClick:g.createNewBranch,disabled:g.gitStore.isCreatingBranch},[g.gitStore.isCreatingBranch?(T(),m(S,{key:1,class:`is-loading`},{default:l(()=>[...i[8]||=[h(`svg`,{viewBox:`0 0 1024 1024`,xmlns:`http://www.w3.org/2000/svg`},[h(`path`,{fill:`currentColor`,d:`M512 64a32 32 0 0 1 32 32v192a32 32 0 0 1-64 0V96a32 32 0 0 1 32-32zm0 640a32 32 0 0 1 32 32v192a32 32 0 1 1-64 0V736a32 32 0 0 1 32-32zm448-192a32 32 0 0 1-32 32H736a32 32 0 1 1 0-64h192a32 32 0 0 1 32 32zm-640 0a32 32 0 0 1-32 32H96a32 32 0 0 1 0-64h192a32 32 0 0 1 32 32z`})],-1)]]),_:1})):(T(),m(S,{key:0},{default:l(()=>[e(g.Check)]),_:1})),h(`span`,null,f(g.$t(`@F13B4:创建`)),1)],8,Z)])])]),default:l(()=>[h(`div`,K,[e(j,{model:{newBranchName:g.newBranchName,selectedBaseBranch:g.selectedBaseBranch},"label-position":`top`},{default:l(()=>[e(O,null,{label:l(()=>[h(`div`,q,[e(S,{class:`label-icon`},{default:l(()=>[e(g.Plus)]),_:1}),h(`span`,null,f(g.$t(`@F13B4:新分支名称`)),1)])]),default:l(()=>[e(E,{modelValue:g.newBranchName,"onUpdate:modelValue":i[3]||=e=>g.newBranchName=e,placeholder:g.$t(`@F13B4:请输入新分支名称`),class:`modern-input`,size:`large`,onKeyup:p(g.createNewBranch,[`enter`])},null,8,[`modelValue`,`placeholder`])]),_:1}),e(O,null,{label:l(()=>[h(`div`,J,[e(S,{class:`label-icon`},{default:l(()=>[e(g.Menu)]),_:1}),h(`span`,null,f(g.$t(`@F13B4:基于分支`)),1)])]),default:l(()=>[e(A,{modelValue:g.selectedBaseBranch,"onUpdate:modelValue":i[4]||=e=>g.selectedBaseBranch=e,placeholder:g.$t(`@F13B4:选择基础分支`),class:`modern-select`,size:`large`,style:{width:`100%`}},{default:l(()=>[(T(!0),w(C,null,s(g.gitStore.allBranches,e=>(T(),m(k,{key:e,label:e,value:e},null,8,[`label`,`value`]))),128))]),_:1},8,[`modelValue`,`placeholder`])]),_:1})]),_:1},8,[`model`])])]),_:1},8,[`modelValue`,`title`])])):d(`v-if`,!0)}var $=D(M,[[`render`,Q],[`__scopeId`,`data-v-9548a76f`],[`__file`,`E:/workspace/github_workspace/zen-git/src/ui/client/src/components/BranchSelector.vue`]]);export{$ as default};