@yuandc/aica 0.1.87 → 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.
- package/dist/acp/client/acp-runtime-pool.js +1 -1
- package/dist/acp/providers/codex/codex-path.js +1 -1
- package/dist/acp/providers/codex/permissions.js +1 -1
- package/dist/acp/providers/codex/provider.js +1 -1
- package/dist/acp/providers/codex-acp/adapter.js +4 -4
- package/dist/acp/providers/codex-acp/provider.js +1 -1
- package/dist/acp/providers/mimo/provider.js +5 -5
- package/dist/acp/providers/opencode/provider.js +4 -4
- package/dist/acp/providers/registry.js +1 -1
- package/dist/acp/providers/workbuddy/auth-environment.js +1 -0
- package/dist/acp/providers/workbuddy/auth-gate.js +1 -0
- package/dist/acp/providers/workbuddy/auth-manager.js +1 -0
- package/dist/acp/providers/workbuddy/auth-transport.js +3 -0
- package/dist/acp/providers/workbuddy/config.js +1 -0
- package/dist/acp/providers/workbuddy/discovery.js +1 -0
- package/dist/acp/providers/workbuddy/events.js +1 -0
- package/dist/acp/providers/workbuddy/interactions.js +1 -0
- package/dist/acp/providers/workbuddy/launch.js +1 -0
- package/dist/acp/providers/workbuddy/provider.js +1 -0
- package/dist/acp/providers/workbuddy/supplements.js +1 -0
- package/dist/commands/start.js +1 -1
- package/dist/core/desktop-control.js +1 -1
- package/dist/core/heartbeat.js +3 -3
- package/dist/core/job-worker.js +1 -1
- package/dist/core/machine-filesystem-worker.js +4 -4
- package/dist/core/provider-health.js +1 -1
- package/dist/core/provider-install-mimo.js +1 -0
- package/dist/core/provider-install-paths.js +1 -0
- package/dist/core/provider-installer.js +1 -0
- package/dist/core/workbuddy-auth-worker.js +1 -0
- package/dist/core/workbuddy-credits/query.js +1 -0
- package/dist/core/workbuddy-credits/scheduler.js +1 -0
- package/dist/core/workbuddy-credits/types.js +1 -0
- package/dist/core/workbuddy-credits/worker.js +1 -0
- package/dist/interactions/job-interactions.js +2 -0
- package/dist/interactions/provider-input.js +1 -0
- package/dist/interactions/types.js +1 -0
- package/dist/version.js +1 -1
- package/package.json +1 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
2
|
-
`),await this.lease?.dispose().catch(()=>{}),this.lease=null,this.providerSessionId=null,this.sessionResponse=null,this.resumedExistingSession=!1,this.emitStatus("resuming","OpenCode 会话已失效,正在新建","session/prompt invalid params");const
|
|
3
|
-
`),this.providerSessionId=null}catch{this.providerSessionId=null}this.emitStatus("acp","OpenCode 会话创建中","session/new");const
|
|
4
|
-
`)})}handleSessionUpdate(e){this.updates.push({method:"session/update",params:e}),this.latestContextUsage=
|
|
1
|
+
import{resolveInstalledLaunch as v}from"../../../core/provider-install-paths.js";import{handleProviderInput as x}from"../../../interactions/provider-input.js";import h from"node:fs";import m from"node:path";import{runAbortableRequest as A}from"../../client/abortable-request.js";import{BoundedItemBuffer as O}from"../../client/bounded-buffer.js";import{contextUsageFromAcpSessionUpdate as R}from"../../client/context-usage.js";import{defaultAcpRuntimePool as T}from"../../client/acp-runtime-pool.js";import{providerEventFromAcpSessionUpdate as M,providerStatusFromAcpSessionUpdate as E}from"../standard-acp-events.js";const P={sessionResume:!0,imageInput:!0,fileAttachment:!0,filesystem:!0,terminal:!0,permissionRequest:!0,configOptions:!0,usage:!0,contextUsage:!0,contextCompaction:!1,plan:!0,diff:!0};class Q{id="opencode";name="OpenCode";capabilities=P;async createSession(e){return new _(e.cwd,e.providerSessionId??null,e.timeoutMs)}}class _{cwd;providerSessionId;defaultTimeoutMs;lease=null;sessionResponse=null;statusCallback=null;updateCallback=null;permissionCallback=null;userInputCallback=null;canExecuteTools=!0;abortHandler=null;statusTimeline=[];updates=new O(16);content="";latestContextUsage=null;contextWindow=0;resumedExistingSession=!1;constructor(e,t,s=L()){this.cwd=e,this.providerSessionId=t,this.defaultTimeoutMs=s}async sendPrompt(e){if(!h.existsSync(e.cwd)||!h.statSync(e.cwd).isDirectory())throw new Error(`Project root does not exist or is not a directory: ${e.cwd}`);const t=Date.now();this.statusCallback=e.onStatus??null,this.updateCallback=e.onUpdate??null,this.permissionCallback=e.onPermissionRequest??null,this.userInputCallback=e.onUserInputRequest??null,this.canExecuteTools=e.canExecuteTools!==!1,this.attachAbortSignal(e.signal),this.content="",this.updates.clear(),this.statusTimeline.length=0,this.latestContextUsage=null,this.contextWindow=q(e.contextWindow);let s=null,n=null,o=this.providerSessionId,r;try{this.emitStatus("initializing","OpenCode ACP 启动中","opencode acp");const a=await this.acquireRuntime(e.cwd);this.lease=a,s=a.client,n=a.started,await this.initialize(a),o=await this.establishSession(a,e),await this.applyConfig(s,o,e),this.emitStatus("thinking","思考中");const c=$(e.prompt,e.promptBlocks);try{r=await this.promptWithAbort(s,{sessionId:o,prompt:c,timeoutMs:e.timeoutMs??this.defaultTimeoutMs,signal:e.signal})}catch(l){if(!this.resumedExistingSession||!F(l))throw l;this.appendStderr(`OpenCode resumed session rejected session/prompt with Invalid params; creating a fresh session and retrying once.
|
|
2
|
+
`),await this.lease?.dispose().catch(()=>{}),this.lease=null,this.providerSessionId=null,this.sessionResponse=null,this.resumedExistingSession=!1,this.emitStatus("resuming","OpenCode 会话已失效,正在新建","session/prompt invalid params");const p=await this.acquireRuntime(e.cwd);this.lease=p,s=p.client,n=p.started,await this.initialize(p),o=await this.establishSession(p,e),await this.applyConfig(s,o,e),this.emitStatus("thinking","思考中"),r=await this.promptWithAbort(s,{sessionId:o,prompt:c,timeoutMs:e.timeoutMs??this.defaultTimeoutMs,signal:e.signal})}}finally{this.abortHandler&&e.signal&&(e.signal.removeEventListener("abort",this.abortHandler),this.abortHandler=null),this.releaseLease()}return this.emitStatus("completed","已完成"),{providerSessionId:o,cliType:"builtin",agentType:"opencode",effectiveConfig:{model:f(e.model)||null,mode:e.mode||null,configOptionValues:e.configOptionValues??null,command:n.command,processPid:n.child.pid??null,provider:"opencode",protocol:"acp"},command:n.command,args:n.args,cwd:e.cwd,exitCode:n.child.exitCode,signal:n.child.signalCode,durationMs:Date.now()-t,stdout:s.rpc.stdout,stderr:s.rpc.stderr,content:this.content.trim(),updateCount:this.updates.count(),updates:this.updates.snapshot(),statusTimeline:[...this.statusTimeline],contextUsage:this.latestContextUsage,promptResponse:r}}async cancel(){this.providerSessionId&&this.lease?.client.cancel(this.providerSessionId)}async close(){this.statusCallback=null,this.updateCallback=null,this.permissionCallback=null,this.userInputCallback=null,this.canExecuteTools=!0,this.abortHandler&&(this.abortHandler=null),this.releaseLease()}async acquireRuntime(e){const{command:t,args:s}=v("opencode");return T.acquire({providerId:"opencode",cwd:e,providerSessionId:this.providerSessionId,command:t,args:[...s,"acp","--cwd",e],onSessionUpdate:n=>this.handleSessionUpdate(n),onClientRequest:(n,o,r)=>this.handleClientRequest(n,o,r.id)})}async initialize(e){e.initialized||(await e.client.initialize(6e4),e.markInitialized())}async establishSession(e,t){const s=e.client,n=k(t);if(this.resumedExistingSession=!1,e.providerSessionId&&e.sessionEstablished){if(S(e.sessionResponse,t.model))return this.providerSessionId=e.providerSessionId,this.sessionResponse=e.sessionResponse,this.resumedExistingSession=!0,e.providerSessionId;this.providerSessionId=null}else if(this.providerSessionId)try{this.emitStatus("resuming","OpenCode 会话恢复中","session/load");const a=await s.loadSession({sessionId:this.providerSessionId,cwd:t.cwd},6e4);if(this.sessionResponse=d(a),S(this.sessionResponse,t.model)){const c=g(a)||this.providerSessionId;return this.providerSessionId=c,e.setProviderSession({providerSessionId:c,sessionResponse:this.sessionResponse}),this.resumedExistingSession=!0,c}this.appendStderr(`OpenCode model changed from ${C(this.sessionResponse)??"unknown"} to ${f(t.model)||"default"}, creating new session
|
|
3
|
+
`),this.providerSessionId=null}catch{this.providerSessionId=null}this.emitStatus("acp","OpenCode 会话创建中","session/new");const o=await s.newSession({cwd:t.cwd,config:n},12e4);this.sessionResponse=d(o);const r=g(o);if(!r)throw new Error("OpenCode ACP session/new did not return sessionId");return this.providerSessionId=r,e.setProviderSession({providerSessionId:r,sessionResponse:this.sessionResponse}),r}async applyConfig(e,t,s){const n=w(s.mode);n&&await e.setSessionMode({sessionId:t,modeId:n},6e4).catch(o=>{this.appendStderr(`OpenCode mode config failed: ${y(o)}
|
|
4
|
+
`)})}handleSessionUpdate(e){this.updates.push({method:"session/update",params:e}),this.latestContextUsage=R(e,this.contextWindow)??this.latestContextUsage;const t=M("opencode",e);t&&(t.type==="agent_message_chunk"&&(this.content+=t.text??""),this.updateCallback?.(t));const s=E("opencode",e);s&&this.emitStatus(s.phase,s.label,s.detail,s.updateType)}async handleClientRequest(e,t,s){if(!this.canExecuteTools&&(e==="fs/read_text_file"||e==="fs/write_text_file"))throw new Error(`Chat Room role policy disabled tool execution: ${e}`);switch(e){case"fs/read_text_file":return this.readTextFile(t);case"fs/write_text_file":return this.writeTextFile(t);case"session/request_permission":return this.requestPermission(t,s);case"elicitation/create":case"session/request_user_input":return this.requestUserInput(e,t,s);default:throw new Error(`Unsupported OpenCode ACP client request: ${e}`)}}readTextFile(e){const t=I(this.cwd,u(e,"path")||u(e,"filePath"));return{content:h.readFileSync(t,"utf8")}}writeTextFile(e){const t=I(this.cwd,u(e,"path")||u(e,"filePath")),s=u(e,"content")??u(e,"text")??"";return h.mkdirSync(m.dirname(t),{recursive:!0}),h.writeFileSync(t,s,"utf8"),{}}attachAbortSignal(e){if(e){if(this.abortHandler=()=>{this.cancel()},e.aborted)throw new Error("OpenCode prompt cancelled");e.addEventListener("abort",this.abortHandler,{once:!0})}}async promptWithAbort(e,t){return A({signal:t.signal,cancelledMessage:"OpenCode prompt cancelled",startRequest:()=>e.prompt({sessionId:t.sessionId,prompt:t.prompt},t.timeoutMs),onAbort:async()=>{e.cancel(t.sessionId),await this.lease?.dispose()}})}async requestPermission(e,t){if(!this.permissionCallback)return{outcome:{outcome:"cancelled"}};const s=d(e)??{},n=d(s.request)??s,r=(Array.isArray(n.options)?n.options:[]).map((a,c)=>{const l=d(a)??{};return{optionId:String(l.optionId??l.id??c),kind:String(l.kind??l.outcome??"choice"),...typeof l.label=="string"?{label:l.label}:{}}});return this.emitStatus("requestPermission","等待授权",D(n),"session/request_permission"),this.permissionCallback({requestId:String(n.requestId??t??n.id??`opencode-permission-${Date.now()}`),params:e,expiresAtMs:typeof n.expiresAtMs=="number"?n.expiresAtMs:void 0,options:r})}async requestUserInput(e,t,s){return this.emitStatus("requestPermission","等待回答",void 0,e),x("opencode",e,t,this.userInputCallback,s)}emitStatus(e,t,s,n){const o={phase:e,label:t,...s?{detail:s}:{},...n?{updateType:n}:{},atMs:Date.now()},r=this.statusTimeline[this.statusTimeline.length-1];r&&r.phase===o.phase&&r.label===o.label&&r.detail===o.detail&&r.updateType===o.updateType||(this.statusTimeline.push(o),this.statusCallback?.(o))}appendStderr(e){const t=this.lease?.client;t&&(t.rpc.stderr+=e)}releaseLease(){this.lease?.release(),this.lease=null}}function g(i){const t=d(i)?.sessionId;return typeof t=="string"&&t.trim()?t.trim():null}function d(i){return i&&typeof i=="object"&&!Array.isArray(i)?i:null}function q(i){return typeof i=="number"&&Number.isFinite(i)&&i>0?i:0}function k(i){const e=f(i.model),t=w(i.mode);return{...U(i.configOptionValues),...e?{model:e}:{},...t?{mode:t}:{}}}function U(i){const e={};for(const[t,s]of Object.entries(i??{}))["model","mode","reasoning_effort","effort","variant"].includes(t)||(e[t]=s);return e}function S(i,e){const t=f(e);if(!t)return!0;const s=C(i);return!s||s===t}function C(i){const t=d(i?.models)?.currentModelId;return typeof t=="string"&&t.trim()?t.trim():null}function w(i){const e=String(i||"").trim();return e?["agent-full-access","full-access","bypassPermissions","danger-full-access"].includes(e)?"build":e==="read-only"?"plan":e:null}function u(i,e){const s=d(i)?.[e];return typeof s=="string"&&s.trim()?s:null}function I(i,e){if(!e)throw new Error("ACP file request did not include a path");const t=m.resolve(i,e),s=m.resolve(i);if(t!==s&&!t.startsWith(`${s}${m.sep}`))throw new Error(`ACP file request is outside project root: ${e}`);return t}function D(i){const e=d(i.params)??i;for(const t of["command","cwd","reason","grantRoot","method"]){const s=e[t];if(typeof s=="string"&&s.trim())return b(s)}}function X(i){for(const e of["prompt","message","question","title"]){const t=i[e];if(typeof t=="string"&&t.trim())return b(t)}}function b(i){const e=i.replace(/\s+/g," ").trim();return e.length>120?`${e.slice(0,117)}...`:e}function y(i){return i instanceof Error?i.message:String(i)}function F(i){return/\binvalid params\b/i.test(y(i))}function $(i,e){return(e?.length?e:[{type:"text",text:i}]).map(s=>s.type==="image"?{type:"image",data:s.data,mimeType:s.mimeType}:{type:"text",text:s.text})}function z(){return process.env.ACA_OPENCODE_DEFAULT_MODEL||""}function f(i){const e=String(i||z()).trim();return e==="default"||e==="opencode-default"?"":e}function L(){const i=Number.parseInt(process.env.ACA_OPENCODE_PROMPT_TIMEOUT_MS??process.env.ACA_ACP_PROMPT_TIMEOUT_MS??"",10);return Number.isInteger(i)&&i>=3e4?i:720*60*1e3}export{Q as OpenCodeProvider,P as openCodeCapabilities};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{NativeCodexProvider as
|
|
1
|
+
import{NativeCodexProvider as d}from"./codex/provider.js";import{CodexAcpProvider as i}from"./codex-acp/provider.js";import{MimoCodeProvider as n}from"./mimo/provider.js";import{OpenCodeProvider as t}from"./opencode/provider.js";import{WorkBuddyProvider as c}from"./workbuddy/provider.js";function v(o){const e=r(o.agentType||"codex");if(r(o.cliType||"builtin")!=="builtin")throw new Error(`Unsupported ACP cliType: ${o.cliType??""}`);if(e==="codex"||e==="codexacp"||e==="codexstandard")return new i;if(e==="nativecodex"||e==="codexnative")return new d;if(e==="mimo"||e==="mimocode"||e==="mimocodeprovider")return new n;if(e==="opencode"||e==="opencodeprovider")return new t;if(["workbuddy","codebuddy","tencentworkbuddy","codebuddycode"].includes(e))return new c;throw new Error(`Unsupported ACP agentType: ${o.agentType??""}`)}function r(o){return o.toLowerCase().replace(/[^a-z0-9]+/g,"")}export{v as createAgentProvider};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import o from"node:path";import{getStateDir as r}from"../../../core/paths.js";import{readJsonFile as t,writeJsonFile as i}from"../../../core/fs.js";function a(){const n=process.env.CODEBUDDY_INTERNET_ENVIRONMENT;if(n!==void 0)return n==="internal"?"internal":n==="ioa"?"ioa":"external";const e=t(o.join(r(),"workbuddy-auth.json"));return["internal","external","ioa"].includes(e?.region||"")?e.region:"internal"}function E(n){i(o.join(r(),"workbuddy-auth.json"),{region:n})}function D(n=a()){return n?{CODEBUDDY_INTERNET_ENVIRONMENT:n==="external"?"":n}:{}}function l(){return!!(process.env.CODEBUDDY_API_KEY||process.env.CODEBUDDY_AUTH_TOKEN)}export{E as saveWorkBuddyRegion,l as workBuddyEnvironmentCredentials,a as workBuddyRegion,D as workBuddyRegionEnv};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
let r=0,e=!1;function n(){if(e)throw new Error("腾讯账号正在登录或退出,请完成后重试任务。");r++;let t=!1;return()=>{t||(t=!0,r--)}}function o(){if(e||r)throw new Error("腾讯 Code Buddy 正在执行任务或检测状态,请稍后重试登录操作。");return e=!0,()=>{e=!1}}function u(){return e}export{o as beginWorkBuddyAuthChange,n as enterWorkBuddyTask,u as workBuddyAuthChanging};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{randomUUID as b}from"node:crypto";import{defaultAcpRuntimePool as w}from"../../client/acp-runtime-pool.js";import{managementActivity as A}from"../../../core/management/activity.js";import{beginWorkBuddyAuthChange as y,enterWorkBuddyTask as v}from"./auth-gate.js";import{clearWorkBuddyCapabilities as a}from"./config.js";import{resetWorkBuddyDiscovery as u}from"./discovery.js";import{saveWorkBuddyRegion as I,workBuddyEnvironmentCredentials as d,workBuddyRegion as g}from"./auth-environment.js";import{createWorkBuddyAuthTransport as h,safeWorkBuddyAuthUrl as M}from"./auth-transport.js";class x{timeoutMs;changed;flow;cached;queue=Promise.resolve();stopping=!1;constructor(r=5*6e4,e=async()=>{}){this.timeoutMs=r,this.changed=e}execute(r,e={}){const s=this.queue.then(async()=>{if(this.stopping)throw new Error("Worker 正在停止。");if(r==="status")return this.status();if(r==="start")return this.start(e.region);if(r==="cancel")return this.cancel(String(e.flowId||""));if(r==="logout")return this.logout();throw new Error("不支持的腾讯账号操作。")});return this.queue=s.catch(()=>{}),s}async stop(){this.stopping=!0,await this.queue,this.flow&&await this.cancel(this.flow.snapshot.flowId)}base(r){return{state:r,region:g()||"internal",regionLocked:process.env.CODEBUDDY_INTERNET_ENVIRONMENT!==void 0}}async status(){if(this.flow)return{...this.flow.snapshot};if(d())return{...this.base("environment"),message:"当前使用 Worker 环境变量凭据;网页不能修改或退出这类凭据。"};if(this.cached&&this.cached.until>Date.now())return{...this.cached.snapshot};const r=v();let e;try{e=h(),await e.initialize();const s=await e.account();return this.remember({...this.base(s?"authenticated":"signed_out"),account:s})}catch(s){return this.remember({...this.base("error"),message:c(s)})}finally{try{await e?.close()}finally{r()}}}async start(r){if(this.flow)return{...this.flow.snapshot};if(d())return this.status();if(!["internal","external","ioa"].includes(String(r)))throw new Error("请选择中国版、国际版或 iOA。");const e=r;if(process.env.CODEBUDDY_INTERNET_ENVIRONMENT!==void 0&&e!==g())throw new Error("登录版本由 Worker 环境变量固定,请使用对应版本。");const s=y(),E=A.track("workbuddy-login"),t={stopped:!1,snapshot:{...this.base("starting"),region:e,flowId:b(),expiresAtMs:Date.now()+this.timeoutMs}};this.flow=t,this.cached=void 0;const f=setTimeout(()=>{t.stopped||(t.stopped=!0,t.snapshot={...t.snapshot,state:"expired",authUrl:void 0,message:"登录已超时,请重新发起。"},t.transport?.close())},this.timeoutMs);return f.unref(),t.done=(async()=>{try{if(a(),u(),await w.retireProviderRuntimes("workbuddy"),t.stopped)return;t.transport=h(e,i=>{if(!t.stopped)try{t.snapshot={...t.snapshot,state:"waiting",authUrl:M(i,e)}}catch(m){t.stopped=!0,t.snapshot={...t.snapshot,state:"error",authUrl:void 0,expiresAtMs:void 0,message:m instanceof Error&&m.message==="auth_region_mismatch"?"官方授权链接与所选登录版本不一致,请重新发起登录,并检查 CLI 的版本和环境配置。":"官方返回的授权链接无法安全打开,请升级 CLI 或使用本机终端登录。"},t.transport?.close()}});const o=await t.transport.initialize();if(t.stopped)return;const k=await t.transport.account();if(t.stopped)return;const p=e==="ioa"?"iOA":e;if(!Array.isArray(o.authMethods)||!o.authMethods.some(i=>i?.id===p))throw new Error("auth_method_unsupported");const l=k||await t.transport.authenticate(p,Math.max(100,t.snapshot.expiresAtMs-Date.now()));if(t.stopped)return;if(!l)throw new Error("auth_no_account");I(e),a(),u(),t.snapshot={...t.snapshot,state:"authenticated",account:l,authUrl:void 0,expiresAtMs:void 0}}catch(o){t.stopped||(t.snapshot={...t.snapshot,state:"error",authUrl:void 0,message:c(o)})}finally{clearTimeout(f);try{await t.transport?.close()}finally{t.stopped=!0,this.remember(t.snapshot),this.flow===t&&(this.flow=void 0),s(),E()}t.snapshot.state==="authenticated"&&this.changed().catch(()=>{})}})(),{...t.snapshot}}async cancel(r){const e=this.flow;return!e||e.snapshot.flowId!==r?this.status():(e.stopped=!0,e.snapshot={...e.snapshot,state:"cancelled",authUrl:void 0,message:"已停止等待授权。如已在腾讯页面完成授权,请刷新登录状态。"},await e.transport?.close(),await e.done,{...e.snapshot})}async logout(){if(d())return this.status();if(this.flow)throw new Error("请先取消当前登录,再退出账号。");const r=y();let e;try{return await w.retireProviderRuntimes("workbuddy"),e=h(void 0,void 0,!0),await e.logout(),a(),u(),this.remember(this.base("signed_out"))}catch(s){return this.remember({...this.base("error"),message:c(s)})}finally{try{await e?.close()}finally{r()}this.changed().catch(()=>{})}}remember(r){return this.cached={until:Date.now()+1e4,snapshot:r},{...r}}}function c(n){const r=n instanceof Error?n.message:"";return/ENOENT/.test(r)?"未找到 CodeBuddy CLI,请在 Worker 机器安装 @tencent-ai/codebuddy-code,或配置 ACA_WORKBUDDY_COMMAND。":/method.*not.*found|unsupported|not.*supported/i.test(r)?"当前 CodeBuddy CLI 不支持所需认证接口,请升级 CLI 后重试。":/timeout|timed out/i.test(r)?"腾讯认证服务响应超时,请检查 Worker 网络后重试。":"腾讯账号操作未完成,请检查 Worker 网络和 CodeBuddy CLI 安装后重试。"}export{x as WorkBuddyAuthManager};
|
|
@@ -0,0 +1,3 @@
|
|
|
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};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{resolveWorkBuddyLaunch as _}from"./launch.js";function s(e){return e&&typeof e=="object"&&!Array.isArray(e)?e:{}}const m=[{id:"default",name:"询问授权"},{id:"acceptEdits",name:"自动接受文件修改"},{id:"plan",name:"规划"},{id:"bypassPermissions",name:"完全访问"}];let a=null;function b(){a=null}function w(){return(a?.identity===g()&&a.expiresAtMs>Date.now()?a.value:null)??{source:"aca-static",contextUsage:!0,contextCompaction:!0,liveSupplement:!0,sessionResume:!0,sessionFork:!1,modelCatalogAuthoritative:!0,models:[{modelId:process.env.ACA_WORKBUDDY_DEFAULT_MODEL||"default",name:"默认模型"}],modes:m,configOptions:[{id:"model",category:"model",type:"select",currentValue:process.env.ACA_WORKBUDDY_DEFAULT_MODEL||"default",options:[{value:process.env.ACA_WORKBUDDY_DEFAULT_MODEL||"default",name:"默认模型"}]},{id:"mode",category:"mode",type:"select",currentValue:"default",options:m.map(r=>({value:r.id,name:r.name}))},{id:"thought_level",category:"thought_level",type:"select",currentValue:"enabled",options:[{value:"enabled",name:"模型默认"}]}]}}function v(e){const r=p(e).filter(o=>["model","mode","thought_level","context_window"].includes(String(o.id)));if(!r.length)return;const d=s(e.models).availableModels,i=r.find(o=>o.category==="model"||o.id==="model");a={identity:g(),expiresAtMs:Date.now()+360*60*1e3,value:{source:"codebuddy-acp",fetchedAt:Date.now(),modelCatalogAuthoritative:!0,contextUsage:!0,contextCompaction:!0,liveSupplement:!0,sessionResume:!0,sessionFork:!1,models:Array.isArray(d)?d:Array.isArray(i?.options)?i.options:[],modes:s(e.modes).availableModes||m,configOptions:r}}}function g(){try{return _().runtimeIdentity}catch{return"unavailable"}}function p(e){return Array.isArray(e.configOptions)?e.configOptions.map(s):[]}function D(e){return!e||e==="agent"?"default":e==="read-only"?"plan":["agent-full-access","full-access","danger-full-access"].includes(e)?"bypassPermissions":e}async function x(e,r,d,i){let o=d;const t={...i.configOptionValues},A=i.canExecuteTools===!1?"default":D(i.mode||String(t.mode||"default"));await e.setSessionMode({sessionId:r,modeId:A});const u=i.model||String(t.model||process.env.ACA_WORKBUDDY_DEFAULT_MODEL||"");u?t.model=u==="default"?"default-model":u:delete t.model,delete t.mode;const y=t.thought_level??t.reasoning_effort??t.effort;if(delete t.reasoning_effort,delete t.effort,y!==void 0&&(t.thought_level=y),t.multitask===!0||t.delegate===!0)throw new Error("WorkBuddy 当前不支持后台多任务或委派模式。");p(o).some(n=>n.id==="multitask"&&n.currentValue===!0)&&(t.multitask=!1);for(const[n,l]of Object.entries(t)){const c=p(o).find(f=>f.id===n);if(!c&&n!=="model")throw new Error(`WorkBuddy 不支持配置项:${n}`);if(!(n==="model"&&l==="default-model")&&Array.isArray(c?.options)&&!c.options.some(f=>s(f).value===l))throw new Error(`WorkBuddy 配置值不可用:${n}=${l}`);const h=s(await e.setSessionConfigOption({sessionId:r,configId:n,value:l}));o={...o,...h}}return v(o),o}export{x as applyWorkBuddyConfig,b as clearWorkBuddyCapabilities,p as configOptions,s as record,v as rememberWorkBuddyCapabilities,w as workBuddyHeartbeatCapabilities,D as workBuddyMode,m as workBuddyModes};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import c from"node:fs";import u from"node:os";import m from"node:path";import{AcpClient as f}from"../../client/acp-client.js";import{closeAcpProcess as p,startAcpProcess as y}from"../../client/acp-process.js";import{record as w,rememberWorkBuddyCapabilities as b,workBuddyHeartbeatCapabilities as s}from"./config.js";import{resolveWorkBuddyLaunch as l}from"./launch.js";import{enterWorkBuddyTask as h,workBuddyAuthChanging as k}from"./auth-gate.js";let t=null,i=null;function x(){t=null}async function I(){const e=s();if(k()||e.source==="codebuddy-acp")return e;const o=l(!1);return t?.identity===o.runtimeIdentity&&t.retryAtMs>Date.now()?e:i||(i=B().finally(()=>{i=null}),i)}async function B(){const e=l(!1),o=h();let r,n;try{r=c.mkdtempSync(m.join(u.tmpdir(),"aca-workbuddy-capabilities-")),n=y(e.command,[...e.args,"--no-session-persistence"],r,e.env);const a=new f(n.child,()=>{});await a.initialize(8e3);const d=w(await a.newSession({cwd:r},8e3));b(d),t=null}catch{t={identity:e.runtimeIdentity,retryAtMs:Date.now()+300*1e3}}finally{try{n&&await p(n.child),r&&c.rmSync(r,{recursive:!0,force:!0})}finally{o()}}return s()}export{I as readWorkBuddyCapabilities,x as resetWorkBuddyDiscovery};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{record as d}from"./config.js";class f{children=new Map;route(m){const a=d(m),t=d(a.update),n=d(t._meta),o=d(n["codebuddy.ai/subagent"]),s=typeof o.sessionId=="string"?o.sessionId:typeof n["codebuddy.ai/memberEvent"]=="string"?String(n["codebuddy.ai/memberEvent"]):"";if(!s)return{params:a,child:!1};const e={...this.children.get(s),...o};this.children.set(s,e);const c=String(e.conversationRequestId||d(a._meta)["codebuddy.ai/requestId"]||"current"),r=`workbuddy.${s}.${c}`,b=String(e.status||"unknown"),i={running:"running",pending:"pending",completed:"completed",failed:"failed",cancelled:"interrupted"}[b]||"unknown",u=["completed","failed","interrupted"].includes(i),p=t.sessionUpdate==="session_info_update"&&typeof o.status=="string",l={...t,...p?{sessionUpdate:"tool_call_update",toolCallId:`run.${r}`,kind:"other",title:"子代理执行状态",status:i==="interrupted"?"cancelled":u?i:"in_progress"}:{},_meta:{...n,aca:{...d(n.aca),...p?{subtaskLifecycle:!0,processKind:"subtask"}:{},subtask:{sourceThreadId:s,sourceTurnId:c,parentToolCallId:e.parentToolCallId||`workbuddy.child.${s}`,depth:1,runId:r,run:{runId:r,state:i,name:e.agentNickname||n["codebuddy.ai/memberEvent"]||e.role,description:e.description,sourceTurnId:c,startedAtMs:e.createdAt,lastProgressAtMs:e.updatedAt,...u?{endedAtMs:e.updatedAt,reason:e.error}:{}}}}}};return t.toolCallId&&Object.assign(l,{toolCallId:`subtask.${r}.${t.toolCallId}`}),["plan","plan_update","plan_removed"].includes(String(t.sessionUpdate))&&Object.assign(l,{planId:`subtask.${r}.${t.planId||"current"}`}),{params:{...a,update:l},child:!0}}}export{f as WorkBuddyEventRouter};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{handleProviderInput as y}from"../../../interactions/provider-input.js";import{INTERACTION_TIMEOUT_MS as f,validateInteractionAnswers as w}from"../../../interactions/types.js";import{record as d}from"./config.js";async function g(n,s,c){const o=d(n),a=d(o.schema);if(o.inputType!=="question"||!Array.isArray(a.questions))throw new Error("Unsupported WorkBuddy question schema");const r=a.questions.map(t=>{const e=d(t),m=Array.isArray(e.options)?e.options.map(q=>{const l=d(q),p=String(l.value??l.label??"");return{value:p,label:String(l.label??p),description:typeof l.description=="string"?l.description:void 0}}):void 0;return{id:String(e.id??""),label:String(e.question??""),description:typeof e.header=="string"?e.header:void 0,type:e.multiSelect===!0?"string[]":"string",required:!0,allowOther:!0,options:m}});if(!r.length||r.length>100||r.some(t=>!t.id||!t.label||["__proto__","constructor","prototype"].includes(t.id))||new Set(r.map(t=>t.id)).size!==r.length)throw new Error("Unsupported WorkBuddy question fields");const u={version:1,kind:"questions",provider:"workbuddy",method:"_codebuddy.ai/question",requestId:String(s),runtimeId:"",expiresAtMs:Date.now()+f,prompt:String(o.prompt||"请回答以下问题"),fields:r},i=await c.onUserInputRequest?.({requestId:String(s),prompt:u.prompt,params:n,interaction:u});if(!i||i.outcome.outcome==="cancelled")return{outcome:"cancelled"};if(Object.keys(w(u,i.outcome.answers)).length)throw new Error("Invalid WorkBuddy question answers");return{outcome:"submitted",answers:Object.fromEntries(Object.entries(i.outcome.answers).map(([t,e])=>[t,e.value]))}}async function S(n,s,c,o){if(n==="_codebuddy.ai/question")return g(s,c,o);if(n==="elicitation/create")return y("workbuddy",n,s,o.onUserInputRequest??null,c);if(n!=="session/request_permission")throw new Error(`Unsupported WorkBuddy ACP client request: ${n}`);const a=d(s),r={outcome:{outcome:"cancelled"}};if(o.canExecuteTools===!1||!o.onPermissionRequest)return r;const u=(Array.isArray(a.options)?a.options:[]).map(d).map(e=>({optionId:String(e.optionId||""),kind:String(e.kind||"choice"),label:String(e.name||e.label||e.optionId||"")})).filter(e=>e.optionId),i=await o.onPermissionRequest({requestId:String(c),params:s,options:u}),t=i.outcome;return t.outcome==="selected"&&!u.some(e=>e.optionId===t.optionId)?r:i}export{g as handleWorkBuddyQuestion,S as handleWorkBuddyRequest};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{installedProviderEntry as d,entryLaunch as m}from"../../../core/provider-install-paths.js";import t from"node:fs";import r from"node:path";import{createHash as D}from"node:crypto";import{workBuddyRegionEnv as p}from"./auth-environment.js";function E(e=!0){const s=process.env.ACA_WORKBUDDY_COMMAND||process.env.ACA_CODEBUDDY_COMMAND||"codebuddy",n=_(s),o=[...n.args,"--acp"];e||o.push("--tools","","--strict-mcp-config","--mcp-config",'{"mcpServers":{}}');const c={CODEBUDDY_CODE_DISABLE_BACKGROUND_TASKS:"1",...p()},a=D("sha256").update(JSON.stringify({command:n.command,args:o,env:c,configDir:process.env.CODEBUDDY_CONFIG_DIR,region:process.env.CODEBUDDY_INTERNET_ENVIRONMENT,apiKey:process.env.CODEBUDDY_API_KEY,authToken:process.env.CODEBUDDY_AUTH_TOKEN})).digest("hex");return{command:n.command,args:o,env:c,runtimeIdentity:a}}function _(e){if(e==="codebuddy"&&!process.env.ACA_WORKBUDDY_COMMAND&&!process.env.ACA_CODEBUDDY_COMMAND){const n=d("workbuddy");if(n)return m(n)}if(/\.[cm]?js$/i.test(e)||/[/\\]/.test(e)&&u(e))return{command:i(r.dirname(r.resolve(e))),args:[r.resolve(e)]};if(process.platform!=="win32")return{command:e,args:[]};const s=r.isAbsolute(e)||/[/\\]/.test(e)?[r.dirname(r.resolve(e))]:(process.env.PATH||"").split(r.delimiter);for(const n of s){const o=r.join(n,"node_modules","@tencent-ai","codebuddy-code","bin","codebuddy");if(t.existsSync(o)&&/^(codebuddy(?:-code)?|cbc)(?:\.cmd)?$/i.test(r.basename(e)))return{command:i(n),args:[o]};const c=r.join(n,/\.exe$/i.test(e)?r.basename(e):`${r.basename(e)}.exe`);if(t.existsSync(c))return{command:c,args:[]}}if(/\.cmd$/i.test(e))throw new Error("WorkBuddy 无法解析 CodeBuddy npm 启动入口,请将 ACA_WORKBUDDY_COMMAND 指向官方 bin/codebuddy 的 JS 包装文件或原生 exe。");return{command:e,args:[]}}function i(e){if(process.env.ACA_WORKBUDDY_NODE)return process.env.ACA_WORKBUDDY_NODE;const s=r.join(e,"node.exe");return process.platform==="win32"&&t.existsSync(s)?s:process.isSea||process.env.ACA_SINGLE_FILE_WORKER==="1"?"node":process.execPath}function u(e){let s;try{s=t.openSync(e,"r");const n=Buffer.alloc(128),o=t.readSync(s,n,0,n.length,0);return/^#![^\r\n]*\bnode\b/.test(n.subarray(0,o).toString("utf8"))}catch{return!1}finally{s!==void 0&&t.closeSync(s)}}function v(){const e=Number(process.env.ACA_WORKBUDDY_PROMPT_TIMEOUT_MS||process.env.ACA_ACP_PROMPT_TIMEOUT_MS);return Number.isSafeInteger(e)&&e>=3e4?e:720*60*1e3}export{_ as resolveWorkBuddyExecutable,E as resolveWorkBuddyLaunch,v as workBuddyTimeout};
|
|
@@ -0,0 +1 @@
|
|
|
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};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{record as o}from"./config.js";function u(a,s){return(s?.length?s:[{type:"text",text:a}]).map(e=>e.type==="image"?{type:"image",data:e.data,mimeType:e.mimeType}:{type:"text",text:e.text})}class h{active=!0;receipts=new Map;pending=new Set;channel;constructor(s,e,c){this.channel={threadId:e,turnId:c,receipt:async r=>this.receipts.get(r)??null,send:(r,d)=>{const p=this.receipts.get(r);if(p)return Promise.resolve(p);if(!this.active)return Promise.resolve({status:"failed",error:"执行轮次已结束"});this.receipts.set(r,{status:"sending"});const n=(async()=>{let i;try{const t=o(await s.rpc.request("session/steer",{sessionId:e,expectedRequestId:c,clientUserMessageId:r,contentBlocks:u("",d)},15e3));i=t.steered===!0?{status:"accepted"}:{status:"failed",error:`WorkBuddy 未接受补充:${String(t.reason||"当前状态不支持")}`}}catch(t){i={status:o(t).rpcResponse===!0?"failed":"uncertain",error:t instanceof Error?t.message:String(t)}}return this.receipts.set(r,i),i})();return this.pending.add(n),n.finally(()=>this.pending.delete(n)),n}}}async close(){this.active=!1,await Promise.allSettled(this.pending)}}export{h as WorkBuddySupplements,u as workBuddyPromptContent};
|
package/dist/commands/start.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{
|
|
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 +1 @@
|
|
|
1
|
-
import{managementActivity as e}from"./management/activity.js";import{activeTerminalService as
|
|
1
|
+
import{managementActivity as e}from"./management/activity.js";import{activeTerminalService as g}from"./terminal/service.js";import{AICA_VERSION as m}from"../version.js";function A(u,f=()=>{}){if(process.env.ACA_DESKTOP_CHILD!=="1"||!process.send)return;let o=!1,l=!1,s=process.env.ACA_DESKTOP_STAGED==="1";s&&(e.paused=!0);const p=()=>[...e.busy.filter(n=>n!=="poll"),...g?.hasActivity?["terminal"]:[]],d=async(n=!1)=>{const t=()=>n?p().filter(c=>c==="job"):p();if(o||e.paused&&!s||e.stopping)return{busy:["maintenance"],stopping:!1};const r=t();if(r.length)return{busy:r,stopping:!1};o=!0,e.paused=!0;const a=Date.now()+45e3;try{for(;e.busy.some(i=>!n||i==="poll"||i==="job");){const i=t();if(i.length||Date.now()>=a)return{busy:i.length?i:["poll"],stopping:!1};await new Promise(y=>setTimeout(y,50))}const c=t();return c.length?{busy:c,stopping:!1}:(e.stopping=!0,{busy:[],stopping:!0})}finally{o=!1,!e.stopping&&!l&&!s&&(e.paused=!1)}};process.on("message",n=>{const t=n;if(!(!t?.id||!["aca:status","aca:stop","aca:activate"].includes(t.type||""))){if(t.type==="aca:status"){process.send?.({type:"aca:reply",id:t.id,busy:p(),staged:s,stopping:e.stopping,upgradeStopPolicy:"jobs-only"});return}if(t.type==="aca:activate"){!l&&!o&&!e.stopping&&s&&(s=!1,e.paused=!1,f()),process.send?.({type:"aca:reply",id:t.id,active:!s&&!e.stopping});return}d(t.forUpdate===!0).then(r=>{const a=()=>{r.stopping&&u()};process.connected?process.send?.({type:"aca:reply",id:t.id,...r},a):a()})}}),process.once("disconnect",()=>{l=!0,e.paused=!0;const n=setInterval(()=>{o||e.busy.length||p().length||(clearInterval(n),e.stopping=!0,u())},250)}),process.send({type:"aca:ready",pid:process.pid,protocol:1,configSchema:1,version:m,staged:s})}export{A as startDesktopControl};
|
package/dist/core/heartbeat.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import{
|
|
2
|
-
`));if(!r)continue;const l=M(r.name,r.id,a)??a;o.push({modelId:a,displayName:
|
|
3
|
-
`));if(!r)continue;const l=M(r.name,r.id,a)??a;o.push({modelId:a,displayName:
|
|
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};
|
package/dist/core/job-worker.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{createJobSupplementReceiver as Z}from"./job-supplements.js";import{buildPromptWithAttachments as ee,buildPromptBlocks as te,materializeJobAttachments as ne,summarizeMaterializedAttachments as B}from"./job-attachments.js";import{managementActivity as T}from"./management/activity.js";import{managedClaim as oe}from"./management/claims.js";import{startJobArtifactMonitor as ie,mergeArtifactFiles as V}from"./artifacts/job-artifact-monitor.js";import ae from"node:fs";import{compactAgentContext as re,forkAgentSession as se,sendAgentPrompt as ce}from"../acp/agent.js";import{acaServerRequest as S}from"./aca-server-client.js";import{loadAcaConfig as le}from"./aca-config.js";import{prepareLocalWorktree as de}from"./worktree.js";import{expandWorkerRuntimePath as ue}from"./worker-runtime-paths.js";import{collectAffectedFiles as q,extractAffectedFilePathsFromAcpRaw as pe}from"./affected-files.js";import{resolveJobPermissionPolicy as me}from"./job-permission-policy.js";import{createAcpEventBatcher as fe}from"./acp-event-batcher.js";import{createToolLifecycleTrace as we}from"../acp/tool-lifecycle-trace.js";import{shouldBufferAcpEvent as he}from"./acp-event-upload-filter.js";import{userFacingAcpError as Ie}from"../acp/error-display.js";import{ensureCodexProviderRuntimeRevision as Se}from"./codex-provider-runtime-config.js";import{acpSessionInvalidationFromError as be}from"../acp/session-errors.js";const ye=1500,D=5e3,G=1e3,K=900,ve=1e4,_e=0,Ae=12e3;function Ce(e){return ue(e)}function Te(e){const t=e.project_metadata;return t&&typeof t=="object"&&!Array.isArray(t)&&t.kind==="chat"?!0:typeof e.project_root_path=="string"&&(e.project_root_path.startsWith("~/.aica/chats/")||e.project_root_path.startsWith("~/.aca/chats/")||/(^|\/)\.aica\/chats\//.test(e.project_root_path)||/(^|\/)\.aca\/chats\//.test(e.project_root_path))}function ut(e,t={}){let n=!1,r=null,i=null,l=0,u=0;const p=new Set,a=new Map,b=new Map,R=async()=>{if(n||T.paused||T.stopping||Date.now()<u)return;const h=ge();if(!X(p.size,h))return;const y=T.enter("poll");if(y)try{const m=t.loadConfig?.()??le();if(!m.token)return;for(;!n&&!T.paused&&!T.stopping&&X(p.size,h);){const v=t.claimNextJob?await t.claimNextJob(m,[...a.keys()]):await oe("job",()=>S("POST","/api/client/jobs/claim",{machineId:m.machineId,workspaceId:m.defaultWorkspaceId,activeSessionIds:[...a.keys()]}),{workspaceId:m.defaultWorkspaceId,activeSessionIds:[...a.keys()]});if(l=0,u=0,!v.item)break;const d=v.item;if(n){await(t.cancelClaimedJob?t.cancelClaimedJob(d,m):U(d.job_id,m.machineId,"cancelled")).catch(()=>{});break}const f=d.session_id,E=a.get(f),x=T.track("job");p.add(d.job_id);const k=new AbortController;let _;_=(async()=>{E&&(e.warn(`claimed job=${d.job_id} for already active session=${f}; waiting for the previous run to release its ACP runtime`),await Re(E,d,m,k)),!k.signal.aborted&&await(t.runJob??ke)(d,m,e,k)})().catch(M=>{T.uncertain.add("job-finalize"),e.warn(`job ${d.job_id} failed outside handler: ${M instanceof Error?M.message:String(M)}`)}).finally(()=>{x(),p.delete(d.job_id),a.get(f)===_&&a.delete(f),b.delete(d.job_id)}),a.set(f,_),b.set(d.job_id,{controller:k,promise:_})}}catch(m){if(!n){l+=1;const v=Be(l);u=Date.now()+v,e.warn(`job worker failed, retry in ${v}ms: ${m instanceof Error?m.message:String(m)}`)}}finally{y()}},g=()=>{if(n||r)return;const h=R();r=h,h.finally(()=>{r===h&&(r=null)})};g();const c=setInterval(()=>{g()},t.claimIntervalMs??ye);return{timer:c,stop:()=>i||(i=(async()=>{if(n)return;n=!0,clearInterval(c);for(const y of b.values())y.controller.abort();await r?.catch(()=>{});for(const y of b.values())y.controller.abort();const h=[...b.values()].map(y=>y.promise);h.length!==0&&await Promise.race([Promise.allSettled(h).then(()=>{}),z(Ae)])})(),i)}}async function Re(e,t,n,r){let i=!1;const u=setInterval(()=>{i||r.signal.aborted||(i=!0,Promise.allSettled([S("POST",`/api/client/jobs/${encodeURIComponent(t.job_id)}/heartbeat`,{machineId:n.machineId}),H(t.job_id,r)]).finally(()=>{i=!1}))},D);try{await Promise.race([e,Pe(r.signal)])}finally{clearInterval(u)}}function Pe(e){return e.aborted?Promise.resolve():new Promise(t=>e.addEventListener("abort",()=>t(),{once:!0}))}function ge(){const e=process.env.ACA_MAX_CONCURRENT_JOBS||process.env.ACA_MAX_CONCURRENT_PROJECT_JOBS||"",t=Number.parseInt(e,10);return!e.trim()||!Number.isFinite(t)||t<0?_e:t===0?0:Math.max(1,Math.min(t,8))}function X(e,t){return t===0||e<t}async function ke(e,t,n,r){const i=e.job_id,l=e.session_id,u=e.workspace_id,p=e.project_id??null,a=e.input??{},b=String(a.control?.type||"").toLowerCase();if(!b&&!a.message?.trim()&&(a.attachments??[]).length===0&&!a.sessionReferences?.length){await U(i,t.machineId,"job input missing message");return}const R=t.projects.find(s=>s.projectId===p),g=Ce(R?.rootPath||e.project_root_path||"");if(!g){await U(i,t.machineId,"job project root is unknown");return}Te(e)&&ae.mkdirSync(g,{recursive:!0}),n.info(`claimed job=${i} session=${l} project=${R?.name??p??"unknown"}`);let c=!1,h=!1;const y=setInterval(()=>{c||(c=!0,S("POST",`/api/client/jobs/${encodeURIComponent(i)}/heartbeat`,{machineId:t.machineId}).catch(()=>{}).finally(()=>{c=!1})),h||(h=!0,H(i,r).catch(()=>{}).finally(()=>{h=!1}))},D);let m=0;const v=we(s=>n.info(s)),d=fe(i,t.machineId,{warn:s=>n.warn(s),trace:v}),f=Ne(i,t.machineId),E=new Set;let x=null,k,_;const M=async()=>k?.finish().catch(s=>(T.uncertain.add("job-artifacts"),n.warn(`artifact collection failed job=${i}: ${String(s)}`),[]))??[];try{await f.sendNow({phase:"initializing",label:"准备工作区",detail:e.worktree_id?"worktree":"project root"}).catch(()=>{});const s=de({projectId:p,sessionId:l,projectRoot:g,worktree:e});x=s,await Je(i,t.machineId,s);const A=Ee(a.acpRun);if(Me(A.agentType)&&await Se(e.codex_provider_revision),b==="compact"){if(!e.acp_session_id)throw new Error("Current session does not have an ACP context to compact");const o=await re({agentType:$(A.agentType,"codex"),cliType:$(A.cliType,"builtin"),acpSessionId:e.acp_session_id,cwd:s.cwd,model:a.model,mode:a.mode,configOptionValues:a.configOptionValues,timeoutMs:a.timeoutMs,allowRollover:a.control?.allowRollover===!0,signal:r.signal,onStatus:I=>{f.report({phase:I.phase,label:I.label,detail:I.detail,updateType:I.updateType,raw:I})}});await f.flush(),await S("POST",`/api/client/jobs/${encodeURIComponent(i)}/complete`,{machineId:t.machineId,acpSessionId:o.acpSessionId,suppressTurn:!0,result:{ok:!0,control:{type:"compact",allowRollover:a.control?.allowRollover===!0},acpSessionId:o.acpSessionId,durationMs:o.durationMs,usage:o.contextUsage,maintenance:o.maintenance,worktree:N(s)}}),n.info(`completed context compact job=${i}`);return}if(b==="fork"){const o=String(a.control?.sourceAcpSessionId||"").trim();if(!o)throw new Error("Source session does not have a Codex ACP context to fork");await f.sendNow({phase:"forking",label:"正在分叉 Codex 会话",detail:a.control?.sourceSessionId?`源会话 ${a.control.sourceSessionId}`:void 0}).catch(()=>{});const I=await se({agentType:$(A.agentType,"codex"),cliType:$(A.cliType,"builtin"),sourceProviderSessionId:o,cwd:s.cwd,model:a.model,mode:a.mode,configOptionValues:a.configOptionValues,timeoutMs:a.timeoutMs,signal:r.signal,onStatus:j=>{f.report({phase:j.phase,label:j.label,detail:j.detail,updateType:j.updateType,raw:j})}});await f.flush(),await S("POST",`/api/client/jobs/${encodeURIComponent(i)}/complete`,{machineId:t.machineId,acpSessionId:I.acpSessionId,suppressTurn:!0,result:{ok:!0,control:{type:"fork",sourceSessionId:a.control?.sourceSessionId||null},acpSessionId:I.acpSessionId,worktree:N(s)}}),n.info(`completed Codex session fork job=${i}`);return}const C=await ne({cwd:s.cwd,sessionId:l,jobId:i,attachments:a.attachments??[]});C.length>0&&(n.info(`materialized ${C.length} attachment(s) for job=${i}`),await f.sendNow({phase:"initializing",label:"附件已写入本机",detail:C.map(o=>o.relativePath).join(", "),raw:{attachments:B(C)}}).catch(()=>{}));const O=ee(a,C),P=te(O,C);k=await ie(s.cwd,o=>n.warn(o)).catch(o=>{n.warn(`artifact monitor unavailable job=${i}: ${String(o)}`)}),a.automation||(_=Z({jobId:i,sessionId:l,cwd:s.cwd,logger:n,signal:r.signal}));const w=await ce({agentType:$(A.agentType,"codex"),cliType:$(A.cliType,"builtin"),acpSessionId:e.acp_session_id??null,requireSessionResume:je(e.session_raw),prompt:O,promptBlocks:P,cwd:s.cwd,model:a.model,mode:a.mode,configOptionValues:a.configOptionValues,canExecuteTools:a.canExecuteTools,timeoutMs:a.timeoutMs,contextWindow:$e(A.contextWindow),signal:r.signal,onSupplementReady:o=>_?.setChannel(o),onStatus:o=>{f.report({phase:o.phase,label:o.label,detail:o.detail,updateType:o.updateType,raw:o})},onUpdate:o=>{m+=1,(o.type==="tool_call"||o.type==="tool_call_update"&&["completed","failed","cancelled"].includes(o.status||""))&&v("worker_event",{jobId:i,uploadRunId:d.uploadRunId,toolCallId:o.toolCallId,status:o.status,sequence:m});for(const j of pe(o.raw))E.add(j);const I={turnId:e.user_turn_id??null,eventType:o.type,label:o.label,text:o.text,status:o.status,toolCallId:o.toolCallId,raw:Ue(o.raw,m)};he(I)&&d.push(I)},onPermissionRequest:o=>{const I=me(a,o);return I?Promise.resolve(I):Fe(i,t.machineId,o,r.signal)},onUserInputRequest:o=>ze(i,t.machineId,o,r.signal),onProviderSessionMigration:async o=>{const I=await S("POST",`/api/client/jobs/${encodeURIComponent(i)}/acp-session`,{machineId:t.machineId,previousAcpSessionId:o.previousProviderSessionId,acpSessionId:o.providerSessionId,reason:o.reason,migratedAtMs:o.migratedAtMs});e.acp_session_id=o.providerSessionId,n.info(`migrated ACP session for job=${i} ${o.previousProviderSessionId} -> ${o.providerSessionId}`),await d.flush().catch(()=>{}),await f.sendNow({phase:"resuming",label:"Codex 会话已迁移",detail:o.providerSessionId,updateType:"aca.performance.provider_session_migrated",raw:{migration:o,server:{migrated:I.migrated===!0}}}).catch(()=>{})}}),Y=await M(),F=await d.finish();F.complete||T.uncertain.add("job-event-upload"),await f.flush(),await _?.stop();const W=V(q({cwd:s.cwd,evidencePaths:E}),Y),J=xe(w.promptResponse);if(!(Oe(J)||!!w.content)){await U(i,t.machineId,`${w.agentType} stopped: ${J??"unknown"}`,{acpSessionId:w.acpSessionId,stopReason:J,eventUpload:F,files:W,exitCode:w.exitCode,stderr:Ve(w.stderr,4e3),worktree:N(s)});return}await S("POST",`/api/client/jobs/${encodeURIComponent(i)}/complete`,{machineId:t.machineId,acpSessionId:w.acpSessionId,assistantText:w.content,result:{ok:!0,eventUpload:F,acpSessionId:w.acpSessionId,stopReason:J,durationMs:w.durationMs,acpRun:a.acpRun??null,effectiveConfig:w.effectiveConfig??null,performance:w.performance??null,usage:w.contextUsage??null,providerDiagnostics:w.providerDiagnostics??null,updateCount:w.updateCount,statusTimeline:w.statusTimeline,worktree:N(s),attachments:B(C),files:W}}),n.info(`completed job=${i}`)}catch(s){if(await _?.stop(),We(r.signal)){d.discard(),f.discard(),n.warn(`stopped local job=${i} because ACA Server already finalized it`);return}const A=await M(),C=await d.finish();C.complete||T.uncertain.add("job-event-upload"),await f.flush();const O=r.signal.aborted?"cancelled":s instanceof Error?s.message:String(s),P=be(s),w=V(x?q({cwd:x.cwd,evidencePaths:E}):[],A);await U(i,t.machineId,Ie(O),{rawError:O,eventUpload:C,files:w,providerDiagnostics:L(s,"providerDiagnostics"),statusTimeline:L(s,"statusTimeline"),performance:L(s,"performance"),...P?{acpSessionInvalidated:!0,invalidatedAcpSessionId:P.providerSessionId,failure:{category:P.category,actions:P.actions,details:P.details}}:{}},P?{invalidateAcpSession:!0,invalidatedAcpSessionId:P.providerSessionId}:void 0)}finally{await _?.stop(),k?.dispose(),d.discard(),clearInterval(y),await f.flush()}}function je(e){if(!e||typeof e!="object"||Array.isArray(e))return!1;const t=e.importedProviderSession;return!!(t&&typeof t=="object"&&!Array.isArray(t)&&t.strictResume===!0)}function Ee(e){return!e||typeof e!="object"||Array.isArray(e)?{}:e}function Me(e){const t=String(e||"codex").toLowerCase().replace(/[^a-z0-9]+/g,"");return t==="codex"||t==="codexacp"||t==="codexstandard"}function $e(e){return typeof e=="number"&&Number.isFinite(e)&&e>0?e:void 0}function $(...e){for(const t of e)if(typeof t=="string"&&t.trim())return t.trim();return""}function xe(e){if(!e||typeof e!="object"||Array.isArray(e))return;const t=e;if(typeof t.stopReason=="string")return t.stopReason;if(typeof t.reason=="string")return t.reason;const n=t.part;if(n&&typeof n=="object"&&!Array.isArray(n)&&typeof n.reason=="string")return n.reason}function Oe(e){return e?["end_turn","stop","completed","complete"].includes(e):!1}function Ne(e,t){let n=null,r=0,i="",l=null,u=Promise.resolve(),p=!1;const a=c=>JSON.stringify({phase:c.phase??"",label:c.label??"",detail:c.detail??"",updateType:c.updateType??""}),b=async c=>{await S("POST",`/api/client/jobs/${encodeURIComponent(e)}/presence`,{machineId:t,phase:c.phase,label:c.label,detail:c.detail,updateType:c.updateType,raw:c.raw}),r=Date.now(),i=a(c)},R=()=>{if(l&&(clearTimeout(l),l=null),p)return n=null,u;if(!n)return u;const c=n;return n=null,u=u.then(()=>b(c)).catch(()=>{}),u},g=c=>{l||(l=setTimeout(()=>{R()},c),l.unref())};return{report(c){if(p)return;const h=Date.now(),m=a(c)!==i,v=h-r>=ve;if(!m&&!v)return;n=c;const d=h-r;if(d>=K||r===0){R();return}g(K-d)},sendNow(c){return p?u:(n=c,R())},flush:R,discard(){p=!0,n=null,l&&clearTimeout(l),l=null}}}function Ue(e,t){return e&&typeof e=="object"&&!Array.isArray(e)?{...e,acaSequence:t}:{value:e,acaSequence:t}}async function Je(e,t,n){!n.usedWorktree||!n.worktreeId||await S("POST",`/api/client/jobs/${encodeURIComponent(e)}/worktree`,{machineId:t,baseCommit:n.baseCommit??null,worktreePath:n.cwd,status:n.status??"active",raw:n.raw??N(n)})}function N(e){return{usedWorktree:e.usedWorktree,worktreeId:e.worktreeId??null,cwd:e.cwd,status:e.status??null,baseCommit:e.baseCommit??null,branchName:e.branchName??null,baseBranch:e.baseBranch??null}}async function Fe(e,t,n,r){const l=(await S("POST",`/api/client/jobs/${encodeURIComponent(e)}/permissions`,{machineId:t,request:n})).item.permission_id;for(;!r.aborted;){await z(G);const u=await S("GET",`/api/client/permissions/${encodeURIComponent(l)}`);if(u.item.status==="resolved"&&u.item.response){const p=u.item.response.outcome;return p.outcome==="selected"?{outcome:p}:{outcome:{outcome:"cancelled"}}}}return{outcome:{outcome:"cancelled"}}}async function ze(e,t,n,r){const l=(await S("POST",`/api/client/jobs/${encodeURIComponent(e)}/permissions`,{machineId:t,request:{kind:"elicitation",...n}})).item.permission_id;for(;!r.aborted;){await z(G);const u=await S("GET",`/api/client/permissions/${encodeURIComponent(l)}`);if(u.item.status==="resolved"&&u.item.response){const p=u.item.response.outcome;return p.outcome==="submitted"?{outcome:p}:{outcome:{outcome:"cancelled"}}}}return{outcome:{outcome:"cancelled"}}}async function H(e,t){if(t.signal.aborted)return;const n=await S("GET",`/api/client/jobs/${encodeURIComponent(e)}`);if(Number(n.item.cancel_requested??0)===1){t.abort();return}const r=Le(n.item);r&&t.abort(new Q(e,r))}function Le(e){const t=String(e.status||"").toLowerCase();return t&&!["claimed","running"].includes(t)?t:null}class Q extends Error{constructor(t,n){super(`ACA Server already finalized job=${t} status=${n}`),this.name="ServerFinalizedJobError"}}function We(e){return e.aborted&&e.reason instanceof Q}async function U(e,t,n,r,i){await S("POST",`/api/client/jobs/${encodeURIComponent(e)}/fail`,{machineId:t,error:n,result:r,...i?.invalidateAcpSession?{invalidateAcpSession:!0,invalidatedAcpSessionId:i.invalidatedAcpSessionId}:{}})}function z(e){return new Promise(t=>setTimeout(t,e))}function Be(e){return Math.min(1e4,500*2**Math.min(5,Math.max(0,e-1)))+Math.floor(Math.random()*250)}function Ve(e,t){return e.length<=t?e:`${e.slice(0,t)}...`}function L(e,t){return!e||typeof e!="object"||Array.isArray(e)?null:e[t]??null}export{Le as serverFinalizedJobStatus,ut as startJobWorkerLoop};
|
|
1
|
+
import{createJobInteractions as Y}from"../interactions/job-interactions.js";import{createJobSupplementReceiver as Z}from"./job-supplements.js";import{buildPromptWithAttachments as ee,buildPromptBlocks as te,materializeJobAttachments as ne,summarizeMaterializedAttachments as L}from"./job-attachments.js";import{managementActivity as g}from"./management/activity.js";import{managedClaim as oe}from"./management/claims.js";import{startJobArtifactMonitor as ie,mergeArtifactFiles as D}from"./artifacts/job-artifact-monitor.js";import ae from"node:fs";import{compactAgentContext as re,forkAgentSession as se,sendAgentPrompt as ce}from"../acp/agent.js";import{acaServerRequest as A}from"./aca-server-client.js";import{loadAcaConfig as le}from"./aca-config.js";import{prepareLocalWorktree as de}from"./worktree.js";import{expandWorkerRuntimePath as ue}from"./worker-runtime-paths.js";import{collectAffectedFiles as V,extractAffectedFilePathsFromAcpRaw as pe}from"./affected-files.js";import{resolveJobPermissionPolicy as me}from"./job-permission-policy.js";import{createAcpEventBatcher as fe}from"./acp-event-batcher.js";import{createToolLifecycleTrace as he}from"../acp/tool-lifecycle-trace.js";import{shouldBufferAcpEvent as we}from"./acp-event-upload-filter.js";import{userFacingAcpError as Ie}from"../acp/error-display.js";import{ensureCodexProviderRuntimeRevision as Se}from"./codex-provider-runtime-config.js";import{acpSessionInvalidationFromError as be}from"../acp/session-errors.js";const ye=1500,q=5e3,K=900,ve=1e4,Ae=0,_e=12e3;function Ce(e){return ue(e)}function Te(e){const t=e.project_metadata;return t&&typeof t=="object"&&!Array.isArray(t)&&t.kind==="chat"?!0:typeof e.project_root_path=="string"&&(e.project_root_path.startsWith("~/.aica/chats/")||e.project_root_path.startsWith("~/.aca/chats/")||/(^|\/)\.aica\/chats\//.test(e.project_root_path)||/(^|\/)\.aca\/chats\//.test(e.project_root_path))}function ut(e,t={}){let n=!1,s=null,i=null,l=0,w=0;const I=new Set,a=new Map,y=new Map,_=async()=>{if(n||g.paused||g.stopping||Date.now()<w)return;const m=Pe();if(!X(I.size,m))return;const S=g.enter("poll");if(S)try{const u=t.loadConfig?.()??le();if(!u.token)return;for(;!n&&!g.paused&&!g.stopping&&X(I.size,m);){const v=t.claimNextJob?await t.claimNextJob(u,[...a.keys()]):await oe("job",()=>A("POST","/api/client/jobs/claim",{machineId:u.machineId,workspaceId:u.defaultWorkspaceId,activeSessionIds:[...a.keys()]}),{workspaceId:u.defaultWorkspaceId,activeSessionIds:[...a.keys()]});if(l=0,w=0,!v.item)break;const p=v.item;if(n){await(t.cancelClaimedJob?t.cancelClaimedJob(p,u):J(p.job_id,u.machineId,"cancelled")).catch(()=>{});break}const b=p.session_id,f=a.get(b),$=g.track("job");I.add(p.job_id);const k=new AbortController;let j;j=(async()=>{f&&(e.warn(`claimed job=${p.job_id} for already active session=${b}; waiting for the previous run to release its ACP runtime`),await ge(f,p,u,k)),!k.signal.aborted&&await(t.runJob??ke)(p,u,e,k)})().catch(R=>{g.uncertain.add("job-finalize"),e.warn(`job ${p.job_id} failed outside handler: ${R instanceof Error?R.message:String(R)}`)}).finally(()=>{$(),I.delete(p.job_id),a.get(b)===j&&a.delete(b),y.delete(p.job_id)}),a.set(b,j),y.set(p.job_id,{controller:k,promise:j})}}catch(u){if(!n){l+=1;const v=Be(l);w=Date.now()+v,e.warn(`job worker failed, retry in ${v}ms: ${u instanceof Error?u.message:String(u)}`)}}finally{S()}},M=()=>{if(n||s)return;const m=_();s=m,m.finally(()=>{s===m&&(s=null)})};M();const c=setInterval(()=>{M()},t.claimIntervalMs??ye);return{timer:c,stop:()=>i||(i=(async()=>{if(n)return;n=!0,clearInterval(c);for(const S of y.values())S.controller.abort();await s?.catch(()=>{});for(const S of y.values())S.controller.abort();const m=[...y.values()].map(S=>S.promise);m.length!==0&&await Promise.race([Promise.allSettled(m).then(()=>{}),We(_e)])})(),i)}}async function ge(e,t,n,s){let i=!1;const w=setInterval(()=>{i||s.signal.aborted||(i=!0,Promise.allSettled([A("POST",`/api/client/jobs/${encodeURIComponent(t.job_id)}/heartbeat`,{machineId:n.machineId}),H(t.job_id,s)]).finally(()=>{i=!1}))},q);try{await Promise.race([e,Re(s.signal)])}finally{clearInterval(w)}}function Re(e){return e.aborted?Promise.resolve():new Promise(t=>e.addEventListener("abort",()=>t(),{once:!0}))}function Pe(){const e=process.env.ACA_MAX_CONCURRENT_JOBS||process.env.ACA_MAX_CONCURRENT_PROJECT_JOBS||"",t=Number.parseInt(e,10);return!e.trim()||!Number.isFinite(t)||t<0?Ae:t===0?0:Math.max(1,Math.min(t,8))}function X(e,t){return t===0||e<t}async function ke(e,t,n,s){const i=e.job_id,l=e.session_id,w=e.workspace_id,I=e.project_id??null,a=e.input??{},y=Y(i,t.machineId,String(a.acpRun?.agentType||"codex"),s.signal),_=String(a.control?.type||"").toLowerCase();if(!_&&!a.message?.trim()&&(a.attachments??[]).length===0&&!a.sessionReferences?.length){await J(i,t.machineId,"job input missing message");return}const M=t.projects.find(r=>r.projectId===I),c=Ce(M?.rootPath||e.project_root_path||"");if(!c){await J(i,t.machineId,"job project root is unknown");return}Te(e)&&ae.mkdirSync(c,{recursive:!0}),n.info(`claimed job=${i} session=${l} project=${M?.name??I??"unknown"}`);let m=!1,S=!1;const u=setInterval(()=>{m||(m=!0,A("POST",`/api/client/jobs/${encodeURIComponent(i)}/heartbeat`,{machineId:t.machineId}).catch(()=>{}).finally(()=>{m=!1})),S||(S=!0,H(i,s).catch(()=>{}).finally(()=>{S=!1}))},q);let v=0;const p=he(r=>n.info(r)),b=fe(i,t.machineId,{warn:r=>n.warn(r),trace:p}),f=Ne(i,t.machineId),$=new Set;let k=null,j,R;const W=async()=>j?.finish().catch(r=>(g.uncertain.add("job-artifacts"),n.warn(`artifact collection failed job=${i}: ${String(r)}`),[]))??[];try{await f.sendNow({phase:"initializing",label:"准备工作区",detail:e.worktree_id?"worktree":"project root"}).catch(()=>{});const r=de({projectId:I,sessionId:l,projectRoot:c,worktree:e});k=r,await Fe(i,t.machineId,r);const C=Me(a.acpRun);if(await y.initialize(),xe(C.agentType)&&await Se(e.codex_provider_revision),_==="compact"){if(!e.acp_session_id)throw new Error("Current session does not have an ACP context to compact");const o=await re({agentType:E(C.agentType,"codex"),cliType:E(C.cliType,"builtin"),acpSessionId:e.acp_session_id,cwd:r.cwd,model:a.model,mode:a.mode,configOptionValues:a.configOptionValues,timeoutMs:a.timeoutMs,allowRollover:a.control?.allowRollover===!0,signal:s.signal,onStatus:h=>{f.report({phase:h.phase,label:h.label,detail:h.detail,updateType:h.updateType,raw:h})}});await f.flush(),await A("POST",`/api/client/jobs/${encodeURIComponent(i)}/complete`,{machineId:t.machineId,acpSessionId:o.acpSessionId,suppressTurn:!0,result:{ok:!0,control:{type:"compact",allowRollover:a.control?.allowRollover===!0},acpSessionId:o.acpSessionId,durationMs:o.durationMs,usage:o.contextUsage,maintenance:o.maintenance,worktree:N(r)}}),n.info(`completed context compact job=${i}`);return}if(_==="fork"){const o=String(a.control?.sourceAcpSessionId||"").trim();if(!o)throw new Error("Source session does not have a Codex ACP context to fork");await f.sendNow({phase:"forking",label:"正在分叉 Codex 会话",detail:a.control?.sourceSessionId?`源会话 ${a.control.sourceSessionId}`:void 0}).catch(()=>{});const h=await se({agentType:E(C.agentType,"codex"),cliType:E(C.cliType,"builtin"),sourceProviderSessionId:o,cwd:r.cwd,model:a.model,mode:a.mode,configOptionValues:a.configOptionValues,timeoutMs:a.timeoutMs,signal:s.signal,onStatus:x=>{f.report({phase:x.phase,label:x.label,detail:x.detail,updateType:x.updateType,raw:x})}});await f.flush(),await A("POST",`/api/client/jobs/${encodeURIComponent(i)}/complete`,{machineId:t.machineId,acpSessionId:h.acpSessionId,suppressTurn:!0,result:{ok:!0,control:{type:"fork",sourceSessionId:a.control?.sourceSessionId||null},acpSessionId:h.acpSessionId,worktree:N(r)}}),n.info(`completed Codex session fork job=${i}`);return}const T=await ne({cwd:r.cwd,sessionId:l,jobId:i,attachments:a.attachments??[]});T.length>0&&(n.info(`materialized ${T.length} attachment(s) for job=${i}`),await f.sendNow({phase:"initializing",label:"附件已写入本机",detail:T.map(o=>o.relativePath).join(", "),raw:{attachments:L(T)}}).catch(()=>{}));const O=ee(a,T),P=te(O,T);j=await ie(r.cwd,o=>n.warn(o)).catch(o=>{n.warn(`artifact monitor unavailable job=${i}: ${String(o)}`)}),a.automation||(R=Z({jobId:i,sessionId:l,cwd:r.cwd,logger:n,signal:s.signal}));const d=await ce({agentType:E(C.agentType,"codex"),cliType:E(C.cliType,"builtin"),acpSessionId:e.acp_session_id??null,requireSessionResume:je(e.session_raw),prompt:O,promptBlocks:P,cwd:r.cwd,model:a.model,mode:a.mode,configOptionValues:a.configOptionValues,canExecuteTools:a.canExecuteTools,timeoutMs:a.timeoutMs,contextWindow:Ee(C.contextWindow),signal:s.signal,onSupplementReady:o=>R?.setChannel(o),onStatus:o=>{f.report({phase:o.phase,label:o.label,detail:o.detail,updateType:o.updateType,raw:o})},onUpdate:o=>{v+=1,(o.type==="tool_call"||o.type==="tool_call_update"&&["completed","failed","cancelled"].includes(o.status||""))&&p("worker_event",{jobId:i,uploadRunId:b.uploadRunId,toolCallId:o.toolCallId,status:o.status,sequence:v});for(const x of pe(o.raw))$.add(x);const h={turnId:e.user_turn_id??null,eventType:o.type,label:o.label,text:o.text,status:o.status,toolCallId:o.toolCallId,raw:Je(o.raw,v)};we(h)&&b.push(h)},onPermissionRequest:o=>{const h=me(a,o);return h?Promise.resolve(h):y.permission(o)},onUserInputRequest:o=>y.input(o),onProviderSessionMigration:async o=>{const h=await A("POST",`/api/client/jobs/${encodeURIComponent(i)}/acp-session`,{machineId:t.machineId,previousAcpSessionId:o.previousProviderSessionId,acpSessionId:o.providerSessionId,reason:o.reason,migratedAtMs:o.migratedAtMs});e.acp_session_id=o.providerSessionId,n.info(`migrated ACP session for job=${i} ${o.previousProviderSessionId} -> ${o.providerSessionId}`),await b.flush().catch(()=>{}),await f.sendNow({phase:"resuming",label:"Codex 会话已迁移",detail:o.providerSessionId,updateType:"aca.performance.provider_session_migrated",raw:{migration:o,server:{migrated:h.migrated===!0}}}).catch(()=>{})}}),Q=await W(),U=await b.finish();U.complete||g.uncertain.add("job-event-upload"),await f.flush(),await R?.stop();const B=D(V({cwd:r.cwd,evidencePaths:$}),Q),F=$e(d.promptResponse);if(!(Oe(F)||!!d.content)){await J(i,t.machineId,`${d.agentType} stopped: ${F??"unknown"}`,{acpSessionId:d.acpSessionId,stopReason:F,eventUpload:U,files:B,exitCode:d.exitCode,stderr:Le(d.stderr,4e3),worktree:N(r)});return}await A("POST",`/api/client/jobs/${encodeURIComponent(i)}/complete`,{machineId:t.machineId,acpSessionId:d.acpSessionId,assistantText:d.content,result:{ok:!0,eventUpload:U,acpSessionId:d.acpSessionId,stopReason:F,durationMs:d.durationMs,acpRun:a.acpRun??null,effectiveConfig:d.effectiveConfig??null,performance:d.performance??null,usage:d.contextUsage??null,providerDiagnostics:d.providerDiagnostics??null,updateCount:d.updateCount,statusTimeline:d.statusTimeline,worktree:N(r),attachments:L(T),files:B}}),n.info(`completed job=${i}`)}catch(r){if(await R?.stop(),ze(s.signal)){b.discard(),f.discard(),n.warn(`stopped local job=${i} because ACA Server already finalized it`);return}const C=await W(),T=await b.finish();T.complete||g.uncertain.add("job-event-upload"),await f.flush();const O=s.signal.aborted?"cancelled":r instanceof Error?r.message:String(r),P=be(r),d=D(k?V({cwd:k.cwd,evidencePaths:$}):[],C);await J(i,t.machineId,Ie(O),{rawError:O,eventUpload:T,files:d,providerDiagnostics:z(r,"providerDiagnostics"),statusTimeline:z(r,"statusTimeline"),performance:z(r,"performance"),...P?{acpSessionInvalidated:!0,invalidatedAcpSessionId:P.providerSessionId,failure:{category:P.category,actions:P.actions,details:P.details}}:{}},P?{invalidateAcpSession:!0,invalidatedAcpSessionId:P.providerSessionId}:void 0)}finally{await y.close(),await R?.stop(),j?.dispose(),b.discard(),clearInterval(u),await f.flush()}}function je(e){if(!e||typeof e!="object"||Array.isArray(e))return!1;const t=e.importedProviderSession;return!!(t&&typeof t=="object"&&!Array.isArray(t)&&t.strictResume===!0)}function Me(e){return!e||typeof e!="object"||Array.isArray(e)?{}:e}function xe(e){const t=String(e||"codex").toLowerCase().replace(/[^a-z0-9]+/g,"");return t==="codex"||t==="codexacp"||t==="codexstandard"}function Ee(e){return typeof e=="number"&&Number.isFinite(e)&&e>0?e:void 0}function E(...e){for(const t of e)if(typeof t=="string"&&t.trim())return t.trim();return""}function $e(e){if(!e||typeof e!="object"||Array.isArray(e))return;const t=e;if(typeof t.stopReason=="string")return t.stopReason;if(typeof t.reason=="string")return t.reason;const n=t.part;if(n&&typeof n=="object"&&!Array.isArray(n)&&typeof n.reason=="string")return n.reason}function Oe(e){return e?["end_turn","stop","completed","complete"].includes(e):!1}function Ne(e,t){let n=null,s=0,i="",l=null,w=Promise.resolve(),I=!1;const a=c=>JSON.stringify({phase:c.phase??"",label:c.label??"",detail:c.detail??"",updateType:c.updateType??""}),y=async c=>{await A("POST",`/api/client/jobs/${encodeURIComponent(e)}/presence`,{machineId:t,phase:c.phase,label:c.label,detail:c.detail,updateType:c.updateType,raw:c.raw}),s=Date.now(),i=a(c)},_=()=>{if(l&&(clearTimeout(l),l=null),I)return n=null,w;if(!n)return w;const c=n;return n=null,w=w.then(()=>y(c)).catch(()=>{}),w},M=c=>{l||(l=setTimeout(()=>{_()},c),l.unref())};return{report(c){if(I)return;const m=Date.now(),u=a(c)!==i,v=m-s>=ve;if(!u&&!v)return;n=c;const p=m-s;if(p>=K||s===0){_();return}M(K-p)},sendNow(c){return I?w:(n=c,_())},flush:_,discard(){I=!0,n=null,l&&clearTimeout(l),l=null}}}function Je(e,t){return e&&typeof e=="object"&&!Array.isArray(e)?{...e,acaSequence:t}:{value:e,acaSequence:t}}async function Fe(e,t,n){!n.usedWorktree||!n.worktreeId||await A("POST",`/api/client/jobs/${encodeURIComponent(e)}/worktree`,{machineId:t,baseCommit:n.baseCommit??null,worktreePath:n.cwd,status:n.status??"active",raw:n.raw??N(n)})}function N(e){return{usedWorktree:e.usedWorktree,worktreeId:e.worktreeId??null,cwd:e.cwd,status:e.status??null,baseCommit:e.baseCommit??null,branchName:e.branchName??null,baseBranch:e.baseBranch??null}}async function H(e,t){if(t.signal.aborted)return;const n=await A("GET",`/api/client/jobs/${encodeURIComponent(e)}`);if(Number(n.item.cancel_requested??0)===1){t.abort();return}const s=Ue(n.item);s&&t.abort(new G(e,s))}function Ue(e){const t=String(e.status||"").toLowerCase();return t&&!["claimed","running"].includes(t)?t:null}class G extends Error{constructor(t,n){super(`ACA Server already finalized job=${t} status=${n}`),this.name="ServerFinalizedJobError"}}function ze(e){return e.aborted&&e.reason instanceof G}async function J(e,t,n,s,i){await A("POST",`/api/client/jobs/${encodeURIComponent(e)}/fail`,{machineId:t,error:n,result:s,...i?.invalidateAcpSession?{invalidateAcpSession:!0,invalidatedAcpSessionId:i.invalidatedAcpSessionId}:{}})}function We(e){return new Promise(t=>setTimeout(t,e))}function Be(e){return Math.min(1e4,500*2**Math.min(5,Math.max(0,e-1)))+Math.floor(Math.random()*250)}function Le(e,t){return e.length<=t?e:`${e.slice(0,t)}...`}function z(e,t){return!e||typeof e!="object"||Array.isArray(e)?null:e[t]??null}export{Ue as serverFinalizedJobStatus,ut as startJobWorkerLoop};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import{executeExternalCodexOperation as q}from"./external-codex-worker.js";import{managementActivity as T}from"./management/activity.js";import{managedClaim as tt}from"./management/claims.js";import{activeTerminalService as et}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 rt,acaServerRequest as E}from"./aca-server-client.js";import{loadAcaConfig as L,upsertLocalProject as ot}from"./aca-config.js";import{startLongPollWorker as it}from"./long-poll-worker.js";import{expandWorkerRuntimePath as nt}from"./worker-runtime-paths.js";import{syncCodexProviderRuntimeConfig as st}from"./codex-provider-runtime-config.js";import{defaultAcpRuntimePool as at}from"../acp/client/acp-runtime-pool.js";import{execPowerShell as M}from"./machine-filesystem/powershell.js";const O=25e3,ct=O+1e4,x=300,lt=3e4;let b=null;function ce(t){return it({run:async()=>{const e=L();if(!e.token)throw new Error("Worker 尚未配置认证令牌");const r=await tt("machine",()=>E("GET",`/api/client/machine-operations/claim?machineId=${encodeURIComponent(e.machineId)}&waitMs=${O}`,void 0,{timeoutMs:ct}));if(!r.item)return;const o=T.track("machine");try{const i=await ut(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 ut(t){if(t.operation.startsWith("external_codex_"))return q(t.operation,t.payload);if(t.operation==="sync_codex_provider_config"){const e=await st(),r=await at.retireProviderRuntimes("codex-acp");return{configured:!!e,revision:e?.revision??null,retiredRuntimes:r}}if(t.operation==="filesystem_roots")return Nt();if(t.operation==="list_directory")return Tt({directoryPath:String(t.payload.path||""),includeHidden:t.payload.includeHidden===!0});if(t.operation==="register_project")return Ht(t.payload);if(t.operation==="list_project_directory")return mt(t.payload);if(t.operation==="upload_project_file")return ht(t.payload);if(t.operation==="mutate_project_files")return wt(t.payload);if(t.operation==="get_project_git_status")return B(t.payload);if(t.operation==="get_project_git_diff")return Et(t.payload);if(t.operation==="get_project_git_log")return xt(t.payload);if(t.operation==="get_project_git_commit")return bt(t.payload);if(t.operation==="get_worktree_status")return k(t.payload);if(t.operation==="commit_worktree")return dt(t.payload);if(t.operation==="cleanup_worktree")return ft(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:kt(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)?At(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 dt(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 ft(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(et?.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 mt(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:pt(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 pt(t){try{const e=l.lstatSync(t);return e.isSymbolicLink()?null:e.mtimeMs}catch{return null}}async function ht(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 rt(`/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=St(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 wt(t){const e=p(String(t.rootPath||"")),r=t.action&&typeof t.action=="object"?t.action:{};if(r.type==="delete")return yt(e,String(r.path||""),r.recursive===!0,t.rootKind);throw new Error("project_file_operation_unsupported")}function yt(t,e,r,o){const i=g(e);if(!i)throw new Error("project_file_root_protected");gt(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 gt(t){const e=t.split("/")[0].toLocaleLowerCase("en-US");if([".git",".aca",".aica"].includes(e))throw new Error("project_file_protected")}function St(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:_t(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(!Pt(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 _t(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 Pt(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=>vt(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=$t(e);return{isRepository:!0,branch:Ct(e),files:i,ahead:n.ahead,behind:n.behind,truncated:o.length>=W}}function Et(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=Dt(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 xt(t){const e=p(String(t.rootPath||""));if(!w(e))return{isRepository:!1,items:[]};if(!Rt(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=>jt(n)).filter(n=>!!n),limit:r}}function bt(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 jt(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 vt(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 At(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 kt(t){try{return l.readdirSync(t).length>0}catch{return!1}}function Dt(t,e){try{return m(t,["ls-files","--error-unmatch","--",e],8*1024),!0}catch{return!1}}function Rt(t){try{return m(t,["rev-parse","--verify","HEAD"],8*1024),!0}catch{return!1}}function Ct(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 $t(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
|
|
4
|
-
`),o=Z(
|
|
5
|
-
`),i=Z(
|
|
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};
|