@yuandc/aica 0.1.88 → 0.1.89

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.
@@ -1,3 +1,3 @@
1
- import g from"node:fs";import B from"node:os";import E from"node:path";import{randomUUID as U}from"node:crypto";import{startAcpProcess as I,closeAcpProcess as R}from"../../client/acp-process.js";import{JsonLineRpcClient as A}from"../../client/json-rpc.js";import{resolveWorkBuddyLaunch as N}from"./launch.js";import{record as i}from"./config.js";import{workBuddyRegionEnv as T}from"./auth-environment.js";function x(c){const n=i(c),e=typeof n.userId=="string"?n.userId.slice(0,200):"",r=[n.userNickname,n.userName].find(s=>typeof s=="string"&&s.trim());return e||r?{id:e,name:typeof r=="string"?r.slice(0,200):e}:void 0}function $(c,n){if(typeof c!="string"||c.length>8192)throw new Error("invalid_auth_url");const e=new URL(c),r=["tencent.com","codebuddy.ai","codebuddy.cn"];if(e.protocol!=="https:"||e.username||e.password||e.port&&e.port!=="443"||!r.some(t=>e.hostname===t||e.hostname.endsWith(`.${t}`)))throw new Error("invalid_auth_url");const s=e.hostname==="codebuddy.ai"||e.hostname.endsWith(".codebuddy.ai"),f=e.hostname==="codebuddy.cn"||e.hostname.endsWith(".codebuddy.cn")||e.hostname==="copilot.tencent.com";if(n==="internal"&&s||n==="external"&&f)throw new Error("auth_region_mismatch");return e.href}function M(c,n,e=!1){const r=N(!1),s=g.mkdtempSync(E.join(B.tmpdir(),"aca-workbuddy-auth-")),f=e?r.args.flatMap(o=>o==="--acp"?["--print","--input-format","stream-json","--output-format","stream-json","--verbose"]:[o]):r.args;let t;try{t=I(r.command,[...f,"--no-session-persistence"],s,{...r.env,...T(c),BROWSER:E.join(s,"no-local-browser")})}catch(o){throw g.rmSync(s,{recursive:!0,force:!0}),o}let S;const W=()=>S??=R(t.child).finally(()=>g.rmSync(s,{recursive:!0,force:!0})),m=e?null:new A(t.child,{includeJsonRpc:!0,onNotification(o,d){o==="_codebuddy.ai/authUrl"&&n?.(String(i(d).authUrl||""))}});return{close:W,async initialize(){return i(await m.request("initialize",{protocolVersion:1,clientCapabilities:{fs:{readTextFile:!1,writeTextFile:!1},terminal:!1},clientInfo:{name:"aca-auth",version:"1"}},12e3))},async account(){return x(i(await m.request("_codebuddy.ai/getUserInfo",{},12e3)).userInfo)},async authenticate(o,d){const p=i(await m.request("authenticate",{methodId:o},d));return x(i(p._meta)["codebuddy.ai/userinfo"])},async logout(){const o=U();await new Promise((d,p)=>{let a="";const k=setTimeout(()=>l(new Error("logout_timeout")),15e3),l=u=>{clearTimeout(k),t.child.stdout.off("data",v),t.child.off("error",h),t.child.off("close",b),u?p(u):d()},h=u=>l(u),b=()=>l(new Error("logout_process_exited")),v=u=>{if(a+=u.toString(),Buffer.byteLength(a)>1024*1024)return l(new Error("invalid_auth_response"));let y;for(;(y=a.indexOf(`
2
- `))>=0;){const q=a.slice(0,y);a=a.slice(y+1);let w;try{w=i(JSON.parse(q))}catch{continue}const _=i(w.response);if(!(w.type!=="control_response"||_.request_id!==o))return l(_.subtype==="success"&&i(_.response).success===!0?void 0:new Error("logout_failed"))}};t.child.stdout.on("data",v),t.child.stderr.resume(),t.child.on("error",h),t.child.on("close",b),t.child.stdin.on("error",h),t.child.stdin.write(`${JSON.stringify({type:"control_request",request_id:o,request:{subtype:"logout"}})}
3
- `)})}}}export{M as createWorkBuddyAuthTransport,x as publicWorkBuddyAccount,$ as safeWorkBuddyAuthUrl};
1
+ import _ from"node:fs";import U from"node:os";import v from"node:path";import{randomUUID as W}from"node:crypto";import{startAcpProcess as B,closeAcpProcess as T}from"../../client/acp-process.js";import{JsonLineRpcClient as R}from"../../client/json-rpc.js";import{resolveWorkBuddyLaunch as A}from"./launch.js";import{record as r}from"./config.js";import{workBuddyRegionEnv as N}from"./auth-environment.js";function b(c){const s=r(c),e=typeof s.userId=="string"?s.userId.slice(0,200):"",n=[s.userNickname,s.userName].find(i=>typeof i=="string"&&i.trim());return e||n?{id:e,name:typeof n=="string"?n.slice(0,200):e}:void 0}function $(c,s){if(typeof c!="string"||c.length>8192)throw new Error("invalid_auth_url");const e=new URL(c),n=["tencent.com","codebuddy.ai","codebuddy.cn"];if(e.protocol!=="https:"||e.username||e.password||e.port&&e.port!=="443"||!n.some(o=>e.hostname===o||e.hostname.endsWith(`.${o}`)))throw new Error("invalid_auth_url");const i=e.hostname==="codebuddy.ai"||e.hostname.endsWith(".codebuddy.ai"),m=e.hostname==="codebuddy.cn"||e.hostname.endsWith(".codebuddy.cn")||e.hostname==="copilot.tencent.com";if(s==="internal"&&i||s==="external"&&m)throw new Error("auth_region_mismatch");return e.href}function M(c,s,e=!1){const n=A(!1),i=_.mkdtempSync(v.join(U.tmpdir(),"aca-workbuddy-auth-")),m=e?n.args.flatMap(t=>t==="--acp"?["--print","--input-format","stream-json","--output-format","stream-json","--verbose"]:[t]):n.args;let o;try{o=B(n.command,[...m,"--no-session-persistence"],i,{...n.env,...N(c),BROWSER:v.join(i,"no-local-browser")})}catch(t){throw _.rmSync(i,{recursive:!0,force:!0}),t}let E;const q=()=>E??=T(o.child).finally(()=>_.rmSync(i,{recursive:!0,force:!0})),f=e?null:new R(o.child,{includeJsonRpc:!0,onNotification(t,d){t==="_codebuddy.ai/authUrl"&&s?.(String(r(d).authUrl||""))}});return{close:q,async initialize(){return r(await f.request("initialize",{protocolVersion:1,clientCapabilities:{fs:{readTextFile:!1,writeTextFile:!1},terminal:!1},clientInfo:{name:"aca-auth",version:"1"}},12e3))},async account(){return b(r(await f.request("_codebuddy.ai/getUserInfo",{},12e3)).userInfo)},async billingIdentity(){const t=r(r(await f.request("_codebuddy.ai/getUserInfo",{},12e3)).userInfo);return{account:b(t),token:typeof t.accessToken=="string"?t.accessToken:typeof t.token=="string"?t.token:"",enterpriseId:typeof t.enterpriseId=="string"?t.enterpriseId:""}},async authenticate(t,d){const p=r(await f.request("authenticate",{methodId:t},d));return b(r(p._meta)["codebuddy.ai/userinfo"])},async logout(){const t=W();await new Promise((d,p)=>{let a="";const x=setTimeout(()=>l(new Error("logout_timeout")),15e3),l=u=>{clearTimeout(x),o.child.stdout.off("data",I),o.child.off("error",h),o.child.off("close",k),u?p(u):d()},h=u=>l(u),k=()=>l(new Error("logout_process_exited")),I=u=>{if(a+=u.toString(),Buffer.byteLength(a)>1024*1024)return l(new Error("invalid_auth_response"));let y;for(;(y=a.indexOf(`
2
+ `))>=0;){const S=a.slice(0,y);a=a.slice(y+1);let w;try{w=r(JSON.parse(S))}catch{continue}const g=r(w.response);if(!(w.type!=="control_response"||g.request_id!==t))return l(g.subtype==="success"&&r(g.response).success===!0?void 0:new Error("logout_failed"))}};o.child.stdout.on("data",I),o.child.stderr.resume(),o.child.on("error",h),o.child.on("close",k),o.child.stdin.on("error",h),o.child.stdin.write(`${JSON.stringify({type:"control_request",request_id:t,request:{subtype:"logout"}})}
3
+ `)})}}}export{M as createWorkBuddyAuthTransport,b as publicWorkBuddyAccount,$ as safeWorkBuddyAuthUrl};
@@ -1 +1 @@
1
- import p from"node:fs";import{enterWorkBuddyTask as k}from"./auth-gate.js";import{runAbortableRequest as A}from"../../client/abortable-request.js";import{defaultAcpRuntimePool as m}from"../../client/acp-runtime-pool.js";import{BoundedItemBuffer as w}from"../../client/bounded-buffer.js";import{contextUsageFromAcpSessionUpdate as B}from"../../client/context-usage.js";import{providerEventFromAcpSessionUpdate as E,providerStatusFromAcpSessionUpdate as b}from"../standard-acp-events.js";import{resolveWorkBuddyLaunch as C,workBuddyTimeout as W}from"./launch.js";import{applyWorkBuddyConfig as M,record as o,rememberWorkBuddyCapabilities as f}from"./config.js";import{handleWorkBuddyRequest as R}from"./interactions.js";import{WorkBuddyEventRouter as y}from"./events.js";import{WorkBuddySupplements as D,workBuddyPromptContent as q}from"./supplements.js";const h=new WeakMap,u=new Set,_={sessionResume:!0,liveSupplement:!0,imageInput:!0,fileAttachment:!0,filesystem:!0,terminal:!0,permissionRequest:!0,configOptions:!0,usage:!0,contextUsage:!0,contextCompaction:!0,plan:!0,diff:!0};class G{id="workbuddy";name="腾讯 Code Buddy";capabilities=_;async createSession(s){return new O(s.cwd,s.providerSessionId??null,s.timeoutMs)}}class O{cwd;providerSessionId;timeoutMs;lease=null;input=null;acceptingOutput=!1;busy=!1;content="";usage=null;response={};commands=new Set;router=new y;supplements=null;updates=new w(16);statuses=new w(64);firstEventAtMs=null;lastEventAtMs=null;constructor(s,e,t=W()){this.cwd=s,this.providerSessionId=e,this.timeoutMs=t}async sendPrompt(s){return this.run(s,!1)}async compactContext(s){if(!this.providerSessionId)throw new Error("WorkBuddy 当前没有可压缩的会话。");const e=await this.run({...s,cwd:s.cwd,prompt:"/compact",requireSessionResume:!0},!0);return{providerSessionId:e.providerSessionId,contextUsage:e.contextUsage??null,maintenance:{action:"compact",provider:"workbuddy",protocol:"session/prompt",stopReason:o(e.promptResponse).stopReason},durationMs:e.durationMs}}async run(s,e){if(this.busy)throw new Error("WorkBuddy 会话正在执行另一轮任务。");if(p.realpathSync(s.cwd)!==p.realpathSync(this.cwd))throw new Error("WorkBuddy 会话不能切换工作目录。");if(s.requireSessionResume&&!this.providerSessionId)throw new Error("WorkBuddy 历史会话缺少 Session ID,禁止新建会话。");let t=this.providerSessionId?`${p.realpathSync(this.cwd)}\0${this.providerSessionId}`:null;if(t&&u.has(t))throw new Error("WorkBuddy 会话正在执行另一轮任务。");const a=k();t&&u.add(t),this.busy=!0,this.input=s,this.content="",this.usage=null,this.response={},this.commands.clear(),this.router=new y,this.updates.clear(),this.statuses.clear(),this.firstEventAtMs=this.lastEventAtMs=null;const n=Date.now();try{return await A({signal:s.signal,cancelledMessage:"WorkBuddy 任务已取消",onAbort:()=>this.cancelAndDispose(),startRequest:async()=>{this.status("initializing","WorkBuddy ACP 启动中");const r=C(s.canExecuteTools!==!1);if(this.providerSessionId&&await m.retireSessionRuntimes({providerId:"workbuddy",cwd:this.cwd,providerSessionId:this.providerSessionId,keepRuntimeIdentity:r.runtimeIdentity}),s.signal?.aborted)throw new Error("WorkBuddy 任务已取消");const i=await m.acquire({providerId:"workbuddy",cwd:this.cwd,providerSessionId:this.providerSessionId,...r,onSessionUpdate:d=>this.handleUpdate(d),onClientRequest:(d,g,I)=>this.handleRequest(d,g,I.id)});if(this.lease=i,s.signal?.aborted)throw await i.dispose(),new Error("WorkBuddy 任务已取消");if(await this.initialize(i),await this.establishSession(i),!t){if(t=`${p.realpathSync(this.cwd)}\0${this.providerSessionId}`,u.has(t))throw t=null,new Error("WorkBuddy 会话正在执行另一轮任务。");u.add(t)}const S=o(o(h.get(i.client)?.agentCapabilities).promptCapabilities);if(s.promptBlocks?.some(d=>d.type==="image")&&S.image!==!0)throw new Error("当前 CodeBuddy ACP 版本未声明图片输入支持。");if(e||(this.response=await M(i.client,this.providerSessionId,this.response,s)),i.setSessionResponse(this.response),e&&!this.commands.has("compact"))throw new Error("当前 WorkBuddy 会话未提供 /compact 命令,无法压缩上下文。");this.acceptingOutput=!0,this.status("thinking",e?"正在压缩上下文":"思考中");const l=await i.client.prompt({sessionId:this.providerSessionId,prompt:q(s.prompt,s.promptBlocks)},s.timeoutMs??this.timeoutMs);if(s.signal?.aborted)throw new Error("WorkBuddy 任务已取消");const c=o(l).stopReason;if(c==="cancelled")throw new Error("WorkBuddy 任务已取消");if(e&&c!=="end_turn")throw new Error(`WorkBuddy 压缩未完成:${String(c||"未知状态")}`);return e&&this.usage&&(this.usage={...this.usage,state:"compacted",maintenanceAction:"compact"}),this.status("completed",e?"上下文压缩完成":"已完成"),{providerSessionId:this.providerSessionId,cliType:"builtin",agentType:"workbuddy",effectiveConfig:{model:s.model||null,mode:s.mode||"default",configOptions:this.response.configOptions,provider:"workbuddy",protocol:"acp",command:i.started.command,processPid:i.started.child.pid},command:i.started.command,args:i.started.args,cwd:this.cwd,exitCode:i.started.child.exitCode,signal:i.started.child.signalCode,durationMs:Date.now()-n,stdout:i.client.rpc.stdout,stderr:i.client.rpc.stderr,content:this.content.trim(),updateCount:this.updates.count(),updates:this.updates.snapshot(),statusTimeline:this.statuses.snapshot(),contextUsage:this.usage,promptResponse:l,performance:{startedAtMs:n,firstEventAtMs:this.firstEventAtMs,lastEventAtMs:this.lastEventAtMs,eventCount:this.updates.count(),firstEventLatencyMs:this.firstEventAtMs===null?null:this.firstEventAtMs-n,silentForMs:Date.now()-(this.lastEventAtMs??n),durationMs:Date.now()-n}}}})}catch(r){await this.lease?.dispose();const i=r instanceof Error?r.message:String(r);throw this.status("failed","WorkBuddy 执行失败",i),/authentication required|not authenticated|unauthorized/i.test(i)?new Error("腾讯 Code Buddy 尚未认证:请到设置 → 我的设备 → 腾讯 Code Buddy 登录;也可在运行 ACA Worker 的系统账号下执行 codebuddy 登录,或配置 CODEBUDDY_API_KEY。"):o(r).code==="ENOENT"?new Error("未找到 CodeBuddy CLI,请安装 @tencent-ai/codebuddy-code,或设置 ACA_WORKBUDDY_COMMAND。"):r}finally{this.acceptingOutput=!1,s.onSupplementReady?.(null);try{await this.supplements?.close()}finally{a()}this.supplements=null,this.lease?.release(),this.lease=null,this.input=null,this.busy=!1,t&&u.delete(t)}}async initialize(s){if(s.initialized)return;const e=o(await s.client.rpc.request("initialize",{protocolVersion:1,clientInfo:{name:"aca",version:"0.1.0"},clientCapabilities:{fs:{readTextFile:!1,writeTextFile:!1},terminal:!1,elicitation:{form:{}},_meta:{"codebuddy.ai":{question:!0}}}},6e4));if(e.protocolVersion!==1)throw new Error("WorkBuddy ACP 协议版本不兼容。");h.set(s.client,e),s.markInitialized()}async establishSession(s){if(s.sessionEstablished&&s.providerSessionId){this.providerSessionId=s.providerSessionId,this.response=s.sessionResponse??{};const e=this.response.acaAvailableCommands;Array.isArray(e)&&e.forEach(t=>this.commands.add(String(t)));return}if(this.providerSessionId){if(o(h.get(s.client)?.agentCapabilities).loadSession!==!0)throw new Error("当前 WorkBuddy 版本不支持恢复会话,禁止静默新建。");if(this.status("resuming","WorkBuddy 会话恢复中"),this.response=o(await s.client.loadSession({sessionId:this.providerSessionId,cwd:this.cwd},12e4)),this.response.sessionId&&this.response.sessionId!==this.providerSessionId)throw new Error("WorkBuddy 恢复返回了不同的会话 ID。")}else{if(this.status("acp","WorkBuddy 会话创建中"),this.response=o(await s.client.newSession({cwd:this.cwd},12e4)),typeof this.response.sessionId!="string"||!this.response.sessionId.trim())throw new Error("WorkBuddy session/new 未返回会话 ID。");this.providerSessionId=this.response.sessionId}this.response.acaAvailableCommands=[...this.commands],f(this.response),s.setProviderSession({providerSessionId:this.providerSessionId,sessionResponse:this.response})}handleUpdate(s){const e=o(s),t=o(e.update);if(this.providerSessionId&&e.sessionId!==this.providerSessionId||(t.sessionUpdate==="available_commands_update"&&Array.isArray(t.availableCommands)&&(this.commands=new Set(t.availableCommands.map(i=>String(o(i).name||"").replace(/^\//,""))),this.response.acaAvailableCommands=[...this.commands],this.lease?.setSessionResponse(this.response)),t.sessionUpdate==="config_option_update"&&Array.isArray(t.configOptions)&&(this.response={...this.response,configOptions:t.configOptions},this.lease?.setSessionResponse(this.response),f(this.response)),!this.acceptingOutput||!this.input))return;const a=this.router.route(s);if(this.updates.push({method:"session/update",params:a.params}),this.firstEventAtMs??=Date.now(),this.lastEventAtMs=Date.now(),!a.child){this.usage=B(s,this.input.contextWindow)??this.usage;const i=o(t._meta)["codebuddy.ai/requestId"]||o(e._meta)["codebuddy.ai/requestId"];!this.supplements&&typeof i=="string"&&i&&this.lease&&this.providerSessionId&&(this.supplements=new D(this.lease.client,this.providerSessionId,i),this.input.onSupplementReady?.(this.supplements.channel))}const n=E("workbuddy",a.params);n&&(!a.child&&n.type==="agent_message_chunk"&&(this.content+=n.text??""),this.input.onUpdate?.(n));const r=b("workbuddy",a.params);r&&this.status(r.phase,r.label,r.detail,r.updateType)}async handleRequest(s,e,t){if(!this.input||!this.acceptingOutput)throw new Error("WorkBuddy 当前没有可交互的执行轮次。");const a=o(e).sessionId;if((s!=="elicitation/create"||a!==void 0)&&a!==this.providerSessionId)throw new Error("WorkBuddy 交互请求会话不匹配。");this.status("requestPermission",s==="session/request_permission"?"等待授权":"等待回答",void 0,s);try{return await R(s,e,t,this.input)}finally{this.acceptingOutput&&this.status("thinking","处理中")}}status(s,e,t,a){const n={phase:s,label:e,detail:t,updateType:a,atMs:Date.now()};this.statuses.push(n),this.input?.onStatus?.(n)}async cancel(){await this.cancelAndDispose()}async cancelAndDispose(){this.acceptingOutput=!1,this.input?.onSupplementReady?.(null),this.providerSessionId&&this.lease?.client.cancel(this.providerSessionId),await this.lease?.dispose()}async close(){this.busy?await this.cancelAndDispose():this.lease?.release()}}export{G as WorkBuddyProvider,_ as workBuddyCapabilities};
1
+ import{workBuddyCredits as B}from"../../../core/workbuddy-credits/worker.js";import c from"node:fs";import{enterWorkBuddyTask as A}from"./auth-gate.js";import{runAbortableRequest as b}from"../../client/abortable-request.js";import{defaultAcpRuntimePool as w}from"../../client/acp-runtime-pool.js";import{BoundedItemBuffer as f}from"../../client/bounded-buffer.js";import{contextUsageFromAcpSessionUpdate as E}from"../../client/context-usage.js";import{providerEventFromAcpSessionUpdate as C,providerStatusFromAcpSessionUpdate as W}from"../standard-acp-events.js";import{resolveWorkBuddyLaunch as M,workBuddyTimeout as R}from"./launch.js";import{applyWorkBuddyConfig as D,record as o,rememberWorkBuddyCapabilities as y}from"./config.js";import{handleWorkBuddyRequest as q}from"./interactions.js";import{WorkBuddyEventRouter as v}from"./events.js";import{WorkBuddySupplements as U,workBuddyPromptContent as _}from"./supplements.js";const l=new WeakMap,p=new Set,O={sessionResume:!0,liveSupplement:!0,imageInput:!0,fileAttachment:!0,filesystem:!0,terminal:!0,permissionRequest:!0,configOptions:!0,usage:!0,contextUsage:!0,contextCompaction:!0,plan:!0,diff:!0};class J{id="workbuddy";name="腾讯 Code Buddy";capabilities=O;async createSession(s){return new x(s.cwd,s.providerSessionId??null,s.timeoutMs)}}class x{cwd;providerSessionId;timeoutMs;lease=null;input=null;acceptingOutput=!1;busy=!1;content="";usage=null;response={};commands=new Set;router=new v;supplements=null;updates=new f(16);statuses=new f(64);firstEventAtMs=null;lastEventAtMs=null;constructor(s,e,t=R()){this.cwd=s,this.providerSessionId=e,this.timeoutMs=t}async sendPrompt(s){return this.run(s,!1)}async compactContext(s){if(!this.providerSessionId)throw new Error("WorkBuddy 当前没有可压缩的会话。");const e=await this.run({...s,cwd:s.cwd,prompt:"/compact",requireSessionResume:!0},!0);return{providerSessionId:e.providerSessionId,contextUsage:e.contextUsage??null,maintenance:{action:"compact",provider:"workbuddy",protocol:"session/prompt",stopReason:o(e.promptResponse).stopReason},durationMs:e.durationMs}}async run(s,e){if(this.busy)throw new Error("WorkBuddy 会话正在执行另一轮任务。");if(c.realpathSync(s.cwd)!==c.realpathSync(this.cwd))throw new Error("WorkBuddy 会话不能切换工作目录。");if(s.requireSessionResume&&!this.providerSessionId)throw new Error("WorkBuddy 历史会话缺少 Session ID,禁止新建会话。");let t=this.providerSessionId?`${c.realpathSync(this.cwd)}\0${this.providerSessionId}`:null;if(t&&p.has(t))throw new Error("WorkBuddy 会话正在执行另一轮任务。");const a=A();t&&p.add(t),this.busy=!0,this.input=s,this.content="",this.usage=null,this.response={},this.commands.clear(),this.router=new v,this.updates.clear(),this.statuses.clear(),this.firstEventAtMs=this.lastEventAtMs=null;const n=Date.now();let d;try{return await b({signal:s.signal,cancelledMessage:"WorkBuddy 任务已取消",onAbort:()=>this.cancelAndDispose(),startRequest:async()=>{this.status("initializing","WorkBuddy ACP 启动中");const r=M(s.canExecuteTools!==!1);if(this.providerSessionId&&await w.retireSessionRuntimes({providerId:"workbuddy",cwd:this.cwd,providerSessionId:this.providerSessionId,keepRuntimeIdentity:r.runtimeIdentity}),s.signal?.aborted)throw new Error("WorkBuddy 任务已取消");const i=await w.acquire({providerId:"workbuddy",cwd:this.cwd,providerSessionId:this.providerSessionId,...r,onSessionUpdate:u=>this.handleUpdate(u),onClientRequest:(u,I,k)=>this.handleRequest(u,I,k.id)});if(this.lease=i,s.signal?.aborted)throw await i.dispose(),new Error("WorkBuddy 任务已取消");if(await this.initialize(i),await this.establishSession(i),!t){if(t=`${c.realpathSync(this.cwd)}\0${this.providerSessionId}`,p.has(t))throw t=null,new Error("WorkBuddy 会话正在执行另一轮任务。");p.add(t)}const g=o(o(l.get(i.client)?.agentCapabilities).promptCapabilities);if(s.promptBlocks?.some(u=>u.type==="image")&&g.image!==!0)throw new Error("当前 CodeBuddy ACP 版本未声明图片输入支持。");if(e||(this.response=await D(i.client,this.providerSessionId,this.response,s)),i.setSessionResponse(this.response),e&&!this.commands.has("compact"))throw new Error("当前 WorkBuddy 会话未提供 /compact 命令,无法压缩上下文。");this.acceptingOutput=!0,this.status("thinking",e?"正在压缩上下文":"思考中"),d=B.beginUsage();const m=await i.client.prompt({sessionId:this.providerSessionId,prompt:_(s.prompt,s.promptBlocks)},s.timeoutMs??this.timeoutMs);if(s.signal?.aborted)throw new Error("WorkBuddy 任务已取消");const h=o(m).stopReason;if(h==="cancelled")throw new Error("WorkBuddy 任务已取消");if(e&&h!=="end_turn")throw new Error(`WorkBuddy 压缩未完成:${String(h||"未知状态")}`);return e&&this.usage&&(this.usage={...this.usage,state:"compacted",maintenanceAction:"compact"}),this.status("completed",e?"上下文压缩完成":"已完成"),{providerSessionId:this.providerSessionId,cliType:"builtin",agentType:"workbuddy",effectiveConfig:{model:s.model||null,mode:s.mode||"default",configOptions:this.response.configOptions,provider:"workbuddy",protocol:"acp",command:i.started.command,processPid:i.started.child.pid},command:i.started.command,args:i.started.args,cwd:this.cwd,exitCode:i.started.child.exitCode,signal:i.started.child.signalCode,durationMs:Date.now()-n,stdout:i.client.rpc.stdout,stderr:i.client.rpc.stderr,content:this.content.trim(),updateCount:this.updates.count(),updates:this.updates.snapshot(),statusTimeline:this.statuses.snapshot(),contextUsage:this.usage,promptResponse:m,performance:{startedAtMs:n,firstEventAtMs:this.firstEventAtMs,lastEventAtMs:this.lastEventAtMs,eventCount:this.updates.count(),firstEventLatencyMs:this.firstEventAtMs===null?null:this.firstEventAtMs-n,silentForMs:Date.now()-(this.lastEventAtMs??n),durationMs:Date.now()-n}}}})}catch(r){await this.lease?.dispose();const i=r instanceof Error?r.message:String(r);throw this.status("failed","WorkBuddy 执行失败",i),/authentication required|not authenticated|unauthorized/i.test(i)?new Error("腾讯 Code Buddy 尚未认证:请到设置 → 我的设备 → 腾讯 Code Buddy 登录;也可在运行 ACA Worker 的系统账号下执行 codebuddy 登录,或配置 CODEBUDDY_API_KEY。"):o(r).code==="ENOENT"?new Error("未找到 CodeBuddy CLI,请安装 @tencent-ai/codebuddy-code,或设置 ACA_WORKBUDDY_COMMAND。"):r}finally{d?.(),this.acceptingOutput=!1,s.onSupplementReady?.(null);try{await this.supplements?.close()}finally{a()}this.supplements=null,this.lease?.release(),this.lease=null,this.input=null,this.busy=!1,t&&p.delete(t)}}async initialize(s){if(s.initialized)return;const e=o(await s.client.rpc.request("initialize",{protocolVersion:1,clientInfo:{name:"aca",version:"0.1.0"},clientCapabilities:{fs:{readTextFile:!1,writeTextFile:!1},terminal:!1,elicitation:{form:{}},_meta:{"codebuddy.ai":{question:!0}}}},6e4));if(e.protocolVersion!==1)throw new Error("WorkBuddy ACP 协议版本不兼容。");l.set(s.client,e),s.markInitialized()}async establishSession(s){if(s.sessionEstablished&&s.providerSessionId){this.providerSessionId=s.providerSessionId,this.response=s.sessionResponse??{};const e=this.response.acaAvailableCommands;Array.isArray(e)&&e.forEach(t=>this.commands.add(String(t)));return}if(this.providerSessionId){if(o(l.get(s.client)?.agentCapabilities).loadSession!==!0)throw new Error("当前 WorkBuddy 版本不支持恢复会话,禁止静默新建。");if(this.status("resuming","WorkBuddy 会话恢复中"),this.response=o(await s.client.loadSession({sessionId:this.providerSessionId,cwd:this.cwd},12e4)),this.response.sessionId&&this.response.sessionId!==this.providerSessionId)throw new Error("WorkBuddy 恢复返回了不同的会话 ID。")}else{if(this.status("acp","WorkBuddy 会话创建中"),this.response=o(await s.client.newSession({cwd:this.cwd},12e4)),typeof this.response.sessionId!="string"||!this.response.sessionId.trim())throw new Error("WorkBuddy session/new 未返回会话 ID。");this.providerSessionId=this.response.sessionId}this.response.acaAvailableCommands=[...this.commands],y(this.response),s.setProviderSession({providerSessionId:this.providerSessionId,sessionResponse:this.response})}handleUpdate(s){const e=o(s),t=o(e.update);if(this.providerSessionId&&e.sessionId!==this.providerSessionId||(t.sessionUpdate==="available_commands_update"&&Array.isArray(t.availableCommands)&&(this.commands=new Set(t.availableCommands.map(r=>String(o(r).name||"").replace(/^\//,""))),this.response.acaAvailableCommands=[...this.commands],this.lease?.setSessionResponse(this.response)),t.sessionUpdate==="config_option_update"&&Array.isArray(t.configOptions)&&(this.response={...this.response,configOptions:t.configOptions},this.lease?.setSessionResponse(this.response),y(this.response)),!this.acceptingOutput||!this.input))return;const a=this.router.route(s);if(this.updates.push({method:"session/update",params:a.params}),this.firstEventAtMs??=Date.now(),this.lastEventAtMs=Date.now(),!a.child){this.usage=E(s,this.input.contextWindow)??this.usage;const r=o(t._meta)["codebuddy.ai/requestId"]||o(e._meta)["codebuddy.ai/requestId"];!this.supplements&&typeof r=="string"&&r&&this.lease&&this.providerSessionId&&(this.supplements=new U(this.lease.client,this.providerSessionId,r),this.input.onSupplementReady?.(this.supplements.channel))}const n=C("workbuddy",a.params);n&&(!a.child&&n.type==="agent_message_chunk"&&(this.content+=n.text??""),this.input.onUpdate?.(n));const d=W("workbuddy",a.params);d&&this.status(d.phase,d.label,d.detail,d.updateType)}async handleRequest(s,e,t){if(!this.input||!this.acceptingOutput)throw new Error("WorkBuddy 当前没有可交互的执行轮次。");const a=o(e).sessionId;if((s!=="elicitation/create"||a!==void 0)&&a!==this.providerSessionId)throw new Error("WorkBuddy 交互请求会话不匹配。");this.status("requestPermission",s==="session/request_permission"?"等待授权":"等待回答",void 0,s);try{return await q(s,e,t,this.input)}finally{this.acceptingOutput&&this.status("thinking","处理中")}}status(s,e,t,a){const n={phase:s,label:e,detail:t,updateType:a,atMs:Date.now()};this.statuses.push(n),this.input?.onStatus?.(n)}async cancel(){await this.cancelAndDispose()}async cancelAndDispose(){this.acceptingOutput=!1,this.input?.onSupplementReady?.(null),this.providerSessionId&&this.lease?.client.cancel(this.providerSessionId),await this.lease?.dispose()}async close(){this.busy?await this.cancelAndDispose():this.lease?.release()}}export{J as WorkBuddyProvider,O as workBuddyCapabilities};
@@ -1 +1 @@
1
- import{stopProviderInstaller as M}from"../core/provider-installer.js";import{stopManagedClaims as L}from"../core/management/claims.js";import{workBuddyAuthManager as U}from"../core/workbuddy-auth-worker.js";import{assertManualStartAllowed as F}from"../core/management/manual.js";import{startManagementControl as H}from"../core/management/control-server.js";import{managementActivity as u}from"../core/management/activity.js";import{acquireWorkerLease as K,assertNoWorkerInstances as B}from"../core/worker-instances.js";import{getAcaHome as V}from"../core/paths.js";import{Command as G}from"commander";import I from"node:fs";import D from"node:os";import m from"node:path";import t from"node:process";import{randomUUID as J}from"node:crypto";import{Logger as q}from"../core/logger.js";import{loadCredentials as z,saveApiKeyCredential as Q}from"../core/auth.js";import{getRuntimeStatePath as X}from"../core/paths.js";import{writeJsonFile as Y}from"../core/fs.js";import{HEARTBEAT_INTERVAL_MS as Z,startHeartbeatLoop as ee}from"../core/heartbeat.js";import{startJobWorkerLoop as te}from"../core/job-worker.js";import{startFileTransferWorkerLoop as re}from"../core/file-transfer-worker.js";import{startTerminalWorker as oe}from"../core/terminal/worker.js";import{startMachineFilesystemWorkerLoop as ae}from"../core/machine-filesystem-worker.js";import{cleanupOrphanedAcpProcesses as ie}from"../core/acp-orphan-cleanup.js";import{defaultAcpRuntimePool as _}from"../acp/client/acp-runtime-pool.js";import{isProcessAlive as h,readPidFile as d,writePidFile as ne,removePidFile as x}from"../core/process.js";import{applyRuntimePathOptions as $}from"../core/runtime-options.js";import{loadAcaConfig as W,updateAcaConfig as R}from"../core/aca-config.js";import{acaServerRequest as se}from"../core/aca-server-client.js";import{restoreProjectsAfterLogin as ce}from"../core/project-recovery.js";import{syncCodexProviderRuntimeConfig as me}from"../core/codex-provider-runtime-config.js";import{ACA_WORKER_INSTANCE_ENV as O,ACA_WORKER_PID_ENV as de,ACA_WORKER_START_TICKS_ENV as fe,readProcessStartTicks as le}from"../core/process-identity.js";import{printStartTips as pe,spawnDaemon as ue}from"./daemon.js";import{startDesktopControl as he}from"../core/desktop-control.js";function ot(){return new G("start").description("Start agent service in native mode").option("--aica-home <path>","Runtime home directory (defaults to ~/.aica or AICA_HOME)").option("--aica-config <path>","Config file path (defaults to <aica-home>/config.json)").option("--aca-home <path>","Deprecated alias for --aica-home").option("--aca-config <path>","Deprecated alias for --aica-config").option("--server <url>","ACA Server URL override").option("--machine-id <id>","Machine ID override for this runtime").option("--machine-name <name>","Machine name to register (defaults to hostname)").option("--cli-types <types...>","Specify CLI types to register, `claude` or `codex`").option("--managed-start <operationId>","受管理启动:保持业务入口关闭,等待 Proxy 激活").option("--debug","enable debug output").option("--heartbeat-log","output current timestamp every 5 seconds").option("--auth <api-key>","Use a CLI API key for non-interactive authentication").option("--token <enrollment-token>","Use a one-time Web-generated token to register this machine").option("-b, --background","在后台启动 Worker").action(async e=>{if(e.background){await ge(e);return}await Ae(e)})}async function ge(e){$(e),B();const a=d();if(t.platform!=="linux"&&a&&h(a))throw new Error(`Worker 已在后台运行,PID ${a}。`);a&&x(a);const n=ue(we());if(n.unref(),!n.pid)throw new Error("后台 Worker 启动失败:未获得进程 PID。");if(!await ve(n.pid,3e3)&&!h(n.pid))throw new Error(`后台 Worker 启动失败,请检查 ${m.join(t.env.AICA_HOME||t.env.ACA_HOME||m.join(D.homedir(),".aica"),"logs")}。`);console.log(`Worker 已在后台启动,PID ${n.pid}。`),pe()}function we(){const e=t.argv.findIndex((a,n)=>n>=2&&a==="start");return e<0?[]:t.argv.slice(e+1).filter(a=>a!=="--background"&&a!=="-b")}async function ve(e,a){const n=Date.now();for(;Date.now()-n<a;){if(d()===e)return!0;if(!h(e))return!1;await new Promise(o=>setTimeout(o,50))}return d()===e}async function Ae(e){$(e);const a=await K();try{await Ie(e)}finally{a()}}async function Ie(e){if(e.managedStart||F(),e.managedStart){if(t.platform!=="linux")throw new Error("当前平台尚未支持受管理启动");if(!t.env.AICA_MANAGER_ID)throw new Error("受管理启动缺少管理实例");t.env.AICA_UPDATE_OPERATION=e.managedStart}const a=t.env[O]||J(),n=le(t.pid)||"";t.env[O]=a,t.env[de]=String(t.pid),t.env[fe]=n;const o=new q("start",e.debug?"debug":"info"),g=W(),S={...e.server?.trim()?{serverUrl:e.server.trim()}:{},...e.machineId?{machineId:e.machineId.trim()}:{},...e.machineName?{machineName:e.machineName.trim()}:{}};let c=Object.keys(S).length>0?R(S):g;const f=e.machineName?.trim()||c.machineName||D.hostname();if(e.token?.trim()){const r=await se("POST","/api/auth/machine-enroll",{token:e.token.trim(),machineId:c.machineId,machineName:f},{serverUrl:c.serverUrl});c=R({machineId:r.machineId,machineName:f,token:r.token,username:r.username}),o.info(`Machine enrollment completed for user ${r.username}`);try{const i=await ce();i.restored>0&&o.info(`restored ${i.restored} local project(s) after machine enrollment`),i.unavailable>0&&o.warn(`skipped ${i.unavailable} unavailable project path(s) during machine enrollment recovery`),c=W()}catch(i){o.warn(`project recovery after machine enrollment failed: ${i instanceof Error?i.message:String(i)}`)}}const N=e.auth?Q(e.auth,f):z(),p=d();if(t.platform!=="linux"&&p&&p!==t.pid&&h(p))throw new Error(`Worker 已在运行,PID ${p}`);ne(t.pid),j({pid:t.pid,workerInstanceId:a,workerStartTicks:n,phase:"running",startupStage:"local-compatible",machineId:c.machineId||N?.machine?.machineId||"local-machine",machineName:f,cliTypes:e.cliTypes??Se(),startedAt:new Date().toISOString(),heartbeatIntervalMs:Z,issues:[{severity:"info",message:"aca is running in aca-server compatibility mode; ACP execution happens on this machine."}]}),o.info(`Starting aca local-compatible service on machine ${f}`),o.info(`PID ${t.pid}`);const w=await ie();w.terminated>0&&o.warn(`terminated ${w.terminated} orphaned ACP process(es): ${w.pids.join(",")}`),me().then(r=>o.info(r?`Codex Provider runtime config loaded revision=${r.revision}`:"Codex Provider runtime config is not configured")).catch(r=>o.warn(`Codex Provider runtime config sync failed: ${r instanceof Error?r.message:String(r)}`));let k=!1,v,y,C,P,E;const l=async r=>{if(k)return;k=!0,L(),o.info("Shutting down"),v&&clearInterval(v),C?.stop(),E?.stop(),P?.stop(),await M(),await U.stop(),await y?.stop().catch(s=>o.warn(`job worker shutdown failed: ${s instanceof Error?s.message:String(s)}`));const i=_.stats();await _.disposeAll().catch(s=>o.warn(`ACP runtime shutdown failed: ${s instanceof Error?s.message:String(s)}`)),o.info(`ACP runtimes stopped total=${i.total} inUse=${i.inUse} idle=${i.idle}`),d()===t.pid&&(j({pid:t.pid,phase:"stopped",stoppedAt:new Date().toISOString(),issues:[]}),x(t.pid)),t.exit(r)},b=r=>{if(!u.stopping&&d()===t.pid){const i=m.join(V(),"management");I.mkdirSync(i,{recursive:!0,mode:448}),I.writeFileSync(m.join(i,"manual-stop.json"),JSON.stringify({workerInstanceId:a,at:Date.now()}),{mode:384})}l(r)};t.once("SIGINT",()=>b(130)),t.once("SIGTERM",()=>b(0)),t.once("uncaughtException",r=>{o.error(`uncaught exception: ${r.stack||r.message}`),l(1)}),t.once("unhandledRejection",r=>{const i=r instanceof Error?r.stack||r.message:String(r);o.error(`unhandled rejection: ${i}`),l(1)}),e.heartbeatLog&&setInterval(()=>o.info(`heartbeat: ${new Date().toISOString()}`),5e3);let T=!1;const A=()=>{T||(T=!0,y=te(o),C=re(o),E=ae(o),P=oe(o))};try{await H(a,()=>l(0),A)}catch(r){if(e.managedStart||r instanceof Error&&r.message.includes("已有 Worker 管理实例"))throw r;u.paused=!1,o.warn(`可选管理通道不可用: ${r instanceof Error?r.message:String(r)}`)}v=ee(o),t.env.ACA_DESKTOP_CHILD==="1"&&t.env.ACA_DESKTOP_STAGED==="1"&&(u.paused=!0),u.paused||A(),he(()=>l(0),A),await new Promise(()=>{})}function Se(){return["codex","claude"].filter(e=>ke(e))}function ke(e){const a=t.env.PATH||"";for(const n of a.split(m.delimiter))try{const o=t.platform==="win32"?["",".exe",".cmd",".bat"]:[""];if(n&&o.some(g=>I.existsSync(m.join(n,e+g))))return!0}catch{}return!1}function j(e){Y(X(),e)}export{ot as createStartCommand};
1
+ import{stopProviderInstaller as M}from"../core/provider-installer.js";import{activateWorkBuddyCredits as L,stopWorkBuddyCredits as U}from"../core/workbuddy-credits/worker.js";import{stopManagedClaims as F}from"../core/management/claims.js";import{workBuddyAuthManager as H}from"../core/workbuddy-auth-worker.js";import{assertManualStartAllowed as B}from"../core/management/manual.js";import{startManagementControl as K}from"../core/management/control-server.js";import{managementActivity as u}from"../core/management/activity.js";import{acquireWorkerLease as V,assertNoWorkerInstances as G}from"../core/worker-instances.js";import{getAcaHome as J}from"../core/paths.js";import{Command as q}from"commander";import I from"node:fs";import D from"node:os";import m from"node:path";import t from"node:process";import{randomUUID as z}from"node:crypto";import{Logger as Q}from"../core/logger.js";import{loadCredentials as X,saveApiKeyCredential as Y}from"../core/auth.js";import{getRuntimeStatePath as Z}from"../core/paths.js";import{writeJsonFile as ee}from"../core/fs.js";import{HEARTBEAT_INTERVAL_MS as te,startHeartbeatLoop as re}from"../core/heartbeat.js";import{startJobWorkerLoop as oe}from"../core/job-worker.js";import{startFileTransferWorkerLoop as ae}from"../core/file-transfer-worker.js";import{startTerminalWorker as ie}from"../core/terminal/worker.js";import{startMachineFilesystemWorkerLoop as ne}from"../core/machine-filesystem-worker.js";import{cleanupOrphanedAcpProcesses as se}from"../core/acp-orphan-cleanup.js";import{defaultAcpRuntimePool as _}from"../acp/client/acp-runtime-pool.js";import{isProcessAlive as h,readPidFile as d,writePidFile as ce,removePidFile as W}from"../core/process.js";import{applyRuntimePathOptions as x}from"../core/runtime-options.js";import{loadAcaConfig as $,updateAcaConfig as R}from"../core/aca-config.js";import{acaServerRequest as me}from"../core/aca-server-client.js";import{restoreProjectsAfterLogin as de}from"../core/project-recovery.js";import{syncCodexProviderRuntimeConfig as fe}from"../core/codex-provider-runtime-config.js";import{ACA_WORKER_INSTANCE_ENV as O,ACA_WORKER_PID_ENV as le,ACA_WORKER_START_TICKS_ENV as pe,readProcessStartTicks as ue}from"../core/process-identity.js";import{printStartTips as he,spawnDaemon as ge}from"./daemon.js";import{startDesktopControl as we}from"../core/desktop-control.js";function nt(){return new q("start").description("Start agent service in native mode").option("--aica-home <path>","Runtime home directory (defaults to ~/.aica or AICA_HOME)").option("--aica-config <path>","Config file path (defaults to <aica-home>/config.json)").option("--aca-home <path>","Deprecated alias for --aica-home").option("--aca-config <path>","Deprecated alias for --aica-config").option("--server <url>","ACA Server URL override").option("--machine-id <id>","Machine ID override for this runtime").option("--machine-name <name>","Machine name to register (defaults to hostname)").option("--cli-types <types...>","Specify CLI types to register, `claude` or `codex`").option("--managed-start <operationId>","受管理启动:保持业务入口关闭,等待 Proxy 激活").option("--debug","enable debug output").option("--heartbeat-log","output current timestamp every 5 seconds").option("--auth <api-key>","Use a CLI API key for non-interactive authentication").option("--token <enrollment-token>","Use a one-time Web-generated token to register this machine").option("-b, --background","在后台启动 Worker").action(async e=>{if(e.background){await ve(e);return}await Se(e)})}async function ve(e){x(e),G();const a=d();if(t.platform!=="linux"&&a&&h(a))throw new Error(`Worker 已在后台运行,PID ${a}。`);a&&W(a);const n=ge(Ae());if(n.unref(),!n.pid)throw new Error("后台 Worker 启动失败:未获得进程 PID。");if(!await Ie(n.pid,3e3)&&!h(n.pid))throw new Error(`后台 Worker 启动失败,请检查 ${m.join(t.env.AICA_HOME||t.env.ACA_HOME||m.join(D.homedir(),".aica"),"logs")}。`);console.log(`Worker 已在后台启动,PID ${n.pid}。`),he()}function Ae(){const e=t.argv.findIndex((a,n)=>n>=2&&a==="start");return e<0?[]:t.argv.slice(e+1).filter(a=>a!=="--background"&&a!=="-b")}async function Ie(e,a){const n=Date.now();for(;Date.now()-n<a;){if(d()===e)return!0;if(!h(e))return!1;await new Promise(o=>setTimeout(o,50))}return d()===e}async function Se(e){x(e);const a=await V();try{await ke(e)}finally{a()}}async function ke(e){if(e.managedStart||B(),e.managedStart){if(t.platform!=="linux")throw new Error("当前平台尚未支持受管理启动");if(!t.env.AICA_MANAGER_ID)throw new Error("受管理启动缺少管理实例");t.env.AICA_UPDATE_OPERATION=e.managedStart}const a=t.env[O]||z(),n=ue(t.pid)||"";t.env[O]=a,t.env[le]=String(t.pid),t.env[pe]=n;const o=new Q("start",e.debug?"debug":"info"),g=$(),S={...e.server?.trim()?{serverUrl:e.server.trim()}:{},...e.machineId?{machineId:e.machineId.trim()}:{},...e.machineName?{machineName:e.machineName.trim()}:{}};let c=Object.keys(S).length>0?R(S):g;const f=e.machineName?.trim()||c.machineName||D.hostname();if(e.token?.trim()){const r=await me("POST","/api/auth/machine-enroll",{token:e.token.trim(),machineId:c.machineId,machineName:f},{serverUrl:c.serverUrl});c=R({machineId:r.machineId,machineName:f,token:r.token,username:r.username}),o.info(`Machine enrollment completed for user ${r.username}`);try{const i=await de();i.restored>0&&o.info(`restored ${i.restored} local project(s) after machine enrollment`),i.unavailable>0&&o.warn(`skipped ${i.unavailable} unavailable project path(s) during machine enrollment recovery`),c=$()}catch(i){o.warn(`project recovery after machine enrollment failed: ${i instanceof Error?i.message:String(i)}`)}}const N=e.auth?Y(e.auth,f):X(),p=d();if(t.platform!=="linux"&&p&&p!==t.pid&&h(p))throw new Error(`Worker 已在运行,PID ${p}`);ce(t.pid),j({pid:t.pid,workerInstanceId:a,workerStartTicks:n,phase:"running",startupStage:"local-compatible",machineId:c.machineId||N?.machine?.machineId||"local-machine",machineName:f,cliTypes:e.cliTypes??ye(),startedAt:new Date().toISOString(),heartbeatIntervalMs:te,issues:[{severity:"info",message:"aca is running in aca-server compatibility mode; ACP execution happens on this machine."}]}),o.info(`Starting aca local-compatible service on machine ${f}`),o.info(`PID ${t.pid}`);const w=await se();w.terminated>0&&o.warn(`terminated ${w.terminated} orphaned ACP process(es): ${w.pids.join(",")}`),fe().then(r=>o.info(r?`Codex Provider runtime config loaded revision=${r.revision}`:"Codex Provider runtime config is not configured")).catch(r=>o.warn(`Codex Provider runtime config sync failed: ${r instanceof Error?r.message:String(r)}`));let k=!1,v,y,C,P,E;const l=async r=>{if(k)return;k=!0,F(),o.info("Shutting down"),v&&clearInterval(v),C?.stop(),E?.stop(),P?.stop(),await M(),await U(),await H.stop(),await y?.stop().catch(s=>o.warn(`job worker shutdown failed: ${s instanceof Error?s.message:String(s)}`));const i=_.stats();await _.disposeAll().catch(s=>o.warn(`ACP runtime shutdown failed: ${s instanceof Error?s.message:String(s)}`)),o.info(`ACP runtimes stopped total=${i.total} inUse=${i.inUse} idle=${i.idle}`),d()===t.pid&&(j({pid:t.pid,phase:"stopped",stoppedAt:new Date().toISOString(),issues:[]}),W(t.pid)),t.exit(r)},b=r=>{if(!u.stopping&&d()===t.pid){const i=m.join(J(),"management");I.mkdirSync(i,{recursive:!0,mode:448}),I.writeFileSync(m.join(i,"manual-stop.json"),JSON.stringify({workerInstanceId:a,at:Date.now()}),{mode:384})}l(r)};t.once("SIGINT",()=>b(130)),t.once("SIGTERM",()=>b(0)),t.once("uncaughtException",r=>{o.error(`uncaught exception: ${r.stack||r.message}`),l(1)}),t.once("unhandledRejection",r=>{const i=r instanceof Error?r.stack||r.message:String(r);o.error(`unhandled rejection: ${i}`),l(1)}),e.heartbeatLog&&setInterval(()=>o.info(`heartbeat: ${new Date().toISOString()}`),5e3);let T=!1;const A=()=>{T||(T=!0,L(),y=oe(o),C=ae(o),E=ne(o),P=ie(o))};try{await K(a,()=>l(0),A)}catch(r){if(e.managedStart||r instanceof Error&&r.message.includes("已有 Worker 管理实例"))throw r;u.paused=!1,o.warn(`可选管理通道不可用: ${r instanceof Error?r.message:String(r)}`)}v=re(o),t.env.ACA_DESKTOP_CHILD==="1"&&t.env.ACA_DESKTOP_STAGED==="1"&&(u.paused=!0),u.paused||A(),we(()=>l(0),A),await new Promise(()=>{})}function ye(){return["codex","claude"].filter(e=>Ce(e))}function Ce(e){const a=t.env.PATH||"";for(const n of a.split(m.delimiter))try{const o=t.platform==="win32"?["",".exe",".cmd",".bat"]:[""];if(n&&o.some(g=>I.existsSync(m.join(n,e+g))))return!0}catch{}return!1}function j(e){ee(Z(),e)}export{nt as createStartCommand};
@@ -1,3 +1,3 @@
1
- import{resolveInstalledLaunch as g}from"./provider-install-paths.js";import{execFileSync as y}from"node:child_process";import m from"node:os";import{readJsonFile as v,writeJsonFile as x}from"./fs.js";import{getRuntimeStatePath as b}from"./paths.js";import{acaServerRequest as k}from"./aca-server-client.js";import{loadAcaConfig as C}from"./aca-config.js";import{providerHealthSnapshot as O}from"./provider-health.js";import{readCodexCapabilities as _}from"./codex-capabilities.js";import{AICA_VERSION as E}from"../version.js";import{applyProjectSync as S}from"./project-sync.js";import{workBuddyHeartbeatCapabilities as I}from"../acp/providers/workbuddy/config.js";import{readWorkBuddyCapabilities as D}from"../acp/providers/workbuddy/discovery.js";const A=3e4,N=360*60*1e3,V=360*60*1e3;let p=null,u=null;async function H(e=C(),t={}){if(!e.token)return{ok:!1,skipped:!0,reason:"not_logged_in"};const o=await k("POST","/api/heartbeat",{machineId:e.machineId,machineName:e.machineName,machineInfo:{platform:m.platform(),architecture:m.arch(),osRelease:m.release(),acaVersion:j(),hostname:m.hostname()},workspaceId:e.defaultWorkspaceId,agentConfigs:t.includeProviderDetails===!1?[]:await R(e),projects:e.projects.map(n=>({projectId:n.projectId,workspaceId:n.workspaceId,name:n.name,rootPath:n.rootPath}))});return S(e,o),o}async function R(e){const t=O(),o=await _(),n=o?.capabilities??T(),a=t.workbuddy.available?await D().catch(()=>I()):I();return[{agentId:`${e.machineId}:builtin:codex`,workspaceId:e.defaultWorkspaceId,name:"Codex",agentType:"codex",cliType:"builtin",machineId:e.machineId,model:o?.defaultModel||"gpt-5.5",mode:"agent-full-access",raw:{providerStatus:t.codex,capabilities:n}},{agentId:`${e.machineId}:builtin:mimo`,workspaceId:e.defaultWorkspaceId,name:"MimoCode",agentType:"mimo",cliType:"builtin",machineId:e.machineId,model:"mimo/mimo-auto",mode:"agent-full-access",raw:{providerStatus:t.mimo,capabilities:$()}},{agentId:`${e.machineId}:builtin:opencode`,workspaceId:e.defaultWorkspaceId,name:"OpenCode",agentType:"opencode",cliType:"builtin",machineId:e.machineId,model:process.env.ACA_OPENCODE_DEFAULT_MODEL||null,mode:"agent-full-access",raw:{providerStatus:t.opencode,capabilities:P()}},{agentId:`${e.machineId}:builtin:workbuddy`,workspaceId:e.defaultWorkspaceId,name:"腾讯 Code Buddy",agentType:"workbuddy",cliType:"builtin",machineId:e.machineId,model:process.env.ACA_WORKBUDDY_DEFAULT_MODEL||null,mode:"default",raw:{providerStatus:t.workbuddy,capabilities:a}}]}function j(e=process.env){return e.ACA_VERSION?.trim()||E}function T(){const e=[{id:"read-only",name:"Read-only",description:"Requires approval to edit files and run commands."},{id:"agent",name:"Agent",description:"Read and edit files, and run commands."},{id:"agent-full-access",name:"Agent (full access)",description:"Edit files and run commands with full access."}],t=[{modelId:"gpt-5.5",name:"gpt-5.5",description:"Latest frontier Codex model"},{modelId:"gpt-5.4",name:"gpt-5.4",description:"Frontier Codex model"},{modelId:"gpt-5.4-mini",name:"gpt-5.4-mini",description:"Smaller, faster Codex model"}],o=[{value:"low",name:"Low",description:"Fastest responses"},{value:"medium",name:"Medium",description:"Balanced reasoning"},{value:"high",name:"High",description:"More reasoning for difficult tasks"},{value:"xhigh",name:"X High",description:"Extra reasoning for complex tasks"}];return{source:"aca-static",fetchedAt:Date.now(),contextUsage:!0,contextCompaction:!0,modes:e,models:t,configOptions:[{id:"mode",name:"Mode",category:"mode",type:"select",currentValue:"agent-full-access",options:e.map(n=>({value:n.id,name:n.name,description:n.description}))},{id:"model",name:"Model",category:"model",type:"select",currentValue:"gpt-5.5",options:t.map(n=>({value:n.modelId,name:n.name,description:n.description}))},{id:"reasoning_effort",name:"Reasoning effort",category:"thought_level",type:"select",currentValue:"medium",options:o}]}}function L(){const e=[{id:"agent",name:"Agent",description:"Run MimoCode with normal workspace permissions."},{id:"agent-full-access",name:"Agent (full access)",description:"Run MimoCode and auto-approve supported local permissions."}],t=[{modelId:"mimo/mimo-auto",name:"MiMo Auto (free)",description:"MiMo Auto free model"},{modelId:"xiaomi/mimo-v2.5",name:"mimo-v2.5",description:"MimoCode v2.5"},{modelId:"xiaomi/mimo-v2.5-pro",name:"mimo-v2.5-pro",description:"MimoCode v2.5 Pro"},{modelId:"xiaomi/mimo-v2.5-pro-ultraspeed",name:"mimo-v2.5-pro-ultraspeed",description:"MimoCode v2.5 Pro UltraSpeed"}],o=[{value:"minimal",name:"Minimal",description:"Fastest responses"},{value:"high",name:"High",description:"More reasoning for difficult tasks"},{value:"max",name:"Max",description:"Maximum reasoning when supported"}];return{source:"aca-static",fetchedAt:Date.now(),contextUsage:!0,contextCompaction:!1,modes:e,models:t,configOptions:[{id:"mode",name:"Mode",category:"mode",type:"select",currentValue:"agent-full-access",options:e.map(n=>({value:n.id,name:n.name,description:n.description}))},{id:"model",name:"Model",category:"model",type:"select",currentValue:"mimo/mimo-auto",options:t.map(n=>({value:n.modelId,name:n.name,description:n.description}))},{id:"variant",name:"Variant",category:"thought_level",type:"select",currentValue:"high",options:o}]}}function $(){const e=Date.now();if(p&&p.expiresAtMs>e)return p.capabilities;const t=K()??L();return p={expiresAtMs:e+N,capabilities:t},t}function F(){const e=[{id:"plan",name:"Plan",description:"Inspect the project and propose changes."},{id:"build",name:"Build",description:"Read and edit files, and run commands when approved."},{id:"agent-full-access",name:"Agent (full access)",description:"Run OpenCode with ACA full-access mode mapping."}],t=[{modelId:"",name:"Default",description:"Use the model selected by OpenCode configuration."}];return{source:"aca-static",fetchedAt:Date.now(),contextUsage:!0,contextCompaction:!1,modes:e,models:t,configOptions:[{id:"mode",name:"Mode",category:"mode",type:"select",currentValue:"agent-full-access",options:e.map(o=>({value:o.id,name:o.name,description:o.description}))},{id:"model",name:"Model",category:"model",type:"text",currentValue:process.env.ACA_OPENCODE_DEFAULT_MODEL||"",options:t.map(o=>({value:o.modelId,name:o.name,description:o.description}))}]}}function P(){const e=Date.now();if(u&&u.expiresAtMs>e)return u.capabilities;const t=U()??F();return u={expiresAtMs:e+V,capabilities:t},t}function U(){try{const{command:e,args:t}=g("opencode"),o=y(e,[...t,"models","--verbose"],{windowsHide:!0,encoding:"utf8",timeout:15e3,maxBuffer:8*1024*1024,stdio:["ignore","pipe","pipe"]}),n=B(o);if(!n.length)return null;const a=[{id:"plan",name:"Plan",description:"Inspect the project and propose changes."},{id:"build",name:"Build",description:"Read and edit files, and run commands when approved."},{id:"agent-full-access",name:"Agent (full access)",description:"Run OpenCode with ACA full-access mode mapping."}],s=W(n);return{source:"opencode-cli",fetchedAt:Date.now(),contextUsage:!0,contextCompaction:!1,modes:a,models:n.map(i=>({modelId:i.modelId,name:i.displayName,description:i.description,metadata:i.metadata})),configOptions:[{id:"mode",name:"Mode",category:"mode",type:"select",currentValue:"agent-full-access",options:a.map(i=>({value:i.id,name:i.name,description:i.description}))},{id:"model",name:"Model",category:"model",type:"select",currentValue:s,options:n.map(i=>({value:i.modelId,name:i.displayName,description:i.description}))}]}}catch{return null}}function B(e){const t=e.split(/\r?\n/),o=[];let n=0;for(;n<t.length;){const a=t[n]?.trim()??"";if(n+=1,!/^[a-z0-9_.-]+\/[a-z0-9_.-]+$/i.test(a))continue;const s=[];let i=0,c=!1;for(;n<t.length;){const d=t[n]??"";if(n+=1,!(!c&&!d.trim().startsWith("{"))&&(c=!0,s.push(d),i+=f(d,"{")-f(d,"}"),c&&i<=0))break}const r=w(s.join(`
2
- `));if(!r)continue;const l=M(r.name,r.id,a)??a;o.push({modelId:a,displayName:z(l,r),description:q(r),metadata:r})}return o}function W(e){const t=process.env.ACA_OPENCODE_DEFAULT_MODEL?.trim();return t&&e.some(o=>o.modelId===t)?t:e.find(o=>o.modelId==="opencode/big-pickle")?.modelId||e.find(o=>/free$/i.test(o.modelId))?.modelId||e[0]?.modelId||""}function z(e,t){return J(t)&&!/\bfree\b/i.test(e)?`${e} (free)`:e}function J(e){const t=e.cost;if(!t||typeof t!="object"||Array.isArray(t))return!1;const o=t,n=o.cache&&typeof o.cache=="object"&&!Array.isArray(o.cache)?o.cache:{};return[o.input,o.output,n.read,n.write].every(a=>Number(a??0)===0)}function q(e){const t=e.limit&&typeof e.limit=="object"&&!Array.isArray(e.limit)?e.limit:{},o=Number(t.context??0),n=Number(t.output??0);return o>0&&n>0?`Context ${o}, output ${n}`:o>0?`Context ${o}`:"OpenCode model"}function K(){try{const{command:e,args:t}=g("mimo"),o=y(e,[...t,"models","--verbose"],{windowsHide:!0,encoding:"utf8",timeout:15e3,maxBuffer:2*1024*1024,stdio:["ignore","pipe","pipe"]}),n=X(o);if(!n.length)return null;const a=[{id:"agent",name:"Agent",description:"Run MimoCode with normal workspace permissions."},{id:"agent-full-access",name:"Agent (full access)",description:"Run MimoCode and auto-approve supported local permissions."}],s=Y(n);return{source:"mimo-cli",fetchedAt:Date.now(),contextUsage:!0,contextCompaction:!1,modes:a,models:n.map(i=>({modelId:i.modelId,name:i.displayName,description:i.description,metadata:i.metadata})),configOptions:[{id:"mode",name:"Mode",category:"mode",type:"select",currentValue:"agent-full-access",options:a.map(i=>({value:i.id,name:i.name,description:i.description}))},{id:"model",name:"Model",category:"model",type:"select",currentValue:n.some(i=>i.modelId==="mimo/mimo-auto")?"mimo/mimo-auto":n[0]?.modelId,options:n.map(i=>({value:i.modelId,name:i.displayName,description:i.description}))},{id:"variant",name:"Variant",category:"thought_level",type:"select",currentValue:s.some(i=>i.value==="high")?"high":s[0]?.value,options:s}]}}catch{return null}}function X(e){const t=e.split(/\r?\n/),o=[];let n=0;for(;n<t.length;){const a=t[n]?.trim()??"";if(n+=1,!/^[a-z0-9_.-]+\/[a-z0-9_.-]+$/i.test(a))continue;const s=[];let i=0,c=!1;for(;n<t.length;){const d=t[n]??"";if(n+=1,!(!c&&!d.trim().startsWith("{"))&&(c=!0,s.push(d),i+=f(d,"{")-f(d,"}"),c&&i<=0))break}const r=w(s.join(`
3
- `));if(!r)continue;const l=M(r.name,r.id,a)??a;o.push({modelId:a,displayName:G(l,r),description:Z(r),metadata:r})}return o}function Y(e){const t=new Set;for(const a of e){const s=a.metadata.variants;if(!(!s||typeof s!="object"||Array.isArray(s)))for(const i of Object.keys(s))t.add(i)}const o=["minimal","low","medium","high","max"].filter(a=>t.has(a));return(o.length?o:["minimal","high","max"]).map(a=>({value:a,name:ee(a),description:a==="high"?"More reasoning for difficult tasks":a==="max"?"Maximum reasoning when supported":"Fastest responses"}))}function G(e,t){return Q(t)&&!/\bfree\b/i.test(e)?`${e} (free)`:e}function Q(e){const t=e.cost;if(!t||typeof t!="object"||Array.isArray(t))return!1;const o=t,n=o.cache&&typeof o.cache=="object"&&!Array.isArray(o.cache)?o.cache:{};return[o.input,o.output,n.read,n.write].every(a=>Number(a??0)===0)}function Z(e){const t=e.limit&&typeof e.limit=="object"&&!Array.isArray(e.limit)?e.limit:{},o=Number(t.context??0);return o>0?`Context ${o}`:"MimoCode model"}function w(e){try{const t=JSON.parse(e);return t&&typeof t=="object"&&!Array.isArray(t)?t:null}catch{return null}}function M(...e){for(const t of e)if(typeof t=="string"&&t.trim())return t.trim()}function ee(e){return e.slice(0,1).toUpperCase()+e.slice(1)}function f(e,t){let o=0;for(const n of e)n===t&&(o+=1);return o}function fe(e){let t=!1;const o=async(s,i="heartbeat")=>{const c=new Date().toISOString();try{const r=await H(C(),{includeProviderDetails:s});if(r.skipped){h({lastHeartbeatAt:c,lastHeartbeatStatus:"skipped",lastHeartbeatReason:r.reason??"unknown"}),e.debug(`${i} skipped: ${r.reason??"unknown"}`);return}h({lastHeartbeatAt:c,lastHeartbeatStatus:"ok",lastHeartbeatProjectCount:r.projectCount??null,presenceExpiresAtMs:r.presenceExpiresAtMs??null}),e.debug(`${i} ok projects=${r.projectCount??0}`)}catch(r){const l=r instanceof Error?r.message:String(r);h({lastHeartbeatAt:c,lastHeartbeatStatus:"error",lastHeartbeatError:l}),e.warn(`${i} failed: ${l}`)}},n=async()=>{if(!t){t=!0;try{await o(!0)}finally{t=!1}}};return(async()=>{if(!t){t=!0;try{await o(!1,"initial presence heartbeat"),await o(!0)}finally{t=!1}}})(),setInterval(()=>{n()},A)}function h(e){const t=v(b())??{};x(b(),{...t,heartbeatIntervalMs:A,...e})}export{A as HEARTBEAT_INTERVAL_MS,j as reportedAicaVersion,H as sendHeartbeat,fe as startHeartbeatLoop};
1
+ import{resolveInstalledLaunch as g}from"./provider-install-paths.js";import{execFileSync as y}from"node:child_process";import m from"node:os";import{readJsonFile as v,writeJsonFile as x}from"./fs.js";import{getRuntimeStatePath as b}from"./paths.js";import{acaServerRequest as k}from"./aca-server-client.js";import{loadAcaConfig as C}from"./aca-config.js";import{providerHealthSnapshot as O}from"./provider-health.js";import{readCodexCapabilities as _}from"./codex-capabilities.js";import{AICA_VERSION as E}from"../version.js";import{applyProjectSync as S}from"./project-sync.js";import{workBuddyHeartbeatCapabilities as I}from"../acp/providers/workbuddy/config.js";import{readWorkBuddyCapabilities as D}from"../acp/providers/workbuddy/discovery.js";import{workBuddyCreditsConnected as N}from"./workbuddy-credits/worker.js";const A=3e4,V=360*60*1e3,H=360*60*1e3;let p=null,u=null;async function R(e=C(),t={}){if(!e.token)return{ok:!1,skipped:!0,reason:"not_logged_in"};const o=await k("POST","/api/heartbeat",{machineId:e.machineId,machineName:e.machineName,machineInfo:{platform:m.platform(),architecture:m.arch(),osRelease:m.release(),acaVersion:T(),hostname:m.hostname()},workspaceId:e.defaultWorkspaceId,agentConfigs:t.includeProviderDetails===!1?[]:await j(e),projects:e.projects.map(n=>({projectId:n.projectId,workspaceId:n.workspaceId,name:n.name,rootPath:n.rootPath}))});return S(e,o),N(),o}async function j(e){const t=O(),o=await _(),n=o?.capabilities??L(),a=t.workbuddy.available?await D().catch(()=>I()):I();return[{agentId:`${e.machineId}:builtin:codex`,workspaceId:e.defaultWorkspaceId,name:"Codex",agentType:"codex",cliType:"builtin",machineId:e.machineId,model:o?.defaultModel||"gpt-5.5",mode:"agent-full-access",raw:{providerStatus:t.codex,capabilities:n}},{agentId:`${e.machineId}:builtin:mimo`,workspaceId:e.defaultWorkspaceId,name:"MimoCode",agentType:"mimo",cliType:"builtin",machineId:e.machineId,model:"mimo/mimo-auto",mode:"agent-full-access",raw:{providerStatus:t.mimo,capabilities:F()}},{agentId:`${e.machineId}:builtin:opencode`,workspaceId:e.defaultWorkspaceId,name:"OpenCode",agentType:"opencode",cliType:"builtin",machineId:e.machineId,model:process.env.ACA_OPENCODE_DEFAULT_MODEL||null,mode:"agent-full-access",raw:{providerStatus:t.opencode,capabilities:U()}},{agentId:`${e.machineId}:builtin:workbuddy`,workspaceId:e.defaultWorkspaceId,name:"腾讯 Code Buddy",agentType:"workbuddy",cliType:"builtin",machineId:e.machineId,model:process.env.ACA_WORKBUDDY_DEFAULT_MODEL||null,mode:"default",raw:{providerStatus:t.workbuddy,capabilities:a}}]}function T(e=process.env){return e.ACA_VERSION?.trim()||E}function L(){const e=[{id:"read-only",name:"Read-only",description:"Requires approval to edit files and run commands."},{id:"agent",name:"Agent",description:"Read and edit files, and run commands."},{id:"agent-full-access",name:"Agent (full access)",description:"Edit files and run commands with full access."}],t=[{modelId:"gpt-5.5",name:"gpt-5.5",description:"Latest frontier Codex model"},{modelId:"gpt-5.4",name:"gpt-5.4",description:"Frontier Codex model"},{modelId:"gpt-5.4-mini",name:"gpt-5.4-mini",description:"Smaller, faster Codex model"}],o=[{value:"low",name:"Low",description:"Fastest responses"},{value:"medium",name:"Medium",description:"Balanced reasoning"},{value:"high",name:"High",description:"More reasoning for difficult tasks"},{value:"xhigh",name:"X High",description:"Extra reasoning for complex tasks"}];return{source:"aca-static",fetchedAt:Date.now(),contextUsage:!0,contextCompaction:!0,modes:e,models:t,configOptions:[{id:"mode",name:"Mode",category:"mode",type:"select",currentValue:"agent-full-access",options:e.map(n=>({value:n.id,name:n.name,description:n.description}))},{id:"model",name:"Model",category:"model",type:"select",currentValue:"gpt-5.5",options:t.map(n=>({value:n.modelId,name:n.name,description:n.description}))},{id:"reasoning_effort",name:"Reasoning effort",category:"thought_level",type:"select",currentValue:"medium",options:o}]}}function $(){const e=[{id:"agent",name:"Agent",description:"Run MimoCode with normal workspace permissions."},{id:"agent-full-access",name:"Agent (full access)",description:"Run MimoCode and auto-approve supported local permissions."}],t=[{modelId:"mimo/mimo-auto",name:"MiMo Auto (free)",description:"MiMo Auto free model"},{modelId:"xiaomi/mimo-v2.5",name:"mimo-v2.5",description:"MimoCode v2.5"},{modelId:"xiaomi/mimo-v2.5-pro",name:"mimo-v2.5-pro",description:"MimoCode v2.5 Pro"},{modelId:"xiaomi/mimo-v2.5-pro-ultraspeed",name:"mimo-v2.5-pro-ultraspeed",description:"MimoCode v2.5 Pro UltraSpeed"}],o=[{value:"minimal",name:"Minimal",description:"Fastest responses"},{value:"high",name:"High",description:"More reasoning for difficult tasks"},{value:"max",name:"Max",description:"Maximum reasoning when supported"}];return{source:"aca-static",fetchedAt:Date.now(),contextUsage:!0,contextCompaction:!1,modes:e,models:t,configOptions:[{id:"mode",name:"Mode",category:"mode",type:"select",currentValue:"agent-full-access",options:e.map(n=>({value:n.id,name:n.name,description:n.description}))},{id:"model",name:"Model",category:"model",type:"select",currentValue:"mimo/mimo-auto",options:t.map(n=>({value:n.modelId,name:n.name,description:n.description}))},{id:"variant",name:"Variant",category:"thought_level",type:"select",currentValue:"high",options:o}]}}function F(){const e=Date.now();if(p&&p.expiresAtMs>e)return p.capabilities;const t=X()??$();return p={expiresAtMs:e+V,capabilities:t},t}function P(){const e=[{id:"plan",name:"Plan",description:"Inspect the project and propose changes."},{id:"build",name:"Build",description:"Read and edit files, and run commands when approved."},{id:"agent-full-access",name:"Agent (full access)",description:"Run OpenCode with ACA full-access mode mapping."}],t=[{modelId:"",name:"Default",description:"Use the model selected by OpenCode configuration."}];return{source:"aca-static",fetchedAt:Date.now(),contextUsage:!0,contextCompaction:!1,modes:e,models:t,configOptions:[{id:"mode",name:"Mode",category:"mode",type:"select",currentValue:"agent-full-access",options:e.map(o=>({value:o.id,name:o.name,description:o.description}))},{id:"model",name:"Model",category:"model",type:"text",currentValue:process.env.ACA_OPENCODE_DEFAULT_MODEL||"",options:t.map(o=>({value:o.modelId,name:o.name,description:o.description}))}]}}function U(){const e=Date.now();if(u&&u.expiresAtMs>e)return u.capabilities;const t=B()??P();return u={expiresAtMs:e+H,capabilities:t},t}function B(){try{const{command:e,args:t}=g("opencode"),o=y(e,[...t,"models","--verbose"],{windowsHide:!0,encoding:"utf8",timeout:15e3,maxBuffer:8*1024*1024,stdio:["ignore","pipe","pipe"]}),n=W(o);if(!n.length)return null;const a=[{id:"plan",name:"Plan",description:"Inspect the project and propose changes."},{id:"build",name:"Build",description:"Read and edit files, and run commands when approved."},{id:"agent-full-access",name:"Agent (full access)",description:"Run OpenCode with ACA full-access mode mapping."}],s=z(n);return{source:"opencode-cli",fetchedAt:Date.now(),contextUsage:!0,contextCompaction:!1,modes:a,models:n.map(i=>({modelId:i.modelId,name:i.displayName,description:i.description,metadata:i.metadata})),configOptions:[{id:"mode",name:"Mode",category:"mode",type:"select",currentValue:"agent-full-access",options:a.map(i=>({value:i.id,name:i.name,description:i.description}))},{id:"model",name:"Model",category:"model",type:"select",currentValue:s,options:n.map(i=>({value:i.modelId,name:i.displayName,description:i.description}))}]}}catch{return null}}function W(e){const t=e.split(/\r?\n/),o=[];let n=0;for(;n<t.length;){const a=t[n]?.trim()??"";if(n+=1,!/^[a-z0-9_.-]+\/[a-z0-9_.-]+$/i.test(a))continue;const s=[];let i=0,c=!1;for(;n<t.length;){const d=t[n]??"";if(n+=1,!(!c&&!d.trim().startsWith("{"))&&(c=!0,s.push(d),i+=f(d,"{")-f(d,"}"),c&&i<=0))break}const r=w(s.join(`
2
+ `));if(!r)continue;const l=M(r.name,r.id,a)??a;o.push({modelId:a,displayName:J(l,r),description:K(r),metadata:r})}return o}function z(e){const t=process.env.ACA_OPENCODE_DEFAULT_MODEL?.trim();return t&&e.some(o=>o.modelId===t)?t:e.find(o=>o.modelId==="opencode/big-pickle")?.modelId||e.find(o=>/free$/i.test(o.modelId))?.modelId||e[0]?.modelId||""}function J(e,t){return q(t)&&!/\bfree\b/i.test(e)?`${e} (free)`:e}function q(e){const t=e.cost;if(!t||typeof t!="object"||Array.isArray(t))return!1;const o=t,n=o.cache&&typeof o.cache=="object"&&!Array.isArray(o.cache)?o.cache:{};return[o.input,o.output,n.read,n.write].every(a=>Number(a??0)===0)}function K(e){const t=e.limit&&typeof e.limit=="object"&&!Array.isArray(e.limit)?e.limit:{},o=Number(t.context??0),n=Number(t.output??0);return o>0&&n>0?`Context ${o}, output ${n}`:o>0?`Context ${o}`:"OpenCode model"}function X(){try{const{command:e,args:t}=g("mimo"),o=y(e,[...t,"models","--verbose"],{windowsHide:!0,encoding:"utf8",timeout:15e3,maxBuffer:2*1024*1024,stdio:["ignore","pipe","pipe"]}),n=Y(o);if(!n.length)return null;const a=[{id:"agent",name:"Agent",description:"Run MimoCode with normal workspace permissions."},{id:"agent-full-access",name:"Agent (full access)",description:"Run MimoCode and auto-approve supported local permissions."}],s=G(n);return{source:"mimo-cli",fetchedAt:Date.now(),contextUsage:!0,contextCompaction:!1,modes:a,models:n.map(i=>({modelId:i.modelId,name:i.displayName,description:i.description,metadata:i.metadata})),configOptions:[{id:"mode",name:"Mode",category:"mode",type:"select",currentValue:"agent-full-access",options:a.map(i=>({value:i.id,name:i.name,description:i.description}))},{id:"model",name:"Model",category:"model",type:"select",currentValue:n.some(i=>i.modelId==="mimo/mimo-auto")?"mimo/mimo-auto":n[0]?.modelId,options:n.map(i=>({value:i.modelId,name:i.displayName,description:i.description}))},{id:"variant",name:"Variant",category:"thought_level",type:"select",currentValue:s.some(i=>i.value==="high")?"high":s[0]?.value,options:s}]}}catch{return null}}function Y(e){const t=e.split(/\r?\n/),o=[];let n=0;for(;n<t.length;){const a=t[n]?.trim()??"";if(n+=1,!/^[a-z0-9_.-]+\/[a-z0-9_.-]+$/i.test(a))continue;const s=[];let i=0,c=!1;for(;n<t.length;){const d=t[n]??"";if(n+=1,!(!c&&!d.trim().startsWith("{"))&&(c=!0,s.push(d),i+=f(d,"{")-f(d,"}"),c&&i<=0))break}const r=w(s.join(`
3
+ `));if(!r)continue;const l=M(r.name,r.id,a)??a;o.push({modelId:a,displayName:Q(l,r),description:ee(r),metadata:r})}return o}function G(e){const t=new Set;for(const a of e){const s=a.metadata.variants;if(!(!s||typeof s!="object"||Array.isArray(s)))for(const i of Object.keys(s))t.add(i)}const o=["minimal","low","medium","high","max"].filter(a=>t.has(a));return(o.length?o:["minimal","high","max"]).map(a=>({value:a,name:te(a),description:a==="high"?"More reasoning for difficult tasks":a==="max"?"Maximum reasoning when supported":"Fastest responses"}))}function Q(e,t){return Z(t)&&!/\bfree\b/i.test(e)?`${e} (free)`:e}function Z(e){const t=e.cost;if(!t||typeof t!="object"||Array.isArray(t))return!1;const o=t,n=o.cache&&typeof o.cache=="object"&&!Array.isArray(o.cache)?o.cache:{};return[o.input,o.output,n.read,n.write].every(a=>Number(a??0)===0)}function ee(e){const t=e.limit&&typeof e.limit=="object"&&!Array.isArray(e.limit)?e.limit:{},o=Number(t.context??0);return o>0?`Context ${o}`:"MimoCode model"}function w(e){try{const t=JSON.parse(e);return t&&typeof t=="object"&&!Array.isArray(t)?t:null}catch{return null}}function M(...e){for(const t of e)if(typeof t=="string"&&t.trim())return t.trim()}function te(e){return e.slice(0,1).toUpperCase()+e.slice(1)}function f(e,t){let o=0;for(const n of e)n===t&&(o+=1);return o}function ge(e){let t=!1;const o=async(s,i="heartbeat")=>{const c=new Date().toISOString();try{const r=await R(C(),{includeProviderDetails:s});if(r.skipped){h({lastHeartbeatAt:c,lastHeartbeatStatus:"skipped",lastHeartbeatReason:r.reason??"unknown"}),e.debug(`${i} skipped: ${r.reason??"unknown"}`);return}h({lastHeartbeatAt:c,lastHeartbeatStatus:"ok",lastHeartbeatProjectCount:r.projectCount??null,presenceExpiresAtMs:r.presenceExpiresAtMs??null}),e.debug(`${i} ok projects=${r.projectCount??0}`)}catch(r){const l=r instanceof Error?r.message:String(r);h({lastHeartbeatAt:c,lastHeartbeatStatus:"error",lastHeartbeatError:l}),e.warn(`${i} failed: ${l}`)}},n=async()=>{if(!t){t=!0;try{await o(!0)}finally{t=!1}}};return(async()=>{if(!t){t=!0;try{await o(!1,"initial presence heartbeat"),await o(!0)}finally{t=!1}}})(),setInterval(()=>{n()},A)}function h(e){const t=v(b())??{};x(b(),{...t,heartbeatIntervalMs:A,...e})}export{A as HEARTBEAT_INTERVAL_MS,T as reportedAicaVersion,R as sendHeartbeat,ge as startHeartbeatLoop};
@@ -1,5 +1,5 @@
1
- import{providerInstallOperation as q}from"./provider-installer.js";import{executeExternalCodexOperation as tt}from"./external-codex-worker.js";import{workBuddyAuthManager as et}from"./workbuddy-auth-worker.js";import{managementActivity as T}from"./management/activity.js";import{managedClaim as rt}from"./management/claims.js";import{activeTerminalService as ot}from"./terminal/service.js";import{execFileSync as _}from"node:child_process";import{randomUUID as I}from"node:crypto";import l from"node:fs";import F from"node:os";import u from"node:path";import{acaServerDownloadToFile as it,acaServerRequest as E}from"./aca-server-client.js";import{loadAcaConfig as M,upsertLocalProject as nt}from"./aca-config.js";import{startLongPollWorker as st}from"./long-poll-worker.js";import{expandWorkerRuntimePath as at}from"./worker-runtime-paths.js";import{syncCodexProviderRuntimeConfig as ct}from"./codex-provider-runtime-config.js";import{defaultAcpRuntimePool as lt}from"../acp/client/acp-runtime-pool.js";import{execPowerShell as L}from"./machine-filesystem/powershell.js";const O=25e3,ut=O+1e4,x=300,dt=3e4;let b=null;function fe(t){return st({run:async()=>{const e=M();if(!e.token)throw new Error("Worker 尚未配置认证令牌");const r=await rt("machine",()=>E("GET",`/api/client/machine-operations/claim?machineId=${encodeURIComponent(e.machineId)}&waitMs=${O}`,void 0,{timeoutMs:ut}));if(!r.item)return;const o=T.track("machine");try{const i=await ft(r.item);await E("POST",`/api/client/machine-operations/${encodeURIComponent(r.item.requestId)}/complete`,{result:i})}catch(i){const s=i instanceof Error?i.message:String(i);t.warn(`machine filesystem request failed request=${r.item.requestId}: ${s}`),await E("POST",`/api/client/machine-operations/${encodeURIComponent(r.item.requestId)}/fail`,{message:s}).catch(()=>{T.uncertain.add("machine-finalize")})}finally{o()}},onError:(e,r)=>t.warn(`machine filesystem worker failed, retry in ${r}ms: ${e instanceof Error?e.message:String(e)}`)})}async function ft(t){if(t.operation==="provider_install")return q(t.payload);if(t.operation==="workbuddy_auth")return et.execute(String(t.payload.action||""),t.payload);if(t.operation.startsWith("external_codex_"))return tt(t.operation,t.payload);if(t.operation==="sync_codex_provider_config"){const e=await ct(),r=await lt.retireProviderRuntimes("codex-acp");return{configured:!!e,revision:e?.revision??null,retiredRuntimes:r}}if(t.operation==="filesystem_roots")return It();if(t.operation==="list_directory")return Ft({directoryPath:String(t.payload.path||""),includeHidden:t.payload.includeHidden===!0});if(t.operation==="register_project")return Wt(t.payload);if(t.operation==="list_project_directory")return ht(t.payload);if(t.operation==="upload_project_file")return yt(t.payload);if(t.operation==="mutate_project_files")return gt(t.payload);if(t.operation==="get_project_git_status")return B(t.payload);if(t.operation==="get_project_git_diff")return bt(t.payload);if(t.operation==="get_project_git_log")return jt(t.payload);if(t.operation==="get_project_git_commit")return vt(t.payload);if(t.operation==="get_worktree_status")return k(t.payload);if(t.operation==="commit_worktree")return mt(t.payload);if(t.operation==="cleanup_worktree")return pt(t.payload);throw new Error("不支持的 Machine 文件系统操作。 ")}function k(t){const e=$(String(t.worktreePath||""));if(!l.existsSync(e))return{exists:!1,isRepository:!1,dirty:!1,changedFiles:0,ahead:0};const r=p(e);if(!w(r))return{exists:!0,isRepository:!1,dirty:Rt(r),changedFiles:null,ahead:0};const o=B({rootPath:r}),i=String(t.baseCommit||"").trim(),s=i&&/^[0-9a-f]{7,64}$/i.test(i)?Dt(r,i):0;return{exists:!0,isRepository:!0,dirty:Array.isArray(o.files)&&o.files.length>0,changedFiles:Array.isArray(o.files)?o.files.length:0,branch:o.branch,ahead:s,files:Array.isArray(o.files)?o.files.slice(0,50):[],truncated:o.truncated===!0}}function mt(t){const e=p(String(t.worktreePath||"")),r=String(t.message||"").trim();if(!r)throw new Error("提交说明不能为空。");if(!w(e))throw new Error("当前 Worktree 不是 Git 仓库,无法提交。");_("git",["-C",e,"add","-A"],{windowsHide:!0,stdio:"pipe",timeout:3e4});try{return _("git",["-C",e,"diff","--cached","--quiet"],{windowsHide:!0,stdio:"pipe",timeout:1e4}),{committed:!1,reason:"no_changes",...k(t)}}catch(o){if(Number(o.status)!==1)throw o}return _("git",["-C",e,"commit","-m",r],{windowsHide:!0,stdio:"pipe",timeout:6e4}),{committed:!0,commit:m(e,["rev-parse","HEAD"],8*1024).trim(),...k(t)}}function pt(t){const e=$(String(t.worktreePath||"")),r=p(String(t.projectRoot||""));if(h(e)===h(r))throw new Error("不能清理项目主目录。");if(!l.existsSync(e))return{removed:!1,reason:"not_found"};const o=p(e);if(ot?.hasRunningInDirectory(o,String(t.sessionId||"")))throw new Error("请先结束此目录中的会话终端,再清理 Worktree。");const i=String(t.branchName||"").trim(),s=w(o)&&w(r);if(s){if(_("git",["-C",r,"worktree","remove","--force",o],{windowsHide:!0,stdio:"pipe",timeout:6e4}),t.deleteBranch===!0&&i)try{_("git",["-C",r,"branch","-D",i],{windowsHide:!0,stdio:"pipe",timeout:3e4})}catch(n){const c=String(n.stderr||n);if(!/not found|not exist/i.test(c))throw n}}else l.rmSync(o,{recursive:!0,force:!0,maxRetries:2,retryDelay:200});return{removed:!0,branchDeleted:!!(s&&t.deleteBranch===!0&&i)}}function ht(t){const e=p(String(t.rootPath||"")),r=g(String(t.relativePath||"")),o=v(e,r,!0),i=Math.max(0,Number(t.offset||0)),s=Math.min(300,Math.max(20,Number(t.limit||200))),n=t.sort==="modified-desc"||t.sort==="modified-asc"?t.sort:"name",c=l.readdirSync(o,{withFileTypes:!0}).filter(a=>!a.isSymbolicLink()).map(a=>({name:a.name,path:J(e,u.join(o,a.name)),type:a.isDirectory()?"directory":a.isFile()?"file":"other",hidden:a.name.startsWith("."),extension:a.isFile()?u.extname(a.name).slice(1).toLowerCase():"",modifiedAt:n==="name"?void 0:wt(u.join(o,a.name))})).filter(a=>a.type!=="other").sort((a,f)=>{if(a.type!==f.type)return a.type==="directory"?-1:1;if(n!=="name"){if(a.modifiedAt==null&&f.modifiedAt!=null)return 1;if(f.modifiedAt==null&&a.modifiedAt!=null)return-1;const P=(a.modifiedAt??0)-(f.modifiedAt??0);if(P)return n==="modified-desc"?-P:P}return a.name.localeCompare(f.name,void 0,{numeric:!0,sensitivity:"base"})}),d=c.slice(i,i+s);return{path:r,rootKind:t.rootKind==="worktree"?"worktree":"project",items:d,total:c.length,nextOffset:i+d.length<c.length?i+d.length:null}}function wt(t){try{const e=l.lstatSync(t);return e.isSymbolicLink()?null:e.mtimeMs}catch{return null}}async function yt(t){const e=String(t.uploadId||"").trim();if(!e)throw new Error("项目上传任务缺少 uploadId。");const r=p(String(t.rootPath||"")),o=g(String(t.relativeDirectory||"")),i=v(r,o,!0),s=H(String(t.fileName||"")),n=Number(t.sizeBytes),c=String(t.sha256||"").trim().toLowerCase();if(!Number.isSafeInteger(n)||n<0)throw new Error("项目上传文件大小无效。");if(!/^[0-9a-f]{64}$/.test(c))throw new Error("项目上传文件摘要无效。");const d=u.join(i,`.aca-upload-${I()}.tmp`);try{const a=await it(`/api/client/project-uploads/${encodeURIComponent(e)}`,d,{maxBytes:n,timeoutMs:3e5});if(a.sizeBytes!==n)throw new Error("项目上传文件大小校验失败。");if(a.sha256.toLowerCase()!==c)throw new Error("项目上传文件完整性校验失败。");const f=Pt(r,o,s,d);return{name:u.basename(f),path:J(r,f),directory:o,size:a.sizeBytes,sha256:a.sha256,renamed:u.basename(f)!==s,rootKind:t.rootKind==="worktree"?"worktree":"project"}}finally{l.rmSync(d,{force:!0})}}function gt(t){const e=p(String(t.rootPath||"")),r=t.action&&typeof t.action=="object"?t.action:{};if(r.type==="delete")return St(e,String(r.path||""),r.recursive===!0,t.rootKind);throw new Error("project_file_operation_unsupported")}function St(t,e,r,o){const i=g(e);if(!i)throw new Error("project_file_root_protected");_t(i);const s=i.split("/"),n=s.slice(0,-1).join("/"),c=v(t,n,!0),d=u.join(c,s.at(-1)||"");let a;try{a=l.lstatSync(d)}catch(f){throw f.code==="ENOENT"?new Error("project_file_not_found"):f}if(a.isSymbolicLink())throw new Error("project_file_symbolic_link");if(!a.isFile()&&!a.isDirectory())throw new Error("project_file_type_unsupported");if(a.isDirectory()&&!r&&l.readdirSync(d).length>0)throw new Error("project_directory_not_empty");return a.isDirectory()&&r?l.rmSync(d,{recursive:!0,force:!1,maxRetries:2,retryDelay:150}):a.isDirectory()?l.rmdirSync(d):l.unlinkSync(d),{operation:"delete",path:i,entryType:a.isDirectory()?"directory":"file",parentPath:n,affectedDirectories:[n],rootKind:o==="worktree"?"worktree":"project"}}function _t(t){const e=t.split("/")[0].toLocaleLowerCase("en-US");if([".git",".aca",".aica"].includes(e))throw new Error("project_file_protected")}function Pt(t,e,r,o){const i=p(t),s=g(e),n=v(i,s,!0),c=H(r);if(l.realpathSync.native(u.dirname(o))!==n)throw new Error("项目上传临时文件不在目标目录中。");const a=l.lstatSync(o);if(!a.isFile()||a.isSymbolicLink())throw new Error("项目上传临时文件无效。");for(let f=1;f<=1e4;f+=1){const P=f===1?c:Et(c,f),S=u.join(n,P);try{return l.linkSync(o,S),l.chmodSync(S,420),l.rmSync(o,{force:!0}),S}catch(A){if(G(A))continue;if(!xt(A))throw A;try{return l.copyFileSync(o,S,l.constants.COPYFILE_EXCL),l.chmodSync(S,420),l.rmSync(o,{force:!0}),S}catch(N){if(G(N))continue;throw N}}}throw new Error("同名文件过多,无法生成可用文件名。")}function H(t){const e=t.trim();if(!e||e==="."||e===".."||/[\\/\x00-\x1F]/.test(e))throw new Error("项目上传文件名无效。");if(process.platform==="win32"&&(/[<>:"|?*]/.test(e)||/[. ]$/.test(e)))throw new Error("项目上传文件名在 Windows 上无效。");return e.slice(0,180)}function Et(t,e){const r=u.extname(t);return`${r?t.slice(0,-r.length):t} (${e})${r}`}function G(t){return!!(t&&typeof t=="object"&&t.code==="EEXIST")}function xt(t){const e=t&&typeof t=="object"?t.code:"";return["EPERM","EACCES","EXDEV","ENOTSUP","EOPNOTSUPP"].includes(String(e||""))}const W=500,j=2*1024*1024,y=2*1024*1024;function B(t){const e=p(String(t.rootPath||""));if(!w(e))return{isRepository:!1,branch:null,files:[],ahead:0,behind:0};const o=m(e,["status","--porcelain=v1","-z","--untracked-files=all","--no-renames"],j).split("\0").filter(Boolean).slice(0,W),i=o.map(c=>kt(c)).filter(c=>!!c),s=D(m(e,["diff","--no-ext-diff","--numstat","-z","--no-renames","HEAD","--","."],j));for(const c of i){const d=s.get(c.path);if(d)c.additions=d.additions,c.deletions=d.deletions;else if(c.status==="untracked"){const a=U(e,c.path);c.additions=a.additions,c.deletions=a.deletions}}const n=Tt(e);return{isRepository:!0,branch:Nt(e),files:i,ahead:n.ahead,behind:n.behind,truncated:o.length>=W}}function bt(t){const e=p(String(t.rootPath||"")),r=g(String(t.relativePath||""));if(!r)throw new Error("Diff 文件路径不能为空。");if(!w(e))return{isRepository:!1,path:r,diff:"",additions:0,deletions:0};const o=K(e,r),i=Ct(e,r),s=i?m(e,["diff","--no-ext-diff","--unified=80","HEAD","--",r],y):X(e,["diff","--no-ext-diff","--no-index","--unified=80",process.platform==="win32"?"NUL":"/dev/null",o],y),n=i?D(m(e,["diff","--no-ext-diff","--numstat","-z","--no-renames","HEAD","--",r],32*1024)).get(r):U(e,r);return{isRepository:!0,path:r,diff:V(s),additions:n?.additions??0,deletions:n?.deletions??0,truncated:s.length>y}}function jt(t){const e=p(String(t.rootPath||""));if(!w(e))return{isRepository:!1,items:[]};if(!$t(e))return{isRepository:!0,items:[]};const r=Math.min(200,Math.max(20,Number(t.limit||100)));return{isRepository:!0,items:m(e,["log",`--max-count=${r}`,"--date=iso-strict","--pretty=format:%H%x1f%h%x1f%an%x1f%ae%x1f%aI%x1f%D%x1f%s%x1f%b%x1e"],j).split("").map(n=>At(n)).filter(n=>!!n),limit:r}}function vt(t){const e=p(String(t.rootPath||"")),r=String(t.commit||"").trim();if(!/^[0-9a-f]{7,64}$/i.test(r))throw new Error("Git 提交标识无效。");if(!w(e))return{isRepository:!1,commit:r,diff:"",additions:0,deletions:0};m(e,["cat-file","-e",`${r}^{commit}`],8*1024);const o=m(e,["show","--no-ext-diff","--no-color","--format=","--stat","--patch","--unified=40",r],y),i=[...D(m(e,["show","--no-ext-diff","--numstat","-z","--no-renames","--format=",r],j)).values()];return{isRepository:!0,commit:r,diff:V(o),additions:i.reduce((s,n)=>s+(n.additions||0),0),deletions:i.reduce((s,n)=>s+(n.deletions||0),0),changedFiles:i.length,truncated:o.length>y}}function At(t){const e=t.replace(/^\s+|\s+$/g,"");if(!e)return null;const r=e.split(""),o=String(r[0]||"").trim();return o?{hash:o,shortHash:String(r[1]||o.slice(0,8)).trim(),authorName:String(r[2]||"").trim(),authorEmail:String(r[3]||"").trim(),authoredAt:String(r[4]||"").trim(),refs:String(r[5]||"").trim(),subject:String(r[6]||"").trim(),body:r.slice(7).join("").trim()}:null}function kt(t){if(t.length<4)return null;const e=t[0]||" ",r=t[1]||" ",o=g(t.slice(3));return o?{path:o,status:e==="?"&&r==="?"?"untracked":e==="U"||r==="U"?"conflicted":e==="D"||r==="D"?"deleted":e==="A"||r==="A"?"added":e==="R"||r==="R"?"renamed":"modified",indexStatus:e,worktreeStatus:r,staged:e!==" "&&e!=="?",additions:null,deletions:null}:null}function D(t){const e=new Map;for(const r of t.split("\0")){if(!r)continue;const o=z(r);if(!o||!o.path)continue;const i=g(o.path);i&&e.set(i,{additions:o.additions,deletions:o.deletions})}return e}function U(t,e){const r=K(t,e),i=X(t,["diff","--no-ext-diff","--no-index","--numstat","-z","--",process.platform==="win32"?"NUL":"/dev/null",r],32*1024).split("\0",1)[0]||"",s=z(i);return s?{additions:s.additions,deletions:s.deletions}:{additions:null,deletions:null}}function z(t){const e=t.indexOf(" "),r=e<0?-1:t.indexOf(" ",e+1);if(e<0||r<0)return null;const o=t.slice(0,e),i=t.slice(e+1,r);return{additions:/^\d+$/.test(o)?Number(o):null,deletions:/^\d+$/.test(i)?Number(i):null,path:t.slice(r+1)}}function w(t){try{return m(t,["rev-parse","--is-inside-work-tree"],8*1024).trim()==="true"}catch(e){if(String(e.code||"")==="ENOENT")throw new Error("设备未安装 Git。");return!1}}function Dt(t,e){try{const r=m(t,["rev-list","--count",`${e}..HEAD`],8192).trim(),o=Number(r);return Number.isSafeInteger(o)&&o>0?o:0}catch{return 0}}function Rt(t){try{return l.readdirSync(t).length>0}catch{return!1}}function Ct(t,e){try{return m(t,["ls-files","--error-unmatch","--",e],8*1024),!0}catch{return!1}}function $t(t){try{return m(t,["rev-parse","--verify","HEAD"],8*1024),!0}catch{return!1}}function Nt(t){try{return m(t,["symbolic-ref","--quiet","--short","HEAD"],8*1024).trim()||null}catch{try{return`HEAD (${m(t,["rev-parse","--short","HEAD"],8*1024).trim()})`}catch{return null}}}function Tt(t){try{const e=m(t,["rev-list","--left-right","--count","HEAD...@{upstream}"],8192).trim().split(/\s+/).map(Number);return{ahead:Number.isFinite(e[0])?e[0]:0,behind:Number.isFinite(e[1])?e[1]:0}}catch{return{ahead:0,behind:0}}}function K(t,e){const r=u.resolve(t,...e.split("/")),o=u.relative(t,r);if(o.startsWith("..")||u.isAbsolute(o))throw new Error("Diff 路径超出项目范围。");return r}function m(t,e,r){return _("git",["-C",t,...e],{windowsHide:!0,encoding:"utf8",timeout:2e4,maxBuffer:r,stdio:["ignore","pipe","pipe"]})}function X(t,e,r){try{return m(t,e,r)}catch(o){const i=String(o.stdout||"");if(i)return i;if(Number(o.status)===1)return"";throw o}}function V(t){return t.length<=y?t:`${t.slice(0,y)}
1
+ import{providerInstallOperation as q}from"./provider-installer.js";import{executeExternalCodexOperation as tt}from"./external-codex-worker.js";import{workBuddyAuthManager as et}from"./workbuddy-auth-worker.js";import{workBuddyCredits as rt}from"./workbuddy-credits/worker.js";import{managementActivity as T}from"./management/activity.js";import{managedClaim as ot}from"./management/claims.js";import{activeTerminalService as it}from"./terminal/service.js";import{execFileSync as _}from"node:child_process";import{randomUUID as F}from"node:crypto";import l from"node:fs";import M from"node:os";import u from"node:path";import{acaServerDownloadToFile as nt,acaServerRequest as E}from"./aca-server-client.js";import{loadAcaConfig as k,upsertLocalProject as st}from"./aca-config.js";import{startLongPollWorker as at}from"./long-poll-worker.js";import{expandWorkerRuntimePath as ct}from"./worker-runtime-paths.js";import{syncCodexProviderRuntimeConfig as lt}from"./codex-provider-runtime-config.js";import{defaultAcpRuntimePool as ut}from"../acp/client/acp-runtime-pool.js";import{execPowerShell as L}from"./machine-filesystem/powershell.js";const O=25e3,dt=O+1e4,x=300,ft=3e4;let b=null;function pe(t){return at({run:async()=>{const e=k();if(!e.token)throw new Error("Worker 尚未配置认证令牌");const r=await ot("machine",()=>E("GET",`/api/client/machine-operations/claim?machineId=${encodeURIComponent(e.machineId)}&waitMs=${O}`,void 0,{timeoutMs:dt}));if(!r.item)return;const o=T.track("machine");try{const i=await mt(r.item);await E("POST",`/api/client/machine-operations/${encodeURIComponent(r.item.requestId)}/complete`,{result:i})}catch(i){const s=i instanceof Error?i.message:String(i);t.warn(`machine filesystem request failed request=${r.item.requestId}: ${s}`),await E("POST",`/api/client/machine-operations/${encodeURIComponent(r.item.requestId)}/fail`,{message:s}).catch(()=>{T.uncertain.add("machine-finalize")})}finally{o()}},onError:(e,r)=>t.warn(`machine filesystem worker failed, retry in ${r}ms: ${e instanceof Error?e.message:String(e)}`)})}async function mt(t){if(t.operation==="provider_install")return q(t.payload);if(t.operation==="workbuddy_auth")return et.execute(String(t.payload.action||""),t.payload);if(t.operation==="workbuddy_credits")return{machineId:k().machineId,instanceId:process.env.ACA_WORKER_INSTANCE_ID||String(process.pid),snapshot:await rt.refresh(!0)};if(t.operation.startsWith("external_codex_"))return tt(t.operation,t.payload);if(t.operation==="sync_codex_provider_config"){const e=await lt(),r=await ut.retireProviderRuntimes("codex-acp");return{configured:!!e,revision:e?.revision??null,retiredRuntimes:r}}if(t.operation==="filesystem_roots")return Ft();if(t.operation==="list_directory")return Mt({directoryPath:String(t.payload.path||""),includeHidden:t.payload.includeHidden===!0});if(t.operation==="register_project")return Bt(t.payload);if(t.operation==="list_project_directory")return wt(t.payload);if(t.operation==="upload_project_file")return gt(t.payload);if(t.operation==="mutate_project_files")return St(t.payload);if(t.operation==="get_project_git_status")return B(t.payload);if(t.operation==="get_project_git_diff")return jt(t.payload);if(t.operation==="get_project_git_log")return vt(t.payload);if(t.operation==="get_project_git_commit")return At(t.payload);if(t.operation==="get_worktree_status")return D(t.payload);if(t.operation==="commit_worktree")return pt(t.payload);if(t.operation==="cleanup_worktree")return ht(t.payload);throw new Error("不支持的 Machine 文件系统操作。 ")}function D(t){const e=$(String(t.worktreePath||""));if(!l.existsSync(e))return{exists:!1,isRepository:!1,dirty:!1,changedFiles:0,ahead:0};const r=p(e);if(!w(r))return{exists:!0,isRepository:!1,dirty:Rt(r),changedFiles:null,ahead:0};const o=B({rootPath:r}),i=String(t.baseCommit||"").trim(),s=i&&/^[0-9a-f]{7,64}$/i.test(i)?Ct(r,i):0;return{exists:!0,isRepository:!0,dirty:Array.isArray(o.files)&&o.files.length>0,changedFiles:Array.isArray(o.files)?o.files.length:0,branch:o.branch,ahead:s,files:Array.isArray(o.files)?o.files.slice(0,50):[],truncated:o.truncated===!0}}function pt(t){const e=p(String(t.worktreePath||"")),r=String(t.message||"").trim();if(!r)throw new Error("提交说明不能为空。");if(!w(e))throw new Error("当前 Worktree 不是 Git 仓库,无法提交。");_("git",["-C",e,"add","-A"],{windowsHide:!0,stdio:"pipe",timeout:3e4});try{return _("git",["-C",e,"diff","--cached","--quiet"],{windowsHide:!0,stdio:"pipe",timeout:1e4}),{committed:!1,reason:"no_changes",...D(t)}}catch(o){if(Number(o.status)!==1)throw o}return _("git",["-C",e,"commit","-m",r],{windowsHide:!0,stdio:"pipe",timeout:6e4}),{committed:!0,commit:m(e,["rev-parse","HEAD"],8*1024).trim(),...D(t)}}function ht(t){const e=$(String(t.worktreePath||"")),r=p(String(t.projectRoot||""));if(h(e)===h(r))throw new Error("不能清理项目主目录。");if(!l.existsSync(e))return{removed:!1,reason:"not_found"};const o=p(e);if(it?.hasRunningInDirectory(o,String(t.sessionId||"")))throw new Error("请先结束此目录中的会话终端,再清理 Worktree。");const i=String(t.branchName||"").trim(),s=w(o)&&w(r);if(s){if(_("git",["-C",r,"worktree","remove","--force",o],{windowsHide:!0,stdio:"pipe",timeout:6e4}),t.deleteBranch===!0&&i)try{_("git",["-C",r,"branch","-D",i],{windowsHide:!0,stdio:"pipe",timeout:3e4})}catch(n){const c=String(n.stderr||n);if(!/not found|not exist/i.test(c))throw n}}else l.rmSync(o,{recursive:!0,force:!0,maxRetries:2,retryDelay:200});return{removed:!0,branchDeleted:!!(s&&t.deleteBranch===!0&&i)}}function wt(t){const e=p(String(t.rootPath||"")),r=g(String(t.relativePath||"")),o=v(e,r,!0),i=Math.max(0,Number(t.offset||0)),s=Math.min(300,Math.max(20,Number(t.limit||200))),n=t.sort==="modified-desc"||t.sort==="modified-asc"?t.sort:"name",c=l.readdirSync(o,{withFileTypes:!0}).filter(a=>!a.isSymbolicLink()).map(a=>({name:a.name,path:J(e,u.join(o,a.name)),type:a.isDirectory()?"directory":a.isFile()?"file":"other",hidden:a.name.startsWith("."),extension:a.isFile()?u.extname(a.name).slice(1).toLowerCase():"",modifiedAt:n==="name"?void 0:yt(u.join(o,a.name))})).filter(a=>a.type!=="other").sort((a,f)=>{if(a.type!==f.type)return a.type==="directory"?-1:1;if(n!=="name"){if(a.modifiedAt==null&&f.modifiedAt!=null)return 1;if(f.modifiedAt==null&&a.modifiedAt!=null)return-1;const P=(a.modifiedAt??0)-(f.modifiedAt??0);if(P)return n==="modified-desc"?-P:P}return a.name.localeCompare(f.name,void 0,{numeric:!0,sensitivity:"base"})}),d=c.slice(i,i+s);return{path:r,rootKind:t.rootKind==="worktree"?"worktree":"project",items:d,total:c.length,nextOffset:i+d.length<c.length?i+d.length:null}}function yt(t){try{const e=l.lstatSync(t);return e.isSymbolicLink()?null:e.mtimeMs}catch{return null}}async function gt(t){const e=String(t.uploadId||"").trim();if(!e)throw new Error("项目上传任务缺少 uploadId。");const r=p(String(t.rootPath||"")),o=g(String(t.relativeDirectory||"")),i=v(r,o,!0),s=H(String(t.fileName||"")),n=Number(t.sizeBytes),c=String(t.sha256||"").trim().toLowerCase();if(!Number.isSafeInteger(n)||n<0)throw new Error("项目上传文件大小无效。");if(!/^[0-9a-f]{64}$/.test(c))throw new Error("项目上传文件摘要无效。");const d=u.join(i,`.aca-upload-${F()}.tmp`);try{const a=await nt(`/api/client/project-uploads/${encodeURIComponent(e)}`,d,{maxBytes:n,timeoutMs:3e5});if(a.sizeBytes!==n)throw new Error("项目上传文件大小校验失败。");if(a.sha256.toLowerCase()!==c)throw new Error("项目上传文件完整性校验失败。");const f=Et(r,o,s,d);return{name:u.basename(f),path:J(r,f),directory:o,size:a.sizeBytes,sha256:a.sha256,renamed:u.basename(f)!==s,rootKind:t.rootKind==="worktree"?"worktree":"project"}}finally{l.rmSync(d,{force:!0})}}function St(t){const e=p(String(t.rootPath||"")),r=t.action&&typeof t.action=="object"?t.action:{};if(r.type==="delete")return _t(e,String(r.path||""),r.recursive===!0,t.rootKind);throw new Error("project_file_operation_unsupported")}function _t(t,e,r,o){const i=g(e);if(!i)throw new Error("project_file_root_protected");Pt(i);const s=i.split("/"),n=s.slice(0,-1).join("/"),c=v(t,n,!0),d=u.join(c,s.at(-1)||"");let a;try{a=l.lstatSync(d)}catch(f){throw f.code==="ENOENT"?new Error("project_file_not_found"):f}if(a.isSymbolicLink())throw new Error("project_file_symbolic_link");if(!a.isFile()&&!a.isDirectory())throw new Error("project_file_type_unsupported");if(a.isDirectory()&&!r&&l.readdirSync(d).length>0)throw new Error("project_directory_not_empty");return a.isDirectory()&&r?l.rmSync(d,{recursive:!0,force:!1,maxRetries:2,retryDelay:150}):a.isDirectory()?l.rmdirSync(d):l.unlinkSync(d),{operation:"delete",path:i,entryType:a.isDirectory()?"directory":"file",parentPath:n,affectedDirectories:[n],rootKind:o==="worktree"?"worktree":"project"}}function Pt(t){const e=t.split("/")[0].toLocaleLowerCase("en-US");if([".git",".aca",".aica"].includes(e))throw new Error("project_file_protected")}function Et(t,e,r,o){const i=p(t),s=g(e),n=v(i,s,!0),c=H(r);if(l.realpathSync.native(u.dirname(o))!==n)throw new Error("项目上传临时文件不在目标目录中。");const a=l.lstatSync(o);if(!a.isFile()||a.isSymbolicLink())throw new Error("项目上传临时文件无效。");for(let f=1;f<=1e4;f+=1){const P=f===1?c:xt(c,f),S=u.join(n,P);try{return l.linkSync(o,S),l.chmodSync(S,420),l.rmSync(o,{force:!0}),S}catch(A){if(G(A))continue;if(!bt(A))throw A;try{return l.copyFileSync(o,S,l.constants.COPYFILE_EXCL),l.chmodSync(S,420),l.rmSync(o,{force:!0}),S}catch(N){if(G(N))continue;throw N}}}throw new Error("同名文件过多,无法生成可用文件名。")}function H(t){const e=t.trim();if(!e||e==="."||e===".."||/[\\/\x00-\x1F]/.test(e))throw new Error("项目上传文件名无效。");if(process.platform==="win32"&&(/[<>:"|?*]/.test(e)||/[. ]$/.test(e)))throw new Error("项目上传文件名在 Windows 上无效。");return e.slice(0,180)}function xt(t,e){const r=u.extname(t);return`${r?t.slice(0,-r.length):t} (${e})${r}`}function G(t){return!!(t&&typeof t=="object"&&t.code==="EEXIST")}function bt(t){const e=t&&typeof t=="object"?t.code:"";return["EPERM","EACCES","EXDEV","ENOTSUP","EOPNOTSUPP"].includes(String(e||""))}const W=500,j=2*1024*1024,y=2*1024*1024;function B(t){const e=p(String(t.rootPath||""));if(!w(e))return{isRepository:!1,branch:null,files:[],ahead:0,behind:0};const o=m(e,["status","--porcelain=v1","-z","--untracked-files=all","--no-renames"],j).split("\0").filter(Boolean).slice(0,W),i=o.map(c=>Dt(c)).filter(c=>!!c),s=C(m(e,["diff","--no-ext-diff","--numstat","-z","--no-renames","HEAD","--","."],j));for(const c of i){const d=s.get(c.path);if(d)c.additions=d.additions,c.deletions=d.deletions;else if(c.status==="untracked"){const a=U(e,c.path);c.additions=a.additions,c.deletions=a.deletions}}const n=Tt(e);return{isRepository:!0,branch:Nt(e),files:i,ahead:n.ahead,behind:n.behind,truncated:o.length>=W}}function jt(t){const e=p(String(t.rootPath||"")),r=g(String(t.relativePath||""));if(!r)throw new Error("Diff 文件路径不能为空。");if(!w(e))return{isRepository:!1,path:r,diff:"",additions:0,deletions:0};const o=K(e,r),i=It(e,r),s=i?m(e,["diff","--no-ext-diff","--unified=80","HEAD","--",r],y):X(e,["diff","--no-ext-diff","--no-index","--unified=80",process.platform==="win32"?"NUL":"/dev/null",o],y),n=i?C(m(e,["diff","--no-ext-diff","--numstat","-z","--no-renames","HEAD","--",r],32*1024)).get(r):U(e,r);return{isRepository:!0,path:r,diff:V(s),additions:n?.additions??0,deletions:n?.deletions??0,truncated:s.length>y}}function vt(t){const e=p(String(t.rootPath||""));if(!w(e))return{isRepository:!1,items:[]};if(!$t(e))return{isRepository:!0,items:[]};const r=Math.min(200,Math.max(20,Number(t.limit||100)));return{isRepository:!0,items:m(e,["log",`--max-count=${r}`,"--date=iso-strict","--pretty=format:%H%x1f%h%x1f%an%x1f%ae%x1f%aI%x1f%D%x1f%s%x1f%b%x1e"],j).split("").map(n=>kt(n)).filter(n=>!!n),limit:r}}function At(t){const e=p(String(t.rootPath||"")),r=String(t.commit||"").trim();if(!/^[0-9a-f]{7,64}$/i.test(r))throw new Error("Git 提交标识无效。");if(!w(e))return{isRepository:!1,commit:r,diff:"",additions:0,deletions:0};m(e,["cat-file","-e",`${r}^{commit}`],8*1024);const o=m(e,["show","--no-ext-diff","--no-color","--format=","--stat","--patch","--unified=40",r],y),i=[...C(m(e,["show","--no-ext-diff","--numstat","-z","--no-renames","--format=",r],j)).values()];return{isRepository:!0,commit:r,diff:V(o),additions:i.reduce((s,n)=>s+(n.additions||0),0),deletions:i.reduce((s,n)=>s+(n.deletions||0),0),changedFiles:i.length,truncated:o.length>y}}function kt(t){const e=t.replace(/^\s+|\s+$/g,"");if(!e)return null;const r=e.split(""),o=String(r[0]||"").trim();return o?{hash:o,shortHash:String(r[1]||o.slice(0,8)).trim(),authorName:String(r[2]||"").trim(),authorEmail:String(r[3]||"").trim(),authoredAt:String(r[4]||"").trim(),refs:String(r[5]||"").trim(),subject:String(r[6]||"").trim(),body:r.slice(7).join("").trim()}:null}function Dt(t){if(t.length<4)return null;const e=t[0]||" ",r=t[1]||" ",o=g(t.slice(3));return o?{path:o,status:e==="?"&&r==="?"?"untracked":e==="U"||r==="U"?"conflicted":e==="D"||r==="D"?"deleted":e==="A"||r==="A"?"added":e==="R"||r==="R"?"renamed":"modified",indexStatus:e,worktreeStatus:r,staged:e!==" "&&e!=="?",additions:null,deletions:null}:null}function C(t){const e=new Map;for(const r of t.split("\0")){if(!r)continue;const o=z(r);if(!o||!o.path)continue;const i=g(o.path);i&&e.set(i,{additions:o.additions,deletions:o.deletions})}return e}function U(t,e){const r=K(t,e),i=X(t,["diff","--no-ext-diff","--no-index","--numstat","-z","--",process.platform==="win32"?"NUL":"/dev/null",r],32*1024).split("\0",1)[0]||"",s=z(i);return s?{additions:s.additions,deletions:s.deletions}:{additions:null,deletions:null}}function z(t){const e=t.indexOf(" "),r=e<0?-1:t.indexOf(" ",e+1);if(e<0||r<0)return null;const o=t.slice(0,e),i=t.slice(e+1,r);return{additions:/^\d+$/.test(o)?Number(o):null,deletions:/^\d+$/.test(i)?Number(i):null,path:t.slice(r+1)}}function w(t){try{return m(t,["rev-parse","--is-inside-work-tree"],8*1024).trim()==="true"}catch(e){if(String(e.code||"")==="ENOENT")throw new Error("设备未安装 Git。");return!1}}function Ct(t,e){try{const r=m(t,["rev-list","--count",`${e}..HEAD`],8192).trim(),o=Number(r);return Number.isSafeInteger(o)&&o>0?o:0}catch{return 0}}function Rt(t){try{return l.readdirSync(t).length>0}catch{return!1}}function It(t,e){try{return m(t,["ls-files","--error-unmatch","--",e],8*1024),!0}catch{return!1}}function $t(t){try{return m(t,["rev-parse","--verify","HEAD"],8*1024),!0}catch{return!1}}function Nt(t){try{return m(t,["symbolic-ref","--quiet","--short","HEAD"],8*1024).trim()||null}catch{try{return`HEAD (${m(t,["rev-parse","--short","HEAD"],8*1024).trim()})`}catch{return null}}}function Tt(t){try{const e=m(t,["rev-list","--left-right","--count","HEAD...@{upstream}"],8192).trim().split(/\s+/).map(Number);return{ahead:Number.isFinite(e[0])?e[0]:0,behind:Number.isFinite(e[1])?e[1]:0}}catch{return{ahead:0,behind:0}}}function K(t,e){const r=u.resolve(t,...e.split("/")),o=u.relative(t,r);if(o.startsWith("..")||u.isAbsolute(o))throw new Error("Diff 路径超出项目范围。");return r}function m(t,e,r){return _("git",["-C",t,...e],{windowsHide:!0,encoding:"utf8",timeout:2e4,maxBuffer:r,stdio:["ignore","pipe","pipe"]})}function X(t,e,r){try{return m(t,e,r)}catch(o){const i=String(o.stdout||"");if(i)return i;if(Number(o.status)===1)return"";throw o}}function V(t){return t.length<=y?t:`${t.slice(0,y)}
2
2
 
3
- [Diff 内容过大,已截断]`}function It(){const t=process.platform,e=R(t);return{platform:t,roots:e}}function Ft(t){if(!t.directoryPath.trim())throw new Error("目录路径不能为空。");return process.platform==="win32"?Ht(t.directoryPath,!!t.includeHidden):Ot(t.directoryPath,!!t.includeHidden)}function h(t,e=process.platform){const r=e==="win32"?u.win32:u.posix,o=r.normalize(t),i=r.parse(o).root,s=o.length>i.length?o.replace(/[\\/]+$/,""):o;return e==="win32"?s.toLocaleLowerCase("en-US"):s}function Mt(){const t=l.realpathSync(F.homedir()),e=[{id:"home",label:"用户目录",path:t,kind:"home",available:!0}];return t!==u.parse(t).root&&e.push({id:"root",label:"文件系统",path:"/",kind:"root",available:!0}),e}function R(t){if(b?.platform===t&&b.expiresAtMs>Date.now())return b.roots;const e=t==="win32"?Lt():Mt();return b={platform:t,roots:e,expiresAtMs:Date.now()+dt},e}function Lt(){const t=l.realpathSync.native(F.homedir()),e=new Map;e.set(h(t,"win32"),{id:"home",label:"用户目录",path:t,kind:"home",available:!0});try{const r=["$items = Get-CimInstance Win32_LogicalDisk | ForEach-Object {"," [PSCustomObject]@{ Name = $_.DeviceID; Root = ($_.DeviceID + '\\\\'); DriveType = $_.DriveType; VolumeName = $_.VolumeName }","}","@($items) | ConvertTo-Json -Compress"].join(`
4
- `),o=Z(L(r));for(const i of o){const s=String(i.Root||"");if(!s)continue;const n=Number(i.DriveType||0),c=String(i.Name||s.replace(/[\\/]+$/,"")),d=String(i.VolumeName||"").trim();e.set(h(s,"win32"),{id:`drive-${c.replace(/[^a-z0-9]+/gi,"").toLowerCase()}`,label:d?`${d} (${c})`:Xt(c,n),path:s,kind:Kt(n),available:!0})}}catch{const r=u.win32.parse(t).root;e.set(h(r,"win32"),{id:"drive-system",label:`系统盘 (${r.replace(/\\$/,"")})`,path:r,kind:"fixed",available:!0})}return[...e.values()]}function Ot(t,e){const r=C(t),i=l.readdirSync(r,{withFileTypes:!0}).filter(n=>n.isDirectory()&&!n.isSymbolicLink()&&(e||!n.name.startsWith("."))),s=i.slice(0,x).map(n=>({name:n.name,path:u.join(r,n.name),kind:"directory",hidden:n.name.startsWith("."),hasChildren:!0}));return s.sort((n,c)=>n.name.localeCompare(c.name,void 0,{numeric:!0,sensitivity:"base"})),Y(r,s,i.length>x,"linux")}function Ht(t,e){const r=C(t),o=["$items = Get-ChildItem -LiteralPath $env:ACA_MACHINE_FS_PATH -Directory -Force -ErrorAction Stop | ForEach-Object {"," [PSCustomObject]@{ Name = $_.Name; FullName = $_.FullName; Attributes = $_.Attributes.ToString() }","}","@($items) | ConvertTo-Json -Compress"].join(`
5
- `),i=Z(L(o,{ACA_MACHINE_FS_PATH:r})),s=i.map(n=>{const c=String(n.Attributes||"");return{name:String(n.Name||""),path:String(n.FullName||""),kind:"directory",hidden:/Hidden|System/i.test(c),hasChildren:!0,reparsePoint:/ReparsePoint/i.test(c)}}).filter(n=>n.name&&n.path&&!n.reparsePoint&&(e||!n.hidden)).slice(0,x).sort((n,c)=>n.name.localeCompare(c.name,void 0,{numeric:!0,sensitivity:"base"}));return Y(r,s,i.length>x,"win32")}function Y(t,e,r,o){const i=o==="win32"?u.win32:u.posix,s=i.parse(t).root,n=h(t,o)===h(s,o)?null:i.dirname(t);return{platform:o,path:t,parentPath:n,breadcrumbs:Gt(t,o),readable:Q(t,l.constants.R_OK),writable:Q(t,l.constants.W_OK),items:e,truncated:r}}function Gt(t,e){const r=e==="win32"?u.win32:u.posix,o=r.parse(t),i=t.slice(o.root.length).split(/[\\/]+/).filter(Boolean),s=[{label:o.root.replace(/[\\/]+$/,"")||o.root,path:o.root}];let n=o.root;for(const c of i)n=r.join(n,c),s.push({label:c,path:n});return s}async function Wt(t){const e=String(t.workspaceId||"").trim(),r=String(t.name||"").trim(),o=t.projectType==="chat_room"?"chat_room":"project";if(!e||!r)throw new Error("项目名称和工作区不能为空。");const i=C(String(t.path||""));l.accessSync(i,l.constants.R_OK|l.constants.W_OK);const s=M().machineId,c=(await E("GET",`/api/projects?workspaceId=${encodeURIComponent(e)}`)).items.find(a=>(!a.machine_id||a.machine_id===s)&&h(a.root_path)===h(i)),d=c||(await E("POST","/api/projects",{projectId:I(),workspaceId:e,name:r,rootPath:i,source:"aica",projectType:o})).item;return nt(Bt(d)),{project:d,duplicate:!!c}}function Bt(t){return{projectId:t.project_id,workspaceId:t.workspace_id,name:t.name,rootPath:t.root_path}}function C(t){if(!t.trim()||!u.isAbsolute(t))throw new Error("必须选择绝对目录路径。");if(Ut(t),!l.statSync(t).isDirectory())throw new Error("选择的路径不是目录。");const r=l.realpathSync.native(t);return zt(r),r}function p(t){const e=$(t),r=l.realpathSync.native(e);if(!l.statSync(r).isDirectory())throw new Error("项目根目录不存在。");return r}function $(t){const e=at(t);if(!e||!u.isAbsolute(e))throw new Error("项目根目录无效。");return u.resolve(e)}function g(t){const e=t.replace(/\\/g,"/").replace(/^\/+|\/+$/g,"");if(!e)return"";if(e.includes("\0")||e.split("/").some(r=>!r||r==="."||r===".."))throw new Error("项目相对路径无效。");return e}function v(t,e,r){const o=e?u.join(t,...e.split("/")):t,i=l.realpathSync.native(o),s=u.relative(t,i);if(s.startsWith("..")||u.isAbsolute(s))throw new Error("目录超出项目范围。");const n=l.statSync(i);if(r&&!n.isDirectory())throw new Error("请求路径不是目录。");return i}function J(t,e){return u.relative(t,e).split(u.sep).join("/")}function Ut(t){const e=process.platform==="win32"?u.win32:u.posix;if(!R(process.platform).some(o=>{const i=String(o.path||"");if(!i)return!1;const s=e.relative(i,t);return s===""||!s.startsWith("..")&&!e.isAbsolute(s)}))throw new Error("目录不在允许浏览的位置内。")}function zt(t){const e=process.platform==="win32"?u.win32:u.posix;if(!R(process.platform).some(o=>{const i=String(o.path||"");if(!i)return!1;const s=e.relative(i,t);return s===""||!s.startsWith("..")&&!e.isAbsolute(s)}))throw new Error("目录不在允许浏览的位置内。")}function Q(t,e){try{return l.accessSync(t,e),!0}catch{return!1}}function Z(t){if(!t)return[];const e=JSON.parse(t);return Array.isArray(e)?e.filter(r=>!!(r&&typeof r=="object")):e&&typeof e=="object"?[e]:[]}function Kt(t){return t===2?"removable":t===3?"fixed":t===4?"network":t===5?"optical":"drive"}function Xt(t,e){return e===2?`移动磁盘 (${t})`:e===4?`网络驱动器 (${t})`:e===5?`光驱 (${t})`:`本地磁盘 (${t})`}export{pt as cleanupWorktree,Pt as commitProjectUploadTempFile,mt as commitWorktree,h as comparableMachinePath,vt as getProjectGitCommit,bt as getProjectGitDiff,jt as getProjectGitLog,B as getProjectGitStatus,k as getWorktreeStatus,Ft as listMachineDirectory,It as listMachineFilesystemRoots,ht as listProjectDirectory,gt as mutateProjectFiles,fe as startMachineFilesystemWorkerLoop,yt as uploadProjectFile};
3
+ [Diff 内容过大,已截断]`}function Ft(){const t=process.platform,e=R(t);return{platform:t,roots:e}}function Mt(t){if(!t.directoryPath.trim())throw new Error("目录路径不能为空。");return process.platform==="win32"?Gt(t.directoryPath,!!t.includeHidden):Ht(t.directoryPath,!!t.includeHidden)}function h(t,e=process.platform){const r=e==="win32"?u.win32:u.posix,o=r.normalize(t),i=r.parse(o).root,s=o.length>i.length?o.replace(/[\\/]+$/,""):o;return e==="win32"?s.toLocaleLowerCase("en-US"):s}function Lt(){const t=l.realpathSync(M.homedir()),e=[{id:"home",label:"用户目录",path:t,kind:"home",available:!0}];return t!==u.parse(t).root&&e.push({id:"root",label:"文件系统",path:"/",kind:"root",available:!0}),e}function R(t){if(b?.platform===t&&b.expiresAtMs>Date.now())return b.roots;const e=t==="win32"?Ot():Lt();return b={platform:t,roots:e,expiresAtMs:Date.now()+ft},e}function Ot(){const t=l.realpathSync.native(M.homedir()),e=new Map;e.set(h(t,"win32"),{id:"home",label:"用户目录",path:t,kind:"home",available:!0});try{const r=["$items = Get-CimInstance Win32_LogicalDisk | ForEach-Object {"," [PSCustomObject]@{ Name = $_.DeviceID; Root = ($_.DeviceID + '\\\\'); DriveType = $_.DriveType; VolumeName = $_.VolumeName }","}","@($items) | ConvertTo-Json -Compress"].join(`
4
+ `),o=Z(L(r));for(const i of o){const s=String(i.Root||"");if(!s)continue;const n=Number(i.DriveType||0),c=String(i.Name||s.replace(/[\\/]+$/,"")),d=String(i.VolumeName||"").trim();e.set(h(s,"win32"),{id:`drive-${c.replace(/[^a-z0-9]+/gi,"").toLowerCase()}`,label:d?`${d} (${c})`:Vt(c,n),path:s,kind:Xt(n),available:!0})}}catch{const r=u.win32.parse(t).root;e.set(h(r,"win32"),{id:"drive-system",label:`系统盘 (${r.replace(/\\$/,"")})`,path:r,kind:"fixed",available:!0})}return[...e.values()]}function Ht(t,e){const r=I(t),i=l.readdirSync(r,{withFileTypes:!0}).filter(n=>n.isDirectory()&&!n.isSymbolicLink()&&(e||!n.name.startsWith("."))),s=i.slice(0,x).map(n=>({name:n.name,path:u.join(r,n.name),kind:"directory",hidden:n.name.startsWith("."),hasChildren:!0}));return s.sort((n,c)=>n.name.localeCompare(c.name,void 0,{numeric:!0,sensitivity:"base"})),Y(r,s,i.length>x,"linux")}function Gt(t,e){const r=I(t),o=["$items = Get-ChildItem -LiteralPath $env:ACA_MACHINE_FS_PATH -Directory -Force -ErrorAction Stop | ForEach-Object {"," [PSCustomObject]@{ Name = $_.Name; FullName = $_.FullName; Attributes = $_.Attributes.ToString() }","}","@($items) | ConvertTo-Json -Compress"].join(`
5
+ `),i=Z(L(o,{ACA_MACHINE_FS_PATH:r})),s=i.map(n=>{const c=String(n.Attributes||"");return{name:String(n.Name||""),path:String(n.FullName||""),kind:"directory",hidden:/Hidden|System/i.test(c),hasChildren:!0,reparsePoint:/ReparsePoint/i.test(c)}}).filter(n=>n.name&&n.path&&!n.reparsePoint&&(e||!n.hidden)).slice(0,x).sort((n,c)=>n.name.localeCompare(c.name,void 0,{numeric:!0,sensitivity:"base"}));return Y(r,s,i.length>x,"win32")}function Y(t,e,r,o){const i=o==="win32"?u.win32:u.posix,s=i.parse(t).root,n=h(t,o)===h(s,o)?null:i.dirname(t);return{platform:o,path:t,parentPath:n,breadcrumbs:Wt(t,o),readable:Q(t,l.constants.R_OK),writable:Q(t,l.constants.W_OK),items:e,truncated:r}}function Wt(t,e){const r=e==="win32"?u.win32:u.posix,o=r.parse(t),i=t.slice(o.root.length).split(/[\\/]+/).filter(Boolean),s=[{label:o.root.replace(/[\\/]+$/,"")||o.root,path:o.root}];let n=o.root;for(const c of i)n=r.join(n,c),s.push({label:c,path:n});return s}async function Bt(t){const e=String(t.workspaceId||"").trim(),r=String(t.name||"").trim(),o=t.projectType==="chat_room"?"chat_room":"project";if(!e||!r)throw new Error("项目名称和工作区不能为空。");const i=I(String(t.path||""));l.accessSync(i,l.constants.R_OK|l.constants.W_OK);const s=k().machineId,c=(await E("GET",`/api/projects?workspaceId=${encodeURIComponent(e)}`)).items.find(a=>(!a.machine_id||a.machine_id===s)&&h(a.root_path)===h(i)),d=c||(await E("POST","/api/projects",{projectId:F(),workspaceId:e,name:r,rootPath:i,source:"aica",projectType:o})).item;return st(Ut(d)),{project:d,duplicate:!!c}}function Ut(t){return{projectId:t.project_id,workspaceId:t.workspace_id,name:t.name,rootPath:t.root_path}}function I(t){if(!t.trim()||!u.isAbsolute(t))throw new Error("必须选择绝对目录路径。");if(zt(t),!l.statSync(t).isDirectory())throw new Error("选择的路径不是目录。");const r=l.realpathSync.native(t);return Kt(r),r}function p(t){const e=$(t),r=l.realpathSync.native(e);if(!l.statSync(r).isDirectory())throw new Error("项目根目录不存在。");return r}function $(t){const e=ct(t);if(!e||!u.isAbsolute(e))throw new Error("项目根目录无效。");return u.resolve(e)}function g(t){const e=t.replace(/\\/g,"/").replace(/^\/+|\/+$/g,"");if(!e)return"";if(e.includes("\0")||e.split("/").some(r=>!r||r==="."||r===".."))throw new Error("项目相对路径无效。");return e}function v(t,e,r){const o=e?u.join(t,...e.split("/")):t,i=l.realpathSync.native(o),s=u.relative(t,i);if(s.startsWith("..")||u.isAbsolute(s))throw new Error("目录超出项目范围。");const n=l.statSync(i);if(r&&!n.isDirectory())throw new Error("请求路径不是目录。");return i}function J(t,e){return u.relative(t,e).split(u.sep).join("/")}function zt(t){const e=process.platform==="win32"?u.win32:u.posix;if(!R(process.platform).some(o=>{const i=String(o.path||"");if(!i)return!1;const s=e.relative(i,t);return s===""||!s.startsWith("..")&&!e.isAbsolute(s)}))throw new Error("目录不在允许浏览的位置内。")}function Kt(t){const e=process.platform==="win32"?u.win32:u.posix;if(!R(process.platform).some(o=>{const i=String(o.path||"");if(!i)return!1;const s=e.relative(i,t);return s===""||!s.startsWith("..")&&!e.isAbsolute(s)}))throw new Error("目录不在允许浏览的位置内。")}function Q(t,e){try{return l.accessSync(t,e),!0}catch{return!1}}function Z(t){if(!t)return[];const e=JSON.parse(t);return Array.isArray(e)?e.filter(r=>!!(r&&typeof r=="object")):e&&typeof e=="object"?[e]:[]}function Xt(t){return t===2?"removable":t===3?"fixed":t===4?"network":t===5?"optical":"drive"}function Vt(t,e){return e===2?`移动磁盘 (${t})`:e===4?`网络驱动器 (${t})`:e===5?`光驱 (${t})`:`本地磁盘 (${t})`}export{ht as cleanupWorktree,Et as commitProjectUploadTempFile,pt as commitWorktree,h as comparableMachinePath,At as getProjectGitCommit,jt as getProjectGitDiff,vt as getProjectGitLog,B as getProjectGitStatus,D as getWorktreeStatus,Mt as listMachineDirectory,Ft as listMachineFilesystemRoots,wt as listProjectDirectory,St as mutateProjectFiles,pe as startMachineFilesystemWorkerLoop,gt as uploadProjectFile};
@@ -1 +1 @@
1
- import{WorkBuddyAuthManager as r}from"../acp/providers/workbuddy/auth-manager.js";import{sendHeartbeat as a}from"./heartbeat.js";const n=new r(void 0,async()=>{await a()});export{n as workBuddyAuthManager};
1
+ import{WorkBuddyAuthManager as r}from"../acp/providers/workbuddy/auth-manager.js";import{sendHeartbeat as e}from"./heartbeat.js";import{workBuddyCredits as t}from"./workbuddy-credits/worker.js";const n=new r(void 0,async()=>{t.reset(),await e()});export{n as workBuddyAuthManager};
@@ -0,0 +1 @@
1
+ import{createWorkBuddyAuthTransport as _}from"../../acp/providers/workbuddy/auth-transport.js";import{enterWorkBuddyTask as k}from"../../acp/providers/workbuddy/auth-gate.js";import{workBuddyRegion as R,workBuddyEnvironmentCredentials as S}from"../../acp/providers/workbuddy/auth-environment.js";import{record as a}from"../../acp/providers/workbuddy/config.js";function m(n,t=null){return{state:n,account:t,remaining:null,total:null,unlimited:!1,resources:[]}}function C(n){if(typeof n!="number"&&(typeof n!="string"||!/^-?\d+(?:\.\d+)?$/.test(n)))return;const t=Number(n);return Number.isFinite(t)?t:void 0}function P(n){const t=[];for(const r of n){const e=a(r);if(e.Status!==void 0&&![0,3].includes(Number(e.Status)))continue;const s=C(e.CycleCapacityRemainPrecise??e.CycleCapacityRemain??e.CapacityRemainPrecise??e.CapacityRemain),o=C(e.CycleCapacitySizePrecise??e.CycleCapacitySize??e.CapacitySizePrecise??e.CapacitySize),c=e.Unlimited===!0||e.Unlimited===1||o===-1;if(s===void 0||o===void 0||!c&&(o<0||s<0))throw new Error("invalid_credits");const g=typeof e.CycleEndTime=="string"&&e.CycleEndTime.length<=100?e.CycleEndTime:null;t.push({name:typeof e.PackageName=="string"?e.PackageName.slice(0,200):"积分套餐",remaining:s,total:o,unlimited:c,cycleEnd:g})}const i=t.some(r=>r.unlimited),l=r=>Math.round(t.reduce((e,s)=>e+s[r],0)*100)/100;return{remaining:i?null:l("remaining"),total:i?null:l("total"),unlimited:i,resources:t}}async function B(n={}){let t=null,i,l;try{l=k();const r=R();i=(n.transport??_)(),await i.initialize();const e=await i.billingIdentity();if(!e.account)return m(S()?"unsupported":"signed_out");if(t={...e.account,region:r,scope:e.enterpriseId.slice(0,200)},r==="ioa"||!e.token)return m("unsupported",t);const s=r==="internal"?"https://www.codebuddy.cn":"https://www.codebuddy.ai",o={Authorization:`Bearer ${e.token}`,"Content-Type":"application/json",Accept:"application/json","X-User-Id":t.id};e.enterpriseId&&(o["X-Enterprise-Id"]=e.enterpriseId,o["X-Tenant-Id"]=e.enterpriseId);const c=[],g=AbortSignal.timeout(25e3),h=new Date,w=new Date(h);w.setUTCFullYear(w.getUTCFullYear()+101);const b=p=>p.toISOString().slice(0,19).replace("T"," ");for(let p=1;p<=10;p++){const d=await(n.fetch??fetch)(`${s}/v2/billing/meter/get-user-resource`,{method:"POST",headers:o,redirect:"error",signal:g,body:JSON.stringify({PageNumber:p,PageSize:100,ProductCode:"p_tcaca",Status:[0,3],PackageEndTimeRangeBegin:b(h),PackageEndTimeRangeEnd:b(w)})});if(d.status===429){const f=d.headers.get("Retry-After"),T=f&&/^\d+$/.test(f)?Number(f)*1e3:Date.parse(f||"")-Date.now();return{...m("error",t),retryAfterMs:Number.isFinite(T)?Math.max(0,T):3e5}}if(d.status===401||d.status===403)return m("signed_out");if(!d.ok)throw new Error("credits_http_error");const u=a(await d.json());if(u.code!==void 0&&![0,"0"].includes(u.code))throw new Error("credits_business_error");const y=a(a(a(u.data).Response??u.Response).Data);if(a(a(u.data).Response??u.Response).Error||!Array.isArray(y.Accounts))throw new Error("invalid_credits_response");c.push(...y.Accounts);const E=C(y.TotalCount);if(y.Accounts.length<100||E!==void 0&&c.length>=E)return{state:"ready",account:t,...P(c)}}throw new Error("credits_page_limit")}catch{return m("error",t)}finally{try{await i?.close()}finally{l?.()}}}export{m as emptyCredits,P as parseCreditsResources,B as queryWorkBuddyCredits};
@@ -0,0 +1 @@
1
+ import{emptyCredits as r}from"./query.js";import{ACTIVE_CREDITS_INTERVAL_MS as n,IDLE_CREDITS_INTERVAL_MS as u}from"./types.js";class y{query;changed;now;value={...r("error"),fetchedAtMs:null,checkedAtMs:null,revision:0};lastAttempt=-1/0;retryAt=0;limitedUntil=0;failures=0;dirty=!1;active=0;epoch=0;timer;pending;running=!1;constructor(t,s,e=Date.now){this.query=t,this.changed=s,this.now=e}snapshot(){return this.value}start(){this.running||(this.running=!0,this.tick())}async stop(){this.running=!1,clearTimeout(this.timer),await this.pending}beginUsage(){this.active++,this.dirty=!0,this.running&&this.tick();let t=!1;return()=>{t||(t=!0,this.active--,this.dirty=!0,this.running&&this.tick())}}reset(){this.epoch++,this.lastAttempt=-1/0,this.retryAt=0,this.limitedUntil=0,this.failures=0,this.dirty=!1,this.value={...r("error"),fetchedAtMs:null,checkedAtMs:null,revision:this.value.revision+1},this.changed(this.value),this.running&&(this.pending??Promise.resolve()).then(()=>{if(this.running)return this.tick()})}nextDue(){const t=this.dirty||this.active||this.failures?n:u;return Math.max(this.lastAttempt+t,this.retryAt,this.limitedUntil)}async tick(){return this.pending?this.pending:this.now()>=this.nextDue()?this.refresh():(this.arm(),this.value)}refresh(t=!1){if(this.pending)return this.pending;if(t&&this.now()<this.limitedUntil)return Promise.resolve(this.value);const s=this.epoch,e=this.now();this.lastAttempt=e;const l=this.dirty;return this.dirty=!1,this.pending=Promise.resolve().then(this.query).catch(()=>r("error")).then(i=>{if(s!==this.epoch)return this.value;const o=i.state==="error"&&!i.account||JSON.stringify(i.account)===JSON.stringify(this.value.account),h=i.state==="error";h?(this.failures++,this.dirty||=l,this.retryAt=this.now()+Math.max(n,Math.min(u,n*2**Math.min(4,this.failures-1)))):(this.failures=0,this.retryAt=0,this.limitedUntil=0),i.retryAfterMs&&(this.limitedUntil=this.now()+i.retryAfterMs);const{retryAfterMs:f,...c}=i,a=h&&o&&this.value.fetchedAtMs!==null;return this.value={...c,...a?{account:this.value.account,remaining:this.value.remaining,total:this.value.total,unlimited:this.value.unlimited,resources:this.value.resources}:{},fetchedAtMs:i.state==="ready"?this.now():a?this.value.fetchedAtMs:null,checkedAtMs:e,revision:this.value.revision+1},this.changed(this.value),this.value}).finally(()=>{this.pending=void 0,this.arm()}),this.pending}arm(){clearTimeout(this.timer),!(!this.running||this.pending)&&(this.timer=setTimeout(()=>{this.tick()},Math.min(2147483647,Math.max(1,this.nextDue()-this.now()))),this.timer.unref())}}export{y as WorkBuddyCreditsScheduler};
@@ -0,0 +1 @@
1
+ const _=3e5,E=36e5;export{_ as ACTIVE_CREDITS_INTERVAL_MS,E as IDLE_CREDITS_INTERVAL_MS};
@@ -0,0 +1 @@
1
+ import{acaServerRequest as u}from"../aca-server-client.js";import{loadAcaConfig as a}from"../aca-config.js";import{managementActivity as i}from"../management/activity.js";import{emptyCredits as f,queryWorkBuddyCredits as p}from"./query.js";import{WorkBuddyCreditsScheduler as l}from"./scheduler.js";let r=!1,s=!1,d="",o=Promise.resolve();const n=new l(async()=>{const e=i.enter("workbuddy-credits");if(!e)return f("error");try{return await p()}finally{e()}},()=>{c()});function c(){const e=a();if(!e.token||!r)return Promise.resolve();const t=n.snapshot();return o=o.catch(()=>{}).then(()=>u("POST","/api/client/workbuddy-credits",{machineId:e.machineId,instanceId:process.env.ACA_WORKER_INSTANCE_ID||String(process.pid),snapshot:t},{serverUrl:e.serverUrl,token:e.token})).catch(()=>{}),o}function w(){r=!0,s&&m()}function m(){if(s=!0,!r||i.paused||i.stopping)return;const e=a(),t=`${e.serverUrl}\0${e.machineId}\0${e.token}`;d&&d!==t&&n.reset(),d=t,n.start(),c()}async function g(){r=!1,s=!1,await n.stop(),await o}export{w as activateWorkBuddyCredits,g as stopWorkBuddyCredits,n as workBuddyCredits,m as workBuddyCreditsConnected};
package/dist/version.js CHANGED
@@ -1 +1 @@
1
- const o="0.1.88";export{o as AICA_VERSION};
1
+ const o="0.1.89";export{o as AICA_VERSION};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yuandc/aica",
3
- "version": "0.1.88",
3
+ "version": "0.1.89",
4
4
  "description": "AICA remote Agent worker",
5
5
  "author": "yuandc",
6
6
  "type": "module",