zen-gitsync 2.15.33 → 2.16.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (93) hide show
  1. package/README.md +25 -0
  2. package/package.json +2 -1
  3. package/scripts/run-tests.cjs +1 -0
  4. package/src/cli/ai/agent.js +845 -0
  5. package/src/cli/ai/agent.test.js +114 -0
  6. package/src/cli/ai/images.js +211 -0
  7. package/src/cli/ai/images.test.js +98 -0
  8. package/src/cli/ai/safety.js +215 -0
  9. package/src/cli/ai/safety.test.js +158 -0
  10. package/src/cli/ai/streamFilter.js +82 -0
  11. package/src/cli/ai/streamFilter.test.js +88 -0
  12. package/src/cli/ai/termui.js +378 -0
  13. package/src/cli/ai/termui.test.js +215 -0
  14. package/src/cli/ai/tools.js +482 -0
  15. package/src/cli/ai/tools.test.js +190 -0
  16. package/src/gitCommit.js +9 -0
  17. package/src/ui/public/assets/AppVersionBadge-CS7Trvp-.css +1 -0
  18. package/src/ui/public/assets/AppVersionBadge-DJyfXR_Q.js +2 -0
  19. package/src/ui/public/assets/BranchSelector-B0eLkAdv.css +1 -0
  20. package/src/ui/public/assets/BranchSelector-ClnVPC0Q.js +1 -0
  21. package/src/ui/public/assets/CommitForm-BihM26_4.js +6 -0
  22. package/src/ui/public/assets/CommitForm-DdK8dymc.css +1 -0
  23. package/src/ui/public/assets/CommonDialog-CYoIF2fg.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-DYJQT-Xj.js +0 -0
  27. package/src/ui/public/assets/FlowExecutionViewer-CPblwVA1.js +1 -0
  28. package/src/ui/public/assets/FlowExecutionViewer-jNHI5zdm.css +1 -0
  29. package/src/ui/public/assets/FlowOrchestrationWorkspace-CYGp-PqS.css +1 -0
  30. package/src/ui/public/assets/FlowOrchestrationWorkspace-Cc-Tnhng.js +21 -0
  31. package/src/ui/public/assets/LogList-CeFFytjB.css +1 -0
  32. package/src/ui/public/assets/LogList-va6ANtbL.js +15 -0
  33. package/src/ui/public/assets/MindmapView-3rMfOoNm.css +1 -0
  34. package/src/ui/public/assets/MindmapView-C_9P5ijq.js +1 -0
  35. package/src/ui/public/assets/MonitorView-DRPECOVW.js +1 -0
  36. package/src/ui/public/assets/MonitorView-TaS4kL0Y.css +1 -0
  37. package/src/ui/public/assets/ProjectStartupButton-DSovbKPE.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-C_cBtHcN.js +5 -0
  41. package/src/ui/public/assets/SourceMapView-BNRjvgzH.css +1 -0
  42. package/src/ui/public/assets/SourceMapView-ZiCL8mS8.js +3 -0
  43. package/src/ui/public/assets/SvgIcon-C9YQSvGU.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-CR6o8zJG.js +1 -0
  47. package/src/ui/public/assets/WorkbenchView-CJJrBqBH.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-CzpRIaQR.js → _plugin-vue_export-helper-CHQqCylN.js} +1 -1
  50. package/src/ui/public/assets/{configStore-DaWjJLTW.js → configStore-Bw9U4AHx.js} +1 -1
  51. package/src/ui/public/assets/element-plus-CyGhXd7X.js +25 -0
  52. package/src/ui/public/assets/{flow-mindmap-2CcykUk_.js → flow-mindmap-CUvJaYn2.js} +1 -1
  53. package/src/ui/public/assets/index-BzNpkvm5.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-BSgn8Jx2.js +1 -0
  56. package/src/ui/public/assets/{vendor-CG4K5XlB.js → vendor-CU5NYUKJ.js} +44 -37
  57. package/src/ui/public/assets/vue-flow-CWmfpfPN.js +39 -0
  58. package/src/ui/public/index.html +12 -12
  59. package/src/utils/index.js +5 -0
  60. package/src/ui/public/assets/AppVersionBadge-Ckwa7eM9.js +0 -2
  61. package/src/ui/public/assets/AppVersionBadge-Dj6H729N.css +0 -1
  62. package/src/ui/public/assets/BranchSelector-T_rJm-JY.css +0 -1
  63. package/src/ui/public/assets/BranchSelector-e0465kxE.js +0 -1
  64. package/src/ui/public/assets/CommitForm-862NAe0q.js +0 -6
  65. package/src/ui/public/assets/CommitForm-CUvl0VcF.css +0 -1
  66. package/src/ui/public/assets/CommonDialog-BVPvUoQ0.js +0 -1
  67. package/src/ui/public/assets/CommonDialog-Cyh3xu0p.css +0 -1
  68. package/src/ui/public/assets/EditorView-CRYGVw_r.css +0 -1
  69. package/src/ui/public/assets/EditorView-sZoyx-nK.js +0 -0
  70. package/src/ui/public/assets/FlowExecutionViewer-B5kh7VxS.css +0 -1
  71. package/src/ui/public/assets/FlowExecutionViewer-CowPPIpm.js +0 -1
  72. package/src/ui/public/assets/FlowOrchestrationWorkspace-BAQDU9Jd.css +0 -1
  73. package/src/ui/public/assets/FlowOrchestrationWorkspace-Dno2fn7O.js +0 -21
  74. package/src/ui/public/assets/LogList-DJYpDUTZ.css +0 -1
  75. package/src/ui/public/assets/LogList-Dy42jDbI.js +0 -7
  76. package/src/ui/public/assets/MindmapView-CWoqfZRL.css +0 -1
  77. package/src/ui/public/assets/MindmapView-Ds2XDHgS.js +0 -1
  78. package/src/ui/public/assets/MonitorView-DxFogQIt.css +0 -1
  79. package/src/ui/public/assets/MonitorView-Dy158z7E.js +0 -1
  80. package/src/ui/public/assets/ProjectStartupButton-B7zvqRj_.css +0 -1
  81. package/src/ui/public/assets/ProjectStartupButton-eozWTyGl.js +0 -1
  82. package/src/ui/public/assets/RemoteRepoCard-CTfNrrx4.js +0 -1
  83. package/src/ui/public/assets/RemoteRepoCard-d0klonVb.css +0 -1
  84. package/src/ui/public/assets/SourceMapView-Bl5jEKf6.js +0 -3
  85. package/src/ui/public/assets/SourceMapView-DG_a_076.css +0 -1
  86. package/src/ui/public/assets/SvgIcon-D7FdXvWv.js +0 -1
  87. package/src/ui/public/assets/UserInputNode-Bcxe_5-C.js +0 -1
  88. package/src/ui/public/assets/UserInputNode-DDtvv9Iq.css +0 -1
  89. package/src/ui/public/assets/WorkbenchView-CZSPrO3Q.js +0 -12
  90. package/src/ui/public/assets/element-plus-d0668myq.js +0 -17
  91. package/src/ui/public/assets/index-CG24iOR5.js +0 -38
  92. package/src/ui/public/assets/socket-io-B1foIbJS.js +0 -1
  93. package/src/ui/public/assets/vue-flow-CVRzDji7.js +0 -39
@@ -0,0 +1,158 @@
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/safety.js 单元测试。
16
+ // 覆盖两大类:
17
+ // 1. 系统级毁灭命令必须拦截(rm -rf / · format · shutdown · dd · fork bomb 等)
18
+ // 2. 项目内正常操作必须放行(rm -rf node_modules · git clean · del /s build 等)
19
+ // —— 第 2 类和第 1 类同等重要,误伤正常操作会让智能体不可用。
20
+ import { test } from 'node:test'
21
+ import assert from 'node:assert/strict'
22
+ import { checkDangerousCommand } from './safety.js'
23
+
24
+ // ========== 必须拦截:递归删除根/系统目录 ==========
25
+
26
+ const BLOCKED_RM = [
27
+ 'rm -rf /',
28
+ 'rm -rf /*',
29
+ 'rm -fr /',
30
+ 'rm -r -f /',
31
+ 'rm --recursive --force /',
32
+ 'rm -rf ~',
33
+ 'rm -rf ~/',
34
+ 'rm -rf $HOME',
35
+ 'rm -rf ${HOME}',
36
+ 'rm -rf /etc',
37
+ 'rm -rf /usr',
38
+ 'rm -rf /boot',
39
+ 'rm -rf /System',
40
+ 'rm -rf /windows',
41
+ 'rm -rf /c',
42
+ 'rm -rf /c/',
43
+ 'rm -rf /d/',
44
+ 'rm -rf C:\\',
45
+ 'rm -rf C:/',
46
+ 'rm -rf C:',
47
+ 'sudo rm -rf /',
48
+ 'rm -rf / && echo done',
49
+ 'echo start; rm -rf ~',
50
+ 'rm -rf /usr/*',
51
+ ]
52
+
53
+ for (const cmd of BLOCKED_RM) {
54
+ test(`拦截递归删除: ${cmd}`, () => {
55
+ const r = checkDangerousCommand(cmd)
56
+ assert.equal(r.blocked, true, `应拦截: ${cmd}`)
57
+ assert.ok(r.reason, '应给出中文原因')
58
+ })
59
+ }
60
+
61
+ // ========== 必须拦截:Windows 删除/格式化/系统破坏 ==========
62
+
63
+ const BLOCKED_OTHERS = [
64
+ 'del /s /q C:\\',
65
+ 'del /s C:\\*.*',
66
+ 'rd /s /q C:\\',
67
+ 'rmdir /s C:\\Windows',
68
+ 'format C:',
69
+ 'format d: /q',
70
+ 'mkfs.ext4 /dev/sda1',
71
+ 'mkfs /dev/sdb',
72
+ 'diskpart',
73
+ 'bcdedit /set {bootmgr} displaybootmenu no',
74
+ 'bootrec /fixmbr',
75
+ 'dd if=/dev/zero of=/dev/sda',
76
+ 'dd of=/dev/nvme0n1 if=./img.iso',
77
+ 'echo x > /dev/sda',
78
+ 'cat /dev/urandom > /dev/sdb',
79
+ 'shutdown /s /t 0',
80
+ 'shutdown -h now',
81
+ 'reboot',
82
+ 'sudo poweroff',
83
+ 'halt',
84
+ 'init 0',
85
+ 'Stop-Computer',
86
+ 'Restart-Computer -Force',
87
+ ':(){ :|:& };:',
88
+ 'reg delete HKLM\\SOFTWARE\\test /f',
89
+ 'reg delete "HKEY_LOCAL_MACHINE\\SOFTWARE\\x"',
90
+ 'reg delete HKCR\\* /f',
91
+ 'chmod -R 777 /',
92
+ 'chmod -R 755 /etc',
93
+ 'chown -R user:user /',
94
+ 'Remove-Item C:\\ -Recurse -Force',
95
+ 'Remove-Item $env:SystemDrive -Recurse',
96
+ 'Remove-Item ~ -Recurse -Force',
97
+ 'shred /dev/sda',
98
+ 'wipefs -a /dev/sda',
99
+ ]
100
+
101
+ for (const cmd of BLOCKED_OTHERS) {
102
+ test(`拦截系统破坏: ${cmd}`, () => {
103
+ const r = checkDangerousCommand(cmd)
104
+ assert.equal(r.blocked, true, `应拦截: ${cmd}`)
105
+ assert.ok(r.reason, '应给出中文原因')
106
+ })
107
+ }
108
+
109
+ // ========== 必须放行:项目内正常操作(误伤=智能体残废) ==========
110
+
111
+ const ALLOWED = [
112
+ 'rm -rf node_modules',
113
+ 'rm -rf ./dist build',
114
+ 'rm -rf /tmp/my-scratch',
115
+ 'rm -rf /usr/local/bin/my-tool',
116
+ 'rm file.txt',
117
+ 'rm -f package-lock.json',
118
+ 'git clean -fdx',
119
+ 'git push --force-with-lease',
120
+ 'git reset --hard origin/main',
121
+ 'del /s /q build\\*.tmp',
122
+ 'rd /s /q dist',
123
+ 'rmdir /s /q coverage',
124
+ 'npm run shutdown',
125
+ 'echo shutdown now please',
126
+ 'npm run format',
127
+ 'prettier --write .',
128
+ 'Remove-Item ./dist -Recurse -Force',
129
+ 'Remove-Item .\\node_modules -Recurse -Force',
130
+ 'reg delete HKCU\\Software\\myapp /f',
131
+ 'chmod -R 755 ./src',
132
+ 'chown -R user:user ./project',
133
+ 'npm test',
134
+ 'dir C:\\',
135
+ 'type C:\\Windows\\system.ini',
136
+ 'ping 127.0.0.1',
137
+ 'node -e "console.log(1)"',
138
+ 'g --ai',
139
+ 'git add . && git commit -m "test" && git push',
140
+ ]
141
+
142
+ for (const cmd of ALLOWED) {
143
+ test(`放行正常操作: ${cmd}`, () => {
144
+ const r = checkDangerousCommand(cmd)
145
+ assert.equal(r.blocked, false, `不应拦截: ${cmd}(原因: ${r.reason})`)
146
+ assert.equal(r.reason, null)
147
+ })
148
+ }
149
+
150
+ // ========== 边界输入 ==========
151
+
152
+ test('非字符串/空输入不拦截也不报错', () => {
153
+ for (const bad of [null, undefined, 123, {}, [], '', ' ']) {
154
+ const r = checkDangerousCommand(bad)
155
+ assert.equal(r.blocked, false)
156
+ assert.equal(r.reason, null)
157
+ }
158
+ })
@@ -0,0 +1,82 @@
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
+ // MiniMax 系模型的 <think> 标签流式过滤器。
16
+ //
17
+ // 背景:MiniMax-M2/M3 等模型不走 reasoning_content 字段,而是把思考过程
18
+ // 以 <think>...</think> 标签**内联在 content 流**里推送,且标签本身会被
19
+ // SSE 分片切碎(如 '<thi' + 'nk>')。直接透传到终端会看到裸标签。
20
+ //
21
+ // 本模块提供一个增量状态机:feed() 接收任意分片的 content 字符串,
22
+ // 返回 [{content}|{thinking}] 段数组,调用方按类型分别渲染
23
+ // (content 正常显示,thinking 置灰)。标签本身被剥离,不进显示。
24
+ //
25
+ // 注意:这里只影响**终端显示**;写回对话历史的仍是模型原始 content
26
+ // (含标签),保证上下文对模型保真。
27
+
28
+ const OPEN = '<think>'
29
+ const CLOSE = '</think>'
30
+ // 保留尾部长度:开标签 7 字符 / 闭标签 8 字符,跨分片的半个标签不能提前吐出
31
+ const KEEP_OPEN = OPEN.length - 1 // 6
32
+ const KEEP_CLOSE = CLOSE.length - 1 // 7
33
+
34
+ /**
35
+ * 创建一个增量 think 标签过滤器。
36
+ * @returns {{ feed: (chunk: string) => Array<object>, flush: () => Array<object> }}
37
+ */
38
+ export function createThinkFilter() {
39
+ let mode = 'normal' // 'normal' | 'think'
40
+ let pending = ''
41
+
42
+ function feed(chunk) {
43
+ pending += String(chunk || '')
44
+ const out = []
45
+ while (pending.length > 0) {
46
+ const tag = mode === 'normal' ? OPEN : CLOSE
47
+ const keep = mode === 'normal' ? KEEP_OPEN : KEEP_CLOSE
48
+ const i = pending.indexOf(tag)
49
+ if (i === -1) {
50
+ // 没找到完整标签:吐出除"可能是半拉标签"的尾部以外的内容
51
+ const safe = pending.length - keep
52
+ if (safe > 0) {
53
+ out.push({ [mode === 'normal' ? 'content' : 'thinking']: pending.slice(0, safe) })
54
+ pending = pending.slice(safe)
55
+ }
56
+ break
57
+ }
58
+ if (i > 0) {
59
+ // 标签前有正文,先吐出
60
+ out.push({ [mode === 'normal' ? 'content' : 'thinking']: pending.slice(0, i) })
61
+ pending = pending.slice(i)
62
+ continue
63
+ }
64
+ // 命中完整标签:切换模式,吃掉标签
65
+ mode = mode === 'normal' ? 'think' : 'normal'
66
+ pending = pending.slice(tag.length)
67
+ }
68
+ return out
69
+ }
70
+
71
+ // 流结束时冲刷剩余 buffer(含未闭合的 think 块,按当前模式吐出)
72
+ function flush() {
73
+ if (!pending) return []
74
+ const seg = { [mode === 'normal' ? 'content' : 'thinking']: pending }
75
+ pending = ''
76
+ return [seg]
77
+ }
78
+
79
+ return { feed, flush }
80
+ }
81
+
82
+ export default { createThinkFilter }
@@ -0,0 +1,88 @@
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/streamFilter.js 单元测试。
16
+ // 重点覆盖 SSE 分片把 <think>/</think> 标签切碎的场景 —— 这正是
17
+ // MiniMax 模型裸标签问题的根源,整标签输入反而没啥可测的。
18
+ import { test } from 'node:test'
19
+ import assert from 'node:assert/strict'
20
+ import { createThinkFilter } from './streamFilter.js'
21
+
22
+ // 辅助:把一串分片喂给过滤器,返回渲染后的 {content, thinking} 全文
23
+ function runChunks(chunks) {
24
+ const f = createThinkFilter()
25
+ const segs = []
26
+ for (const c of chunks) segs.push(...f.feed(c))
27
+ segs.push(...f.flush())
28
+ return {
29
+ segs,
30
+ content: segs.map(s => s.content || '').join(''),
31
+ thinking: segs.map(s => s.thinking || '').join(''),
32
+ }
33
+ }
34
+
35
+ test('纯文本无标签原样通过', () => {
36
+ const r = runChunks(['你好,', '这是正文。'])
37
+ assert.equal(r.content, '你好,这是正文。')
38
+ assert.equal(r.thinking, '')
39
+ })
40
+
41
+ test('完整 think 块:标签剥离,内容归类 thinking', () => {
42
+ const r = runChunks(['<think>让我想想</think>答案是 42。'])
43
+ assert.equal(r.content, '答案是 42。')
44
+ assert.equal(r.thinking, '让我想想')
45
+ })
46
+
47
+ test('开标签被分片切碎(<thi + nk>)也能识别', () => {
48
+ const r = runChunks(['<thi', 'nk>思考中</thi', 'nk>正文'])
49
+ assert.equal(r.content, '正文')
50
+ assert.equal(r.thinking, '思考中')
51
+ })
52
+
53
+ test('闭标签跨 3 个分片也能识别', () => {
54
+ const r = runChunks(['<think>想</', 'th', 'ink>做完'])
55
+ assert.equal(r.content, '做完')
56
+ assert.equal(r.thinking, '想')
57
+ })
58
+
59
+ test('正文与 think 块交替多次', () => {
60
+ const r = runChunks(['先想<think>一</think>再说<think>二</think>完'])
61
+ assert.equal(r.content, '先想再说完')
62
+ assert.equal(r.thinking, '一二')
63
+ })
64
+
65
+ test('未闭合的 think 块在 flush 时按 thinking 吐出', () => {
66
+ const r = runChunks(['前置<think>想到一半被截断'])
67
+ assert.equal(r.content, '前置')
68
+ assert.equal(r.thinking, '想到一半被截断')
69
+ })
70
+
71
+ test('空 think 块不产生空段干扰', () => {
72
+ const r = runChunks(['<think></think>正文'])
73
+ assert.equal(r.content, '正文')
74
+ assert.equal(r.thinking, '')
75
+ })
76
+
77
+ test('逐字符投喂的极端分片', () => {
78
+ const full = '<think>深</think>答'
79
+ const r = runChunks([...full])
80
+ assert.equal(r.content, '答')
81
+ assert.equal(r.thinking, '深')
82
+ })
83
+
84
+ test('文末残留的半个开标签在 flush 时按正文吐出(不是标签)', () => {
85
+ const r = runChunks(['正常结尾<thi'])
86
+ assert.equal(r.content, '正常结尾<thi')
87
+ assert.equal(r.thinking, '')
88
+ })