zen-gitsync 2.18.16 → 2.18.18
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.
- package/README.md +44 -6
- package/package.json +3 -2
- package/src/ui/public/assets/AgentView-BE7GD8rc.css +1 -0
- package/src/ui/public/assets/AgentView-fJbuH0N-.js +12 -0
- package/src/ui/public/assets/{AppVersionBadge-C8aeLqSe.js → AppVersionBadge-BfgAvMQg.js} +2 -2
- package/src/ui/public/assets/BranchSelector-B55Ta_HD.js +4 -0
- package/src/ui/public/assets/{ChatSilenceBadge-CX-Q1BK4.js → ChatSilenceBadge-BDltHrA2.js} +1 -1
- package/src/ui/public/assets/{CommitForm-5QT3698o.js → CommitForm-C1Jql1he.js} +3 -3
- package/src/ui/public/assets/{CommonDialog-DcXSTg3n.js → CommonDialog-lhBO3OFL.js} +1 -1
- package/src/ui/public/assets/EditorView-Dvi9Js01.css +1 -0
- package/src/ui/public/assets/EditorView-nJTaLq6D.js +46 -0
- package/src/ui/public/assets/{FlowExecutionViewer-CJQEtP0d.js → FlowExecutionViewer-BPMqArlZ.js} +1 -1
- package/src/ui/public/assets/{FlowOrchestrationWorkspace-DxclUFA_.js → FlowOrchestrationWorkspace-Cq-1Knnp.js} +1 -1
- package/src/ui/public/assets/{LogList-BXIbzGgN.js → LogList-Bk7GAfle.js} +4 -4
- package/src/ui/public/assets/{MindmapView-Cp8DqQ1U.js → MindmapView-CBOfycTa.js} +1 -1
- package/src/ui/public/assets/MindmapView-DZHwQrhd.css +1 -0
- package/src/ui/public/assets/MonitorView-3g87o1gD.js +3 -0
- package/src/ui/public/assets/MonitorView-DFLZjKpz.css +1 -0
- package/src/ui/public/assets/ProjectManagerView-DgMq7Jed.js +10 -0
- package/src/ui/public/assets/{ProjectManagerView-DVIx9AVp.css → ProjectManagerView-DhC-Y5PR.css} +1 -1
- package/src/ui/public/assets/ProjectStartupButton-CkJEFz-S.js +1 -0
- package/src/ui/public/assets/{RecentDirectoriesChat-CYvrKZJ0.js → RecentDirectoriesChat-CQ6vjPkE.js} +1 -1
- package/src/ui/public/assets/RemoteManagerDialog-D9arrFOG.js +4 -0
- package/src/ui/public/assets/{RemoteRepoCard-Bf19DvWd.js → RemoteRepoCard-ZQ_SMlQZ.js} +1 -1
- package/src/ui/public/assets/{SourceMapView-BeVBU82G.js → SourceMapView-1q3TRufj.js} +1 -1
- package/src/ui/public/assets/{SvgIcon-kIrDXBzK.js → SvgIcon-CfjWD31j.js} +1 -1
- package/src/ui/public/assets/{UserInputNode-DdStei_J.js → UserInputNode-BdU3sSVR.js} +1 -1
- package/src/ui/public/assets/WorkbenchView-D6orzzEp.css +1 -0
- package/src/ui/public/assets/WorkbenchView-D8WdRklc.js +360 -0
- package/src/ui/public/assets/_plugin-vue_export-helper-D4DL0jyB.js +19 -0
- package/src/ui/public/assets/agentConversations-DJ8rPauy.js +10 -0
- package/src/ui/public/assets/{configStore-Bo0r1aq6.js → configStore-BmWis0h4.js} +1 -1
- package/src/ui/public/assets/{index-9h6sH4q0.js → index-DRDito6l.js} +106 -97
- package/src/ui/public/assets/index-DSQ6mp_N.css +1 -0
- package/src/ui/public/assets/{relativeTime-BzT7yFma.js → relativeTime-BpGKkKiW.js} +1 -1
- package/src/ui/public/assets/toolsStore-CmVXtAw_.js +1 -0
- package/src/ui/public/assets/useOrchestrator-DKr4ShyB.js +1 -0
- package/src/ui/public/assets/useWorkbenchProjects-D169QQnx.js +9 -0
- package/src/ui/public/index.html +7 -7
- package/src/ui/server/routes/fileOpen.js +277 -0
- package/src/ui/server/routes/workbench/agentMarketplace.js +17 -1
- package/src/ui/server/routes/workbench/runExternalTurn.js +9 -0
- package/src/ui/server/routes/workbench/runExternalTurn.test.js +20 -0
- package/src/ui/server/routes/workbench/taskRunner.codebuddy.test.js +42 -0
- package/src/ui/server/routes/workbench/taskRunner.js +34 -3
- package/src/ui/public/assets/AgentView-C-6_QbmJ.css +0 -1
- package/src/ui/public/assets/AgentView-CW4NHZhG.js +0 -12
- package/src/ui/public/assets/BranchSelector-B1qhhHaR.js +0 -4
- package/src/ui/public/assets/EditorView-DkXHZdKh.css +0 -1
- package/src/ui/public/assets/EditorView-eCxdCV3J.js +0 -38
- package/src/ui/public/assets/MindmapView-B2Oz9nCS.css +0 -1
- package/src/ui/public/assets/MonitorView-BDHs5wki.js +0 -3
- package/src/ui/public/assets/MonitorView-CTtuMls4.css +0 -1
- package/src/ui/public/assets/ProjectManagerView-BN-Rk3dE.js +0 -10
- package/src/ui/public/assets/ProjectStartupButton-B0UZ1c5m.js +0 -1
- package/src/ui/public/assets/RemoteManagerDialog-ByB-9iUe.js +0 -4
- package/src/ui/public/assets/WorkbenchView-DK2vT3Dr.css +0 -1
- package/src/ui/public/assets/WorkbenchView-DtOjBTLw.js +0 -358
- package/src/ui/public/assets/_plugin-vue_export-helper-BhoBCXbu.js +0 -19
- package/src/ui/public/assets/agentConversations-DYws7uRd.js +0 -10
- package/src/ui/public/assets/index-mEFBooIF.css +0 -1
- package/src/ui/public/assets/toolsStore-Dw93cKG_.js +0 -1
- package/src/ui/public/assets/useOrchestrator-5ZwvNS0V.js +0 -1
- package/src/ui/public/assets/useWorkbenchProjects-CQY-iTuV.js +0 -7
|
@@ -1 +1 @@
|
|
|
1
|
-
import{n as e}from"./_plugin-vue_export-helper-
|
|
1
|
+
import{n as e}from"./_plugin-vue_export-helper-D4DL0jyB.js";var t=6e4,n=60*t,r=24*n;function i(i,a=Date.now()){if(!i)return``;let o=new Date(i).getTime();if(Number.isNaN(o))return``;let s=Math.max(0,a-o);return s<t?e(`@WORKBENCH:刚刚`):s<n?e(`@WORKBENCH:N 分钟前`,{n:Math.floor(s/t)}):s<r?e(`@WORKBENCH:N 小时前`,{n:Math.floor(s/n)}):s<30*r?e(`@WORKBENCH:N 天前`,{n:Math.floor(s/r)}):new Date(o).toLocaleDateString()}function a(t){if(typeof t!=`number`||!Number.isFinite(t)||t<0)return``;let n=Math.floor(t/1e3),r=Math.floor(n/3600),i=Math.floor(n%3600/60),a=n%60;return r>0?e(`@WORKBENCH:N 小时 M 分`,{n:r,m:i}):i>0?e(`@WORKBENCH:N 分 M 秒`,{n:i,m:a}):e(`@WORKBENCH:N 秒`,{n:a})}function o(e,t){return e.getFullYear()===t.getFullYear()&&e.getMonth()===t.getMonth()&&e.getDate()===t.getDate()}function s(t,n=Date.now()){if(!t)return``;let r=new Date(t);if(Number.isNaN(r.getTime()))return``;let i=e=>String(e).padStart(2,`0`),a=`${i(r.getHours())}:${i(r.getMinutes())}:${i(r.getSeconds())}`,s=new Date(n);if(o(r,s))return a;if(o(r,new Date(s.getFullYear(),s.getMonth(),s.getDate()-1)))return e(`@WORKBENCH:昨天 {time}`,{time:a});let c=`${r.getMonth()+1}/${r.getDate()}`;return r.getFullYear()===s.getFullYear()?`${c} ${a}`:`${r.getFullYear()}/${c} ${a}`}export{a as n,i as r,s as t};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{gr as e,lr as t}from"./element-plus-Bz6lYur-.js";import{S as n}from"./vendor-C2k0GBzj.js";import{n as r}from"./_plugin-vue_export-helper-D4DL0jyB.js";import{c as i,s as a}from"./configStore-BmWis0h4.js";t();var o=6e5;function s(e){if(!e||typeof e!=`object`)return null;let t=e;return typeof t.name!=`string`||!t.name?null:{name:t.name,detail:typeof t.detail==`string`&&t.detail?t.detail:null,provider:typeof t.provider==`string`&&t.provider?t.provider:null,source:t.source===`config`||t.source===`state`?t.source:null}}var c=n(`tools`,()=>{let t=e(!1),n=e(!1),c=e(!1),l=e(!1),u=e(!1),d=e(!1),f=e(!1),p=e(!1),m=e(!1),h=e(null),g=e(!1),_=e(``),v=e({}),y=e({}),b=e({}),x=e({}),S=e(!1),C=0,w=null;function T(e=!1){return!e&&Date.now()-C<3e5?Promise.resolve():w||(S.value=!0,w=(async()=>{try{let e=await(await fetch(`/api/latest-tool-versions`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({})})).json();e.success&&e.latest&&typeof e.latest==`object`&&(x.value=e.latest,C=Date.now())}catch{}finally{S.value=!1,w=null}})(),w)}let E=null;function D(){return E||(g.value=!0,E=(async()=>{try{let e=await(await fetch(`/api/check-tools`)).json();e.success&&(t.value=!!e.vscode,n.value=!!e.claude,c.value=!!e.codex,l.value=!!e.opencode,u.value=!!e.codebuddy,d.value=!!e.pi,f.value=!!e.kimi,p.value=!!e.zcode,m.value=!!e.dsh,_.value=typeof e.platform==`string`?e.platform:``,v.value=e.installers&&typeof e.installers==`object`?e.installers:{},b.value=e.desktopApps&&typeof e.desktopApps==`object`?e.desktopApps:{},y.value=e.versions&&typeof e.versions==`object`?e.versions:{},h.value=Date.now())}catch{}finally{g.value=!1,E=null}})(),E)}function O(e){switch(e){case`vscode`:return t.value;case`claude`:return n.value;case`codex`:return c.value;case`opencode`:return l.value;case`codebuddy`:return u.value;case`pi`:return d.value;case`kimi`:return f.value;case`zcode`:return p.value;case`dsh`:return m.value}}function k(e){return b.value[e]}let A=e({}),j=e(!1),M=0,N=null;function P(e){if(!j.value)return{status:`unknown`};if(!i(e))return{status:`unset`};let t=A.value[e];return t?{status:`set`,info:t}:{status:`unset`}}function F(e=!1){return!e&&j.value&&Date.now()-M<6e5?Promise.resolve():N||(N=(async()=>{try{let e=await(await fetch(`/api/workbench/executor-models`)).json();if(e?.success&&e.models&&typeof e.models==`object`){let t={};for(let n of a)t[n.id]=s(e.models[n.id]);A.value=t,j.value=!0,M=Date.now()}}catch{}finally{N=null}})(),N)}function I(e){let t=P(e);return t.status===`unknown`?``:t.status===`set`?t.info.name:r(`@42BB9:未在配置中指定`)}function L(e){let t=P(e);return t.status===`set`?[t.info.source===`state`?r(`@42BB9:CLI 内最近使用`):``,t.info.detail,t.info.provider].filter(Boolean).join(` · `):``}let R=null;function z(){R||=(D(),F(),setInterval(()=>{D(),F()},o))}function B(){R&&=(clearInterval(R),null)}return{vscodeAvailable:t,claudeAvailable:n,codexAvailable:c,opencodeAvailable:l,codebuddyAvailable:u,piAvailable:d,kimiAvailable:f,zcodeAvailable:p,dshAvailable:m,lastCheckedAt:h,isChecking:g,platform:_,installers:v,versions:y,latestVersions:x,isFetchingLatest:S,isToolAvailable:O,desktopApps:b,desktopAppOf:k,executorModels:A,executorModelsReady:j,executorModelState:P,executorModelText:I,executorModelDetail:L,fetchExecutorModels:F,checkTools:D,fetchLatestVersions:T,startPolling:z,stopPolling:B}});export{c as t};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import"./rolldown-runtime-C0FnF6B9.js";import{gr as e,i as t,lr as n}from"./element-plus-Bz6lYur-.js";import{n as r}from"./_plugin-vue_export-helper-D4DL0jyB.js";import{t as i}from"./configStore-BmWis0h4.js";import{h as a,m as o,y as s}from"./index-DRDito6l.js";n();var c=1e5;function l(){let n=e([]),l=e([]),u=e(c),d=e([]),f=e(null),p=e(!1),m=e(!1),h=e(``),g=e({}),_=e([]),v=e(!1),y=e(!1),b=e(a);async function x(e=!1){try{let i=await fetch(`/api/workbench/orchestrator`,{cache:`no-store`}).then(e=>e.json());return i?.success?(Number.isInteger(i.maxInstructionChars)&&i.maxInstructionChars>0&&(u.value=i.maxInstructionChars),n.value=Array.isArray(i.instructions)?i.instructions:[],l.value=Array.isArray(i.activity)?i.activity:[],d.value=Array.isArray(i.running)?i.running:[],o().setRunning(d.value.length),f.value=i.updatedAt||null,h.value=typeof i.defaultPrompt==`string`?i.defaultPrompt:``,g.value=i.projectPrompts&&typeof i.projectPrompts==`object`?i.projectPrompts:{},b.value=s(i.reportIntervalMs),p.value=!0,!0):(e||t.error(i?.error||r(`@WORKBENCH:读取编排状态失败`)),!1)}catch(n){return e||t.error(r(`@WORKBENCH:网络错误: `)+(n?.message||n)),!1}}async function S(e=!1){if(v.value)return!1;v.value=!0;try{let n=await fetch(`/api/workbench/orchestrator/reports`,{cache:`no-store`}).then(e=>e.json());return n?.success?(_.value=Array.isArray(n.reports)?n.reports:[],!0):(e||t.error(n?.error||r(`@WORKBENCH:读取进度报告失败`)),!1)}catch(n){return e||t.error(r(`@WORKBENCH:网络错误: `)+(n?.message||n)),!1}finally{v.value=!1}}async function C(){if(y.value)return null;y.value=!0;try{let e=String(i().locale||``).startsWith(`en`)?`en`:`zh`,n=await fetch(`/api/workbench/orchestrator/report`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({locale:e})}).then(e=>e.json());return n?.success?n.report?(_.value=[n.report,..._.value].slice(0,20),n.report):(t.info(r(`@WORKBENCH:当前没有正在执行的任务,没有可汇报的进度`)),null):(t.error(n?.error||r(`@WORKBENCH:生成进度报告失败`)),null)}catch(e){return t.error(r(`@WORKBENCH:网络错误: `)+(e?.message||e)),null}finally{y.value=!1}}async function w(e){try{let n=await fetch(`/api/workbench/orchestrator/report-interval`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({intervalMs:e})}).then(e=>e.json());return n?.success?(b.value=s(n.reportIntervalMs),!0):(t.error(n?.error||r(`@WORKBENCH:保存失败`)),!1)}catch(e){return t.error(r(`@WORKBENCH:网络错误: `)+(e?.message||e)),!1}}async function T(e){try{let n=await fetch(`/api/workbench/orchestrator/default-prompt`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({prompt:e})}).then(e=>e.json());return n?.success?(h.value=typeof n.defaultPrompt==`string`?n.defaultPrompt:``,t.success(r(`@WORKBENCH:已保存全局预设提示词`)),!0):(t.error(n?.error||r(`@WORKBENCH:保存失败`)),!1)}catch(e){return t.error(r(`@WORKBENCH:网络错误: `)+(e?.message||e)),!1}}async function E(e,n){try{let i=await fetch(`/api/workbench/orchestrator/project-prompt`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({projectPath:e,prompt:n})}).then(e=>e.json());return i?.success?(g.value=i.projectPrompts&&typeof i.projectPrompts==`object`?i.projectPrompts:{},t.success(r(`@WORKBENCH:已保存项目预设提示词`)),!0):(t.error(i?.error||r(`@WORKBENCH:保存失败`)),!1)}catch(e){return t.error(r(`@WORKBENCH:网络错误: `)+(e?.message||e)),!1}}async function D(e){if(m.value)return null;m.value=!0;try{let n=await fetch(`/api/workbench/orchestrator/dispatch`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({text:e.text,projectPath:e.projectPath||``,autoRun:e.autoRun!==!1,useDefaultPrompt:e.useDefaultPrompt!==!1,executor:e.executor,attachments:(e.attachments||[]).map(e=>({id:e.id,ext:e.ext,originalName:e.originalName}))})}).then(e=>e.json());return n?.success?(await x(!0),{task:n.task||null,ran:!!n.ran}):(t.error(n?.error||r(`@WORKBENCH:派发失败`)),null)}catch(e){return t.error(r(`@WORKBENCH:网络错误: `)+(e?.message||e)),null}finally{m.value=!1}}return{instructions:n,activity:l,running:d,updatedAt:f,loaded:p,dispatching:m,maxInstructionChars:u,defaultPrompt:h,projectPrompts:g,reports:_,loadingReports:v,generatingReport:y,reportIntervalMs:b,loadOrchestrator:x,dispatch:D,saveDefaultPrompt:T,saveProjectPrompt:E,loadReports:S,generateReport:C,setReportInterval:w}}export{l as t};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import"./rolldown-runtime-C0FnF6B9.js";import{An as e,Cn as t,In as n,J as r,Lt as i,Mn as a,Nt as o,Sn as s,Tn as c,Wr as l,Xr as u,Yn as d,_n as f,gn as p,gr as m,hn as h,i as g,ir as _,jr as v,lr as y,on as b,qn as x,st as S,wn as C,xn as w,xt as T}from"./element-plus-Bz6lYur-.js";import{n as E,t as ee}from"./_plugin-vue_export-helper-D4DL0jyB.js";import{t as te}from"./SvgIcon-CfjWD31j.js";import{t as D}from"./toolsStore-CmVXtAw_.js";import{B as O,F as k,G as A,H as j,I as M,L as N,M as P,N as F,P as I,R as L,U as R,V as z,W as B,z as V}from"./index-DRDito6l.js";function H(){let e=D();function t(t){return e.lastCheckedAt!==null&&!e.isToolAvailable(t)}async function n(t,n,r){if(!n)return`no-dir`;if(!await L())return g.warning(E(`@67CE7:工具检测失败,请稍后重试`)),`failed`;if(!e.isToolAvailable(t))return`install`;let i=await R(t,n,r);return i.success?(g.success(i.message||E(`@67CE7:已用 {tool} 打开目录`,{tool:k[t]})),`opened`):(g.error(`${E(`@67CE7:打开失败: `)}${i.error??``}`),`failed`)}return{toolMissing:t,tryOpenWithTool:n}}n(),y();var U=a({__name:`ProjectOpenMenuList`,props:{path:{type:String,required:!0},name:{type:String,required:!0}},emits:[`done`],setup(e,{expose:t,emit:n}){t();let r=e,a=n,{toolMissing:s,tryOpenWithTool:c}=H(),l=D(),u=m(!1),d=m(null),f=w(()=>F.filter(e=>l.desktopAppOf(e)).map(e=>({id:e,name:k[e],icon:I.find(t=>t.id===e)?.icon??``})));async function p(e){let t=l.desktopAppOf(e);if(!t)return;if(a(`done`),!t.installed){window.open(t.downloadUrl,`_blank`,`noopener,noreferrer`);return}let n=await O(e);n.success?g.success(n.message||E(`@67CE7:已打开 {app}`,{app:t.name})):g.error(n.error||E(`@WORKBENCH:打开桌面端失败`))}async function h(){a(`done`);let e=await z(r.path);e.success?g.success(e.message||E(`@WORKBENCH:已在文件管理器中打开文件夹`)):g.error(e.error||E(`@WORKBENCH:打开文件夹失败`))}async function _(){a(`done`);let e=await j(r.path);e.success?g.success(e.message||E(`@67CE7:已在终端中打开目录`)):g.error(e.error||E(`@WORKBENCH:打开终端失败`))}async function v(){a(`done`);let e=await V(r.path);e.success?g.success(E(`@WORKBENCH:已在新标签页启动 g ui`)):g.error(e.error||E(`@WORKBENCH:启动 g ui 失败`))}async function y(){if(a(`done`),r.name)try{await navigator.clipboard.writeText(r.name),g.success(E(`@67CE7:已复制文件夹名称`))}catch{g.error(E(`@67CE7:复制失败`))}}async function b(){if(a(`done`),r.path)try{await navigator.clipboard.writeText(r.path),g.success(E(`@67CE7:已复制文件夹路径`))}catch{g.error(E(`@67CE7:复制失败`))}}async function x(e,t){if(await c(e,r.path,t)===`install`){d.value=e,u.value=!0;return}a(`done`)}let C={props:r,emit:a,toolMissing:s,tryOpenWithTool:c,toolsStore:l,installVisible:u,installTool:d,desktopTools:f,openOrDownloadDesktop:p,openInFileManager:h,openInTerminal:_,launchGui:v,copyFolderName:y,copyFolderPath:b,openWithTool:x,get CopyDocument(){return S},get FolderOpened(){return T},get Monitor(){return o},get Promotion(){return i},SvgIcon:te,ToolInstallDialog:A,get claudeCodeIcon(){return B},get $t(){return E},get DARK_TILE_ICON_IDS(){return P},get OPEN_WITH_TOOLS(){return I},get desktopAppHint(){return M},get desktopAppLabel(){return N}};return Object.defineProperty(C,"__isScriptSetup",{enumerable:!1,value:!0}),C}});n(),v(),b();var W=[`aria-label`],G=[`onKeydown`],K={class:`proj-menu__icon`},q={class:`proj-menu__label`},J=[`onKeydown`],Y={class:`proj-menu__icon`},X={class:`proj-menu__label`},Z=[`onKeydown`],Q={class:`proj-menu__icon`},ne={class:`proj-menu__label`},re=[`onKeydown`],ie={class:`proj-menu__icon`},ae={class:`proj-menu__label`},oe=[`onKeydown`],se={class:`proj-menu__icon`},ce={class:`proj-menu__label`},le={class:`proj-menu__title`,role:`presentation`},ue=[`onClick`,`onKeydown`],de={class:`proj-menu__icon`},fe={class:`proj-menu__label`},pe={key:0,class:`proj-menu__hint`},me={class:`proj-menu__icon`},he=[`src`],ge={class:`proj-menu__label`},_e={class:`proj-menu__hint`},ve={class:`proj-menu__icon`},ye=[`src`],be={class:`proj-menu__label`},xe={class:`proj-menu__icon`},Se={class:`proj-menu__label`},Ce={class:`proj-menu__icon`},we={class:`proj-menu__label`},Te={key:0,class:`proj-menu__hint`},Ee={key:1,class:`proj-menu__hint`},De={class:`proj-menu__icon`},$={class:`proj-menu__label`},Oe={class:`proj-menu__icon`},ke={class:`proj-menu__label`},Ae={class:`proj-menu__icon`},je={class:`proj-menu__label`},Me={class:`proj-menu__title`,role:`presentation`},Ne=[`onClick`,`onKeydown`],Pe={class:`proj-menu__icon`},Fe=[`src`],Ie={class:`proj-menu__label`},Le={class:`proj-menu__hint`};function Re(n,i,a,o,m,g){let v=r;return x(),c(f,null,[s(`ul`,{class:`proj-menu`,role:`menu`,"aria-label":o.$t(`@WORKBENCH:打开方式`)},[s(`li`,{class:`proj-menu__item`,role:`menuitem`,tabindex:`-1`,onClick:o.openInFileManager,onKeydown:[h(p(o.openInFileManager,[`prevent`]),[`enter`]),h(p(o.openInFileManager,[`prevent`]),[`space`])]},[s(`span`,K,[e(v,{"aria-hidden":`true`},{default:_(()=>[e(o.FolderOpened)]),_:1})]),s(`span`,q,u(o.$t(`@WORKBENCH:在文件管理器中打开`)),1)],40,G),s(`li`,{class:`proj-menu__item`,role:`menuitem`,tabindex:`-1`,onClick:o.openInTerminal,onKeydown:[h(p(o.openInTerminal,[`prevent`]),[`enter`]),h(p(o.openInTerminal,[`prevent`]),[`space`])]},[s(`span`,Y,[e(v,{"aria-hidden":`true`},{default:_(()=>[e(o.Monitor)]),_:1})]),s(`span`,X,u(o.$t(`@WORKBENCH:在终端中打开`)),1)],40,J),s(`li`,{class:`proj-menu__item`,role:`menuitem`,tabindex:`-1`,onClick:o.launchGui,onKeydown:[h(p(o.launchGui,[`prevent`]),[`enter`]),h(p(o.launchGui,[`prevent`]),[`space`])]},[s(`span`,Q,[e(v,{"aria-hidden":`true`},{default:_(()=>[e(o.Promotion)]),_:1})]),s(`span`,ne,u(o.$t(`@WORKBENCH:在新标签页启动 g ui`)),1)],40,Z),C(` 复制文件夹名称:放在"打开"这一组里,因为它操作的是项目目录本身,
|
|
2
|
+
而不是"用哪个工具打开"(下面那组有独立标题)。 `),s(`li`,{class:`proj-menu__item`,role:`menuitem`,tabindex:`-1`,onClick:o.copyFolderName,onKeydown:[h(p(o.copyFolderName,[`prevent`]),[`enter`]),h(p(o.copyFolderName,[`prevent`]),[`space`])]},[s(`span`,ie,[e(v,{"aria-hidden":`true`},{default:_(()=>[e(o.CopyDocument)]),_:1})]),s(`span`,ae,u(o.$t(`@67CE7:复制文件夹名称`)),1)],40,re),C(` 复制文件夹路径:与名称成对(名称=最后一级目录名,路径=完整绝对路径,
|
|
3
|
+
粘进终端 cd 用)。同属"操作目录本身"的一组,排在名称下面。 `),s(`li`,{class:`proj-menu__item`,role:`menuitem`,tabindex:`-1`,onClick:o.copyFolderPath,onKeydown:[h(p(o.copyFolderPath,[`prevent`]),[`enter`]),h(p(o.copyFolderPath,[`prevent`]),[`space`])]},[s(`span`,se,[e(v,{"aria-hidden":`true`},{default:_(()=>[e(o.CopyDocument)]),_:1})]),s(`span`,ce,u(o.$t(`@67CE7:复制文件夹路径`)),1)],40,oe),i[23]||=s(`li`,{class:`proj-menu__sep`,role:`separator`},null,-1),s(`li`,le,u(o.$t(`@WORKBENCH:用工具打开`)),1),(x(!0),c(f,null,d(o.OPEN_WITH_TOOLS,t=>(x(),c(`li`,{key:t.id,class:l([`proj-menu__item`,{"is-missing":o.toolMissing(t.id)}]),role:`menuitem`,tabindex:`-1`,onClick:e=>o.openWithTool(t.id),onKeydown:[h(p(e=>o.openWithTool(t.id),[`prevent`]),[`enter`]),h(p(e=>o.openWithTool(t.id),[`prevent`]),[`space`])]},[s(`span`,de,[e(o.SvgIcon,{"icon-class":t.icon,class:l({"icon-invert-dark":t.id===`codex`,"icon-outline-dark":o.DARK_TILE_ICON_IDS.has(t.id)})},null,8,[`icon-class`,`class`])]),s(`span`,fe,u(o.$t(t.labelKey)),1),o.toolMissing(t.id)?(x(),c(`span`,pe,u(o.$t(`@67CE7:未安装`)),1)):C(`v-if`,!0)],42,ue))),128)),C(` claude 有两种权限模式,不混进上面的工具列表(顶栏也是单独排布的) `),s(`li`,{class:l([`proj-menu__item`,{"is-missing":o.toolMissing(`claude`)}]),role:`menuitem`,tabindex:`-1`,onClick:i[0]||=e=>o.openWithTool(`claude`),onKeydown:[i[1]||=h(p(e=>o.openWithTool(`claude`),[`prevent`]),[`enter`]),i[2]||=h(p(e=>o.openWithTool(`claude`),[`prevent`]),[`space`])]},[s(`span`,me,[s(`img`,{src:o.claudeCodeIcon,alt:``,class:`proj-menu__img`},null,8,he)]),s(`span`,ge,u(o.$t(`@67CE7:用 Claude Code 打开`)),1),s(`span`,_e,u(o.toolMissing(`claude`)?o.$t(`@67CE7:未安装`):o.$t(`@67CE7:默认权限`)),1)],34),s(`li`,{class:l([`proj-menu__item proj-menu__item--danger`,{"is-missing":o.toolMissing(`claude`)}]),role:`menuitem`,tabindex:`-1`,onClick:i[3]||=e=>o.openWithTool(`claude`,`bypassPermissions`),onKeydown:[i[4]||=h(p(e=>o.openWithTool(`claude`,`bypassPermissions`),[`prevent`]),[`enter`]),i[5]||=h(p(e=>o.openWithTool(`claude`,`bypassPermissions`),[`prevent`]),[`space`])]},[s(`span`,ve,[s(`img`,{src:o.claudeCodeIcon,alt:``,class:`proj-menu__img`},null,8,ye)]),s(`span`,be,u(o.$t(`@67CE7:用 Claude Code 打开(完全批准)`)),1)],34),C(` opencode 的「完全批准」(\`--auto\`)与 claude 的 bypass 档并列收在末尾:
|
|
4
|
+
它带警示色,混进上面的普通工具列表会把"一行一个工具"的节奏打乱,
|
|
5
|
+
而它又确实得单独成项(默认档已经在上面那条 v-for 里了)。 `),s(`li`,{class:l([`proj-menu__item proj-menu__item--danger`,{"is-missing":o.toolMissing(`opencode`)}]),role:`menuitem`,tabindex:`-1`,onClick:i[6]||=e=>o.openWithTool(`opencode`,`auto`),onKeydown:[i[7]||=h(p(e=>o.openWithTool(`opencode`,`auto`),[`prevent`]),[`enter`]),i[8]||=h(p(e=>o.openWithTool(`opencode`,`auto`),[`prevent`]),[`space`])]},[s(`span`,xe,[e(o.SvgIcon,{"icon-class":`opencode`})]),s(`span`,Se,u(o.$t(`@67CE7:用 OpenCode 打开(完全批准)`)),1)],34),C(` codex 天然有三档(默认档已在上面那条 v-for 里):自动批准但仍有沙箱 →
|
|
6
|
+
免批准 + 免沙箱。两档都单独成项,理由同 claude / opencode 那两项。 `),s(`li`,{class:l([`proj-menu__item`,{"is-missing":o.toolMissing(`codex`)}]),role:`menuitem`,tabindex:`-1`,onClick:i[9]||=e=>o.openWithTool(`codex`,`sandboxed`),onKeydown:[i[10]||=h(p(e=>o.openWithTool(`codex`,`sandboxed`),[`prevent`]),[`enter`]),i[11]||=h(p(e=>o.openWithTool(`codex`,`sandboxed`),[`prevent`]),[`space`])]},[s(`span`,Ce,[e(o.SvgIcon,{"icon-class":`codex`,class:`icon-invert-dark`})]),s(`span`,we,u(o.$t(`@67CE7:用 Codex 打开(自动批准)`)),1),o.toolMissing(`codex`)?(x(),c(`span`,Ee,u(o.$t(`@67CE7:未安装`)),1)):(x(),c(`span`,Te,u(o.$t(`@67CE7:沙箱内`)),1))],34),s(`li`,{class:l([`proj-menu__item proj-menu__item--danger`,{"is-missing":o.toolMissing(`codex`)}]),role:`menuitem`,tabindex:`-1`,onClick:i[12]||=e=>o.openWithTool(`codex`,`bypass`),onKeydown:[i[13]||=h(p(e=>o.openWithTool(`codex`,`bypass`),[`prevent`]),[`enter`]),i[14]||=h(p(e=>o.openWithTool(`codex`,`bypass`),[`prevent`]),[`space`])]},[s(`span`,De,[e(o.SvgIcon,{"icon-class":`codex`,class:`icon-invert-dark`})]),s(`span`,$,u(o.$t(`@67CE7:用 Codex 打开(完全批准)`)),1)],34),C(" codebuddy / kimi 的「完全批准」档(默认档已在上面那条 v-for 里)。\n token 与服务端 buildCodebuddyArgs / buildKimiArgs 对齐:\n codebuddy 'bypassPermissions' → `--permission-mode bypassPermissions`\n kimi 'auto' → `--auto`(Never Ask:从不打断) "),s(`li`,{class:l([`proj-menu__item proj-menu__item--danger`,{"is-missing":o.toolMissing(`codebuddy`)}]),role:`menuitem`,tabindex:`-1`,onClick:i[15]||=e=>o.openWithTool(`codebuddy`,`bypassPermissions`),onKeydown:[i[16]||=h(p(e=>o.openWithTool(`codebuddy`,`bypassPermissions`),[`prevent`]),[`enter`]),i[17]||=h(p(e=>o.openWithTool(`codebuddy`,`bypassPermissions`),[`prevent`]),[`space`])]},[s(`span`,Oe,[e(o.SvgIcon,{"icon-class":`codebuddy`})]),s(`span`,ke,u(o.$t(`@67CE7:用 CodeBuddy Code 打开(完全批准)`)),1)],34),s(`li`,{class:l([`proj-menu__item proj-menu__item--danger`,{"is-missing":o.toolMissing(`kimi`)}]),role:`menuitem`,tabindex:`-1`,onClick:i[18]||=e=>o.openWithTool(`kimi`,`auto`),onKeydown:[i[19]||=h(p(e=>o.openWithTool(`kimi`,`auto`),[`prevent`]),[`enter`]),i[20]||=h(p(e=>o.openWithTool(`kimi`,`auto`),[`prevent`]),[`space`])]},[s(`span`,Ae,[e(o.SvgIcon,{"icon-class":`kimi`})]),s(`span`,je,u(o.$t(`@67CE7:用 Kimi Code 打开(完全批准)`)),1)],34),C(` 桌面端分组:官方有桌面应用的工具各一项(装了点它启动桌面应用,没装跳
|
|
7
|
+
官网下载页)。vscode / zcode 不进这组 —— 它们的主操作本身就是桌面应用;
|
|
8
|
+
pi 没有官方桌面版。 `),o.desktopTools.length?(x(),c(f,{key:0},[i[22]||=s(`li`,{class:`proj-menu__sep`,role:`separator`},null,-1),s(`li`,Me,u(o.$t(`@WORKBENCH:桌面端`)),1),(x(!0),c(f,null,d(o.desktopTools,e=>(x(),c(`li`,{key:`desktop-${e.id}`,class:`proj-menu__item`,role:`menuitem`,tabindex:`-1`,onClick:t=>o.openOrDownloadDesktop(e.id),onKeydown:[h(p(t=>o.openOrDownloadDesktop(e.id),[`prevent`]),[`enter`]),h(p(t=>o.openOrDownloadDesktop(e.id),[`prevent`]),[`space`])]},[s(`span`,Pe,[e.id===`claude`?(x(),c(`img`,{key:0,src:o.claudeCodeIcon,alt:``,class:`proj-menu__img`},null,8,Fe)):(x(),t(o.SvgIcon,{key:1,"icon-class":e.icon,class:l({"icon-invert-dark":e.id===`codex`,"icon-outline-dark":o.DARK_TILE_ICON_IDS.has(e.id)})},null,8,[`icon-class`,`class`]))]),s(`span`,Ie,u(o.desktopAppLabel(o.toolsStore.desktopAppOf(e.id))),1),s(`span`,Le,u(o.desktopAppHint(o.toolsStore.desktopAppOf(e.id))),1)],40,Ne))),128))],64)):C(`v-if`,!0)],8,W),C(` 未安装的工具:复用顶栏那套安装引导(同一个组件),不在这里另写一份说明。
|
|
9
|
+
跟着菜单内容一起挂载,所以同时最多只有一份(宿主只渲染打开的那一行)。 `),e(o.ToolInstallDialog,{modelValue:o.installVisible,"onUpdate:modelValue":i[21]||=e=>o.installVisible=e,tool:o.installTool},null,8,[`modelValue`,`tool`])],64)}var ze=ee(U,[[`render`,Re],[`__scopeId`,`data-v-9d06f50a`],[`__file`,`D:/workspace/github_workspace/zen-gitsync/src/ui/client/src/components/ProjectOpenMenuList.vue`]]);y();function Be(){let e=m([]),t=m([]),n=m(``),r=m(!1),i=m(!1);async function a(a=!1){a||(r.value=!0);try{let r=await fetch(`/api/workbench/projects`,{cache:`no-store`}).then(e=>e.json());return r?.success?(e.value=Array.isArray(r.projects)?r.projects:[],t.value=Array.isArray(r.tasks)?r.tasks:[],n.value=r.currentProjectPath||``,i.value=!0,!0):(a||g.error(r?.error||E(`@WORKBENCH:读取项目列表失败`)),!1)}catch(e){return a||g.error(E(`@WORKBENCH:网络错误: `)+(e?.message||e)),!1}finally{r.value=!1}}async function o(e){if(!e)return{ok:!1,keptTasks:0};try{let t=await fetch(`/api/workbench/projects/remove`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({path:e})}).then(e=>e.json()).catch(()=>null);return t?.success?{ok:!0,keptTasks:Number.isFinite(t.keptTasks)?t.keptTasks:0}:(g.error(t?.error||E(`@WORKBENCH:移除项目失败`)),{ok:!1,keptTasks:0})}catch(e){return g.error(E(`@WORKBENCH:网络错误: `)+(e?.message||e)),{ok:!1,keptTasks:0}}}async function s(e){if(!e)return{ok:!1,alreadyKnown:!1};try{let t=await fetch(`/api/workbench/projects/add`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({path:e})}).then(e=>e.json()).catch(()=>null);return t?.success?{ok:!0,alreadyKnown:!!t.alreadyKnown}:(g.error(t?.error||E(`@PROJM:添加项目失败`)),{ok:!1,alreadyKnown:!1})}catch(e){return g.error(E(`@WORKBENCH:网络错误: `)+(e?.message||e)),{ok:!1,alreadyKnown:!1}}}return{projects:e,boardTasks:t,currentProjectPath:n,loading:r,loaded:i,loadProjects:a,removeProject:o,addProject:s}}export{ze as n,Be as t};
|
package/src/ui/public/index.html
CHANGED
|
@@ -10,19 +10,19 @@
|
|
|
10
10
|
<meta name="theme-color" content="#141820" media="(prefers-color-scheme: dark)" />
|
|
11
11
|
<title>Zen GitSync</title>
|
|
12
12
|
<!-- 字体改为自托管(@font-face 见 src/main.css),不再依赖 Google Fonts 外链 -->
|
|
13
|
-
<script type="module" crossorigin src="/assets/index-
|
|
13
|
+
<script type="module" crossorigin src="/assets/index-DRDito6l.js"></script>
|
|
14
14
|
<link rel="modulepreload" crossorigin href="/assets/rolldown-runtime-C0FnF6B9.js">
|
|
15
15
|
<link rel="modulepreload" crossorigin href="/assets/dagre-CpKxpMNH.js">
|
|
16
16
|
<link rel="modulepreload" crossorigin href="/assets/element-plus-Bz6lYur-.js">
|
|
17
17
|
<link rel="modulepreload" crossorigin href="/assets/vendor-C2k0GBzj.js">
|
|
18
|
-
<link rel="modulepreload" crossorigin href="/assets/_plugin-vue_export-helper-
|
|
19
|
-
<link rel="modulepreload" crossorigin href="/assets/SvgIcon-
|
|
18
|
+
<link rel="modulepreload" crossorigin href="/assets/_plugin-vue_export-helper-D4DL0jyB.js">
|
|
19
|
+
<link rel="modulepreload" crossorigin href="/assets/SvgIcon-CfjWD31j.js">
|
|
20
20
|
<link rel="modulepreload" crossorigin href="/assets/socket-io-BFqQXqAp.js">
|
|
21
|
-
<link rel="modulepreload" crossorigin href="/assets/configStore-
|
|
22
|
-
<link rel="modulepreload" crossorigin href="/assets/toolsStore-
|
|
21
|
+
<link rel="modulepreload" crossorigin href="/assets/configStore-BmWis0h4.js">
|
|
22
|
+
<link rel="modulepreload" crossorigin href="/assets/toolsStore-CmVXtAw_.js">
|
|
23
23
|
<link rel="modulepreload" crossorigin href="/assets/monaco-CvMddat7.js">
|
|
24
24
|
<link rel="modulepreload" crossorigin href="/assets/flow-mindmap-CpjwoduB.js">
|
|
25
|
-
<link rel="modulepreload" crossorigin href="/assets/CommonDialog-
|
|
25
|
+
<link rel="modulepreload" crossorigin href="/assets/CommonDialog-lhBO3OFL.js">
|
|
26
26
|
<link rel="modulepreload" crossorigin href="/assets/vue-flow-Cnxq2qrs.js">
|
|
27
27
|
<link rel="stylesheet" crossorigin href="/assets/element-plus-D-6gla1z.css">
|
|
28
28
|
<link rel="stylesheet" crossorigin href="/assets/vendor-CSFGMcMB.css">
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
<link rel="stylesheet" crossorigin href="/assets/flow-mindmap-DqCKZUlr.css">
|
|
32
32
|
<link rel="stylesheet" crossorigin href="/assets/CommonDialog-w6EmBEXv.css">
|
|
33
33
|
<link rel="stylesheet" crossorigin href="/assets/vue-flow-CavUTEzp.css">
|
|
34
|
-
<link rel="stylesheet" crossorigin href="/assets/index-
|
|
34
|
+
<link rel="stylesheet" crossorigin href="/assets/index-DSQ6mp_N.css">
|
|
35
35
|
</head>
|
|
36
36
|
<body>
|
|
37
37
|
<div id="app"></div>
|
|
@@ -52,6 +52,99 @@ const TOOL_DOCS_URLS = Object.freeze({
|
|
|
52
52
|
gitee: 'https://gitee.com/oschina/gitee-cli',
|
|
53
53
|
});
|
|
54
54
|
|
|
55
|
+
// ── 官方桌面应用(顶栏「打开桌面端 / 下载桌面端」用)────────────────────
|
|
56
|
+
// 顶栏这排工具大多只有 CLI;这里只收**官方有桌面版**的六个:
|
|
57
|
+
// claude → Claude 桌面端(Code 标签页就是桌面版 Claude Code)
|
|
58
|
+
// codex → Codex 桌面版(2026-07 起并入 ChatGPT 桌面应用,是其中一个模式)
|
|
59
|
+
// opencode → OpenCode 桌面版
|
|
60
|
+
// codebuddy → CodeBuddy IDE
|
|
61
|
+
// kimi → Kimi 桌面端
|
|
62
|
+
// dsh → DeepSeek Harness 桌面版(官方仓库 apps/desktop,Electron 壳)
|
|
63
|
+
// 不收的:vscode(主按钮本身就是打开桌面应用,`code <目录>` 拉起的正是 GUI)、
|
|
64
|
+
// zcode(整个就是桌面应用,主按钮直接启动)、pi(没有官方桌面版,
|
|
65
|
+
// 社区壳不代表官方立场,不代收)。
|
|
66
|
+
// downloadUrl 给没装桌面端的用户点(前端直接 window.open,不经服务端);
|
|
67
|
+
// 可以是字符串,也可以是 (platform, arch) => string —— dsh 的官方固定下载
|
|
68
|
+
// 地址按平台/架构分(win-x64 exe、mac arm64/x64 dmg,没有落地页),由服务端
|
|
69
|
+
// 判(UI 与后端同机,process.platform/arch 就是用户机器的)。
|
|
70
|
+
// name 是菜单展示名,刻意和 CLI 版的名字区分("Claude 桌面端" vs "Claude Code"),
|
|
71
|
+
// 否则用户分不清点下去开的是哪个。
|
|
72
|
+
const TOOL_DESKTOP_APPS = Object.freeze({
|
|
73
|
+
claude: {
|
|
74
|
+
name: 'Claude 桌面端',
|
|
75
|
+
downloadUrl: 'https://claude.com/download',
|
|
76
|
+
winExeName: 'Claude.exe',
|
|
77
|
+
winExeRe: /Claude\.exe$/i,
|
|
78
|
+
winPaths: ['AnthropicClaude/Claude.exe', 'Programs/Claude/Claude.exe', 'Claude/Claude.exe'],
|
|
79
|
+
winLnkRe: /^Claude\.lnk$/i,
|
|
80
|
+
macApp: 'Claude.app',
|
|
81
|
+
linuxPaths: ['/opt/Claude/claude', '/usr/bin/claude-desktop'],
|
|
82
|
+
},
|
|
83
|
+
codex: {
|
|
84
|
+
name: 'Codex 桌面版',
|
|
85
|
+
downloadUrl: 'https://developers.openai.com/codex/app/',
|
|
86
|
+
winExeName: 'ChatGPT.exe',
|
|
87
|
+
winExeRe: /ChatGPT\.exe$/i,
|
|
88
|
+
winPaths: ['Microsoft/WindowsApps/ChatGPT.exe', 'Programs/ChatGPT/ChatGPT.exe', 'OpenAI/ChatGPT/ChatGPT.exe'],
|
|
89
|
+
winLnkRe: /^(ChatGPT|Codex)\.lnk$/i,
|
|
90
|
+
macApp: 'ChatGPT.app',
|
|
91
|
+
linuxPaths: [], // Codex 桌面版只有 macOS / Windows
|
|
92
|
+
},
|
|
93
|
+
opencode: {
|
|
94
|
+
name: 'OpenCode 桌面版',
|
|
95
|
+
downloadUrl: 'https://opencode.ai/download',
|
|
96
|
+
winExeName: 'opencode.exe',
|
|
97
|
+
winExeRe: /opencode.*\.exe$/i,
|
|
98
|
+
winPaths: ['Programs/opencode/opencode.exe', 'Programs/OpenCode/OpenCode.exe', 'Programs/opencode-desktop/opencode-desktop.exe'],
|
|
99
|
+
winLnkRe: /^OpenCode\.lnk$/i,
|
|
100
|
+
macApp: 'OpenCode.app',
|
|
101
|
+
linuxPaths: ['/usr/bin/opencode-desktop', '/usr/local/bin/opencode-desktop', '/opt/opencode-desktop/opencode-desktop'],
|
|
102
|
+
},
|
|
103
|
+
codebuddy: {
|
|
104
|
+
name: 'CodeBuddy IDE',
|
|
105
|
+
downloadUrl: 'https://www.codebuddy.cn/ide/',
|
|
106
|
+
winExeName: 'CodeBuddy.exe',
|
|
107
|
+
winExeRe: /CodeBuddy\.exe$/i,
|
|
108
|
+
winPaths: ['Programs/CodeBuddy/CodeBuddy.exe', 'CodeBuddy/CodeBuddy.exe'],
|
|
109
|
+
winLnkRe: /^CodeBuddy( IDE)?\.lnk$/i,
|
|
110
|
+
macApp: 'CodeBuddy.app',
|
|
111
|
+
linuxPaths: ['/usr/bin/codebuddy', '/usr/local/bin/codebuddy', '/opt/codebuddy/codebuddy'],
|
|
112
|
+
},
|
|
113
|
+
kimi: {
|
|
114
|
+
name: 'Kimi 桌面端',
|
|
115
|
+
downloadUrl: 'https://www.kimi.com/products/kimi-work',
|
|
116
|
+
winExeName: 'Kimi.exe',
|
|
117
|
+
winExeRe: /Kimi\.exe$/i,
|
|
118
|
+
winPaths: ['Programs/Kimi/Kimi.exe', 'Kimi/Kimi.exe'],
|
|
119
|
+
winLnkRe: /^Kimi\.lnk$/i,
|
|
120
|
+
macApp: 'Kimi.app',
|
|
121
|
+
linuxPaths: [], // Kimi 桌面端只有 Windows / macOS(Apple 芯片)
|
|
122
|
+
},
|
|
123
|
+
dsh: {
|
|
124
|
+
name: 'DeepSeek Harness 桌面版',
|
|
125
|
+
// 官方固定下载地址(apps/desktop/publishing/README:upload:latest:* 传到
|
|
126
|
+
// download.deepseek.com/desktop/dsh-latest-*,就是安装包本身、没有落地页)。
|
|
127
|
+
// 官方 targets 只有 win-x64 / mac-arm64 / mac-x64 —— Linux 没有官方桌面版,
|
|
128
|
+
// 回落仓库里的 Desktop 文档。安装器是 per-user NSIS,卸载项在 HKCU 根
|
|
129
|
+
// (scanWindowsUninstallPaths 本来就查 HKCU)。
|
|
130
|
+
downloadUrl: (platform, arch) => {
|
|
131
|
+
if (platform === 'win32') return 'https://download.deepseek.com/desktop/dsh-latest-windows-x64.exe';
|
|
132
|
+
if (platform === 'darwin') {
|
|
133
|
+
return `https://download.deepseek.com/desktop/dsh-latest-macos-${arch === 'arm64' ? 'arm64' : 'x64'}.dmg`;
|
|
134
|
+
}
|
|
135
|
+
return 'https://github.com/deepseek-ai/deepseek-harness/tree/master/apps/desktop';
|
|
136
|
+
},
|
|
137
|
+
// electron-builder productName 是 'DeepSeek Harness' → 装出来的 exe / .app /
|
|
138
|
+
// 开始菜单快捷方式都叫这个名字(artifactName 的 deepseek-harness-* 只是安装包文件名)
|
|
139
|
+
winExeName: 'DeepSeek Harness.exe',
|
|
140
|
+
winExeRe: /DeepSeek Harness\.exe$/i,
|
|
141
|
+
winPaths: ['Programs/DeepSeek Harness/DeepSeek Harness.exe'],
|
|
142
|
+
winLnkRe: /^DeepSeek Harness\.lnk$/i,
|
|
143
|
+
macApp: 'DeepSeek Harness.app',
|
|
144
|
+
linuxPaths: [], // 官方没有 Linux 桌面版
|
|
145
|
+
},
|
|
146
|
+
});
|
|
147
|
+
|
|
55
148
|
function commandExists(command, platform = process.platform) {
|
|
56
149
|
const checker = platform === 'win32' ? 'where.exe' : 'which';
|
|
57
150
|
const result = spawnSync(checker, [command], {
|
|
@@ -804,6 +897,152 @@ async function findZCodeExecutable() {
|
|
|
804
897
|
return null;
|
|
805
898
|
}
|
|
806
899
|
|
|
900
|
+
// ── 官方桌面应用探测 / 启动 ────────────────────────────────────────────
|
|
901
|
+
|
|
902
|
+
async function isFileAt(p) {
|
|
903
|
+
try { return (await fs.stat(p)).isFile(); } catch { return false; }
|
|
904
|
+
}
|
|
905
|
+
|
|
906
|
+
async function isDirAt(p) {
|
|
907
|
+
try { return (await fs.stat(p)).isDirectory(); } catch { return false; }
|
|
908
|
+
}
|
|
909
|
+
|
|
910
|
+
/**
|
|
911
|
+
* Windows 卸载注册表里所有 DisplayIcon / InstallLocation 的值(三个根各查一次,
|
|
912
|
+
* 五个桌面应用共用这一遍 —— check-tools 每 10 分钟跑一轮,不能每个应用各查三遍)。
|
|
913
|
+
* 为什么查注册表:NSIS / Inno 安装的桌面应用(Claude、CodeBuddy IDE、Kimi)
|
|
914
|
+
* 安装路径随用户选择变,固定路径猜不中,注册表是唯一权威登记处。
|
|
915
|
+
*/
|
|
916
|
+
function scanWindowsUninstallPaths() {
|
|
917
|
+
const roots = [
|
|
918
|
+
'HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall',
|
|
919
|
+
'HKLM\\Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall',
|
|
920
|
+
'HKLM\\Software\\WOW6432Node\\Microsoft\\Windows\\CurrentVersion\\Uninstall',
|
|
921
|
+
];
|
|
922
|
+
const values = [];
|
|
923
|
+
for (const root of roots) {
|
|
924
|
+
const output = spawnSync('reg.exe', ['query', root, '/s'], {
|
|
925
|
+
encoding: 'utf8',
|
|
926
|
+
windowsHide: true,
|
|
927
|
+
}).stdout || '';
|
|
928
|
+
for (const line of output.split(/\r?\n/)) {
|
|
929
|
+
if (!/DisplayIcon|InstallLocation/i.test(line)) continue;
|
|
930
|
+
const value = line.replace(/^.*?REG_(?:EXPAND_)?SZ\s+/i, '').trim().replace(/^"|"$/g, '').replace(/,\d+$/, '');
|
|
931
|
+
if (value) values.push(value);
|
|
932
|
+
}
|
|
933
|
+
}
|
|
934
|
+
return values;
|
|
935
|
+
}
|
|
936
|
+
|
|
937
|
+
/** 递归找一个名字匹配的项(开始菜单目录都很浅,递归开销可忽略) */
|
|
938
|
+
async function walkFindFile(dir, nameRe) {
|
|
939
|
+
let entries;
|
|
940
|
+
try {
|
|
941
|
+
entries = await fs.readdir(dir, { withFileTypes: true });
|
|
942
|
+
} catch {
|
|
943
|
+
return null;
|
|
944
|
+
}
|
|
945
|
+
for (const entry of entries) {
|
|
946
|
+
const full = path.join(dir, entry.name);
|
|
947
|
+
if (entry.isDirectory()) {
|
|
948
|
+
const hit = await walkFindFile(full, nameRe);
|
|
949
|
+
if (hit) return hit;
|
|
950
|
+
} else if (nameRe.test(entry.name)) {
|
|
951
|
+
return full;
|
|
952
|
+
}
|
|
953
|
+
}
|
|
954
|
+
return null;
|
|
955
|
+
}
|
|
956
|
+
|
|
957
|
+
/**
|
|
958
|
+
* 开始菜单两个 Programs 根里按文件名找快捷方式。
|
|
959
|
+
* 为什么需要它:微软商店分发的桌面应用(Codex 桌面版所在的 ChatGPT 桌面)
|
|
960
|
+
* 不进经典 Uninstall 注册表、安装路径也猜不中,但开始菜单一定有 .lnk;
|
|
961
|
+
* 且 .lnk 本身就是可启动目标(ShellExecute 认快捷方式,见 launchDesktopAppTarget)。
|
|
962
|
+
*/
|
|
963
|
+
async function findStartMenuShortcut(nameRe) {
|
|
964
|
+
const roots = [
|
|
965
|
+
path.join(process.env.APPDATA || '', 'Microsoft', 'Windows', 'Start Menu', 'Programs'),
|
|
966
|
+
path.join(process.env.PROGRAMDATA || '', 'Microsoft', 'Windows', 'Start Menu', 'Programs'),
|
|
967
|
+
].filter(Boolean);
|
|
968
|
+
for (const root of roots) {
|
|
969
|
+
const hit = await walkFindFile(root, nameRe);
|
|
970
|
+
if (hit) return hit;
|
|
971
|
+
}
|
|
972
|
+
return null;
|
|
973
|
+
}
|
|
974
|
+
|
|
975
|
+
/**
|
|
976
|
+
* 探测某个工具的官方桌面应用:装没装、启动目标是什么。
|
|
977
|
+
* target 是**可直接启动的东西**(exe / .app / .lnk / Linux 可执行文件);
|
|
978
|
+
* 没装上 target 为 null —— 前端据此把菜单项翻成「下载 X」并打开官网地址。
|
|
979
|
+
*
|
|
980
|
+
* 三平台口径:
|
|
981
|
+
* Windows 固定安装路径 → 卸载注册表(DisplayIcon 指向 exe / InstallLocation
|
|
982
|
+
* 是目录时拼期望 exe 名)→ 开始菜单 .lnk(商店版应用走这条)
|
|
983
|
+
* macOS /Applications/<App>.app
|
|
984
|
+
* Linux 固定候选路径(桌面版分发最不稳的平台,猜不中就降级成"去官网下载")
|
|
985
|
+
*/
|
|
986
|
+
async function findDesktopApp(tool) {
|
|
987
|
+
const app = TOOL_DESKTOP_APPS[tool];
|
|
988
|
+
if (!app) return { installed: false, target: null };
|
|
989
|
+
|
|
990
|
+
if (process.platform === 'win32') {
|
|
991
|
+
const roots = [process.env.LOCALAPPDATA, process.env.PROGRAMFILES].filter(Boolean);
|
|
992
|
+
for (const rel of app.winPaths || []) {
|
|
993
|
+
for (const root of roots) {
|
|
994
|
+
const candidate = path.join(root, rel);
|
|
995
|
+
if (await isFileAt(candidate)) return { installed: true, target: candidate };
|
|
996
|
+
}
|
|
997
|
+
}
|
|
998
|
+
// 注册表值两种形态:DisplayIcon 多直接指向 exe;InstallLocation 是目录,
|
|
999
|
+
// 在目录下拼期望的 exe 名。值与 exe 名对不上的一律不算(防误报)。
|
|
1000
|
+
for (const value of scanWindowsUninstallPaths()) {
|
|
1001
|
+
if (/\.(ico|png|jpg|jpeg|svg)$/i.test(value)) continue;
|
|
1002
|
+
if (app.winExeRe.test(value) && (await isFileAt(value))) {
|
|
1003
|
+
return { installed: true, target: value };
|
|
1004
|
+
}
|
|
1005
|
+
if (await isDirAt(value)) {
|
|
1006
|
+
const exe = path.join(value, app.winExeName);
|
|
1007
|
+
if (await isFileAt(exe)) return { installed: true, target: exe };
|
|
1008
|
+
}
|
|
1009
|
+
}
|
|
1010
|
+
const lnk = await findStartMenuShortcut(app.winLnkRe);
|
|
1011
|
+
if (lnk) return { installed: true, target: lnk };
|
|
1012
|
+
return { installed: false, target: null };
|
|
1013
|
+
}
|
|
1014
|
+
|
|
1015
|
+
if (process.platform === 'darwin') {
|
|
1016
|
+
const appDir = `/Applications/${app.macApp}`;
|
|
1017
|
+
return (await isDirAt(appDir)) ? { installed: true, target: appDir } : { installed: false, target: null };
|
|
1018
|
+
}
|
|
1019
|
+
|
|
1020
|
+
for (const candidate of app.linuxPaths || []) {
|
|
1021
|
+
if (await isFileAt(candidate)) return { installed: true, target: candidate };
|
|
1022
|
+
}
|
|
1023
|
+
return { installed: false, target: null };
|
|
1024
|
+
}
|
|
1025
|
+
|
|
1026
|
+
/**
|
|
1027
|
+
* 启动桌面应用。target 的三种形态分开处理:
|
|
1028
|
+
* .lnk(开始菜单快捷方式)不能直接 spawn(CreateProcess 不认快捷方式),
|
|
1029
|
+
* 走 cmd /c start 让 ShellExecute 去解析;
|
|
1030
|
+
* .app(macOS)是目录,交给 open 命令;
|
|
1031
|
+
* 其余(Windows exe / Linux 可执行文件)直接 detached spawn,与 zcode 一致。
|
|
1032
|
+
*/
|
|
1033
|
+
async function launchDesktopAppTarget(target) {
|
|
1034
|
+
if (process.platform === 'win32') {
|
|
1035
|
+
if (/\.lnk$/i.test(target)) {
|
|
1036
|
+
return spawnDetached('cmd.exe', ['/c', 'start', '""', target]);
|
|
1037
|
+
}
|
|
1038
|
+
return spawnDetached(target, []);
|
|
1039
|
+
}
|
|
1040
|
+
if (/\.app$/i.test(target)) {
|
|
1041
|
+
return spawnDetached('open', [target]);
|
|
1042
|
+
}
|
|
1043
|
+
return spawnDetached(target, []);
|
|
1044
|
+
}
|
|
1045
|
+
|
|
807
1046
|
async function launchToolInstaller(installer, dirPath = process.cwd(), { update = false } = {}) {
|
|
808
1047
|
// update=true 时改用 updateXxx 字段(升级命令与安装命令动词不同:
|
|
809
1048
|
// npm @latest / winget upgrade / brew upgrade / snap refresh / kimi 脚本幂等重跑)
|
|
@@ -1357,6 +1596,27 @@ export function registerFileOpenRoutes({
|
|
|
1357
1596
|
});
|
|
1358
1597
|
}));
|
|
1359
1598
|
|
|
1599
|
+
// 打开某个工具的官方桌面应用(顶栏 / 项目菜单里的「打开 X 桌面端」走这里)。
|
|
1600
|
+
// 没装桌面端时返回 400 并带上官网地址 —— 前端在检测阶段就分叉了(没装直接
|
|
1601
|
+
// window.open 官网),会走到这里说明是检测之后才卸载的边角情况。
|
|
1602
|
+
app.post('/api/open-desktop-app', asyncRoute(async (req, res) => {
|
|
1603
|
+
const { tool } = req.body || {};
|
|
1604
|
+
const app = TOOL_DESKTOP_APPS[tool];
|
|
1605
|
+
if (!app) {
|
|
1606
|
+
throw new HttpError(400, '该工具没有桌面端');
|
|
1607
|
+
}
|
|
1608
|
+
const found = await findDesktopApp(tool);
|
|
1609
|
+
if (!found.installed || !found.target) {
|
|
1610
|
+
throw new HttpError(400, `未检测到 ${app.name},请先到官网下载安装:${app.downloadUrl}`);
|
|
1611
|
+
}
|
|
1612
|
+
try {
|
|
1613
|
+
await launchDesktopAppTarget(found.target);
|
|
1614
|
+
res.json({ success: true, message: `已打开 ${app.name}` });
|
|
1615
|
+
} catch (error) {
|
|
1616
|
+
res.status(500).json({ success: false, error: error.message || `无法启动 ${app.name}` });
|
|
1617
|
+
}
|
|
1618
|
+
}));
|
|
1619
|
+
|
|
1360
1620
|
// 检测本地工具是否已安装 + 采集版本号(供前端决定按钮行为、tooltip 显示版本)
|
|
1361
1621
|
// 检测方式: spawn 'tool --version',exit 0 即视为已安装;stdout/stderr 里提取 semver。
|
|
1362
1622
|
// 超时 15s:Claude CLI / opencode 等 Node 包装的工具首次冷启动
|
|
@@ -1430,10 +1690,27 @@ export function registerFileOpenRoutes({
|
|
|
1430
1690
|
]);
|
|
1431
1691
|
|
|
1432
1692
|
const installers = getToolInstallers();
|
|
1693
|
+
// 官方桌面应用探测:与 CLI 检测并行跑(各自只做 fs / 注册表查询,不起进程)。
|
|
1694
|
+
// 前端据此把菜单项翻成「打开 X 桌面端」(装了)/「下载 X 桌面端」(没装)。
|
|
1695
|
+
// downloadUrl 可能是函数(dsh 按平台/架构给固定下载地址),在这里统一解析成
|
|
1696
|
+
// 字符串再下发 —— 前端只认一个地址,平台判断留在服务端(与 UI 同机)。
|
|
1697
|
+
const desktopEntries = await Promise.all(
|
|
1698
|
+
Object.entries(TOOL_DESKTOP_APPS).map(async ([tool, app]) => {
|
|
1699
|
+
const found = await findDesktopApp(tool);
|
|
1700
|
+
return [tool, {
|
|
1701
|
+
installed: found.installed,
|
|
1702
|
+
name: app.name,
|
|
1703
|
+
downloadUrl: typeof app.downloadUrl === 'function'
|
|
1704
|
+
? app.downloadUrl(process.platform, process.arch)
|
|
1705
|
+
: app.downloadUrl,
|
|
1706
|
+
}];
|
|
1707
|
+
}),
|
|
1708
|
+
);
|
|
1433
1709
|
res.json({
|
|
1434
1710
|
success: true,
|
|
1435
1711
|
platform: process.platform,
|
|
1436
1712
|
installers: publicInstallerInfo(installers),
|
|
1713
|
+
desktopApps: Object.fromEntries(desktopEntries),
|
|
1437
1714
|
vscode: vscodeRes.installed,
|
|
1438
1715
|
claude: claudeRes.installed,
|
|
1439
1716
|
codex: codexRes.installed,
|
|
@@ -144,6 +144,7 @@ const BUILTIN_MCPS = [
|
|
|
144
144
|
description: '在显式授权的目录里读写文件。',
|
|
145
145
|
package: '@modelcontextprotocol/server-filesystem',
|
|
146
146
|
args: ['-y', '@modelcontextprotocol/server-filesystem', '${project}'],
|
|
147
|
+
repository: 'modelcontextprotocol/servers',
|
|
147
148
|
upstream: 'https://github.com/modelcontextprotocol/servers/tree/main/src/filesystem',
|
|
148
149
|
tags: ['official', 'files'],
|
|
149
150
|
},
|
|
@@ -152,6 +153,7 @@ const BUILTIN_MCPS = [
|
|
|
152
153
|
name: 'Memory',
|
|
153
154
|
description: '基于知识图谱的长期记忆,跨会话记住事实与关系。',
|
|
154
155
|
package: '@modelcontextprotocol/server-memory',
|
|
156
|
+
repository: 'modelcontextprotocol/servers',
|
|
155
157
|
upstream: 'https://github.com/modelcontextprotocol/servers/tree/main/src/memory',
|
|
156
158
|
tags: ['official', 'memory'],
|
|
157
159
|
},
|
|
@@ -160,6 +162,7 @@ const BUILTIN_MCPS = [
|
|
|
160
162
|
name: 'Sequential Thinking',
|
|
161
163
|
description: '把复杂问题拆成可修订的连续思考步骤。',
|
|
162
164
|
package: '@modelcontextprotocol/server-sequential-thinking',
|
|
165
|
+
repository: 'modelcontextprotocol/servers',
|
|
163
166
|
upstream: 'https://github.com/modelcontextprotocol/servers/tree/main/src/sequentialthinking',
|
|
164
167
|
tags: ['official', 'reasoning'],
|
|
165
168
|
},
|
|
@@ -168,6 +171,7 @@ const BUILTIN_MCPS = [
|
|
|
168
171
|
name: 'Everything (示例服务器)',
|
|
169
172
|
description: '官方参考实现,涵盖 prompts/resources/tools 全套能力,适合验证链路。',
|
|
170
173
|
package: '@modelcontextprotocol/server-everything',
|
|
174
|
+
repository: 'modelcontextprotocol/servers',
|
|
171
175
|
upstream: 'https://github.com/modelcontextprotocol/servers/tree/main/src/everything',
|
|
172
176
|
tags: ['official', 'reference'],
|
|
173
177
|
},
|
|
@@ -176,6 +180,7 @@ const BUILTIN_MCPS = [
|
|
|
176
180
|
name: 'GitHub',
|
|
177
181
|
description: '操作仓库、Issue、Pull Request 与代码搜索。',
|
|
178
182
|
package: '@modelcontextprotocol/server-github',
|
|
183
|
+
repository: 'modelcontextprotocol/servers',
|
|
179
184
|
envKeys: ['GITHUB_PERSONAL_ACCESS_TOKEN'],
|
|
180
185
|
upstream: 'https://github.com/modelcontextprotocol/servers/tree/main/src/github',
|
|
181
186
|
tags: ['official', 'github'],
|
|
@@ -185,6 +190,7 @@ const BUILTIN_MCPS = [
|
|
|
185
190
|
name: 'Context7',
|
|
186
191
|
description: '拉取任意库的最新版文档与代码示例,避免模型凭记忆写过期 API。',
|
|
187
192
|
package: '@upstash/context7-mcp',
|
|
193
|
+
repository: 'upstash/context7',
|
|
188
194
|
upstream: 'https://github.com/upstash/context7',
|
|
189
195
|
tags: ['docs', 'popular'],
|
|
190
196
|
},
|
|
@@ -193,6 +199,7 @@ const BUILTIN_MCPS = [
|
|
|
193
199
|
name: 'Playwright',
|
|
194
200
|
description: '浏览器自动化:打开页面、点击、截图、取 DOM。',
|
|
195
201
|
package: '@playwright/mcp',
|
|
202
|
+
repository: 'microsoft/playwright-mcp',
|
|
196
203
|
upstream: 'https://github.com/microsoft/playwright-mcp',
|
|
197
204
|
tags: ['browser', 'popular'],
|
|
198
205
|
},
|
|
@@ -201,6 +208,7 @@ const BUILTIN_MCPS = [
|
|
|
201
208
|
name: 'Chrome DevTools',
|
|
202
209
|
description: '直接操作真实 Chrome:性能轨迹、网络、控制台。',
|
|
203
210
|
package: 'chrome-devtools-mcp',
|
|
211
|
+
repository: 'ChromeDevTools/chrome-devtools-mcp',
|
|
204
212
|
upstream: 'https://github.com/ChromeDevTools/chrome-devtools-mcp',
|
|
205
213
|
tags: ['browser'],
|
|
206
214
|
},
|
|
@@ -468,13 +476,21 @@ function normalizeNpmSearch(data, type) {
|
|
|
468
476
|
return objects.map(entry => {
|
|
469
477
|
const name = String(entry?.package?.name || '').trim();
|
|
470
478
|
if (!name || !SAFE_PACKAGE.test(name)) return null;
|
|
479
|
+
// npm 的 links.repository 常是完整 URL(git+https://github.com/owner/repo.git),
|
|
480
|
+
// 归一成 owner/repo:卡片据此取 GitHub 组织头像当图标。
|
|
481
|
+
// 只给 mcp 用 —— skill 的 installSkill 见 repository 就改走 git clone
|
|
482
|
+
// (agentMarketplace.js installSkill),给 npm skill 带上它会悄悄改变安装语义。
|
|
483
|
+
const repo = String(entry.package?.links?.repository || '')
|
|
484
|
+
.replace(/^git\+/, '')
|
|
485
|
+
.replace(/^https?:\/\/(www\.)?github\.com\//, '')
|
|
486
|
+
.replace(/\.git$/, '');
|
|
471
487
|
return {
|
|
472
488
|
id: `npm-${safeId(name.replace(/^@/, '').replace('/', '-'))}`,
|
|
473
489
|
type,
|
|
474
490
|
name: clampText(entry.package.name, 80),
|
|
475
491
|
description: clampText(entry.package.description || 'npm package', 400),
|
|
476
492
|
package: name,
|
|
477
|
-
repository: '',
|
|
493
|
+
repository: type === 'mcp' && SAFE_REPOSITORY.test(repo) ? repo : '',
|
|
478
494
|
upstream: entry.package?.links?.npm || `https://www.npmjs.com/package/${encodeURIComponent(name)}`,
|
|
479
495
|
downloads: Number(entry.downloads?.weekly) || 0,
|
|
480
496
|
tags: ['npm'],
|
|
@@ -255,6 +255,15 @@ export async function runExternalTurn({
|
|
|
255
255
|
if (lineBuf.stderr.trim()) for (const l of lineBuf.stderr.split('\n')) handleLine(l, 'stderr');
|
|
256
256
|
tracker.flush?.();
|
|
257
257
|
|
|
258
|
+
// 协议层报错(各家方言不同,handler 统一落到 job.agentError)。
|
|
259
|
+
// 与内置 g ai 同口径:只发 error、不落一条空的 assistant —— 否则前端拿到的是
|
|
260
|
+
// 一个空气泡(2026-10-21 用户报的"续接时不输出,直接结束"就是这条路径:
|
|
261
|
+
// codebuddy 用错会话 id 时发 {"type":"error"} 且退出码 0)。
|
|
262
|
+
if (!aborted && job.agentError) {
|
|
263
|
+
send({ type: 'error', error: `${label}: ${job.agentError}` });
|
|
264
|
+
return { aborted };
|
|
265
|
+
}
|
|
266
|
+
|
|
258
267
|
// 落盘:assistant(带 tool_calls)+ 逐条 tool 结果,形状与 g ai 那条链路一致,
|
|
259
268
|
// 前端 convertSessionToMessages 的合并逻辑不用改就能渲染历史。
|
|
260
269
|
const calls = Array.isArray(job.toolCalls) ? job.toolCalls : [];
|
|
@@ -293,6 +293,26 @@ test('launcher 抛错(CLI 没装)→ 发 error,不让整轮静默失败',
|
|
|
293
293
|
assert.equal(c.of('done').length, 0, '启动失败不该再发 done');
|
|
294
294
|
});
|
|
295
295
|
|
|
296
|
+
test('协议层报错(codebuddy 找不到会话)→ 发 error,不落空 assistant、不发 done', async () => {
|
|
297
|
+
// 实测:用子会话 id 去 --resume,CLI 只吐这一条 NDJSON,退出码 0
|
|
298
|
+
const lines = [
|
|
299
|
+
JSON.stringify({ type: 'error', error: 'No conversation found with session ID: sub-sess' }),
|
|
300
|
+
];
|
|
301
|
+
const session = baseSession();
|
|
302
|
+
session.engineSessionId = 'sub-sess';
|
|
303
|
+
const c = collector();
|
|
304
|
+
await runExternalTurn({
|
|
305
|
+
session, engine: 'codebuddy', userMessage: '接着聊', cwd: 'D:/ws',
|
|
306
|
+
send: c.send,
|
|
307
|
+
launchers: { codebuddy: async () => ({ pid: 1, child: fakeChild(lines) }) },
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
assert.equal(c.of('error').length, 1);
|
|
311
|
+
assert.match(c.of('error')[0].error, /No conversation found/);
|
|
312
|
+
assert.equal(c.of('done').length, 0, '失败轮不该发 done(否则界面显示成"已完成")');
|
|
313
|
+
assert.equal(session.messages.filter(m => m.role === 'assistant').length, 0, '不该落一条空的 assistant');
|
|
314
|
+
});
|
|
315
|
+
|
|
296
316
|
test('未知引擎 → 明确报错(不静默当内置处理)', async () => {
|
|
297
317
|
const c = collector();
|
|
298
318
|
await runExternalTurn({ session: baseSession(), engine: 'gemini', userMessage: 'hi', cwd: 'D:/ws', send: c.send });
|
|
@@ -273,3 +273,45 @@ test('codebuddy 事件:init 重复推送(实测出现过两条)只认第
|
|
|
273
273
|
assert.equal(job.claudeSessionId, 'cbc-sess-x');
|
|
274
274
|
assert.deepEqual(sink.output, []);
|
|
275
275
|
});
|
|
276
|
+
|
|
277
|
+
// 2026-10-21 实测(CLI 2.168.0,一轮派了 Explore 子智能体):一轮里出现 4 条 init ——
|
|
278
|
+
// 主会话(78 件工具)→ 子会话(36 件工具)→ 子会话再补一条 → 结尾主会话又补一条。
|
|
279
|
+
// 子会话**不注册进会话库**(CLI 日志 "Child session skipped registration"),
|
|
280
|
+
// 拿它的 id 去 --resume 只会得到 {"type":"error","error":"No conversation found with
|
|
281
|
+
// session ID: …"} 且退出码 0 —— 用户看到的就是"续接不输出、直接结束"。
|
|
282
|
+
test('codebuddy 事件:子智能体的 init 不能顶掉主会话 id(--resume 全靠它)', () => {
|
|
283
|
+
const job = {};
|
|
284
|
+
const sink = makeSink();
|
|
285
|
+
const handle = createCodebuddyEventHandler(job, sink.appendOutput, sink.appendThinking, makeTrackerSpy());
|
|
286
|
+
|
|
287
|
+
handle({ type: 'system', subtype: 'init', session_id: 'main-sess', tools: new Array(78).fill('t') });
|
|
288
|
+
// 子会话 init(工具集更小,且紧跟一条带 parent_tool_use_id 的 assistant)
|
|
289
|
+
handle({ type: 'system', subtype: 'init', session_id: 'sub-sess', tools: new Array(36).fill('t') });
|
|
290
|
+
handle({ type: 'assistant', parent_tool_use_id: 'call_1', message: { content: [{ type: 'text', text: '子智能体的汇报' }] } });
|
|
291
|
+
// 结尾主会话再补一条 init
|
|
292
|
+
handle({ type: 'system', subtype: 'init', session_id: 'main-sess' });
|
|
293
|
+
|
|
294
|
+
assert.equal(job.claudeSessionId, 'main-sess', '续接标识必须留在主会话上');
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
test('codebuddy 事件:error / result.is_error 落 agentError(退出码 0 也不能当完成)', () => {
|
|
298
|
+
// 形状取自实测:用错的会话 id 续接时 CLI 只吐这一条,然后 exit 0
|
|
299
|
+
const job = {};
|
|
300
|
+
const handle = createCodebuddyEventHandler(job, () => {}, () => {}, makeTrackerSpy());
|
|
301
|
+
handle({ type: 'error', error: 'No conversation found with session ID: sub-sess' });
|
|
302
|
+
assert.equal(job.agentError, 'No conversation found with session ID: sub-sess');
|
|
303
|
+
|
|
304
|
+
// result 的失败分支:errors 是错误对象/字符串数组,没有 result 字段
|
|
305
|
+
const job2 = {};
|
|
306
|
+
const handle2 = createCodebuddyEventHandler(job2, () => {}, () => {}, makeTrackerSpy());
|
|
307
|
+
handle2({ type: 'result', subtype: 'error_during_execution', is_error: true, errors: ['Authentication required'] });
|
|
308
|
+
assert.equal(job2.agentError, 'Authentication required');
|
|
309
|
+
|
|
310
|
+
// success 的 result 不能误判成失败(它带 result 正文,is_error=false)
|
|
311
|
+
const job3 = {};
|
|
312
|
+
const sink3 = makeSink();
|
|
313
|
+
const handle3 = createCodebuddyEventHandler(job3, sink3.appendOutput, sink3.appendThinking, makeTrackerSpy());
|
|
314
|
+
handle3({ type: 'result', subtype: 'success', is_error: false, result: 'ok' });
|
|
315
|
+
assert.equal(job3.agentError, undefined);
|
|
316
|
+
assert.deepEqual(sink3.output, [], 'result.result 是终态快照,不是新的正文分片');
|
|
317
|
+
});
|