zen-gitsync 2.18.27 → 2.19.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 (51) hide show
  1. package/package.json +6 -2
  2. package/src/cli/ai/tools.js +71 -8
  3. package/src/cli/ai/webSearch.js +108 -21
  4. package/src/cli/peer.js +272 -0
  5. package/src/config.js +14 -4
  6. package/src/gitCommit.js +34 -1
  7. package/src/paths.js +6 -0
  8. package/src/peer/client.js +165 -0
  9. package/src/peer/localAsk.js +130 -0
  10. package/src/peer/server.js +183 -0
  11. package/src/peer/store.js +292 -0
  12. package/src/ui/public/assets/{AgentView-DunbYHMn.js → AgentView-BtnAYtte.js} +1 -1
  13. package/src/ui/public/assets/AppVersionBadge-BcXo-yuu.js +2 -0
  14. package/src/ui/public/assets/{BranchSelector-BKA3fL9E.js → BranchSelector-5t-T3FB6.js} +1 -1
  15. package/src/ui/public/assets/{CommitForm-CVPGpci8.js → CommitForm-BMfp3Eqh.js} +1 -1
  16. package/src/ui/public/assets/{CommonDialog-ZwJlTF5A.js → CommonDialog-BiF2fMgr.js} +1 -1
  17. package/src/ui/public/assets/{EditorView-DDzACKq1.js → EditorView-CtmZ7RZp.js} +1 -1
  18. package/src/ui/public/assets/{FlowExecutionViewer-BJOiCa6S.js → FlowExecutionViewer-D2lchSo7.js} +1 -1
  19. package/src/ui/public/assets/{FlowOrchestrationWorkspace-BDnAdRr9.js → FlowOrchestrationWorkspace-BY2zLwWB.js} +1 -1
  20. package/src/ui/public/assets/{LogList-DbGz7_dy.js → LogList-DTfAGnlt.js} +1 -1
  21. package/src/ui/public/assets/{MindmapView-mbTFeVj8.js → MindmapView-DArneR4u.js} +1 -1
  22. package/src/ui/public/assets/{MonitorView-O7I40RiB.js → MonitorView-BQvMyjEj.js} +1 -1
  23. package/src/ui/public/assets/{ProjectManagerView-D1NVbIOW.js → ProjectManagerView-BRUyrHfg.js} +1 -1
  24. package/src/ui/public/assets/{ProjectStartupButton-C21zy3Bg.js → ProjectStartupButton-D0f8OAb-.js} +1 -1
  25. package/src/ui/public/assets/{RemoteManagerDialog-CpSrItn2.js → RemoteManagerDialog-DXDXCfyD.js} +1 -1
  26. package/src/ui/public/assets/{RemoteRepoCard-CXDXPJK2.js → RemoteRepoCard-BRnjC6vm.js} +1 -1
  27. package/src/ui/public/assets/{SourceMapView-C9qTeaOC.js → SourceMapView-B6xgG5mi.js} +1 -1
  28. package/src/ui/public/assets/{SvgIcon-BmXhx-vA.js → SvgIcon-GLj_3KcS.js} +1 -1
  29. package/src/ui/public/assets/{UserInputNode-i7ER8ZCn.js → UserInputNode-C6Z6Kys_.js} +1 -1
  30. package/src/ui/public/assets/{WorkbenchView-pZz9ykZT.js → WorkbenchView-B2EUXleS.js} +1 -1
  31. package/src/ui/public/assets/_plugin-vue_export-helper-DaO48JOV.js +19 -0
  32. package/src/ui/public/assets/configStore-B_2ifbl_.js +1 -0
  33. package/src/ui/public/assets/{index-DW-2cjNW.js → index-DUzH5036.js} +63 -60
  34. package/src/ui/public/assets/index-zdGFM_7W.css +1 -0
  35. package/src/ui/public/assets/{relativeTime-DsG6UERK.js → relativeTime-DzYQUHQo.js} +1 -1
  36. package/src/ui/public/assets/{taskExecutionExport-C3dV5cuE.js → taskExecutionExport-C3vFixdJ.js} +1 -1
  37. package/src/ui/public/assets/{useWorkbenchProjects-B6k5mmVC.js → useWorkbenchProjects-0fWZsDRX.js} +1 -1
  38. package/src/ui/public/index.html +6 -6
  39. package/src/ui/server/index.js +130 -10
  40. package/src/ui/server/routes/config.js +1317 -1316
  41. package/src/ui/server/routes/peerSettings.js +164 -0
  42. package/src/ui/server/routes/workbench/agentChat.js +811 -811
  43. package/src/ui/server/routes/workbench/agentRoutes.js +9 -0
  44. package/src/ui/server/utils/ensureHub.js +101 -0
  45. package/src/ui/server/utils/instanceRegistry.js +8 -3
  46. package/src/ui/server/utils/peerRuntime.js +77 -0
  47. package/src/ui/server/utils/startServerOnAvailablePort.js +6 -0
  48. package/src/ui/public/assets/AppVersionBadge-9B4b0hbP.js +0 -2
  49. package/src/ui/public/assets/_plugin-vue_export-helper-D6ACRIgd.js +0 -19
  50. package/src/ui/public/assets/configStore-BQoByr2v.js +0 -1
  51. package/src/ui/public/assets/index-B9mqQJGC.css +0 -1
@@ -31,6 +31,7 @@ import { normalizeAgentEngine, isExternalEngine, engineLabel } from './agentEngi
31
31
  import { saveAgentAttachments, MAX_ATTACHMENTS } from '../../utils/agentAttachments.js';
32
32
  import { registerAgentMarketplaceRoutes } from './agentMarketplace.js';
33
33
  import { createProjectListProvider } from './projectTool.js';
34
+ import { askPeerByRef, listPeerStatus } from '../../../../peer/client.js';
34
35
  import { createDispatcher } from './dispatchInstruction.js';
35
36
  import { createWorkspaceSnapshotter } from '../aiContext/wiring.js';
36
37
  // 用户固定提示词(g ai 默认提示词 + 派发提示词)的解析 —— 两条引擎链路共用同一份
@@ -205,6 +206,12 @@ export function registerAgentRoutes({ app, getCurrentProjectPath, configManager,
205
206
  // 不给它加缓存:agent 一轮里最多问一两次,而"项目状态"本来就要现读才准。
206
207
  const listProjects = createProjectListProvider({ configManager, getCurrentProjectPath });
207
208
 
209
+ // ask_peer 的两半:对端清单(实时探活)与"问一句"。数据源是 <DATA_DIR>/peers.json,
210
+ // 与本机项目无关,所以直接复用 src/peer/ 的实现 —— 这里不做二次包装。
211
+ // 工具在 CLI(g ai)下没有 peers 上下文,见 tools.js 的 toolAskPeer 兜底说明。
212
+ const askPeer = askPeerByRef;
213
+ const listPeers = () => listPeerStatus({ probe: true });
214
+
208
215
  // 把目录登记进「常用目录」(config.json 的 recentDirectories)。register_project 工具
209
216
  // 与 run_command 里"建了仓库就顺手登记"那一步共用它 —— 智能体建/克隆完新项目,
210
217
  // 用户下次在「切换工作目录」弹窗里就能直接看到,不必重启 GUI 或自己翻路径。
@@ -812,6 +819,8 @@ export function registerAgentRoutes({ app, getCurrentProjectPath, configManager,
812
819
  dispatchTask,
813
820
  listProjects,
814
821
  registerProject,
822
+ askPeer,
823
+ listPeers,
815
824
  getContextBlock,
816
825
  // 每轮请求的上下文预算(解析结果)。agentChat 拿它调 prepareRequestMessages,
817
826
  // 与 CLI turn.js 同一条口径。
@@ -0,0 +1,101 @@
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
+ // 「开机就有 hub」:项目窗口起来时,发现这台机器上没有活着的 hub,就顺手拉一个。
16
+ //
17
+ // 为什么不要求用户手动先起 hub:拆成两个角色之后,工作台 / 项目管理 / 系统监控 /
18
+ // 思维导图只存在于 hub 进程里。如果每次都得先记得「先开 hub 再开项目窗口」,
19
+ // 这套拆分就是个负担而不是改进。
20
+ //
21
+ // 三段式(顺序很重要):
22
+ // 1. 查实例注册表 —— 有 role=hub 且心跳新鲜的,直接复用(不重复拉进程);
23
+ // 2. 没有才 spawn 一个 detached 的 `g ui --role=hub --port=<固定端口>`;
24
+ // 3. 无论哪种结果都把「hub 在哪个端口」返回给调用方打日志。
25
+ //
26
+ // 为什么端口固定:它要长期被别的窗口/脚本按固定地址访问。hub 那边 maxTries=1,
27
+ // 被占就直接失败 —— 所以两个窗口同时启动时,后起那个只是抢不到端口并退出,
28
+ // 不会出现"hub 悄悄跑到 5601 而没人知道"。
29
+
30
+ import { spawn } from 'node:child_process';
31
+ import { openSync, closeSync, promises as fsPromises } from 'node:fs';
32
+ import { fileURLToPath } from 'node:url';
33
+ import path from 'node:path';
34
+ import os from 'node:os';
35
+
36
+ import { createInstanceRegistry, getRegistryPath } from './instanceRegistry.js';
37
+ import { DATA_DIR } from '../../../paths.js';
38
+
39
+ /** hub 的默认固定端口。peer 默认用 5600(见 src/peer/store.js),两者不重叠。 */
40
+ export const HUB_DEFAULT_PORT = 5599;
41
+
42
+ /** 自动拉起的 hub 的日志(stdio 走 ignore 的话,端口冲突这类失败会彻底静默)。 */
43
+ export const HUB_LOG_FILE = path.join(DATA_DIR, 'hub.log');
44
+
45
+ /** 从模块位置定位 CLI 入口,不依赖调用方怎么被启动(g ui / node server.js / nodemon)。 */
46
+ function resolveCliEntry() {
47
+ return fileURLToPath(new URL('../../../gitCommit.js', import.meta.url));
48
+ }
49
+
50
+ /**
51
+ * 确保本机有活着的 hub。
52
+ *
53
+ * @param {object} [opts]
54
+ * @param {number} [opts.port] 期望的 hub 端口(默认 HUB_DEFAULT_PORT)
55
+ * @param {string} [opts.cwd] hub 进程的工作目录(默认继承当前进程)
56
+ * @returns {Promise<{reused:boolean, port:number, pid:number|null}>}
57
+ */
58
+ export async function ensureHubRunning({ port = HUB_DEFAULT_PORT, cwd } = {}) {
59
+ const registry = createInstanceRegistry({
60
+ fs: fsPromises,
61
+ path,
62
+ os,
63
+ registryPath: getRegistryPath()
64
+ });
65
+
66
+ // ① 已经有一个活着的 hub(心跳新鲜 + pid 存活由 list() 统一裁剪)
67
+ try {
68
+ const instances = await registry.list();
69
+ const live = instances.find((i) => i.role === 'hub');
70
+ if (live) return { reused: true, port: live.port, pid: live.pid };
71
+ } catch {
72
+ // 注册表读挂了不该挡住主流程,继续往下走去 spawn
73
+ }
74
+
75
+ // ② 拉一个新的
76
+ let fd = null;
77
+ try {
78
+ fd = openSync(HUB_LOG_FILE, 'a');
79
+ } catch {
80
+ // 日志都开不了就退化成 ignore —— hub 照起,只是失败信息看不到
81
+ }
82
+
83
+ // 浏览器要开:hub 是"另一扇窗",只在**新拉起**时开一次(复用已有 hub 时不开 ——
84
+ // 否则每开一个项目窗口都多蹦一个标签页,而那个页面本来就在)。
85
+ // ZEN_NO_OPEN=1 可以关掉(CI / 无头环境)。
86
+ const args = [resolveCliEntry(), 'ui', '--role=hub', `--port=${port}`];
87
+ if (process.env.ZEN_NO_OPEN === '1') args.push('--no-open');
88
+
89
+ const child = spawn(process.execPath, args, {
90
+ detached: true,
91
+ // 关键:不继承 stdout,否则父窗口退出时管道断开会把 hub 一起带走
92
+ stdio: fd === null ? 'ignore' : ['ignore', fd, fd],
93
+ cwd: cwd || process.cwd(),
94
+ windowsHide: true,
95
+ env: { ...process.env, ZEN_HUB_AUTOSTART: '1' }
96
+ });
97
+ child.unref();
98
+ if (fd !== null) { try { closeSync(fd); } catch {} }
99
+
100
+ return { reused: false, port, pid: child.pid ?? null };
101
+ }
@@ -317,7 +317,7 @@ export function createInstanceRegistry({
317
317
  //
318
318
  // 关键点:register / heartbeat / unregister 都只读写 *自己* 的 entry 文件,
319
319
  // 跨进程并发时不同 PID 的写入互不干扰,从根本上消除 read-modify-write 覆盖。
320
- async function register({ pid, port, projectPath, projectName, hostname } = {}) {
320
+ async function register({ pid, port, projectPath, projectName, hostname, role } = {}) {
321
321
  if (!Number.isInteger(pid) || pid <= 0) throw new Error('register: pid 必填');
322
322
  if (!Number.isInteger(port) || port <= 0) throw new Error('register: port 必填');
323
323
  if (!projectPath) throw new Error('register: projectPath 必填');
@@ -333,7 +333,10 @@ export function createInstanceRegistry({
333
333
  projectPath,
334
334
  startedAt: Date.now(),
335
335
  lastHeartbeat: Date.now(),
336
- hostname: hostname || osMod.hostname()
336
+ hostname: hostname || osMod.hostname(),
337
+ // 运行角色(project | hub | full)。用来回答"这台机器上有没有活着的 hub"——
338
+ // 没有就由新起的项目窗口拉一个(见 utils/ensureHub.js)。
339
+ role: role === 'hub' ? 'hub' : (role === 'full' ? 'full' : 'project')
337
340
  };
338
341
 
339
342
  await enqueueWrite(async () => {
@@ -383,7 +386,8 @@ export function createInstanceRegistry({
383
386
  projectPath: updates.projectPath,
384
387
  startedAt: Date.now(),
385
388
  lastHeartbeat: Date.now(),
386
- hostname: updates.hostname || osMod.hostname()
389
+ hostname: updates.hostname || osMod.hostname(),
390
+ role: updates.role === 'hub' ? 'hub' : (updates.role === 'full' ? 'full' : 'project')
387
391
  };
388
392
  logger.info(
389
393
  `[instanceRegistry] 心跳检测到本实例条目丢失,已自愈重建 pid=${pid} port=${updates.port}`
@@ -394,6 +398,7 @@ export function createInstanceRegistry({
394
398
  ...(updates.projectPath ? { projectPath: updates.projectPath } : {}),
395
399
  ...(updates.projectName ? { projectName: updates.projectName } : {}),
396
400
  ...(Number.isInteger(updates.port) && updates.port > 0 ? { port: updates.port } : {}),
401
+ ...(updates.role ? { role: updates.role } : {}),
397
402
  lastHeartbeat: Date.now()
398
403
  };
399
404
  }
@@ -0,0 +1,77 @@
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
+ // peer 监听的生命周期(进程内单例)。
16
+ //
17
+ // 为什么要有这一层:设置面板上「允许其他机器提问」是个开关,如果它只在启动时读一次,
18
+ // 用户改完还得重启 g ui —— 那这个开关就等于摆设。这里把"读配置 → 起/停监听"收成
19
+ // 一个函数,启动时调一次,开关一改再调一次,行为完全一致。
20
+ //
21
+ // 单例的必要性:同一进程只能绑同一个端口。重复 apply 必须先停旧的再起新的,
22
+ // 否则改端口会留下一个"还在旧端口上回答"的幽灵实例。
23
+
24
+ import { startPeerServer } from '../../../peer/server.js';
25
+ import logger from './logger.js';
26
+
27
+ /** @type {import('node:http').Server|null} */
28
+ let handle = null;
29
+ /** 本机 g ui 的端口(peer 服务端收到提问后要环回调用它)。 */
30
+ let localPort = 0;
31
+
32
+ /** g ui 真正就绪后由 index.js 注入。 */
33
+ export function setPeerLocalPort(port) {
34
+ localPort = Number(port) || 0;
35
+ }
36
+
37
+ /** 当前是否在监听。 */
38
+ export function isPeerRunning() {
39
+ return !!handle;
40
+ }
41
+
42
+ /** 停掉监听(幂等)。 */
43
+ export async function stopPeerRuntime() {
44
+ if (!handle) return;
45
+ const h = handle;
46
+ handle = null;
47
+ await new Promise((resolve) => {
48
+ try {
49
+ h.close(() => resolve());
50
+ } catch {
51
+ resolve();
52
+ }
53
+ });
54
+ }
55
+
56
+ /**
57
+ * 按当前身份配置应用 peer 监听:enabled=true 就起,false 就停。
58
+ *
59
+ * @param {object} self peers.json 里的 self
60
+ * @param {{getVersion?: () => string}} [opts]
61
+ * @returns {Promise<{running:boolean, port:number|null}>}
62
+ */
63
+ export async function applyPeerRuntime(self, { getVersion } = {}) {
64
+ await stopPeerRuntime();
65
+ if (!self?.enabled || !self.token) return { running: false, port: null };
66
+ if (!localPort) {
67
+ logger.warn('[peer] 本机端口未知,暂时无法开启(主服务还没 listening)');
68
+ return { running: false, port: null };
69
+ }
70
+
71
+ handle = await startPeerServer({
72
+ self,
73
+ getLocalPort: () => localPort,
74
+ getVersion
75
+ });
76
+ return { running: true, port: self.port };
77
+ }
@@ -252,6 +252,12 @@ export async function startServerOnAvailablePort({
252
252
  }
253
253
  }
254
254
 
255
+ // 固定端口模式(maxTries=1):把"被谁占 / 为什么不让绑"说清楚,别让用户去猜
256
+ if (maxTries <= 1) {
257
+ logger.error(`固定端口 ${startPort} 不可用(${lastErrorCode || '未知原因'})。固定端口模式不会顺延。`);
258
+ logger.error('处理办法:换一个 --port,或先关掉占用该端口的进程(netstat -ano | findstr :' + startPort + ')。');
259
+ process.exit(1);
260
+ }
255
261
  logger.error(`无法找到可用端口 (尝试范围: ${startPort}-${maxPort - 1})`);
256
262
  if (process.platform === 'win32' && getExcludedPortRanges().length) {
257
263
  logger.error(
@@ -1,2 +0,0 @@
1
- import"./rolldown-runtime-C0FnF6B9.js";import{An as e,Bn as t,Cn as n,Dt as r,Gn as i,In as a,J as o,Mn as s,Sn as c,Tn as l,Un as u,W as d,Wr as f,Xr as p,gr as m,i as h,ir as g,jr as _,kn as v,lr as y,nr as b,nt as x,qn as S,r as C,tt as w,wn as T,xn as E,z as D}from"./element-plus-CosPN4uA.js";import{n as O,t as k}from"./_plugin-vue_export-helper-D6ACRIgd.js";import{t as A}from"./CommonDialog-ZwJlTF5A.js";async function j(e,t){let n=e.body.getReader(),r=new TextDecoder(`utf-8`),i=``;for(;;){let{done:e,value:a}=await n.read();if(e)break;i+=r.decode(a,{stream:!0});let o=i.split(`
2
- `);i=o.pop()??``;for(let e of o)e.trim()&&t(e)}i.trim()&&t(i)}async function M(){let e=await fetch(`/api/app-version`);return e.ok?e.json():{success:!1,current:``,latest:null,hasUpdate:!1,error:`HTTP ${e.status}`}}async function N(e){let t=await fetch(`/api/app-upgrade`,{method:`POST`});if(!t.ok){let n=`HTTP ${t.status}`;try{let e=await t.json();e?.error&&(n=e.error)}catch{}e({type:`error`,message:n}),e({type:`done`,code:t.status});return}if(!t.body){e({type:`error`,message:`响应无 body`}),e({type:`done`,code:-1});return}await j(t,t=>{try{e(JSON.parse(t))}catch{}})}async function P(){let e=await fetch(`/api/app-restart`,{method:`POST`});if(!e.ok){let t=`HTTP ${e.status}`;try{let n=await e.json();n?.error&&(t=n.error)}catch{}throw Error(t)}if(!e.body)throw Error(`响应无 body`);return new Promise((t,n)=>{let r=!1,i=e=>{r||(r=!0,e())};j(e,e=>{try{let r=JSON.parse(e);r.type===`ready`&&typeof r.port==`number`&&r.port>0?i(()=>t(r.port)):r.type===`timeout`?i(()=>n(Error(`restart.timeout`))):r.type===`error`&&i(()=>n(Error(r.message||`restart.failed`)))}catch{}}).then(()=>{i(()=>n(Error(`restart.stream-closed`)))}).catch(()=>{})})}a(),y();var F=`npm install -g zen-gitsync --registry https://registry.npmjs.org/`,I=s({__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:n,emit:i}){n();let a=e,o=i,s=m(null);b(()=>a.logs,async()=>{await t(),s.value&&(s.value.scrollTop=s.value.scrollHeight)});function c(){a.status!==`running`&&(a.status===`success`&&a.countdown>0&&o(`cancel`),o(`update:modelValue`,!1))}function l(){o(`retry`)}function u(){o(`restart`)}function d(){o(`cancel`),o(`update:modelValue`,!1)}async function f(){try{await navigator.clipboard.writeText(F),h.success(O(`@F13B4:已复制升级命令`))}catch{h.warning(O(`@F13B4:复制失败,请手动复制`))}}let p={MANUAL_COMMAND:F,props:a,emit:o,logEl:s,close:c,onRetry:l,onRestart:u,onLater:d,onCopyCommand:f,get Loading(){return r},get CircleCheck(){return w},get CircleClose(){return x},get $t(){return O},CommonDialog:A};return Object.defineProperty(p,"__isScriptSetup",{enumerable:!1,value:!0}),p}});a(),_();var L={key:3},R={key:4},z={key:5},B={key:0,class:`upgrade-hint`},V={key:0,class:`upgrade-countdown`},H={key:1,class:`upgrade-manual`},U={class:`upgrade-manual__label`};function W(t,r,i,a,s,u){let d=o,m=D;return S(),n(a.CommonDialog,{"model-value":i.modelValue,"onUpdate:modelValue":r[0]||=e=>a.emit(`update:modelValue`,e),title:a.$t(`@F13B4:升级`),width:`680px`,type:`flex`,"close-on-click-modal":!1,"close-on-press-escape":!1,"show-close":i.status!==`running`,"append-to-body":``,onClose:a.close},{footer:g(()=>[i.status===`failed`?(S(),n(m,{key:0,type:`primary`,onClick:a.onRetry},{default:g(()=>[v(p(a.$t(`@F13B4:重试`)),1)]),_:1})):T(`v-if`,!0),i.status===`failed`?(S(),n(m,{key:1,onClick:a.onCopyCommand},{default:g(()=>[v(p(a.$t(`@F13B4:复制升级命令`)),1)]),_:1})):T(`v-if`,!0),i.status===`success`?(S(),n(m,{key:2,type:`primary`,onClick:a.onRestart},{default:g(()=>[v(p(a.$t(`@F13B4:立即重启并刷新`)),1)]),_:1})):T(`v-if`,!0),i.status===`success`?(S(),n(m,{key:3,onClick:a.onLater},{default:g(()=>[v(p(a.$t(`@F13B4:稍后手动重启`)),1)]),_:1})):T(`v-if`,!0),i.status===`running`?(S(),n(m,{key:4,disabled:``},{default:g(()=>[v(p(a.$t(`@F13B4:升级中`))+`... `,1)]),_:1})):T(`v-if`,!0)]),default:g(()=>[c(`div`,{class:f([`upgrade-status`,`is-${i.status}`])},[i.status===`running`?(S(),n(d,{key:0,class:`is-loading`},{default:g(()=>[e(a.Loading)]),_:1})):i.status===`success`?(S(),n(d,{key:1,color:`var(--color-success)`},{default:g(()=>[e(a.CircleCheck)]),_:1})):(S(),n(d,{key:2,color:`var(--color-danger)`},{default:g(()=>[e(a.CircleClose)]),_:1})),i.status===`running`?(S(),l(`span`,L,p(a.$t(`@F13B4:升级中`)),1)):i.status===`success`?(S(),l(`span`,R,p(a.$t(`@F13B4:升级完成`)),1)):(S(),l(`span`,z,p(a.$t(`@F13B4:升级失败`)),1))],2),i.status===`success`?(S(),l(`p`,B,[v(p(a.$t(`@F13B4:新版本已全局安装,需要重启服务才能生效`))+` `,1),i.countdown>0?(S(),l(`span`,V,p(a.$t(`@F13B4:{seconds} 秒后自动刷新`,{seconds:i.countdown})),1)):T(`v-if`,!0)])):T(`v-if`,!0),i.status===`failed`?(S(),l(`p`,H,[c(`span`,U,p(a.$t(`@F13B4:手动升级命令`)),1),c(`code`,{class:`upgrade-manual__cmd`},p(a.MANUAL_COMMAND))])):T(`v-if`,!0),c(`pre`,{ref:`logEl`,class:`upgrade-log`},p(i.logs||a.$t(`@F13B4:等待日志输出`)),513)]),_:1},8,[`model-value`,`title`,`show-close`])}var G=k(I,[[`render`,W],[`__scopeId`,`data-v-bc566bba`],[`__file`,`D:/workspace/github_workspace/zen-gitsync/src/ui/client/src/components/UpgradeDialog.vue`]]);a(),y();var K=`https://github.com/xz333221/zen-gitsync/tags`,q=5,J=`app-version-check`,Y=36e5,X=s({__name:`AppVersionBadge`,setup(e,{expose:t}){t();let n=`2.18.27`,r=m(n),a=E(()=>r.value||n),o=m(null),s=m(!1),c=m(``),l=m(`running`),d=m(0),f=null,p=m(!1);function g(){try{let e=sessionStorage.getItem(J);if(!e)return null;let{at:t,data:n}=JSON.parse(e);return Date.now()-t>Y?null:n}catch{return null}}function _(e){try{sessionStorage.setItem(J,JSON.stringify({at:Date.now(),data:e}))}catch{}}async function v(e=!1){let t=g();t&&(o.value=t);try{let t=await M();o.value=t,_(t),t.current&&(r.value=t.current),!e&&t.success&&t.hasUpdate&&h({type:`success`,message:O(`@F13B4:发现新版本 {version}`,{version:t.latest??``}),duration:4e3})}catch(t){e||h({type:`warning`,message:O(`@F13B4:检查更新失败 {error}`,{error:t?.message||``})})}}async function y(){if(o.value?.hasUpdate){try{await C.confirm(O(`@F13B4:确定要从 {current} 升级到 {latest} 吗?`,{current:o.value.current,latest:o.value.latest}),O(`@F13B4:升级`),{confirmButtonText:O(`@F13B4:确定升级`),cancelButtonText:O(`@F13B4:取消`),type:`info`})}catch{return}c.value=``,l.value=`running`,s.value=!0,await N(e=>{if(e.type===`done`){if(l.value=e.code===0?`success`:`failed`,e.code===0){try{sessionStorage.removeItem(J)}catch{}o.value?.latest&&(r.value=o.value.latest),b()}}else e.message&&(c.value+=e.message)})}}function b(){x(),d.value=q,f=setInterval(()=>{--d.value,d.value<=0&&(f&&=(clearInterval(f),null),w())},1e3)}function x(){f&&=(clearInterval(f),null),d.value=0}function S(){x()}async function w(){if(x(),!p.value){p.value=!0;try{let e=await P(),{pathname:t,search:n,hash:r}=window.location;window.location.href=`http://localhost:${e}${t}${n}${r}`}catch(e){let t=e?.message||``,n={"restart.timeout":`@F13B4:重启超时`,"restart.stream-closed":`@F13B4:重启失败,请刷新页面重试`,"restart.failed":`@F13B4:重启失败`}[t];h({type:`warning`,message:n?O(n):O(`@F13B4:重启失败 {error}`,{error:t})}),p.value=!1}}}u(()=>{x()}),i(()=>{v(!0)});let T={buildVersion:n,releasesUrl:K,runtimeVersion:r,displayVersion:a,latestInfo:o,upgradeDialogVisible:s,upgradeLogs:c,upgradeStatus:l,AUTO_REFRESH_SECONDS:q,countdown:d,get countdownTimer(){return f},set countdownTimer(e){f=e},restartInFlight:p,CACHE_KEY:J,CACHE_TTL_MS:Y,readCache:g,writeCache:_,checkUpdate:v,handleUpgrade:y,startAutoRefreshCountdown:b,cancelAutoRefreshCountdown:x,handleCancelAutoRefresh:S,handleRestart:w,get $t(){return O},UpgradeDialog:G};return Object.defineProperty(T,"__isScriptSetup",{enumerable:!1,value:!0}),T}});a(),_();var Z={class:`app-version-badge`},Q=[`aria-label`],$=[`aria-label`],ee=[`title`,`aria-label`],te={class:`upgrade-btn__label`},ne={class:`upgrade-btn__version`};function re(t,n,r,i,a,o){let s=d;return S(),l(`div`,Z,[T(` GitHub 链接 `),e(s,{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 上查看源码`)},[...n[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,Q)]),_:1},8,[`content`]),T(` NPM 链接 `),e(s,{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 上查看包`)},[...n[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,$)]),_:1},8,[`content`]),T(` 版本号 `),e(s,{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`+p(i.displayVersion),1)]),_:1},8,[`content`]),i.latestInfo?.hasUpdate?(S(),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},[n[3]||=c(`span`,{class:`upgrade-btn__dot`,"aria-hidden":`true`},null,-1),c(`span`,te,p(i.$t(`@F13B4:升级`)),1),c(`span`,ne,`v`+p(i.latestInfo.latest),1)],8,ee)):T(`v-if`,!0),e(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 ie=k(X,[[`render`,re],[`__scopeId`,`data-v-7e0300ec`],[`__file`,`D:/workspace/github_workspace/zen-gitsync/src/ui/client/src/components/AppVersionBadge.vue`]]);export{ie as default};