zen-gitsync 2.16.42 → 2.16.43

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 (60) hide show
  1. package/package.json +1 -1
  2. package/src/ui/public/assets/AgentView-CVfH_WTL.js +4 -0
  3. package/src/ui/public/assets/AppVersionBadge-BKQZrA46.js +2 -0
  4. package/src/ui/public/assets/BranchSelector-BrmzYPb2.js +1 -0
  5. package/src/ui/public/assets/CommitForm-CSAwdqxy.js +6 -0
  6. package/src/ui/public/assets/CommonDialog-ChSKsEz2.css +1 -0
  7. package/src/ui/public/assets/CommonDialog-Clc3mo5h.js +1 -0
  8. package/src/ui/public/assets/EditorView-B05M4l8X.js +1 -0
  9. package/src/ui/public/assets/FlowExecutionViewer-Du0JPzV8.js +1 -0
  10. package/src/ui/public/assets/FlowOrchestrationWorkspace-Kg7HaggD.js +21 -0
  11. package/src/ui/public/assets/LogList-DSl8gjqq.js +7 -0
  12. package/src/ui/public/assets/MindmapView-KeS9uPEF.js +2 -0
  13. package/src/ui/public/assets/MonitorView-BYqPPmpL.js +1 -0
  14. package/src/ui/public/assets/ProjectStartupButton-COEXIVyh.js +1 -0
  15. package/src/ui/public/assets/{RemoteRepoCard-DFWs9d6t.js → RemoteRepoCard-Cqyksiy4.js} +2 -2
  16. package/src/ui/public/assets/SourceMapView-DhqMQTQD.js +3 -0
  17. package/src/ui/public/assets/SvgIcon-CDNsk4-Q.css +1 -0
  18. package/src/ui/public/assets/SvgIcon-DLr4V32f.js +1 -0
  19. package/src/ui/public/assets/UserInputNode-Do__vhj9.js +1 -0
  20. package/src/ui/public/assets/WorkbenchView-Dk5OlqZz.js +28 -0
  21. package/src/ui/public/assets/_plugin-vue_export-helper-CMAHJXhc.js +17 -0
  22. package/src/ui/public/assets/configStore-D666qKi0.js +1 -0
  23. package/src/ui/public/assets/element-plus-D3Dn7nX0.css +1 -0
  24. package/src/ui/public/assets/element-plus-DXuL5yWp.js +39 -0
  25. package/src/ui/public/assets/{flow-mindmap-BqudHiZw.js → flow-mindmap-Bi227PSm.js} +1 -1
  26. package/src/ui/public/assets/index-BT90zGWp.js +89 -0
  27. package/src/ui/public/assets/index-RKGE_YSG.css +1 -0
  28. package/src/ui/public/assets/{monaco-DPQ3OaiL.js → monaco-43aputPh.js} +2 -2
  29. package/src/ui/public/assets/{office-docx-DOvu6SBd.js → office-docx-ZOPof2Jf.js} +12 -12
  30. package/src/ui/public/assets/{office-excel-oizyPOWk.js → office-excel-DxreApmb.js} +2 -2
  31. package/src/ui/public/assets/{office-pptx-CCaqOF63.js → office-pptx-CR0bf9zY.js} +1 -1
  32. package/src/ui/public/assets/socket-io-BZIzo1US.js +1 -0
  33. package/src/ui/public/assets/toolsStore-DsvD8S_8.js +1 -0
  34. package/src/ui/public/assets/{ts.worker-METxwbDZ.js → ts.worker-Dth06zuC.js} +16 -1
  35. package/src/ui/public/assets/{vendor-CD2SBxQj.js → vendor-I3yQCqJK.js} +38 -38
  36. package/src/ui/public/assets/{vue-flow-BEmfD-aa.js → vue-flow-DV5FYL9P.js} +2 -2
  37. package/src/ui/public/index.html +16 -9
  38. package/src/ui/server/routes/fileOpen.js +248 -20
  39. package/src/ui/server/routes/fileOpen.test.js +239 -0
  40. package/src/ui/server/routes/workbench/agentSessionStore.js +173 -173
  41. package/src/ui/public/assets/AgentView-DjZTGHd_.js +0 -4
  42. package/src/ui/public/assets/AppVersionBadge-jSrdjNi3.js +0 -2
  43. package/src/ui/public/assets/BranchSelector-CNHhFgaH.js +0 -1
  44. package/src/ui/public/assets/CommitForm-B4rwEBIH.js +0 -6
  45. package/src/ui/public/assets/EditorView-D8Zdu0id.js +0 -1
  46. package/src/ui/public/assets/FlowExecutionViewer-Ci56tI0_.js +0 -1
  47. package/src/ui/public/assets/FlowOrchestrationWorkspace-WqB1AJl6.js +0 -21
  48. package/src/ui/public/assets/LogList-DkwwTvhf.js +0 -7
  49. package/src/ui/public/assets/MindmapView-CX1qlHl3.js +0 -2
  50. package/src/ui/public/assets/MonitorView-fnUCn0oW.js +0 -1
  51. package/src/ui/public/assets/ProjectStartupButton-DJZiuAuR.js +0 -1
  52. package/src/ui/public/assets/SourceMapView-mCG7TN0y.js +0 -3
  53. package/src/ui/public/assets/UserInputNode-C6pUnTWe.js +0 -1
  54. package/src/ui/public/assets/WorkbenchView-DgeUjdHD.js +0 -28
  55. package/src/ui/public/assets/element-plus-CXfbWXaJ.css +0 -1
  56. package/src/ui/public/assets/element-plus-DqggPOBO.js +0 -47
  57. package/src/ui/public/assets/index-B2a65GAv.css +0 -1
  58. package/src/ui/public/assets/index-DAgSqrPI.js +0 -104
  59. package/src/ui/public/assets/socket-io-B9UuzwTN.js +0 -1
  60. /package/src/ui/public/assets/{dagre-BCXBo6I9.js → dagre-97WoLjFt.js} +0 -0
@@ -0,0 +1,239 @@
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
+ // findDshExecutable 回归测试(2026-09-05)
16
+ // 旧实现只查 %APPDATA%\npm + npm prefix -g,在 nvm4w 用户(全局包在
17
+ // C:\nvm4w\nodejs\) 上"未安装"误报。改用 where.exe dsh 优先后,这里守住
18
+ // "返回的路径必须真实存在"这条契约。
19
+ import { test } from 'node:test'
20
+ import assert from 'node:assert/strict'
21
+ import fs from 'node:fs/promises'
22
+ import { fetchLatestVersion, findDshExecutable, getToolInstallers, parseVersionOutput, registerFileOpenRoutes } from './fileOpen.js'
23
+
24
+ // ── parseVersionOutput(版本号采集,2026-09-05 check-tools 增强)────────────
25
+
26
+ test('parseVersionOutput: 各工具的 --version 输出都能提取 semver', () => {
27
+ assert.equal(parseVersionOutput('0.1.1-rc.2\n'), '0.1.1-rc.2') // dsh(带 prerelease)
28
+ assert.equal(parseVersionOutput('1.0.60 (Claude Code)'), '1.0.60') // claude
29
+ assert.equal(parseVersionOutput('1.92.0 abc123def (commit)\n'), '1.92.0') // vscode
30
+ assert.equal(parseVersionOutput('opencode 0.1.30\n'), '0.1.30') // opencode 前缀
31
+ assert.equal(parseVersionOutput('codex-cli 0.9.2+build.1'), '0.9.2+build.1') // build 元数据
32
+ })
33
+
34
+ test('parseVersionOutput: 边界输入返回 null', () => {
35
+ assert.equal(parseVersionOutput(''), null)
36
+ assert.equal(parseVersionOutput(null), null)
37
+ assert.equal(parseVersionOutput(undefined), null)
38
+ assert.equal(parseVersionOutput('no version here'), null)
39
+ // 多段数字但不是 semver(如日期 2026.09.05 会被提取——可接受,总比没有强)
40
+ assert.ok(parseVersionOutput('version: 2.16.42\nsomething 1.0.0').startsWith('2.16.42'))
41
+ })
42
+
43
+ test('findDshExecutable: Windows 上返回 string 或 null,且非 null 时路径必须存在', async () => {
44
+ const result = await findDshExecutable()
45
+ if (process.platform !== 'win32') {
46
+ // 非 win32 平台走 commandExists('dsh') 路径:要么是 'dsh'(which 命中),
47
+ // 要么是 null。不强求特定值,只断言类型。
48
+ assert.ok(result === null || typeof result === 'string')
49
+ return
50
+ }
51
+ if (result !== null) {
52
+ assert.equal(typeof result, 'string')
53
+ // 核心契约:返回的路径必须真实存在且是文件。
54
+ // 这正是这次修复要解决的:不允许再出现"返回看似合法但 fs.stat 不到的路径"
55
+ // (旧实现里 APPDATA/npm/dsh.cmd + npm prefix -g 拼出的废地址都是 fs.stat 不到的)。
56
+ const stat = await fs.stat(result)
57
+ assert.equal(stat.isFile(), true, `${result} 不是文件`)
58
+ }
59
+ })
60
+
61
+ // ── 工具升级(updateCommand)──────────────────────────────────────────
62
+ // 2026-09-05 新增:右键工具 → 更新。install 与 update 命令动词不同
63
+ // (npm @latest / winget upgrade / brew upgrade / snap refresh / kimi 脚本重跑)。
64
+
65
+ test('getToolInstallers: npm 包工具的升级变体带 @latest', () => {
66
+ const installers = getToolInstallers('win32', () => true)
67
+ for (const tool of ['claude', 'codex', 'opencode', 'dsh']) {
68
+ const inst = installers[tool]
69
+ assert.match(inst.updateCommand, /@latest$/, `${tool}.updateCommand 应带 @latest`)
70
+ assert.ok(inst.updateExecutable, `${tool}.updateExecutable 缺失`)
71
+ assert.ok(
72
+ inst.updateArgs.at(-1).endsWith('@latest'),
73
+ `${tool}.updateArgs 最后一项应带 @latest`,
74
+ )
75
+ // 安装命令不被升级字段污染
76
+ assert.doesNotMatch(inst.command, /@latest/, `${tool}.command 不应带 @latest`)
77
+ }
78
+ })
79
+
80
+ test('getToolInstallers: 各平台 vscode 升级用各自的 upgrade 动词', () => {
81
+ const win = getToolInstallers('win32', () => true)
82
+ assert.match(win.vscode.updateCommand, /^winget upgrade/)
83
+ assert.deepEqual(win.vscode.updateArgs.slice(0, 2), ['upgrade', '--id'])
84
+
85
+ const mac = getToolInstallers('darwin', () => true)
86
+ assert.match(mac.vscode.updateCommand, /^brew upgrade/)
87
+
88
+ const linux = getToolInstallers('linux', () => true)
89
+ assert.match(linux.vscode.updateCommand, /^sudo snap refresh/)
90
+ })
91
+
92
+ test('getToolInstallers: kimi 升级复用幂等脚本,zcode 不支持一键升级', () => {
93
+ const installers = getToolInstallers('win32', () => true)
94
+ assert.equal(installers.kimi.updateKind, 'script')
95
+ assert.match(installers.kimi.updateCommand, /install\.ps1/)
96
+ // zcode 是桌面应用,updateCommand 必须为空 → /api/update-tool 对它返回 400
97
+ assert.equal(installers.zcode.updateCommand, undefined)
98
+ })
99
+
100
+ test('update-tool 路由: zcode(无 updateCommand)返回 400 且不启动任何进程', async () => {
101
+ // zcode 的 400 在 launchToolInstaller 之前抛出,本测试不会真的打开终端窗口。
102
+ const routes = new Map()
103
+ const app = {
104
+ get(path, handler) { routes.set(`GET ${path}`, handler) },
105
+ post(path, handler) { routes.set(`POST ${path}`, handler) },
106
+ }
107
+ registerFileOpenRoutes({ app })
108
+ const handler = routes.get('POST /api/update-tool')
109
+ assert.ok(handler, 'POST /api/update-tool 路由未注册')
110
+
111
+ let statusCode = 200
112
+ let payload = null
113
+ const res = {
114
+ status(code) { statusCode = code; return this },
115
+ json(value) { payload = value; return this },
116
+ }
117
+ await handler({ method: 'POST', path: '/api/update-tool', body: { tool: 'zcode' } }, res, () => {})
118
+
119
+ assert.equal(statusCode, 400)
120
+ assert.equal(payload.success, false)
121
+ assert.match(payload.error, /不支持一键更新|桌面应用/)
122
+ })
123
+
124
+ test('update-tool 路由: 未知工具 id 返回 400', async () => {
125
+ const routes = new Map()
126
+ const app = {
127
+ get(path, handler) { routes.set(`GET ${path}`, handler) },
128
+ post(path, handler) { routes.set(`POST ${path}`, handler) },
129
+ }
130
+ registerFileOpenRoutes({ app })
131
+ const handler = routes.get('POST /api/update-tool')
132
+
133
+ let statusCode = 200
134
+ let payload = null
135
+ const res = {
136
+ status(code) { statusCode = code; return this },
137
+ json(value) { payload = value; return this },
138
+ }
139
+ await handler({ method: 'POST', path: '/api/update-tool', body: { tool: 'not-a-tool' } }, res, () => {})
140
+
141
+ assert.equal(statusCode, 400)
142
+ assert.equal(payload.success, false)
143
+ })
144
+
145
+ // ── fetchLatestVersion / latest-tool-versions(2026-09-05 更新菜单「当前→最新」)──
146
+
147
+ test('fetchLatestVersion: 非法包名直接返回 null,不发请求', async () => {
148
+ let fetchCalls = 0
149
+ const fakeFetch = async () => { fetchCalls++; return { ok: true, json: async () => ({ version: '1.0.0' }) } }
150
+ assert.equal(await fetchLatestVersion(''), null)
151
+ assert.equal(await fetchLatestVersion(null), null)
152
+ assert.equal(await fetchLatestVersion('bad pkg name with spaces'), null)
153
+ assert.equal(await fetchLatestVersion('../etc/passwd'), null)
154
+ assert.equal(fetchCalls, 0, '非法包名不应触发任何网络请求')
155
+ })
156
+
157
+ test('fetchLatestVersion: 命中 registry 返回 version;非 200 / 抛异常 / 字段缺失都降级为 null', async () => {
158
+ const okFetch = async () => ({ ok: true, json: async () => ({ version: '2.3.4' }) })
159
+ assert.equal(await fetchLatestVersion('opencode-ai', { fetchImpl: okFetch }), '2.3.4')
160
+
161
+ const notOkFetch = async () => ({ ok: false, json: async () => ({}) })
162
+ assert.equal(await fetchLatestVersion('opencode-ai', { fetchImpl: notOkFetch }), null)
163
+
164
+ const throwFetch = async () => { throw new Error('network down') }
165
+ assert.equal(await fetchLatestVersion('opencode-ai', { fetchImpl: throwFetch }), null)
166
+
167
+ const missingFieldFetch = async () => ({ ok: true, json: async () => ({ name: 'opencode-ai' }) })
168
+ assert.equal(await fetchLatestVersion('opencode-ai', { fetchImpl: missingFieldFetch }), null)
169
+ })
170
+
171
+ test('latest-tool-versions 路由: tools 非数组 / 含未知工具 → 400,且不发请求', async () => {
172
+ const routes = new Map()
173
+ const app = {
174
+ get(path, handler) { routes.set(`GET ${path}`, handler) },
175
+ post(path, handler) { routes.set(`POST ${path}`, handler) },
176
+ }
177
+ registerFileOpenRoutes({ app })
178
+ const handler = routes.get('POST /api/latest-tool-versions')
179
+ assert.ok(handler, 'POST /api/latest-tool-versions 路由未注册')
180
+
181
+ const makeRes = () => {
182
+ let statusCode = 200
183
+ let payload = null
184
+ return {
185
+ res: { status(code) { statusCode = code; return this }, json(value) { payload = value; return this } },
186
+ get status() { return statusCode },
187
+ get payload() { return payload },
188
+ }
189
+ }
190
+
191
+ let ctx = makeRes()
192
+ await handler({ method: 'POST', body: { tools: 'claude' } }, ctx.res, () => {})
193
+ assert.equal(ctx.status, 400)
194
+
195
+ ctx = makeRes()
196
+ await handler({ method: 'POST', body: { tools: [123] } }, ctx.res, () => {})
197
+ assert.equal(ctx.status, 400)
198
+
199
+ ctx = makeRes()
200
+ await handler({ method: 'POST', body: { tools: ['claude', 'zcode'] } }, ctx.res, () => {})
201
+ // zcode 不在 TOOL_INSTALL_PACKAGES(npm 工具白名单)里 → 400
202
+ assert.equal(ctx.status, 400)
203
+ assert.match(ctx.payload.error, /不支持的工具/)
204
+ })
205
+
206
+ test('latest-tool-versions 路由: 正常路径调 registry 并把结果按 tool id 归还', async () => {
207
+ // 用全局 fetch mock 验证「路由 → fetchLatestVersion → registry URL」整条接线,
208
+ // 测试完必须恢复,不然会污染其它测试。
209
+ const requestedUrls = []
210
+ const originalFetch = globalThis.fetch
211
+ globalThis.fetch = async (url) => {
212
+ requestedUrls.push(String(url))
213
+ return { ok: true, json: async () => ({ version: '9.9.9' }) }
214
+ }
215
+ try {
216
+ const routes = new Map()
217
+ const app = {
218
+ get(path, handler) { routes.set(`GET ${path}`, handler) },
219
+ post(path, handler) { routes.set(`POST ${path}`, handler) },
220
+ }
221
+ registerFileOpenRoutes({ app })
222
+ const handler = routes.get('POST /api/latest-tool-versions')
223
+
224
+ let payload = null
225
+ const res = {
226
+ status() { return this },
227
+ json(value) { payload = value; return this },
228
+ }
229
+ await handler({ method: 'POST', body: { tools: ['claude', 'dsh'] } }, res, () => {})
230
+
231
+ assert.equal(payload.success, true)
232
+ assert.deepEqual(payload.latest, { claude: '9.9.9', dsh: '9.9.9' })
233
+ assert.equal(requestedUrls.length, 2)
234
+ assert.ok(requestedUrls[0].includes('/@anthropic-ai/claude-code/latest'), `URL 不对: ${requestedUrls[0]}`)
235
+ assert.ok(requestedUrls[1].includes('/@deepseek-ai/dsh/latest'), `URL 不对: ${requestedUrls[1]}`)
236
+ } finally {
237
+ globalThis.fetch = originalFetch
238
+ }
239
+ })
@@ -1,173 +1,173 @@
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
- // 智能体对话 session 持久化层。
16
- //
17
- // 与 ai-split-sessions 不同,这里存储的是完整 agent 对话(含工具调用)。
18
- // 消息格式兼容 OpenAI chat completions:
19
- // - { role: 'system', content: string }
20
- // - { role: 'user', content: string | content_parts[] }
21
- // - { role: 'assistant', content: string|null, tool_calls?: [{id,type,function:{name,arguments}}] }
22
- // - { role: 'tool', tool_call_id, name, content: string }
23
- //
24
- // 文件命名: {sessionId}.json,原子写(tmp + rename)。
25
- // 数据目录: ~/.zen-gitsync/agent-sessions/
26
-
27
- import fsp from 'fs/promises';
28
- import path from 'path';
29
- import { logger } from './shared.js';
30
-
31
- // 数据目录:与 ai-split-sessions 平级
32
- export const AGENT_SESSIONS_DIR = path.join(
33
- path.dirname(path.dirname(path.dirname(path.dirname(new URL(import.meta.url).pathname.replace(/^\//, ''))))),
34
- '.zen-gitsync',
35
- 'agent-sessions'
36
- );
37
- // 上面路径在 Windows 下不可靠,改用 os.homedir
38
- import os from 'os';
39
- export const AGENT_SESSIONS_DIR_FIXED = path.join(os.homedir(), '.zen-gitsync', 'agent-sessions');
40
-
41
- export const MAX_AGENT_SESSIONS = 200;
42
- export const AGENT_SESSIONS_KEEP = 100;
43
-
44
- // 16 字符: 时间戳后 8 位 + 随机 8 位 base36
45
- export function genAgentSessionId() {
46
- return `ag-${Date.now().toString(36).slice(-8)}-${Math.random().toString(36).slice(2, 10)}`;
47
- }
48
-
49
- // 从第一条 user 消息自动生成标题
50
- export function autoTitle(messages) {
51
- for (const m of messages) {
52
- if (m.role === 'user') {
53
- let text = '';
54
- if (typeof m.content === 'string') {
55
- text = m.content;
56
- } else if (Array.isArray(m.content)) {
57
- text = m.content.filter(p => p?.type === 'text').map(p => p.text).join(' ');
58
- }
59
- text = text.trim();
60
- if (text) {
61
- // 取第一行,截断到 40 字符
62
- const firstLine = text.split('\n')[0].trim();
63
- return firstLine.length > 40 ? firstLine.slice(0, 40) + '…' : firstLine;
64
- }
65
- }
66
- }
67
- return '(新会话)';
68
- }
69
-
70
- async function readAgentSessionFile(sessionId) {
71
- if (!/^ag-[a-z0-9-]{4,32}$/i.test(sessionId)) {
72
- const err = new Error('非法 sessionId'); err.statusCode = 400; throw err;
73
- }
74
- const file = path.join(AGENT_SESSIONS_DIR_FIXED, `${sessionId}.json`);
75
- try {
76
- return JSON.parse(await fsp.readFile(file, 'utf-8'));
77
- } catch (err) {
78
- if (err.code === 'ENOENT') {
79
- const e = new Error('会话不存在'); e.statusCode = 404; throw e;
80
- }
81
- throw err;
82
- }
83
- }
84
-
85
- async function writeAgentSessionFile(sessionId, data) {
86
- await fsp.mkdir(AGENT_SESSIONS_DIR_FIXED, { recursive: true });
87
- const file = path.join(AGENT_SESSIONS_DIR_FIXED, `${sessionId}.json`);
88
- const tmp = `${file}.tmp.${process.pid}.${Date.now()}`;
89
- await fsp.writeFile(tmp, JSON.stringify(data, null, 2), 'utf-8');
90
- await fsp.rename(tmp, file);
91
- }
92
-
93
- async function deleteAgentSessionFile(sessionId) {
94
- if (!/^ag-[a-z0-9-]{4,32}$/i.test(sessionId)) {
95
- const err = new Error('非法 sessionId'); err.statusCode = 400; throw err;
96
- }
97
- const file = path.join(AGENT_SESSIONS_DIR_FIXED, `${sessionId}.json`);
98
- try {
99
- await fsp.unlink(file);
100
- } catch (err) {
101
- if (err.code === 'ENOENT') {
102
- const e = new Error('会话不存在'); e.statusCode = 404; throw e;
103
- }
104
- throw err;
105
- }
106
- }
107
-
108
- // 列表只读 metadata
109
- async function listAgentSessionsMeta() {
110
- await fsp.mkdir(AGENT_SESSIONS_DIR_FIXED, { recursive: true });
111
- const files = await fsp.readdir(AGENT_SESSIONS_DIR_FIXED);
112
- const list = [];
113
- for (const f of files) {
114
- if (!f.endsWith('.json')) continue;
115
- const sid = f.slice(0, -5);
116
- try {
117
- const file = path.join(AGENT_SESSIONS_DIR_FIXED, f);
118
- const stat = await fsp.stat(file);
119
- const data = JSON.parse(await fsp.readFile(file, 'utf-8'));
120
- list.push({
121
- sessionId: data.sessionId || sid,
122
- title: data.title || '(无标题)',
123
- source: data.source || 'web', // 'web' | 'cli'
124
- cwd: data.cwd || '',
125
- model: data.model || '',
126
- createdAt: data.createdAt || '',
127
- updatedAt: data.updatedAt || '',
128
- messageCount: Array.isArray(data.messages) ? data.messages.length : 0,
129
- size: stat.size
130
- });
131
- } catch { /* 跳过坏文件 */ }
132
- }
133
- list.sort((a, b) => (b.updatedAt || '').localeCompare(a.updatedAt || ''));
134
- return list;
135
- }
136
-
137
- async function enforceAgentSessionsRetention() {
138
- const files = await fsp.readdir(AGENT_SESSIONS_DIR_FIXED).catch(() => []);
139
- const jsonFiles = files.filter(f => f.endsWith('.json'));
140
- if (jsonFiles.length < MAX_AGENT_SESSIONS) return;
141
- const stats = await Promise.all(jsonFiles.map(async f => {
142
- const full = path.join(AGENT_SESSIONS_DIR_FIXED, f);
143
- const s = await fsp.stat(full);
144
- return { file: full, mtime: s.mtimeMs };
145
- }));
146
- stats.sort((a, b) => b.mtime - a.mtime);
147
- const toDelete = stats.slice(AGENT_SESSIONS_KEEP);
148
- await Promise.all(toDelete.map(s => fsp.unlink(s.file).catch(() => {})));
149
- if (toDelete.length > 0) {
150
- logger.info(`[agent] sessions: cleaned up ${toDelete.length} old files`);
151
- }
152
- }
153
-
154
- // 更新会话标题
155
- async function renameAgentSession(sessionId, title) {
156
- const data = await readAgentSessionFile(sessionId);
157
- data.title = String(title || '').trim() || '(无标题)';
158
- data.updatedAt = new Date().toISOString();
159
- await writeAgentSessionFile(sessionId, data);
160
- return data;
161
- }
162
-
163
- export const agentSessionStore = {
164
- genSessionId: genAgentSessionId,
165
- autoTitle,
166
- read: readAgentSessionFile,
167
- write: writeAgentSessionFile,
168
- delete: deleteAgentSessionFile,
169
- listMeta: listAgentSessionsMeta,
170
- enforceRetention: enforceAgentSessionsRetention,
171
- rename: renameAgentSession,
172
- DIR: AGENT_SESSIONS_DIR_FIXED,
173
- };
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
+ // 智能体对话 session 持久化层。
16
+ //
17
+ // 与 ai-split-sessions 不同,这里存储的是完整 agent 对话(含工具调用)。
18
+ // 消息格式兼容 OpenAI chat completions:
19
+ // - { role: 'system', content: string }
20
+ // - { role: 'user', content: string | content_parts[] }
21
+ // - { role: 'assistant', content: string|null, tool_calls?: [{id,type,function:{name,arguments}}] }
22
+ // - { role: 'tool', tool_call_id, name, content: string }
23
+ //
24
+ // 文件命名: {sessionId}.json,原子写(tmp + rename)。
25
+ // 数据目录: ~/.zen-gitsync/agent-sessions/
26
+
27
+ import fsp from 'fs/promises';
28
+ import path from 'path';
29
+ import { logger } from './shared.js';
30
+
31
+ // 数据目录:与 ai-split-sessions 平级
32
+ export const AGENT_SESSIONS_DIR = path.join(
33
+ path.dirname(path.dirname(path.dirname(path.dirname(new URL(import.meta.url).pathname.replace(/^\//, ''))))),
34
+ '.zen-gitsync',
35
+ 'agent-sessions'
36
+ );
37
+ // 上面路径在 Windows 下不可靠,改用 os.homedir
38
+ import os from 'os';
39
+ export const AGENT_SESSIONS_DIR_FIXED = path.join(os.homedir(), '.zen-gitsync', 'agent-sessions');
40
+
41
+ export const MAX_AGENT_SESSIONS = 200;
42
+ export const AGENT_SESSIONS_KEEP = 100;
43
+
44
+ // 16 字符: 时间戳后 8 位 + 随机 8 位 base36
45
+ export function genAgentSessionId() {
46
+ return `ag-${Date.now().toString(36).slice(-8)}-${Math.random().toString(36).slice(2, 10)}`;
47
+ }
48
+
49
+ // 从第一条 user 消息自动生成标题
50
+ export function autoTitle(messages) {
51
+ for (const m of messages) {
52
+ if (m.role === 'user') {
53
+ let text = '';
54
+ if (typeof m.content === 'string') {
55
+ text = m.content;
56
+ } else if (Array.isArray(m.content)) {
57
+ text = m.content.filter(p => p?.type === 'text').map(p => p.text).join(' ');
58
+ }
59
+ text = text.trim();
60
+ if (text) {
61
+ // 取第一行,截断到 40 字符
62
+ const firstLine = text.split('\n')[0].trim();
63
+ return firstLine.length > 40 ? firstLine.slice(0, 40) + '…' : firstLine;
64
+ }
65
+ }
66
+ }
67
+ return '(新会话)';
68
+ }
69
+
70
+ async function readAgentSessionFile(sessionId) {
71
+ if (!/^ag-[a-z0-9-]{4,32}$/i.test(sessionId)) {
72
+ const err = new Error('非法 sessionId'); err.statusCode = 400; throw err;
73
+ }
74
+ const file = path.join(AGENT_SESSIONS_DIR_FIXED, `${sessionId}.json`);
75
+ try {
76
+ return JSON.parse(await fsp.readFile(file, 'utf-8'));
77
+ } catch (err) {
78
+ if (err.code === 'ENOENT') {
79
+ const e = new Error('会话不存在'); e.statusCode = 404; throw e;
80
+ }
81
+ throw err;
82
+ }
83
+ }
84
+
85
+ async function writeAgentSessionFile(sessionId, data) {
86
+ await fsp.mkdir(AGENT_SESSIONS_DIR_FIXED, { recursive: true });
87
+ const file = path.join(AGENT_SESSIONS_DIR_FIXED, `${sessionId}.json`);
88
+ const tmp = `${file}.tmp.${process.pid}.${Date.now()}`;
89
+ await fsp.writeFile(tmp, JSON.stringify(data, null, 2), 'utf-8');
90
+ await fsp.rename(tmp, file);
91
+ }
92
+
93
+ async function deleteAgentSessionFile(sessionId) {
94
+ if (!/^ag-[a-z0-9-]{4,32}$/i.test(sessionId)) {
95
+ const err = new Error('非法 sessionId'); err.statusCode = 400; throw err;
96
+ }
97
+ const file = path.join(AGENT_SESSIONS_DIR_FIXED, `${sessionId}.json`);
98
+ try {
99
+ await fsp.unlink(file);
100
+ } catch (err) {
101
+ if (err.code === 'ENOENT') {
102
+ const e = new Error('会话不存在'); e.statusCode = 404; throw e;
103
+ }
104
+ throw err;
105
+ }
106
+ }
107
+
108
+ // 列表只读 metadata
109
+ async function listAgentSessionsMeta() {
110
+ await fsp.mkdir(AGENT_SESSIONS_DIR_FIXED, { recursive: true });
111
+ const files = await fsp.readdir(AGENT_SESSIONS_DIR_FIXED);
112
+ const list = [];
113
+ for (const f of files) {
114
+ if (!f.endsWith('.json')) continue;
115
+ const sid = f.slice(0, -5);
116
+ try {
117
+ const file = path.join(AGENT_SESSIONS_DIR_FIXED, f);
118
+ const stat = await fsp.stat(file);
119
+ const data = JSON.parse(await fsp.readFile(file, 'utf-8'));
120
+ list.push({
121
+ sessionId: data.sessionId || sid,
122
+ title: data.title || '(无标题)',
123
+ source: data.source || 'web', // 'web' | 'cli'
124
+ cwd: data.cwd || '',
125
+ model: data.model || '',
126
+ createdAt: data.createdAt || '',
127
+ updatedAt: data.updatedAt || '',
128
+ messageCount: Array.isArray(data.messages) ? data.messages.length : 0,
129
+ size: stat.size
130
+ });
131
+ } catch { /* 跳过坏文件 */ }
132
+ }
133
+ list.sort((a, b) => (b.updatedAt || '').localeCompare(a.updatedAt || ''));
134
+ return list;
135
+ }
136
+
137
+ async function enforceAgentSessionsRetention() {
138
+ const files = await fsp.readdir(AGENT_SESSIONS_DIR_FIXED).catch(() => []);
139
+ const jsonFiles = files.filter(f => f.endsWith('.json'));
140
+ if (jsonFiles.length < MAX_AGENT_SESSIONS) return;
141
+ const stats = await Promise.all(jsonFiles.map(async f => {
142
+ const full = path.join(AGENT_SESSIONS_DIR_FIXED, f);
143
+ const s = await fsp.stat(full);
144
+ return { file: full, mtime: s.mtimeMs };
145
+ }));
146
+ stats.sort((a, b) => b.mtime - a.mtime);
147
+ const toDelete = stats.slice(AGENT_SESSIONS_KEEP);
148
+ await Promise.all(toDelete.map(s => fsp.unlink(s.file).catch(() => {})));
149
+ if (toDelete.length > 0) {
150
+ logger.info(`[agent] sessions: cleaned up ${toDelete.length} old files`);
151
+ }
152
+ }
153
+
154
+ // 更新会话标题
155
+ async function renameAgentSession(sessionId, title) {
156
+ const data = await readAgentSessionFile(sessionId);
157
+ data.title = String(title || '').trim() || '(无标题)';
158
+ data.updatedAt = new Date().toISOString();
159
+ await writeAgentSessionFile(sessionId, data);
160
+ return data;
161
+ }
162
+
163
+ export const agentSessionStore = {
164
+ genSessionId: genAgentSessionId,
165
+ autoTitle,
166
+ read: readAgentSessionFile,
167
+ write: writeAgentSessionFile,
168
+ delete: deleteAgentSessionFile,
169
+ listMeta: listAgentSessionsMeta,
170
+ enforceRetention: enforceAgentSessionsRetention,
171
+ rename: renameAgentSession,
172
+ DIR: AGENT_SESSIONS_DIR_FIXED,
173
+ };
@@ -1,4 +0,0 @@
1
- import"./rolldown-runtime-BM3Ffeng.js";import{Bt as e,Dn as t,Et as n,G as r,Gn as i,Gr as a,In as o,Pt as s,Qn as c,Sn as l,Un as u,Vr as d,Y as f,a as p,dr as m,er as h,et as g,ht as _,i as v,kn as y,lr as b,lt as ee,mn as x,o as S,pn as C,rn as w,tr as T,un as E,vn as D,xn as O,yn as k,zr as A}from"./element-plus-DqggPOBO.js";import{a as j,r as M,t as N}from"./vendor-CD2SBxQj.js";import{D as P,N as F,j as I}from"./index-DAgSqrPI.js";var L=64*1024;function R(e){if(!e||!Array.isArray(e.messages))return[];let t=[],n=e.messages.length;for(let r=0;r<n;r++){let i=e.messages[r];if(i.role!==`system`){if(i.role===`user`){let e=``,n;if(typeof i.content==`string`)e=i.content;else if(Array.isArray(i.content)){e=i.content.filter(e=>e?.type===`text`).map(e=>e.text||``).join(` `);let t=i.content.filter(e=>e?.type===`image_url`&&typeof e.image_url?.url==`string`).map((e,t)=>({id:`att-${r}-${t}`,name:`image`,size:0,type:V(e.image_url?.url||``)||`image/*`,preview:e.image_url?.url}));t.length>0&&(n=t)}(e||n)&&t.push({id:`msg-${r}`,role:`user`,content:e,attachments:n,status:`done`,createdAt:Date.now()})}else if(i.role===`assistant`){let a=``;typeof i.content==`string`&&(a=i.content),a.length>L&&(a=`…(前文已截断)\n${a.slice(-L)}`);let o=[];if(i.tool_calls&&i.tool_calls.length>0)for(let e of i.tool_calls)o.push({id:e.id||e.function?.name||M(),name:e.function?.name||``,arguments:e.function?.arguments||``,argsPreview:z(e.function?.name||``,e.function?.arguments||``),status:`done`,result:``});let s=r+1;for(;s<n&&e.messages[s].role===`tool`;){let t=e.messages[s],n=t.tool_call_id||t.name||``,i=o.find(e=>e.id===n);if(i){let e=t.content;typeof e!=`string`&&(e=`(non-text result)`),e.length>L&&(e=`…(前文已截断)\n${e.slice(-L)}`),i.result=e}r=s,s++}let c=!!a,l=o.length>0;(c||l)&&t.push({id:`msg-${r}`,role:`assistant`,content:a||``,toolCalls:l?o:void 0,status:`done`,createdAt:Date.now()})}}}return t}function z(e,t){try{let n=JSON.parse(t);switch(e){case`run_command`:return String(n.command||``).slice(0,200);case`read_file`:case`write_file`:case`edit_file`:return String(n.path||``);case`list_files`:return String(n.path||`.`);case`search_text`:return String(n.pattern||``);default:return JSON.stringify(n).slice(0,200)}}catch{return t.slice(0,200)}}function B(e){return new Promise((t,n)=>{let r=new FileReader;r.onload=()=>t(String(r.result||``)),r.onerror=()=>n(r.error||Error(`FileReader failed`)),r.readAsDataURL(e)})}function V(e){let t=/^data:([^;,]+)/.exec(e);return t?t[1]:``}function H(){let e=P(),t=m([]),n=m(!1),r=m(null),i=m([]),a=m(!1),o=m(!1),s=null,c=0,l=0;async function u(){let r=++l,i=e.currentDirectory||``;if(!i){t.value=[],n.value=!1;return}n.value=!0;try{let e=`/api/agent/sessions?cwd=${encodeURIComponent(i)}`,n=await fetch(e).then(e=>e.json());if(r!==l)return;if(!n.success){p.error(n.error||F(`@AGENT:加载会话列表失败`));return}t.value=Array.isArray(n.sessions)?n.sessions:[]}catch(e){if(r!==l)return;p.error(F(`@AGENT:加载会话列表失败`)+`: `+(e?.message||e))}finally{r===l&&(n.value=!1)}}async function d(e){o.value=!0,r.value=e;try{let t=await fetch(`/api/agent/sessions/${encodeURIComponent(e)}`).then(e=>e.json());if(!t.success){p.error(t.error||F(`@AGENT:加载会话失败`));return}i.value=R(t.session)}catch(e){p.error(F(`@AGENT:加载会话失败`)+`: `+(e?.message||e))}finally{o.value=!1}}async function f(e){try{await v.confirm(F(`@AGENT:确认删除该会话`),F(`@AGENT:删除后无法恢复`),{type:`warning`})}catch{return}try{let n=await fetch(`/api/agent/sessions/${encodeURIComponent(e)}`,{method:`DELETE`}).then(e=>e.json());if(!n.success){p.error(n.error||F(`@AGENT:删除失败`));return}t.value=t.value.filter(t=>t.sessionId!==e),r.value===e&&(r.value=null,i.value=[]),p.success(F(`@AGENT:已删除`))}catch(e){p.error(F(`@AGENT:删除失败`)+`: `+(e?.message||e))}}async function h(e,n){try{let r=await fetch(`/api/agent/sessions/${encodeURIComponent(e)}`,{method:`PUT`,headers:{"Content-Type":`application/json`},body:JSON.stringify({title:n})}).then(e=>e.json());if(!r.success){p.error(r.error||F(`@AGENT:重命名失败`));return}let i=t.value.find(t=>t.sessionId===e);i&&(i.title=n),p.success(F(`@AGENT:已重命名`))}catch(e){p.error(F(`@AGENT:重命名失败`)+`: `+(e?.message||e))}}function g(){r.value=null,i.value=[]}async function _(t,n=[]){let o=n.filter(e=>e?.file?.type?.startsWith(`image/`));if(o.length<n.length&&p.warning(F(`@AGENT:仅支持发送图片,非图片文件已忽略`)),!t.trim()&&o.length===0||a.value)return;let l=(await Promise.allSettled(o.map(e=>B(e.file)))).map(e=>e.status===`fulfilled`?e.value:``).filter(e=>e.startsWith(`data:image/`)),d=++c;s=new AbortController;let f=s,m={id:M(),role:`user`,content:t,status:`done`,createdAt:Date.now()};o.length>0&&(m.attachments=o.map(e=>({id:e.id,name:e.file.name||`image`,size:e.file.size||0,type:e.file.type||``,preview:e.preview}))),i.value.push(m);let h=b({id:M(),role:`assistant`,content:``,status:`pending`,createdAt:Date.now()});i.value.push(h),a.value=!0;let g=[];try{let n=await fetch(`/api/agent/chat`,{method:`POST`,headers:{"Content-Type":`application/json`,Accept:`text/event-stream`},body:JSON.stringify({sessionId:r.value||``,userMessage:t,cwd:e.currentDirectory||``,...l.length>0?{images:l}:{}}),signal:f.signal});if(d!==c)return;if(!n.ok||!n.body){let e=await n.text().catch(()=>``);throw Error(e||`HTTP ${n.status}`)}let a=n.body.getReader(),o=new TextDecoder(`utf-8`),s=``,p=r.value;for(;;){let{value:e,done:t}=await a.read();if(d!==c)return;if(t)break;s+=o.decode(e,{stream:!0});let n=s.split(`
2
- `);s=n.pop()??``;for(let e of n){let t=e.trim();if(!t.startsWith(`data:`))continue;let n=t.slice(5).trim();if(!n)continue;let i;try{i=JSON.parse(n)}catch{continue}switch(i.type){case`meta`:i.sessionId&&(p=i.sessionId,r.value=i.sessionId);break;case`thinking`:h.reasoning||(h.reasoning=``,h.reasoningStatus=`streaming`),h.reasoning+=String(i.delta||``),h.status=`streaming`;break;case`content`:h.status===`pending`&&(h.status=`streaming`);{let e=String(i.delta||``);h.content+=e;let t=N(h.content);t.reasoning&&(h.reasoning||(h.reasoning=``,h.reasoningStatus=`streaming`),h.reasoning=t.reasoning,h.content=t.content)}break;case`tool_call_start`:{let e=b({id:i.toolCallId||M(),name:i.name||``,argsPreview:i.argsPreview||``,arguments:i.argsPreview||``,status:`running`,result:``});g.push(e),h.toolCalls||=[],h.toolCalls.push(e),h.status===`pending`&&(h.status=`streaming`);break}case`tool_result`:{let e=g.find(e=>e.id===i.toolCallId);e&&(e.result=String(i.result||``),e.status=`done`);break}case`done`:{let e=N(i.content||h.content);e.reasoning&&!h.reasoning&&(h.reasoning=e.reasoning),h.content=e.content,h.status=`done`,h.reasoningStatus===`streaming`&&(h.reasoningStatus=`done`);break}case`error`:h.status=`error`,h.error=String(i.error||F(`@AGENT:未知错误`)),h.reasoningStatus===`streaming`&&(h.reasoningStatus=`done`);break}}i.value=[...i.value]}if(d!==c)return;h.status===`pending`&&(h.status=`done`),p&&p!==r.value&&(r.value=p),u().catch(()=>{})}catch(e){if(d!==c)return;e?.name===`AbortError`||f.signal.aborted?(h.content=(h.content||``)+`
3
-
4
- [`+F(`@AGENT:已停止`)+`]`,h.status=`done`,h.reasoningStatus===`streaming`&&(h.reasoningStatus=`done`)):(h.status=`error`,h.error=e?.message||String(e),p.error(h.error||F(`@AGENT:对话失败`)))}finally{d===c&&(a.value=!1,s=null)}}function y(){s&&=(s.abort(),null)}return{sessions:t,sessionsLoading:n,currentSessionId:r,messages:i,isStreaming:a,sessionLoading:o,loadSessions:u,loadSession:d,deleteSession:f,renameSession:h,newSession:g,sendMessage:_,stop:y}}var U=y({__name:`AgentView`,setup(t,{expose:i}){i();let a=P(),{sessions:l,sessionsLoading:u,currentSessionId:d,messages:h,isStreaming:y,sessionLoading:b,loadSessions:x,loadSession:S,deleteSession:C,renameSession:w,newSession:T,sendMessage:E,stop:O}=H(),k=D(()=>{let e=a.theme;return e===`dark`||e===`auto`&&window.matchMedia(`(prefers-color-scheme: dark)`).matches?`dark`:`light`}),A=m(``),M=D(()=>{if(!A.value.trim())return l.value;let e=A.value.toLowerCase();return l.value.filter(t=>(t.title||``).toLowerCase().includes(e)||(t.model||``).toLowerCase().includes(e))}),N=D(()=>[{id:`p1`,label:F(`@AGENT:查看项目结构`),prompt:F(`@AGENT:prompt_p1`)},{id:`p2`,label:F(`@AGENT:分析代码质量`),prompt:F(`@AGENT:prompt_p2`)},{id:`p3`,label:F(`@AGENT:帮我写测试`),prompt:F(`@AGENT:prompt_p3`)},{id:`p4`,label:F(`@AGENT:Git 状态检查`),prompt:F(`@AGENT:prompt_p4`)}]);async function I(e){await E(e.text,e.files),await o(),z()}async function L(e){await E(e.prompt),await o(),z()}let R=m(null);function z(e=!0){R.value?.scrollToBottom(e)}c(()=>h.value.length,()=>{o(()=>z(!1))});function B(e){if(y.value){p.warning(F(`@AGENT:请先停止当前生成`));return}S(e)}function V(){if(y.value){p.warning(F(`@AGENT:请先停止当前生成`));return}T()}function U(e,t){if(t.stopPropagation(),y.value&&d.value===e){p.warning(F(`@AGENT:请先停止当前生成`));return}C(e)}async function W(e,t,n){n.stopPropagation();try{let{value:n}=await v.prompt(F(`@AGENT:请输入新的会话标题`),F(`@AGENT:重命名会话`),{inputValue:t,inputPattern:/.+/,inputErrorMessage:F(`@AGENT:标题不能为空`)});n&&n!==t&&await w(e,n)}catch{}}function G(e){if(!e)return``;try{let t=new Date(e);if(Number.isNaN(t.getTime()))return e;let n=new Date,r=n.getTime()-t.getTime(),i=e=>String(e).padStart(2,`0`);if(t.toDateString()===n.toDateString())return`${i(t.getHours())}:${i(t.getMinutes())}`;if(r<10080*60*1e3){let e=Math.floor(r/(1440*60*1e3));return e===0?F(`@AGENT:今天`):`${e}${F(`@AGENT:天前`)}`}return`${t.getFullYear()}-${i(t.getMonth()+1)}-${i(t.getDate())}`}catch{return e}}let K=m(280),q=m(!1);function J(e){q.value=!0;let t=e.clientX,n=K.value,r=e=>{let r=e.clientX-t;K.value=Math.max(200,Math.min(500,n+r))},i=()=>{q.value=!1,document.removeEventListener(`mousemove`,r),document.removeEventListener(`mouseup`,i)};document.addEventListener(`mousemove`,r),document.addEventListener(`mouseup`,i)}c(()=>[a.currentDirectory,y.value],async([e,t],n)=>{let r=!n||e!==n[0],i=!!n&&n[1]===!0&&!t;(r||i)&&await x(),d.value&&!y.value&&!l.value.some(e=>e.sessionId===d.value)&&T()},{immediate:!0});let Y={configStore:a,sessions:l,sessionsLoading:u,currentSessionId:d,messages:h,isStreaming:y,sessionLoading:b,loadSessions:x,loadSession:S,deleteSession:C,renameSession:w,newSession:T,sendMessage:E,stop:O,chatTheme:k,searchQuery:A,filteredSessions:M,presetQuestions:N,onSend:I,onSelectPreset:L,chatContainerRef:R,scrollToBottom:z,selectSession:B,handleNewSession:V,handleDelete:U,handleRename:W,formatDate:G,sidebarWidth:K,isResizing:q,startResize:J,get $t(){return F},get ElTooltip(){return r},get ElIcon(){return f},get Plus(){return s},get Search(){return e},get Delete(){return ee},get Edit(){return _},get ChatLineRound(){return g},get Loading(){return n},get ChatContainer(){return j}};return Object.defineProperty(Y,`__isScriptSetup`,{enumerable:!1,value:!0}),Y}}),W={class:`agent-view`},G={class:`sidebar-header`},K={class:`sidebar-search`},q=[`placeholder`],J={class:`session-list`},Y={key:0,class:`empty-state`},X={class:`empty-text`},Z={key:0,class:`empty-hint`},Q=[`onClick`],te={class:`session-item-main`},ne={class:`session-item-title`},re={class:`session-item-meta`},ie={class:`meta-time`},ae={class:`meta-count`},$={key:0,class:`meta-source cli`},oe=[`onClick`],se=[`onClick`],ce={class:`agent-chat-area`},le={key:0,class:`chat-loading`},ue={key:0,class:`stop-button-bar`};function de(e,n,r,o,s,c){let f=S;return u(),l(`div`,W,[O(` ═══ 左侧:会话列表 ═══ `),k(`aside`,{class:`agent-sidebar`,style:d({width:o.sidebarWidth+`px`})},[O(` 顶部操作栏 `),k(`div`,G,[k(`button`,{class:`new-session-btn`,onClick:o.handleNewSession},[t(o.ElIcon,null,{default:h(()=>[t(o.Plus)]),_:1}),k(`span`,null,a(o.$t(`@AGENT:新建会话`)),1)])]),O(` 搜索框 `),k(`div`,K,[t(o.ElIcon,{class:`search-icon`},{default:h(()=>[t(o.Search)]),_:1}),T(k(`input`,{"onUpdate:modelValue":n[0]||=e=>o.searchQuery=e,placeholder:o.$t(`@AGENT:搜索会话...`),class:`search-input`,type:`text`},null,8,q),[[E,o.searchQuery]])]),O(` 会话列表 `),T((u(),l(`div`,J,[o.filteredSessions.length===0&&!o.sessionsLoading?(u(),l(`div`,Y,[t(o.ElIcon,{size:28,color:`var(--text-tertiary)`},{default:h(()=>[t(o.ChatLineRound)]),_:1}),k(`div`,X,a(o.searchQuery?o.$t(`@AGENT:未找到匹配的会话`):o.$t(`@AGENT:暂无会话`)),1),o.searchQuery?O(`v-if`,!0):(u(),l(`div`,Z,a(o.$t(`@AGENT:点击上方按钮开始对话`)),1))])):O(`v-if`,!0),(u(!0),l(x,null,i(o.filteredSessions,e=>(u(),l(`div`,{key:e.sessionId,class:A([`session-item`,{active:o.currentSessionId===e.sessionId}]),onClick:t=>o.selectSession(e.sessionId)},[k(`div`,te,[k(`div`,ne,a(e.title||o.$t(`@AGENT:无标题`)),1),k(`div`,re,[k(`span`,ie,a(o.formatDate(e.updatedAt)),1),n[3]||=k(`span`,{class:`meta-dot`},`·`,-1),k(`span`,ae,a(e.messageCount)+` `+a(o.$t(`@AGENT:条`)),1),e.source===`cli`?(u(),l(`span`,$,`CLI`)):O(`v-if`,!0)])]),k(`div`,{class:`session-item-actions`,onClick:n[1]||=C(()=>{},[`stop`])},[t(o.ElTooltip,{content:o.$t(`@AGENT:重命名`),placement:`top`,"show-after":500},{default:h(()=>[k(`button`,{class:`item-action-btn`,onClick:t=>o.handleRename(e.sessionId,e.title,t)},[t(o.ElIcon,null,{default:h(()=>[t(o.Edit)]),_:1})],8,oe)]),_:2},1032,[`content`]),t(o.ElTooltip,{content:o.$t(`@AGENT:删除`),placement:`top`,"show-after":500},{default:h(()=>[k(`button`,{class:`item-action-btn danger`,onClick:t=>o.handleDelete(e.sessionId,t)},[t(o.ElIcon,null,{default:h(()=>[t(o.Delete)]),_:1})],8,se)]),_:2},1032,[`content`])])],10,Q))),128))])),[[f,o.sessionsLoading]])],4),O(` 拖拽分隔条 `),k(`div`,{class:A([`sidebar-resizer`,{active:o.isResizing}]),onMousedown:o.startResize},null,34),O(` ═══ 右侧:对话区域 ═══ `),k(`main`,ce,[O(` 加载中 `),o.sessionLoading?(u(),l(`div`,le,[t(o.ElIcon,{class:`is-loading`,size:32},{default:h(()=>[t(o.Loading)]),_:1}),k(`span`,null,a(o.$t(`@AGENT:加载会话中...`)),1)])):(u(),l(x,{key:1},[O(` ChatContainer `),t(o.ChatContainer,{ref:`chatContainerRef`,messages:o.messages,"preset-questions":o.presetQuestions,"welcome-title":o.$t(`@AGENT:智能体助手`),"welcome-description":o.$t(`@AGENT:我可以帮你阅读代码、执行命令、修改文件。选择下方话题或直接输入你的问题。`),"assistant-name":`g ai`,theme:o.chatTheme,disabled:o.isStreaming,"upload-config":{accept:`image/*`},placeholder:o.isStreaming?o.$t(`@AGENT:正在生成中...`):o.$t(`@AGENT:输入消息,Enter 发送`),onSend:o.onSend,onSelect:o.onSelectPreset},null,8,[`messages`,`preset-questions`,`welcome-title`,`welcome-description`,`theme`,`disabled`,`placeholder`])],2112)),O(` 停止按钮浮层 `),t(w,{name:`fade`},{default:h(()=>[o.isStreaming?(u(),l(`div`,ue,[k(`button`,{class:`stop-button`,onClick:n[2]||=(...e)=>o.stop&&o.stop(...e)},[t(o.ElIcon,null,{default:h(()=>[t(o.Loading,{class:`is-loading`})]),_:1}),k(`span`,null,a(o.$t(`@AGENT:停止生成`)),1)])])):O(`v-if`,!0)]),_:1})])])}var fe=I(U,[[`render`,de],[`__scopeId`,`data-v-bd681ddd`],[`__file`,`C:/workspace/github_workspace/zen-gitsync/src/ui/client/src/views/AgentView.vue`]]);export{fe as default};
@@ -1,2 +0,0 @@
1
- import"./rolldown-runtime-BM3Ffeng.js";import{B as e,D as t,Dn as n,En as r,Et as i,G as a,Gr as o,In as s,Qn as c,Sn as l,Un as u,Vn as d,Y as f,a as p,bn as m,dr as h,er as g,i as _,kn as v,nt as y,rt as b,vn as x,xn as S,yn as C,zn as w,zr as T}from"./element-plus-DqggPOBO.js";import{N as E,j as D}from"./index-DAgSqrPI.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=v({__name:`UpgradeDialog`,props:{modelValue:{type:Boolean,required:!0},logs:{type:String,required:!0},status:{type:String,required:!0},countdown:{type:Number,required:!1,default:0}},emits:[`update:modelValue`,`retry`,`restart`,`cancel`],setup(e,{expose:t,emit:n}){t();let r=e,a=n,o=h(null);c(()=>r.logs,async()=>{await s(),o.value&&(o.value.scrollTop=o.value.scrollHeight)});function l(){r.status!==`running`&&(r.status===`success`&&r.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:r,emit:a,logEl:o,close:l,onRetry:u,onRestart:d,onLater:f,get Loading(){return i},get CircleCheck(){return y},get CircleClose(){return b},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(i,a,s,c,d,p){let h=f,_=e,v=t;return u(),m(v,{"model-value":s.modelValue,"onUpdate:modelValue":a[0]||=e=>c.emit(`update:modelValue`,e),title:c.$t(`@F13B4:升级`),width:`680px`,"close-on-click-modal":!1,"close-on-press-escape":!1,"show-close":s.status!==`running`,"append-to-body":``,onClose:c.close},{footer:g(()=>[s.status===`failed`?(u(),m(_,{key:0,type:`primary`,onClick:c.onRetry},{default:g(()=>[r(o(c.$t(`@F13B4:重试`)),1)]),_:1})):S(`v-if`,!0),s.status===`success`?(u(),m(_,{key:1,type:`primary`,onClick:c.onRestart},{default:g(()=>[r(o(c.$t(`@F13B4:立即重启并刷新`)),1)]),_:1})):S(`v-if`,!0),s.status===`success`?(u(),m(_,{key:2,onClick:c.onLater},{default:g(()=>[r(o(c.$t(`@F13B4:稍后手动重启`)),1)]),_:1})):S(`v-if`,!0),s.status===`running`?(u(),m(_,{key:3,disabled:``},{default:g(()=>[r(o(c.$t(`@F13B4:升级中`))+`... `,1)]),_:1})):S(`v-if`,!0)]),default:g(()=>[C(`div`,{class:T([`upgrade-status`,`is-${s.status}`])},[s.status===`running`?(u(),m(h,{key:0,class:`is-loading`},{default:g(()=>[n(c.Loading)]),_:1})):s.status===`success`?(u(),m(h,{key:1,color:`#67c23a`},{default:g(()=>[n(c.CircleCheck)]),_:1})):(u(),m(h,{key:2,color:`#f56c6c`},{default:g(()=>[n(c.CircleClose)]),_:1})),s.status===`running`?(u(),l(`span`,N,o(c.$t(`@F13B4:升级中`)),1)):s.status===`success`?(u(),l(`span`,P,o(c.$t(`@F13B4:升级完成`)),1)):(u(),l(`span`,F,o(c.$t(`@F13B4:升级失败`)),1))],2),s.status===`success`?(u(),l(`p`,I,[r(o(c.$t(`@F13B4:新版本已全局安装,需要重启服务才能生效`))+` `,1),s.countdown>0?(u(),l(`span`,L,o(c.$t(`@F13B4:{seconds} 秒后自动刷新`,{seconds:s.countdown})),1)):S(`v-if`,!0)])):S(`v-if`,!0),C(`pre`,{ref:`logEl`,class:`upgrade-log`},o(s.logs||c.$t(`@F13B4:等待日志输出`)),513)]),_:1},8,[`model-value`,`title`,`show-close`])}var z=D(M,[[`render`,R],[`__scopeId`,`data-v-bc566bba`],[`__file`,`C:/workspace/github_workspace/zen-gitsync/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=v({__name:`AppVersionBadge`,setup(e,{expose:t}){t();let n=`2.16.42`,r=h(n),i=x(()=>r.value||n),a=h(null),o=h(!1),s=h(``),c=h(`running`),l=h(0),u=null,f=h(!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 v(e=!1){let t=m();t&&(a.value=t);try{let t=await k();a.value=t,g(t),t.current&&(r.value=t.current),!e&&t.success&&t.hasUpdate&&p({type:`success`,message:E(`@F13B4:发现新版本 {version}`,{version:t.latest??``}),duration:4e3})}catch(t){e||p({type:`warning`,message:E(`@F13B4:检查更新失败 {error}`,{error:t?.message||``})})}}async function y(){if(a.value?.hasUpdate){try{await _.confirm(E(`@F13B4:确定要从 {current} 升级到 {latest} 吗?`,{current:a.value.current,latest:a.value.latest}),E(`@F13B4:升级`),{confirmButtonText:E(`@F13B4:确定升级`),cancelButtonText:E(`@F13B4:取消`),type:`info`})}catch{return}s.value=``,c.value=`running`,o.value=!0,await A(e=>{if(e.type===`done`){if(c.value=e.code===0?`success`:`failed`,e.code===0){try{sessionStorage.removeItem(H)}catch{}a.value?.latest&&(r.value=a.value.latest),b()}}else e.message&&(s.value+=e.message)})}}function b(){S(),l.value=V,u=setInterval(()=>{--l.value,l.value<=0&&(u&&=(clearInterval(u),null),T())},1e3)}function S(){u&&=(clearInterval(u),null),l.value=0}function C(){S()}async function T(){if(S(),!f.value){f.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];p({type:`warning`,message:n?E(n):E(`@F13B4:重启失败 {error}`,{error:t})}),f.value=!1}}}w(()=>{S()}),d(()=>{v(!0)});let D={buildVersion:n,releasesUrl:B,runtimeVersion:r,displayVersion:i,latestInfo:a,upgradeDialogVisible:o,upgradeLogs:s,upgradeStatus:c,AUTO_REFRESH_SECONDS:V,countdown:l,get countdownTimer(){return u},set countdownTimer(e){u=e},restartInFlight:f,CACHE_KEY:H,CACHE_TTL_MS:U,readCache:m,writeCache:g,checkUpdate:v,handleUpgrade:y,startAutoRefreshCountdown:b,cancelAutoRefreshCountdown:S,handleCancelAutoRefresh:C,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,t,r,i,s,c){let d=a;return u(),l(`div`,G,[S(` GitHub 链接 `),n(d,{content:i.$t(`@F13B4:在 GitHub 上查看源码`),placement:`top`,effect:`dark`,"show-after":300},{default:g(()=>[C(`a`,{href:`https://github.com/xz333221/zen-gitsync`,target:`_blank`,rel:`noopener noreferrer`,class:`badge-icon-link`,"aria-label":i.$t(`@F13B4:在 GitHub 上查看源码`)},[...t[1]||=[C(`svg`,{viewBox:`0 0 24 24`,width:`15`,height:`15`,"aria-hidden":`true`,fill:`currentColor`,xmlns:`http://www.w3.org/2000/svg`},[C(`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`]),S(` NPM 链接 `),n(d,{content:i.$t(`@F13B4:在 npm 上查看包`),placement:`top`,effect:`dark`,"show-after":300},{default:g(()=>[C(`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 上查看包`)},[...t[2]||=[C(`svg`,{viewBox:`0 0 24 24`,width:`15`,height:`15`,"aria-hidden":`true`,fill:`currentColor`,xmlns:`http://www.w3.org/2000/svg`},[C(`path`,{d:`M2 2v20h20V2H2zm17.167 17.167H12.5v-10h-3.333v10H4.833V4.833h14.334v14.334z`})],-1)]],8,q)]),_:1},8,[`content`]),S(` 版本号 `),n(d,{content:i.$t(`@F13B4:查看更新`),placement:`top`,effect:`dark`,"show-after":300},{default:g(()=>[C(`a`,{href:i.releasesUrl,target:`_blank`,rel:`noopener noreferrer`,class:`version-link`},`v`+o(i.displayVersion),1)]),_:1},8,[`content`]),i.latestInfo?.hasUpdate?(u(),l(`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},[t[3]||=C(`span`,{class:`upgrade-btn__dot`,"aria-hidden":`true`},null,-1),C(`span`,Y,o(i.$t(`@F13B4:升级`)),1),C(`span`,X,`v`+o(i.latestInfo.latest),1)],8,J)):S(`v-if`,!0),n(i.UpgradeDialog,{modelValue:i.upgradeDialogVisible,"onUpdate:modelValue":t[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`,`C:/workspace/github_workspace/zen-gitsync/src/ui/client/src/components/AppVersionBadge.vue`]]);export{Q as default};