@yuandc/aica 0.1.95 → 0.1.97
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-client.js +1 -1
- package/dist/acp/client/acp-events.js +1 -1
- package/dist/acp/client/acp-runtime-pool.js +1 -1
- package/dist/acp/client/json-rpc.js +3 -3
- package/dist/acp/providers/codex/provider.js +1 -1
- package/dist/acp/providers/codex-acp/adapter.js +3 -3
- package/dist/acp/providers/codex-acp/provider.js +1 -1
- package/dist/acp/providers/mimo/provider.js +2 -2
- package/dist/acp/providers/opencode/provider.js +3 -3
- package/dist/acp/providers/standard-acp-events.js +1 -1
- package/dist/acp/providers/workbuddy/auth-manager.js +1 -1
- package/dist/acp/providers/workbuddy/auth-transport.js +3 -3
- package/dist/acp/providers/workbuddy/interactions.js +1 -1
- package/dist/acp/providers/workbuddy/provider.js +1 -1
- package/dist/acp/tool-lifecycle-trace.js +2 -2
- package/dist/commands/daemon.js +2 -2
- package/dist/commands/start.js +1 -1
- package/dist/core/job-worker.js +1 -1
- package/dist/core/logger.js +3 -3
- package/dist/core/logging/rotating-log.js +3 -0
- package/dist/interactions/connection-interactions.js +1 -0
- package/dist/interactions/elicitation-tracker.js +1 -0
- package/dist/interactions/form-schema.js +1 -0
- package/dist/interactions/job-interactions.js +1 -1
- package/dist/interactions/provider-input.js +1 -1
- package/dist/interactions/types.js +1 -1
- package/dist/version.js +1 -1
- package/dist/worker-cli.js +1 -1
- package/package.json +1 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{parseAcpCommands as w}from"../../client/commands.js";import{openCodeContextUsage as x}from"./usage.js";import{configureOpenCode as R,currentOpenCodeModel as O}from"./config.js";import{assertOpenCodeCompleted as A,diagnoseOpenCodeEmpty as
|
|
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 m=await this.acquireRuntime(e.cwd);this.lease=m,t=m.client,o=m.started,await this.initialize(m),n=await this.establishSession(m,e),await this.applyConfig(t,n,e),this.emitStatus("thinking","思考中"),r=await this.promptWithAbort(t,{sessionId:n,prompt:
|
|
1
|
+
import{parseAcpCommands as w}from"../../client/commands.js";import{openCodeContextUsage as x}from"./usage.js";import{configureOpenCode as R,currentOpenCodeModel as O}from"./config.js";import{assertOpenCodeCompleted as A,diagnoseOpenCodeEmpty as E,openCodeError as _,OpenCodeRunError as h}from"./errors.js";import{enterOpenCodeTask as P}from"../../../core/opencode/gate.js";import{resolveInstalledLaunch as T}from"../../../core/provider-install-paths.js";import{handleProviderInput as q}from"../../../interactions/provider-input.js";import f from"node:fs";import C from"node:path";import{runAbortableRequest as U}from"../../client/abortable-request.js";import{BoundedItemBuffer as k}from"../../client/bounded-buffer.js";import{acquireOpenCodeRuntime as M}from"./runtime.js";import{monitorOpenCodeRetry as D}from"./retry.js";import{providerEventFromAcpSessionUpdate as S,providerStatusFromAcpSessionUpdate as F}from"../standard-acp-events.js";const N={sessionResume:!0,imageInput:!0,fileAttachment:!0,filesystem:!0,terminal:!0,permissionRequest:!0,configOptions:!0,usage:!0,contextUsage:!0,contextCompaction:!0,plan:!0,diff:!0};class le{id="opencode";name="OpenCode";capabilities=N;async createSession(e){return new $(e.cwd,e.providerSessionId??null,e.timeoutMs)}}class ${cwd;providerSessionId;defaultTimeoutMs;busy=!1;promptActive=!1;promptProgress=!1;commands=null;lease=null;sessionResponse=null;statusCallback=null;updateCallback=null;permissionCallback=null;userInputCallback=null;canExecuteTools=!0;abortHandler=null;statusTimeline=[];updates=new k(16);content="";latestContextUsage=null;contextWindow=0;resumedExistingSession=!1;constructor(e,s,t=J()){this.cwd=e,this.providerSessionId=s,this.defaultTimeoutMs=t}async compactContext(e){if(!this.providerSessionId)throw new h("incomplete","OpenCode 当前没有可压缩的会话。");const s=await this.sendPrompt({...e,prompt:"/compact",requireSessionResume:!0});if(!s.providerSessionId)throw new h("incomplete","OpenCode 未返回压缩后的会话标识。");return{providerSessionId:s.providerSessionId,durationMs:s.durationMs,contextUsage:s.contextUsage?{...s.contextUsage,state:"compacted",maintenanceAction:"compact"}:null,maintenance:{action:"compact",provider:"opencode",protocol:"session/prompt"}}}async sendPrompt(e){if(this.busy)throw new Error("OpenCode 会话正在执行另一项操作。");this.busy=!0;try{return await this.runPrompt(e)}finally{this.busy=!1}}async runPrompt(e){if(!f.existsSync(e.cwd)||!f.statSync(e.cwd).isDirectory())throw new Error(`Project root does not exist or is not a directory: ${e.cwd}`);const s=Date.now();this.statusCallback=e.onStatus??null,this.updateCallback=e.onUpdate??null,this.permissionCallback=e.onPermissionRequest??null,this.userInputCallback=e.onUserInputRequest??null,this.elicitationEnabled=e.elicitation??!!e.onUserInputRequest,this.elicitationComplete=e.onElicitationComplete,this.canExecuteTools=e.canExecuteTools!==!1,this.content="",this.updates.clear(),this.statusTimeline.length=0,this.latestContextUsage=null,this.contextWindow=z(e.contextWindow),this.promptProgress=!1;let t=null,o=null,n=this.providerSessionId,r;const c=P();try{this.attachAbortSignal(e.signal),this.emitStatus("initializing","OpenCode ACP 启动中","opencode acp");const d=await this.acquireRuntime(e.cwd);this.lease=d,t=d.client,o=d.started,await this.initialize(d),n=await this.establishSession(d,e),await this.applyConfig(t,n,e),this.emitStatus("thinking","思考中");const a=V(e.prompt,e.promptBlocks);try{r=await this.promptWithAbort(t,{sessionId:n,prompt:a,timeoutMs:e.timeoutMs??this.defaultTimeoutMs,signal:e.signal})}catch(u){if(e.requireSessionResume||!this.resumedExistingSession||!j(u)||this.promptProgress)throw u;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 m=await this.acquireRuntime(e.cwd);this.lease=m,t=m.client,o=m.started,await this.initialize(m),n=await this.establishSession(m,e),await this.applyConfig(t,n,e),this.emitStatus("thinking","思考中"),r=await this.promptWithAbort(t,{sessionId:n,prompt:a,timeoutMs:e.timeoutMs??this.defaultTimeoutMs,signal:e.signal})}if(A(r),!this.content.trim()){const u=await E(e.cwd,n,s,e.signal);if(u)throw u;if(!e.prompt.trim().startsWith("/"))throw new h("incomplete","OpenCode 未返回回复正文,请检查模型认证及已有执行结果。")}}catch(d){const a=_(e.signal?.aborted?new Error("cancelled"):d);throw this.emitStatus("failed",a.message),a}finally{c(),this.abortHandler&&e.signal&&(e.signal.removeEventListener("abort",this.abortHandler),this.abortHandler=null),this.releaseLease()}return this.emitStatus("completed","已完成"),{providerSessionId:n,cliType:"builtin",agentType:"opencode",providerDiagnostics:this.commands===null?{}:{opencode:{availableCommands:this.commands}},effectiveConfig:{model:O(this.sessionResponse)||g(e.model)||null,mode:e.mode||null,configOptionValues:e.configOptionValues??null,command:o.command,processPid:o.child.pid??null,provider:"opencode",protocol:"acp"},command:o.command,args:o.args,cwd:e.cwd,exitCode:o.child.exitCode,signal:o.child.signalCode,durationMs:Date.now()-s,stdout:t.rpc.stdout,stderr:t.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()}elicitationEnabled=!1;elicitationComplete;async acquireRuntime(e){const{command:s,args:t}=T("opencode");return M({providerId:"opencode",elicitation:this.elicitationEnabled,onElicitationComplete:this.elicitationComplete,cwd:e,providerSessionId:this.providerSessionId,command:s,args:[...t,"acp","--cwd",e],onSessionUpdate:o=>this.handleSessionUpdate(o),onClientRequest:(o,n,r)=>this.handleClientRequest(o,n,r.id)})}async initialize(e){e.initialized||(await e.client.initialize(6e4),e.markInitialized())}async establishSession(e,s){const t=e.client,o=L(s);if(this.resumedExistingSession=!1,e.providerSessionId&&e.sessionEstablished)return this.providerSessionId=e.providerSessionId,this.sessionResponse=e.sessionResponse,this.commands=w(this.sessionResponse?.acaOpenCodeCommands),this.commands!==null&&this.handleSessionUpdate({sessionId:e.providerSessionId,update:{sessionUpdate:"available_commands_update",availableCommands:this.commands}}),this.resumedExistingSession=!0,e.providerSessionId;if(this.providerSessionId)try{this.emitStatus("resuming","OpenCode 会话恢复中","session/load");const c=await t.loadSession({sessionId:this.providerSessionId,cwd:s.cwd},6e4);this.sessionResponse=l(c);const d=v(c)||this.providerSessionId;return this.providerSessionId=d,e.setProviderSession({providerSessionId:d,sessionResponse:this.sessionResponse}),this.resumedExistingSession=!0,d}catch{if(s.requireSessionResume)throw new h("incomplete","OpenCode 原会话恢复失败,已取消压缩,请检查会话状态。");this.providerSessionId=null}if(s.requireSessionResume)throw new h("incomplete","OpenCode 原会话不可用,已取消压缩。");this.emitStatus("acp","OpenCode 会话创建中","session/new");const n=await t.newSession({cwd:s.cwd,config:o},12e4);this.sessionResponse=l(n);const r=v(n);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,s,t){this.sessionResponse=await R(e,s,this.sessionResponse,t,g(t.model),y(t.mode)),this.sessionResponse={...this.sessionResponse,acaOpenCodeCommands:this.commands},this.lease?.setSessionResponse(this.sessionResponse)}handleSessionUpdate(e){const s=l(e);if(this.providerSessionId&&s?.sessionId&&s.sessionId!==this.providerSessionId)return;const t=l(s?.update),o=t?.sessionUpdate==="available_commands_update"||t?.sessionUpdate==="config_options_update";if(!this.promptActive&&!o)return;t?.sessionUpdate==="available_commands_update"&&(this.commands=w(t.availableCommands),this.sessionResponse={...this.sessionResponse,acaOpenCodeCommands:this.commands},this.lease?.setSessionResponse(this.sessionResponse)),t?.sessionUpdate==="config_options_update"&&Array.isArray(t.configOptions)&&(this.sessionResponse={...this.sessionResponse,configOptions:t.configOptions}),this.promptActive&&!o&&t?.sessionUpdate!=="usage_update"&&(this.promptProgress=!0),this.updates.push({method:"session/update",params:e}),this.latestContextUsage=x(e,this.contextWindow)??this.latestContextUsage;const n=S("opencode",e);n&&(n.type==="agent_message_chunk"&&(this.content+=n.text??""),this.updateCallback?.(n));const r=F("opencode",e);r&&this.emitStatus(r.phase,r.label,r.detail,r.updateType)}async handleClientRequest(e,s,t){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(s);case"fs/write_text_file":return this.writeTextFile(s);case"session/request_permission":return this.requestPermission(s,t);case"elicitation/create":case"session/request_user_input":return this.requestUserInput(e,s,t);default:throw new Error(`Unsupported OpenCode ACP client request: ${e}`)}}readTextFile(e){const s=b(this.cwd,p(e,"path")||p(e,"filePath"));return{content:f.readFileSync(s,"utf8")}}writeTextFile(e){const s=b(this.cwd,p(e,"path")||p(e,"filePath")),t=p(e,"content")??p(e,"text")??"";return f.mkdirSync(C.dirname(s),{recursive:!0}),f.writeFileSync(s,t,"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,s){this.promptActive=!0,this.promptProgress=!1;const t=D({lease:this.lease,sessionId:s.sessionId,onNotice:o=>this.emitRuntimeNotice(o.label,o.detail),onRecovered:()=>this.emitRuntimeNotice("OpenCode 已恢复,继续执行")});try{return await Promise.race([t.failure,U({signal:s.signal,cancelledMessage:"OpenCode prompt cancelled",startRequest:()=>e.prompt({sessionId:s.sessionId,prompt:s.prompt},s.timeoutMs),onAbort:async()=>{e.cancel(s.sessionId),await this.lease?.dispose()}})])}catch(o){throw e.cancel(s.sessionId),await this.lease?.dispose().catch(()=>{}),o}finally{this.promptActive=!1,await t.stop()}}emitRuntimeNotice(e,s){const t=`${e}${s?`:${s}`:""}
|
|
3
3
|
|
|
4
|
-
`,o={sessionId:this.providerSessionId,update:{sessionUpdate:"agent_progress_chunk",content:{type:"text",text:t}}},n=S("opencode",o);this.updates.push({method:"session/update",params:o}),n&&this.updateCallback?.(n),e!=="OpenCode 状态监测暂不可用"&&(this.promptProgress=!0),this.emitStatus("thinking",e,s,"session.status")}async requestPermission(e,s){if(!this.permissionCallback)return{outcome:{outcome:"cancelled"}};const t=l(e)??{},o=l(t.request)??t,r=(Array.isArray(o.options)?o.options:[]).map((c,
|
|
4
|
+
`,o={sessionId:this.providerSessionId,update:{sessionUpdate:"agent_progress_chunk",content:{type:"text",text:t}}},n=S("opencode",o);this.updates.push({method:"session/update",params:o}),n&&this.updateCallback?.(n),e!=="OpenCode 状态监测暂不可用"&&(this.promptProgress=!0),this.emitStatus("thinking",e,s,"session.status")}async requestPermission(e,s){if(!this.permissionCallback)return{outcome:{outcome:"cancelled"}};const t=l(e)??{},o=l(t.request)??t,r=(Array.isArray(o.options)?o.options:[]).map((c,d)=>{const a=l(c)??{};return{optionId:typeof a.optionId=="string"?a.optionId:"",kind:String(a.kind??a.outcome??"choice"),...typeof a.name=="string"?{label:a.name}:typeof a.label=="string"?{label:a.label}:{}}}).filter(c=>c.optionId);return this.emitStatus("requestPermission","等待授权",H(o),"session/request_permission"),this.permissionCallback({requestId:String(o.requestId??s??o.id??`opencode-permission-${Date.now()}`),params:e,expiresAtMs:typeof o.expiresAtMs=="number"?o.expiresAtMs:void 0,options:r})}async requestUserInput(e,s,t){return this.emitStatus("requestPermission","等待回答",void 0,e),q("opencode",e,s,this.userInputCallback,t)}emitStatus(e,s,t,o){const n={phase:e,label:s,...t?{detail:t}:{},...o?{updateType:o}:{},atMs:Date.now()},r=this.statusTimeline[this.statusTimeline.length-1];r&&r.phase===n.phase&&r.label===n.label&&r.detail===n.detail&&r.updateType===n.updateType||(this.statusTimeline.push(n),this.statusCallback?.(n))}appendStderr(e){const s=this.lease?.client;s&&(s.rpc.stderr+=e)}releaseLease(){this.lease?.release(),this.lease=null}}function v(i){const s=l(i)?.sessionId;return typeof s=="string"&&s.trim()?s.trim():null}function l(i){return i&&typeof i=="object"&&!Array.isArray(i)?i:null}function z(i){return typeof i=="number"&&Number.isFinite(i)&&i>0?i:0}function L(i){const e=g(i.model),s=y(i.mode);return{...W(i.configOptionValues),...e?{model:e}:{},...s?{mode:s}:{}}}function W(i){const e={};for(const[s,t]of Object.entries(i??{}))["model","mode","reasoning_effort","effort","variant"].includes(s)||(e[s]=t);return e}function y(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 p(i,e){const t=l(i)?.[e];return typeof t=="string"&&t.trim()?t:null}function b(i,e){if(!e)throw new Error("ACP file request did not include a path");const s=C.resolve(i,e),t=C.resolve(i);if(s!==t&&!s.startsWith(`${t}${C.sep}`))throw new Error(`ACP file request is outside project root: ${e}`);return s}function H(i){const e=l(i.params)??i;for(const s of["command","cwd","reason","grantRoot","method"]){const t=e[s];if(typeof t=="string"&&t.trim())return I(t)}}function ce(i){for(const e of["prompt","message","question","title"]){const s=i[e];if(typeof s=="string"&&s.trim())return I(s)}}function I(i){const e=i.replace(/\s+/g," ").trim();return e.length>120?`${e.slice(0,117)}...`:e}function B(i){return i instanceof Error?i.message:String(i)}function j(i){return/\binvalid params\b/i.test(B(i))}function V(i,e){return(e?.length?e:[{type:"text",text:i}]).map(t=>t.type==="image"?{type:"image",data:t.data,mimeType:t.mimeType}:{type:"text",text:t.text})}function G(){return process.env.ACA_OPENCODE_DEFAULT_MODEL||""}function g(i){const e=String(i||G()).trim();return e==="default"||e==="opencode-default"?"":e}function J(){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{le as OpenCodeProvider,N as openCodeCapabilities};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{eventFromAcpSessionUpdate as
|
|
1
|
+
import{eventFromAcpSessionUpdate as y,statusFromAcpSessionUpdate as h}from"../client/acp-events.js";function P(t,n){return y(d(t,n))}function L(t,n){return h(d(t,n))}function d(t,n){const o=I(n);if(!o)return n;const e=o.update,s=A(e);if(!s)return n;const i={...e,sessionUpdate:s};return(s==="tool_call"||s==="tool_call_update")&&U(i,t,e),s==="plan"&&w(i),(s==="agent_message_chunk"||s==="agent_progress_chunk"||s==="agent_thought_chunk")&&(i.content=C(e.content??e.text??e.delta)),{...o.params,...o.sessionId?{sessionId:o.sessionId}:{},update:i}}function w(t){const n=l(t.plan,t.taskPlan,t.task_plan),o=z(t.entries,t.items,t.steps,t.todos,t.tasks,t.checklist,n?.entries,n?.items,n?.steps,n?.todos,n?.tasks,n?.checklist);o&&!Array.isArray(t.entries)&&(t.entries=o);const e=r(t.planId,t.plan_id,t.taskPlanId,t.task_plan_id,n?.id);e&&!t.planId&&(t.planId=e);const s=r(t.markdown,t.planText,t.plan_text,n?.markdown);s&&!t.markdown&&(t.markdown=s)}function I(t){if(!t||typeof t!="object"||Array.isArray(t))return null;const n=t,o=l(n.update,n.sessionUpdate,n.session_update,n.event,n.notification)??n,e=o===n&&!S(n)?l(n.data,n.payload):o;return e?{params:n,sessionId:r(n.sessionId,n.session_id),update:e}:null}function A(t){const o=r(t.sessionUpdate,t.session_update,t.updateType,t.update_type,t.eventType,t.event,t.type)?.toLowerCase().replace(/[-./ ]+/g,"_");return o?["agent_message_chunk","message_chunk","assistant_message","assistant_text","text_delta"].includes(o)?"agent_message_chunk":["agent_progress_chunk","progress_chunk","progress"].includes(o)?"agent_progress_chunk":["agent_thought_chunk","thought_chunk","reasoning_chunk","reasoning"].includes(o)?"agent_thought_chunk":["tool_call","tool_use","toolcall","tool_use_start","tool_call_start"].includes(o)?"tool_call":["tool_call_update","tool_use_update","toolcall_update","tool_result","tool_call_result","tool_use_result"].includes(o)?"tool_call_update":["plan","plan_update","plan_updated","plan_changed","task_plan","task_plan_update","todo","todo_update","todo_updated","tasks"].includes(o)?"plan":["plan_removed","plan_deleted","todo_removed","todo_deleted"].includes(o)?"plan_removed":o:null}function U(t,n,o){const e=l(t.tool,t.toolCall,t.tool_call,t.toolUse,t.tool_use,t.data),s=r(t.toolCallId,t.tool_call_id,t.callId,t.call_id,t.itemId,t.item_id,t.id,e?.toolCallId,e?.tool_call_id,e?.id)||`${n}.${Date.now()}`,i=r(t.title,t.name,t.toolName,t.tool_name,e?.title,e?.name,e?.toolName,e?.tool_name),u=r(t.kind,t.toolKind,t.tool_kind,e?.kind,i)||"other",g=r(o.sessionUpdate,o.session_update,o.updateType,o.update_type,o.eventType,o.event,o.type)?.toLowerCase().replace(/[-./ ]+/g,"_"),k=r(t.status,t.state,t.phase,e?.status,e?.state)||(["tool_result","tool_call_result","tool_use_result"].includes(g||"")?"completed":"in_progress"),_=m(t.rawInput,t.raw_input,t.input,t.arguments,t.args,t.params,e?.rawInput,e?.raw_input,e?.input,e?.arguments,e?.args,e?.params,x(t)),a=m(t.rawOutput,t.raw_output,t.output,t.result,t.error,t.aggregatedOutput,t.aggregated_output,t.resultText,e?.rawOutput,e?.raw_output,e?.output,e?.result,e?.error);t.toolCallId=s,t.kind=u;const f=T(u,i,_);f&&(t.title=f),t.status=k,_!==void 0&&(t.rawInput=_),a!==void 0&&(t.rawOutput=a),t.content===void 0&&a!==void 0&&(t.content=p(a)),t._meta={...c(t._meta)?t._meta:{},provider_normalized:n}}function T(t,n,o){const e=l(o),s=r(e?.path,e?.filePath,e?.file_path);if(s&&(t.toLowerCase().includes("read")||t.toLowerCase().includes("view")))return`Read file ${s}`;if(s&&t.toLowerCase().includes("list"))return`List files in ${s}`;if(n)return n}function x(t){const n={};for(const o of["command","query","path","filePath","file_path","url","cwd"])t[o]!==void 0&&(n[o]=t[o]);return Object.keys(n).length?n:void 0}function p(t){return typeof t=="string"?{type:"content",content:t}:(Array.isArray(t)||c(t),t)}function C(t){return typeof t=="string"?{type:"text",text:t}:c(t)&&typeof t.text=="string"?t:p(t)}function S(t){return["sessionUpdate","session_update","updateType","update_type","eventType","event","type"].some(n=>t[n]!==void 0)}function l(...t){for(const n of t)if(c(n))return n;return null}function z(...t){return t.find(Array.isArray)}function c(t){return!!(t&&typeof t=="object"&&!Array.isArray(t))}function m(...t){return t.find(n=>n!=null)}function r(...t){for(const n of t)if(typeof n=="string"&&n.trim())return n.trim()}export{d as normalizeProviderSessionUpdate,P as providerEventFromAcpSessionUpdate,L as providerStatusFromAcpSessionUpdate};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{randomUUID as b}from"node:crypto";import{defaultAcpRuntimePool as
|
|
1
|
+
import{randomUUID as b}from"node:crypto";import{defaultAcpRuntimePool as y}from"../../client/acp-runtime-pool.js";import{managementActivity as A}from"../../../core/management/activity.js";import{beginWorkBuddyAuthChange as g,enterWorkBuddyTask as I}from"./auth-gate.js";import{clearWorkBuddyCapabilities as u}from"./config.js";import{resetWorkBuddyDiscovery as d}from"./discovery.js";import{saveWorkBuddyRegion as M,workBuddyEnvironmentCredentials as h,workBuddyRegion as E}from"./auth-environment.js";import{createWorkBuddyAuthTransport as c,safeWorkBuddyAuthUrl as v}from"./auth-transport.js";class T{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:E()||"internal",regionLocked:process.env.CODEBUDDY_INTERNET_ENVIRONMENT!==void 0}}async status(){if(this.flow)return{...this.flow.snapshot};if(h())return{...this.base("environment"),message:"当前使用 Worker 环境变量凭据;网页不能修改或退出这类凭据。"};if(this.cached&&this.cached.until>Date.now())return{...this.cached.snapshot};const r=I();let e;try{e=c(),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:p(s)})}finally{try{await e?.close()}finally{r()}}}async start(r){if(this.flow)return{...this.flow.snapshot};if(h())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!==E())throw new Error("登录版本由 Worker 环境变量固定,请使用对应版本。");const s=g(),k=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(u(),d(),await y.retireProviderRuntimes("workbuddy"),t.stopped)return;t.transport=c(e,a=>{if(!t.stopped)try{t.snapshot={...t.snapshot,state:"waiting",authUrl:v(a,e)}}catch(w){t.stopped=!0,t.snapshot={...t.snapshot,state:"error",authUrl:void 0,expiresAtMs:void 0,message:w instanceof Error&&w.message==="auth_region_mismatch"?"官方授权链接与所选登录版本不一致,请重新发起登录,并检查 CLI 的版本和环境配置。":"官方返回的授权链接无法安全打开,请升级 CLI 或使用本机终端登录。"},t.transport?.close()}},!1,!0);const o=await t.transport.initialize();if(t.stopped)return;const n=await t.transport.account();if(t.stopped)return;const l=e==="ioa"?"iOA":e;if(!Array.isArray(o.authMethods)||!o.authMethods.some(a=>a?.id===l))throw new Error("auth_method_unsupported");const m=n||await t.transport.authenticate(l,Math.max(100,t.snapshot.expiresAtMs-Date.now()));if(t.stopped)return;if(!m)throw new Error("auth_no_account");M(e),u(),d(),t.snapshot={...t.snapshot,state:"authenticated",account:m,authUrl:void 0,expiresAtMs:void 0}}catch(o){if(!t.stopped){const n=Date.now()>=(t.snapshot.expiresAtMs??1/0);t.snapshot={...t.snapshot,state:n?"expired":"error",authUrl:void 0,message:n?"登录已超时,请重新发起。":p(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(),k()}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(h())return this.status();if(this.flow)throw new Error("请先取消当前登录,再退出账号。");const r=g();let e;try{return await y.retireProviderRuntimes("workbuddy"),e=c(void 0,void 0,!0),await e.logout(),u(),d(),this.remember(this.base("signed_out"))}catch(s){return this.remember({...this.base("error"),message:p(s)})}finally{try{await e?.close()}finally{r()}this.changed().catch(()=>{})}}remember(r){return this.cached={until:Date.now()+1e4,snapshot:r},{...r}}}function p(i){const r=i instanceof Error?i.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{T as WorkBuddyAuthManager};
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import
|
|
2
|
-
`))>=0;){const
|
|
3
|
-
`)})}}}export{
|
|
1
|
+
import{createConnectionInteractions as W}from"../../../interactions/connection-interactions.js";import{handleProviderInput as B}from"../../../interactions/provider-input.js";import{ElicitationTracker as j}from"../../../interactions/elicitation-tracker.js";import v from"node:fs";import C from"node:os";import x from"node:path";import{randomUUID as O}from"node:crypto";import{startAcpProcess as P,closeAcpProcess as A}from"../../client/acp-process.js";import{JsonLineRpcClient as N}from"../../client/json-rpc.js";import{resolveWorkBuddyLaunch as J}from"./launch.js";import{record as n}from"./config.js";import{workBuddyRegionEnv as L}from"./auth-environment.js";function E(u){const s=n(u),t=typeof s.userId=="string"?s.userId.slice(0,200):"",d=[s.userNickname,s.userName].find(c=>typeof c=="string"&&c.trim());return t||d?{id:t,name:typeof d=="string"?d.slice(0,200):t}:void 0}function Z(u,s){if(typeof u!="string"||u.length>8192)throw new Error("invalid_auth_url");const t=new URL(u),d=["tencent.com","codebuddy.ai","codebuddy.cn"];if(t.protocol!=="https:"||t.username||t.password||t.port&&t.port!=="443"||!d.some(w=>t.hostname===w||t.hostname.endsWith(`.${w}`)))throw new Error("invalid_auth_url");const c=t.hostname==="codebuddy.ai"||t.hostname.endsWith(".codebuddy.ai"),f=t.hostname==="codebuddy.cn"||t.hostname.endsWith(".codebuddy.cn")||t.hostname==="copilot.tencent.com";if(s==="internal"&&c||s==="external"&&f)throw new Error("auth_region_mismatch");return t.href}function ee(u,s,t=!1,d=!1){const c=J(!1),f=v.mkdtempSync(x.join(C.tmpdir(),"aca-workbuddy-auth-")),w=t?c.args.flatMap(e=>e==="--acp"?["--print","--input-format","stream-json","--output-format","stream-json","--verbose"]:[e]):c.args;let o;try{o=P(c.command,[...w,"--no-session-persistence"],f,{...c.env,...L(u),BROWSER:x.join(f,"no-local-browser")})}catch(e){throw v.rmSync(f,{recursive:!0,force:!0}),e}const l=!t&&d?W("workbuddy","workbuddy-auth"):void 0,g=new j;let R;const S=()=>R??=Promise.all([A(o.child),l?.close()]).then(()=>{}).finally(()=>v.rmSync(f,{recursive:!0,force:!0}));o.child.once("close",()=>{l?.close()});const h=t?null:new N(o.child,{includeJsonRpc:!0,onNotification(e,i){e==="elicitation/complete"&&g.complete(i),e==="_codebuddy.ai/authUrl"&&s?.(String(n(i).authUrl||""))},async onRequest(e,i,p){if(e!=="elicitation/create"||!l?.supported)throw Object.assign(new Error("Unsupported authentication client request"),{rpcCode:-32602});const a=n(i);if(a.sessionId!=null||!h.hasPendingRequest(a.requestId))throw Object.assign(new Error("Invalid authentication elicitation scope"),{rpcCode:-32602});const b=g.begin(i,p.id,"workbuddy",r=>{l.complete(r)});try{const r=await B("workbuddy",e,i,y=>l.input(y),p.id);return g.settle(b,r),r}catch(r){throw g.settle(b,null),r}}});return{close:S,async initialize(){return await l?.initialize(),n(await h.request("initialize",{protocolVersion:1,clientCapabilities:{fs:{readTextFile:!1,writeTextFile:!1},terminal:!1,...l?.supported?{elicitation:{form:{},url:{}}}:{}},clientInfo:{name:"aca-auth",version:"1"}},12e3))},async account(){return E(n(await h.request("_codebuddy.ai/getUserInfo",{},12e3)).userInfo)},async billingIdentity(){const e=n(n(await h.request("_codebuddy.ai/getUserInfo",{},12e3)).userInfo);return{account:E(e),token:typeof e.accessToken=="string"?e.accessToken:typeof e.token=="string"?e.token:"",enterpriseId:typeof e.enterpriseId=="string"?e.enterpriseId:""}},async authenticate(e,i){const p=n(await h.request("authenticate",{methodId:e},i));return E(n(p._meta)["codebuddy.ai/userinfo"])},async logout(){const e=O();await new Promise((i,p)=>{let a="";const b=setTimeout(()=>r(new Error("logout_timeout")),15e3),r=m=>{clearTimeout(b),o.child.stdout.off("data",U),o.child.off("error",y),o.child.off("close",q),m?p(m):i()},y=m=>r(m),q=()=>r(new Error("logout_process_exited")),U=m=>{if(a+=m.toString(),Buffer.byteLength(a)>1024*1024)return r(new Error("invalid_auth_response"));let _;for(;(_=a.indexOf(`
|
|
2
|
+
`))>=0;){const T=a.slice(0,_);a=a.slice(_+1);let k;try{k=n(JSON.parse(T))}catch{continue}const I=n(k.response);if(!(k.type!=="control_response"||I.request_id!==e))return r(I.subtype==="success"&&n(I.response).success===!0?void 0:new Error("logout_failed"))}};o.child.stdout.on("data",U),o.child.stderr.resume(),o.child.on("error",y),o.child.on("close",q),o.child.stdin.on("error",y),o.child.stdin.write(`${JSON.stringify({type:"control_request",request_id:e,request:{subtype:"logout"}})}
|
|
3
|
+
`)})}}}export{ee as createWorkBuddyAuthTransport,E as publicWorkBuddyAccount,Z as safeWorkBuddyAuthUrl};
|
|
@@ -1 +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
|
|
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 p=d(q),l=String(p.value??p.label??"");return{value:l,label:String(p.label??l),description:typeof p.description=="string"?p.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!=="submitted")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};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{workBuddyCredits as B}from"../../../core/workbuddy-credits/worker.js";import h from"node:fs";import{enterWorkBuddySession as A}from"./session-lock.js";import{forkWorkBuddySession as E}from"./fork.js";import{runAbortableRequest as W}from"../../client/abortable-request.js";import{defaultAcpRuntimePool as m}from"../../client/acp-runtime-pool.js";import{BoundedItemBuffer as f}from"../../client/bounded-buffer.js";import{contextUsageFromAcpSessionUpdate as M}from"../../client/context-usage.js";import{providerEventFromAcpSessionUpdate as w,providerStatusFromAcpSessionUpdate as R}from"../standard-acp-events.js";import{resolveWorkBuddyLaunch as U,workBuddyTimeout as x}from"./launch.js";import{applyWorkBuddyConfig as q,record as n,rememberWorkBuddyCapabilities as y}from"./config.js";import{handleWorkBuddyRequest as D}from"./interactions.js";import{WorkBuddyEventRouter as g}from"./events.js";import{WorkBuddySupplementIdentity as v,WorkBuddySupplements as _,workBuddyPromptContent as O}from"./supplements.js";import{assertWorkBuddyPromptCompleted as P,workBuddyError as S}from"./errors.js";import{WorkBuddyTurnCredits as I,workBuddyContextUsage as T}from"./usage.js";import{workBuddyCommands as k}from"./commands.js";const c=new WeakMap,z={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 os{id="workbuddy";name="腾讯 Code Buddy";capabilities=z;async createSession(s){return new F(s.cwd,s.providerSessionId??null,s.timeoutMs)}async forkSession(s){return E(s)}}class F{cwd;providerSessionId;timeoutMs;lease=null;input=null;acceptingOutput=!1;busy=!1;content="";usage=null;response={};commands=new Set;commandCatalog=null;credits=new I;technicalError=null;router=new g;supplements=null;supplementIdentity=new v;updates=new f(16);statuses=new f(64);firstEventAtMs=null;lastEventAtMs=null;constructor(s,t,o=x()){this.cwd=s,this.providerSessionId=t,this.timeoutMs=o}async sendPrompt(s){return this.run(s,!1)}async compactContext(s){if(!this.providerSessionId)throw new Error("WorkBuddy 当前没有可压缩的会话。");const t=await this.run({...s,cwd:s.cwd,prompt:"/compact",requireSessionResume:!0},!0);return{providerSessionId:t.providerSessionId,contextUsage:t.contextUsage??null,maintenance:{action:"compact",provider:"workbuddy",protocol:"session/prompt",stopReason:n(t.promptResponse).stopReason},durationMs:t.durationMs}}async run(s,t){if(this.busy)throw new Error("WorkBuddy 会话正在执行另一轮任务。");if(h.realpathSync(s.cwd)!==h.realpathSync(this.cwd))throw new Error("WorkBuddy 会话不能切换工作目录。");if(s.requireSessionResume&&!this.providerSessionId)throw new Error("WorkBuddy 历史会话缺少 Session ID,禁止新建会话。");if(/^\s*\/(fork|branch|fork-bg)(?:\s|$)/i.test(s.prompt))throw new Error("请使用会话菜单中的“从当前会话分叉”,以便 ACA 正确保存新会话。");const o=A(this.providerSessionId);this.busy=!0,this.input=s,this.content="",this.usage=null,this.response={},this.commands.clear(),this.commandCatalog=null,this.credits=new I,this.technicalError=null,this.router=new g,this.supplementIdentity=new v,this.updates.clear(),this.statuses.clear(),this.firstEventAtMs=this.lastEventAtMs=null;const i=Date.now();let a;try{return await W({signal:s.signal,cancelledMessage:"WorkBuddy 任务已取消",onAbort:()=>this.cancelAndDispose(),startRequest:async()=>{this.status("initializing","WorkBuddy ACP 启动中");const l=U(s.canExecuteTools!==!1);if(this.providerSessionId&&await m.retireSessionRuntimes({providerId:"workbuddy",cwd:this.cwd,providerSessionId:this.providerSessionId,keepRuntimeIdentity:l.runtimeIdentity}),s.signal?.aborted)throw new Error("WorkBuddy 任务已取消");const e=await m.acquire({providerId:"workbuddy",cwd:this.cwd,providerSessionId:this.providerSessionId,...l,onSessionUpdate:r=>this.handleUpdate(r),onClientRequest:(r,u,C)=>this.handleRequest(r,u,C.id)});if(this.lease=e,s.signal?.aborted)throw await e.dispose(),new Error("WorkBuddy 任务已取消");await this.initialize(e),await this.establishSession(e),o.bind(this.providerSessionId);const d=n(n(c.get(e.client)?.agentCapabilities).promptCapabilities);if(s.promptBlocks?.some(r=>r.type==="image")&&d.image!==!0)throw new Error("当前 CodeBuddy ACP 版本未声明图片输入支持。");if(t||(this.response=await q(e.client,this.providerSessionId,this.response,s)),e.setSessionResponse(this.response),t&&!this.commands.has("compact"))throw new Error("当前 WorkBuddy 会话未提供 /compact 命令,无法压缩上下文。");this.acceptingOutput=!0,this.publishCommands(),this.status("thinking",t?"正在压缩上下文":"思考中"),a=B.beginUsage();const p=await e.client.prompt({sessionId:this.providerSessionId,prompt:O(s.prompt,s.promptBlocks)},s.timeoutMs??this.timeoutMs);if(s.signal?.aborted)throw new Error("WorkBuddy 任务已取消");if(this.credits.identifyResponse(p),this.technicalError)throw S(this.technicalError);return P(p),t&&this.usage&&(this.usage={...this.usage,state:"compacted",maintenanceAction:"compact"}),this.status("completed",t?"上下文压缩完成":"已完成"),{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:e.started.command,processPid:e.started.child.pid},command:e.started.command,args:e.started.args,cwd:this.cwd,exitCode:e.started.child.exitCode,signal:e.started.child.signalCode,durationMs:Date.now()-i,stdout:e.client.rpc.stdout,stderr:e.client.rpc.stderr,content:this.content.trim(),updateCount:this.updates.count(),updates:this.updates.snapshot(),statusTimeline:this.statuses.snapshot(),contextUsage:this.usage,promptResponse:p,providerDiagnostics:this.diagnostics(),performance:{startedAtMs:i,firstEventAtMs:this.firstEventAtMs,lastEventAtMs:this.lastEventAtMs,eventCount:this.updates.count(),firstEventLatencyMs:this.firstEventAtMs===null?null:this.firstEventAtMs-i,silentForMs:Date.now()-(this.lastEventAtMs??i),durationMs:Date.now()-i}}}})}catch(l){await this.lease?.dispose();const e=S(l);throw this.status(e.failure.category==="cancelled"?"cancelled":"failed",e.message),Object.assign(e,{providerDiagnostics:this.diagnostics(),statusTimeline:this.statuses.snapshot()})}finally{a?.(),this.acceptingOutput=!1,s.onSupplementReady?.(null);try{await this.supplements?.close()}finally{o.release()}this.supplements=null,this.lease?.release(),this.lease=null,this.input=null,this.busy=!1}}async initialize(s){if(s.initialized)return;const t=n(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(t.protocolVersion!==1)throw new Error("WorkBuddy ACP 协议版本不兼容。");c.set(s.client,t),s.markInitialized()}async establishSession(s){if(s.sessionEstablished&&s.providerSessionId){this.providerSessionId=s.providerSessionId,this.response=s.sessionResponse??{};const t=this.response.acaAvailableCommands;Array.isArray(t)&&(this.commandCatalog=k(t),this.commands=new Set(this.commandCatalog.map(o=>o.name)));return}if(this.providerSessionId){if(n(c.get(s.client)?.agentCapabilities).loadSession!==!0)throw new Error("当前 WorkBuddy 版本不支持恢复会话,禁止静默新建。");if(this.status("resuming","WorkBuddy 会话恢复中"),this.response=n(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=n(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.commandCatalog&&(this.response.acaAvailableCommands=this.commandCatalog),y(this.response),s.setProviderSession({providerSessionId:this.providerSessionId,sessionResponse:this.response})}handleUpdate(s){const t=n(s),o=n(t.update);if(this.providerSessionId&&t.sessionId!==this.providerSessionId)return;const i=this.router.route(s),a=i.child?null:this.supplementIdentity.observe(s);if(!i.child&&o.sessionUpdate==="available_commands_update"&&Array.isArray(o.availableCommands)&&(this.commandCatalog=k(o.availableCommands),this.commands=new Set(this.commandCatalog.map(p=>p.name)),this.response.acaAvailableCommands=this.commandCatalog,this.lease?.setSessionResponse(this.response)),!i.child&&o.sessionUpdate==="config_option_update"&&Array.isArray(o.configOptions)&&(this.response={...this.response,configOptions:o.configOptions},this.lease?.setSessionResponse(this.response),y(this.response)),!this.acceptingOutput||!this.input)return;if(this.updates.push({method:"session/update",params:i.params}),this.firstEventAtMs??=Date.now(),this.lastEventAtMs=Date.now(),!i.child){this.credits.observe(s);const p=M(s,this.input.contextWindow);this.usage=T(s,p)??this.usage;const r=n(o._meta)["codebuddy.ai/error"];r&&(this.technicalError=r);const u=a;!this.supplements&&typeof u=="string"&&u&&this.lease&&this.providerSessionId&&(this.supplements=new _(this.lease.client,this.providerSessionId,u),this.input.onSupplementReady?.(this.supplements.channel))}const l=!i.child&&o.sessionUpdate==="available_commands_update"?{...i.params,update:{...o,availableCommands:this.commandCatalog??[]}}:i.params,e=w("workbuddy",l);e&&(!i.child&&e.type==="agent_message_chunk"&&(this.content+=e.text??""),this.input.onUpdate?.(e));const d=R("workbuddy",i.params);d&&this.status(d.phase,d.label,d.detail,d.updateType)}diagnostics(){return{workbuddy:{creditUsage:this.credits.snapshot(),...this.commandCatalog!==null?{availableCommands:this.commandCatalog}:{}}}}publishCommands(){if(this.commandCatalog===null)return;const s=w("workbuddy",{sessionId:this.providerSessionId,update:{sessionUpdate:"available_commands_update",availableCommands:this.commandCatalog}});s&&this.input?.onUpdate?.(s)}async handleRequest(s,t,o){if(!this.input||!this.acceptingOutput)throw new Error("WorkBuddy 当前没有可交互的执行轮次。");const i=n(t).sessionId;if((s!=="elicitation/create"||i!==void 0)&&i!==this.providerSessionId)throw new Error("WorkBuddy 交互请求会话不匹配。");this.status("requestPermission",s==="session/request_permission"?"等待授权":"等待回答",void 0,s);try{return await D(s,t,o,this.input)}finally{this.acceptingOutput&&this.status("thinking","处理中")}}status(s,t,o,i){const a={phase:s,label:t,detail:o,updateType:i,atMs:Date.now()};this.statuses.push(a),this.input?.onStatus?.(a)}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{os as WorkBuddyProvider,z as workBuddyCapabilities};
|
|
1
|
+
import{workBuddyCredits as B}from"../../../core/workbuddy-credits/worker.js";import h from"node:fs";import{enterWorkBuddySession as E}from"./session-lock.js";import{forkWorkBuddySession as A}from"./fork.js";import{runAbortableRequest as R}from"../../client/abortable-request.js";import{defaultAcpRuntimePool as m}from"../../client/acp-runtime-pool.js";import{BoundedItemBuffer as f}from"../../client/bounded-buffer.js";import{contextUsageFromAcpSessionUpdate as W}from"../../client/context-usage.js";import{providerEventFromAcpSessionUpdate as w,providerStatusFromAcpSessionUpdate as M}from"../standard-acp-events.js";import{resolveWorkBuddyLaunch as U,workBuddyTimeout as q}from"./launch.js";import{applyWorkBuddyConfig as x,record as n,rememberWorkBuddyCapabilities as y}from"./config.js";import{handleWorkBuddyRequest as D}from"./interactions.js";import{WorkBuddyEventRouter as g}from"./events.js";import{WorkBuddySupplementIdentity as v,WorkBuddySupplements as _,workBuddyPromptContent as O}from"./supplements.js";import{assertWorkBuddyPromptCompleted as P,workBuddyError as S}from"./errors.js";import{WorkBuddyTurnCredits as I,workBuddyContextUsage as z}from"./usage.js";import{workBuddyCommands as k}from"./commands.js";const c=new WeakMap,T={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 os{id="workbuddy";name="腾讯 Code Buddy";capabilities=T;async createSession(s){return new F(s.cwd,s.providerSessionId??null,s.timeoutMs)}async forkSession(s){return A(s)}}class F{cwd;providerSessionId;timeoutMs;lease=null;input=null;acceptingOutput=!1;busy=!1;content="";usage=null;response={};commands=new Set;commandCatalog=null;credits=new I;technicalError=null;router=new g;supplements=null;supplementIdentity=new v;updates=new f(16);statuses=new f(64);firstEventAtMs=null;lastEventAtMs=null;constructor(s,e,t=q()){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:n(e.promptResponse).stopReason},durationMs:e.durationMs}}async run(s,e){if(this.busy)throw new Error("WorkBuddy 会话正在执行另一轮任务。");if(h.realpathSync(s.cwd)!==h.realpathSync(this.cwd))throw new Error("WorkBuddy 会话不能切换工作目录。");if(s.requireSessionResume&&!this.providerSessionId)throw new Error("WorkBuddy 历史会话缺少 Session ID,禁止新建会话。");if(/^\s*\/(fork|branch|fork-bg)(?:\s|$)/i.test(s.prompt))throw new Error("请使用会话菜单中的“从当前会话分叉”,以便 ACA 正确保存新会话。");const t=E(this.providerSessionId);this.busy=!0,this.input=s,this.content="",this.usage=null,this.response={},this.commands.clear(),this.commandCatalog=null,this.credits=new I,this.technicalError=null,this.router=new g,this.supplementIdentity=new v,this.updates.clear(),this.statuses.clear(),this.firstEventAtMs=this.lastEventAtMs=null;const o=Date.now();let a;try{return await R({signal:s.signal,cancelledMessage:"WorkBuddy 任务已取消",onAbort:()=>this.cancelAndDispose(),startRequest:async()=>{this.status("initializing","WorkBuddy ACP 启动中");const p=U(s.canExecuteTools!==!1);if(this.providerSessionId&&await m.retireSessionRuntimes({providerId:"workbuddy",cwd:this.cwd,providerSessionId:this.providerSessionId,keepRuntimeIdentity:p.runtimeIdentity}),s.signal?.aborted)throw new Error("WorkBuddy 任务已取消");const i=await m.acquire({providerId:"workbuddy",cwd:this.cwd,elicitation:s.elicitation??!!s.onUserInputRequest,onElicitationComplete:s.onElicitationComplete,providerSessionId:this.providerSessionId,...p,normalizeClientRequest:(r,u)=>this.normalizeLegacyRequest(r,u),onSessionUpdate:r=>this.handleUpdate(r),onClientRequest:(r,u,C)=>this.handleRequest(r,u,C.id)});if(this.lease=i,s.signal?.aborted)throw await i.dispose(),new Error("WorkBuddy 任务已取消");await this.initialize(i),await this.establishSession(i),t.bind(this.providerSessionId);const d=n(n(c.get(i.client)?.agentCapabilities).promptCapabilities);if(s.promptBlocks?.some(r=>r.type==="image")&&d.image!==!0)throw new Error("当前 CodeBuddy ACP 版本未声明图片输入支持。");if(e||(this.response=await x(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.publishCommands(),this.status("thinking",e?"正在压缩上下文":"思考中"),a=B.beginUsage();const l=await i.client.prompt({sessionId:this.providerSessionId,prompt:O(s.prompt,s.promptBlocks)},s.timeoutMs??this.timeoutMs);if(s.signal?.aborted)throw new Error("WorkBuddy 任务已取消");if(this.credits.identifyResponse(l),this.technicalError)throw S(this.technicalError);return P(l),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()-o,stdout:i.client.rpc.stdout,stderr:i.client.rpc.stderr,content:this.content.trim(),updateCount:this.updates.count(),updates:this.updates.snapshot(),statusTimeline:this.statuses.snapshot(),contextUsage:this.usage,promptResponse:l,providerDiagnostics:this.diagnostics(),performance:{startedAtMs:o,firstEventAtMs:this.firstEventAtMs,lastEventAtMs:this.lastEventAtMs,eventCount:this.updates.count(),firstEventLatencyMs:this.firstEventAtMs===null?null:this.firstEventAtMs-o,silentForMs:Date.now()-(this.lastEventAtMs??o),durationMs:Date.now()-o}}}})}catch(p){await this.lease?.dispose();const i=S(p);throw this.status(i.failure.category==="cancelled"?"cancelled":"failed",i.message),Object.assign(i,{providerDiagnostics:this.diagnostics(),statusTimeline:this.statuses.snapshot()})}finally{a?.(),this.acceptingOutput=!1,s.onSupplementReady?.(null);try{await this.supplements?.close()}finally{t.release()}this.supplements=null,this.lease?.release(),this.lease=null,this.input=null,this.busy=!1}}async initialize(s){if(s.initialized)return;const e=n(await s.client.rpc.request("initialize",{protocolVersion:1,clientInfo:{name:"aca",version:"0.1.0"},clientCapabilities:{fs:{readTextFile:!1,writeTextFile:!1},terminal:!1,...this.input?.elicitation??!!this.input?.onUserInputRequest?{elicitation:{form:{},url:{}}}:{},_meta:{"codebuddy.ai":{question:!0}}}},6e4));if(e.protocolVersion!==1)throw new Error("WorkBuddy ACP 协议版本不兼容。");c.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)&&(this.commandCatalog=k(e),this.commands=new Set(this.commandCatalog.map(t=>t.name)));return}if(this.providerSessionId){if(n(c.get(s.client)?.agentCapabilities).loadSession!==!0)throw new Error("当前 WorkBuddy 版本不支持恢复会话,禁止静默新建。");if(this.status("resuming","WorkBuddy 会话恢复中"),this.response=n(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=n(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.commandCatalog&&(this.response.acaAvailableCommands=this.commandCatalog),y(this.response),s.setProviderSession({providerSessionId:this.providerSessionId,sessionResponse:this.response})}handleUpdate(s){const e=n(s),t=n(e.update);if(this.providerSessionId&&e.sessionId!==this.providerSessionId)return;const o=this.router.route(s),a=o.child?null:this.supplementIdentity.observe(s);if(!o.child&&t.sessionUpdate==="available_commands_update"&&Array.isArray(t.availableCommands)&&(this.commandCatalog=k(t.availableCommands),this.commands=new Set(this.commandCatalog.map(l=>l.name)),this.response.acaAvailableCommands=this.commandCatalog,this.lease?.setSessionResponse(this.response)),!o.child&&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;if(this.updates.push({method:"session/update",params:o.params}),this.firstEventAtMs??=Date.now(),this.lastEventAtMs=Date.now(),!o.child){this.credits.observe(s);const l=W(s,this.input.contextWindow);this.usage=z(s,l)??this.usage;const r=n(t._meta)["codebuddy.ai/error"];r&&(this.technicalError=r);const u=a;!this.supplements&&typeof u=="string"&&u&&this.lease&&this.providerSessionId&&(this.supplements=new _(this.lease.client,this.providerSessionId,u),this.input.onSupplementReady?.(this.supplements.channel))}const p=!o.child&&t.sessionUpdate==="available_commands_update"?{...o.params,update:{...t,availableCommands:this.commandCatalog??[]}}:o.params,i=w("workbuddy",p);i&&(!o.child&&i.type==="agent_message_chunk"&&(this.content+=i.text??""),this.input.onUpdate?.(i));const d=M("workbuddy",o.params);d&&this.status(d.phase,d.label,d.detail,d.updateType)}diagnostics(){return{workbuddy:{creditUsage:this.credits.snapshot(),...this.commandCatalog!==null?{availableCommands:this.commandCatalog}:{}}}}publishCommands(){if(this.commandCatalog===null)return;const s=w("workbuddy",{sessionId:this.providerSessionId,update:{sessionUpdate:"available_commands_update",availableCommands:this.commandCatalog}});s&&this.input?.onUpdate?.(s)}normalizeLegacyRequest(s,e){const t=n(e);return s==="elicitation/create"&&t.mode===void 0&&t.requestedSchema&&this.input&&this.providerSessionId?{...t,mode:"form",...t.sessionId==null&&t.requestId==null?{sessionId:this.providerSessionId}:{}}:e}async handleRequest(s,e,t){if(!this.input)throw new Error("WorkBuddy 当前没有可交互的执行轮次。");const o=n(e).sessionId;if((s!=="elicitation/create"||o!==void 0)&&o!==this.providerSessionId)throw new Error("WorkBuddy 交互请求会话不匹配。");this.status("requestPermission",s==="session/request_permission"?"等待授权":"等待回答",void 0,s);try{return await D(s,e,t,this.input)}finally{this.acceptingOutput&&this.status("thinking","处理中")}}status(s,e,t,o){const a={phase:s,label:e,detail:t,updateType:o,atMs:Date.now()};this.statuses.push(a),this.input?.onStatus?.(a)}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{os as WorkBuddyProvider,T as workBuddyCapabilities};
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
1
|
+
import f from"node:path";import{getLogDir as s}from"../core/paths.js";import{RotatingLog as u}from"../core/logging/rotating-log.js";const p=new Set(["jobId","threadId","turnId","toolCallId","itemType","status","sequence","method","reason","uploadRunId","batchNo","attempt","firstSequence","lastSequence","eventCount"]);let n;function g(l){let c=0;const i=process.env.ACA_TOOL_LIFECYCLE_TRACE==="1";return(r,a)=>{if(!i||c++>=1e3)return;const o={};for(const[t,e]of Object.entries(a))p.has(t)&&(typeof e=="string"?o[t]=e.slice(0,160):typeof e=="number"&&Number.isFinite(e)&&(o[t]=e));try{const t=`[tool-lifecycle] ${JSON.stringify({at:Date.now(),stage:r,...o})}`;if(l){l(t);return}const e=f.join(s(),"tool-lifecycle.log");n?.file!==e&&(n=new u(e,{legacyPattern:/^tool-lifecycle-\d+\.log(?:\.1)?$/})),n.append(`${t}
|
|
2
|
+
`)}catch{}}}export{g as createToolLifecycleTrace};
|
package/dist/commands/daemon.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import{assertManualStartAllowed as
|
|
1
|
+
import{assertManualStartAllowed as C,prepareManualWorkerAction as f}from"../core/management/manual.js";import{Command as i}from"commander";import s from"chalk";import d from"node:fs";import c from"node:path";import{randomUUID as k}from"node:crypto";import{getLogDir as a,getRuntimeStatePath as $}from"../core/paths.js";import{ensureDir as g,readJsonFile as A}from"../core/fs.js";import{isProcessAlive as v,readPidFile as w,removePidFile as h}from"../core/process.js";import{ACA_WORKER_INSTANCE_ENV as E,readProcessStartTicks as R}from"../core/process-identity.js";import{assertNoWorkerInstances as I,findWorkerInstances as p,sameWorkerInstance as u}from"../core/worker-instances.js";import{applyRuntimePathOptions as l}from"../core/runtime-options.js";import{spawnLoggedProcess as _}from"../core/logging/rotating-log.js";const x=3e4;function Q(){return new i("daemon").description("Run aca as a background daemon service").addCommand(m(new i("start").description("Start aca daemon in the background")).allowUnknownOption(!0).action(async o=>{l(o),C(),I();const e=w();if(process.platform!=="linux"&&e&&v(e)){console.log(`Daemon is already running (PID ${e}).`),process.exitCode=1;return}const n=await P(D("start"));console.log(`Daemon started (PID ${n.pid})`),S()})).addCommand(m(new i("stop").description("Stop the running aca daemon")).action(async o=>{l(o),await f();const e=p();if(!e.length){console.log("当前运行目录没有存活的 Worker。");return}await y(e)?console.log(`Worker 已停止:${e.map(n=>n.pid).join(", ")}`):process.exitCode=1})).addCommand(m(new i("status").description("Show daemon status")).action(o=>{l(o);const e=A($()),n=p(),t=w();if(n.length){console.log(s.green("● Daemon is running")),console.log(` PID: ${n.map(r=>r.pid).join(", ")}`),n.length>1&&console.log(" 检测到重复 Worker;daemon restart 会先停止该运行目录下的全部已确认实例。"),n.some(r=>r.pid===t)||console.log(" PID 文件缺失或失效,已从存活进程恢复实例清单。"),n.some(r=>r.pid===e?.pid)&&(e?.phase&&console.log(` Phase: ${e.phase}`),e?.startupStage&&console.log(` Stage: ${e.startupStage}`));return}t?(console.log(s.red("● Daemon is not running (stale PID file)")),h(t)):console.log(s.red("● Daemon is not running")),process.exitCode=1})).addCommand(m(new i("logs").description("Show daemon logs")).option("-n, --lines <count>","number of lines to show","50").action(o=>{l(o);const e=Number.parseInt(o.lines,10)||50,n=O();if(!n){console.log(`No log files found in ${a()}`),process.exitCode=1;return}const t=d.readFileSync(n,"utf8").trimEnd().split(`
|
|
2
2
|
`);console.log(s.dim(`--- ${n} (last ${e} lines) ---`)),console.log(t.slice(-e).join(`
|
|
3
|
-
`))})).addCommand(
|
|
3
|
+
`))})).addCommand(m(new i("restart").description("Restart the aca daemon")).allowUnknownOption(!0).action(async o=>{if(l(o),await f(),!await y(p())){process.exitCode=1;return}I();const e=await P(D("restart"));console.log(`Daemon started (PID ${e.pid})`),S()}))}function m(o){return o.option("--aica-home <path>","Runtime home directory (defaults to ~/.aica)").option("--aica-config <path>","Config file path").option("--aca-home <path>","Deprecated alias for --aica-home").option("--aca-config <path>","Deprecated alias for --aica-config")}function D(o){const e=process.argv.findIndex((t,r)=>r>=2&&t==="daemon"),n=process.argv.findIndex((t,r)=>r>e&&t===o);return n>=0?process.argv.slice(n+1):[]}async function P(o){const e=W();return g(a()),_({command:e.command,args:e.prefixArgs},{command:e.command,args:[...e.prefixArgs,"start",...o],file:c.join(a(),"daemon-stderr.log"),stdout:!1,legacyPattern:"^daemon-stderr-\\d{4}-\\d{2}-\\d{2}\\.log$"},{...process.env,[E]:k()})}function W(){if(process.env.ACA_SINGLE_FILE_WORKER==="1")return{command:process.execPath,prefixArgs:[]};const o=process.argv[1];if(!o)throw new Error("Unable to resolve the current ACA CLI entrypoint");return{command:process.execPath,prefixArgs:[c.resolve(o)]}}function S(){const o=N();console.log(`Use ${s.yellow(`${o} daemon status`)} to check status`),console.log(`Use ${s.yellow(`${o} daemon stop`)} to stop`),console.log(`Use ${s.yellow(`${o} daemon logs`)} to view logs`)}function N(){if(process.env.ACA_SINGLE_FILE_WORKER==="1")return process.execPath;const o=c.basename(process.argv[1]||"");return/worker-cli|aica/i.test(o)?"aica":"aca"}function O(){g(a());const o=c.join(a(),"worker.log");return d.existsSync(o)?o:d.readdirSync(a()).filter(n=>/^\d{4}-\d{2}-\d{2}\.log$/.test(n)).map(n=>c.join(a(),n)).sort().at(-1)??null}async function U(o,e){const n=Date.now();for(;Date.now()-n<e;){if(!u(o))return!0;await new Promise(t=>setTimeout(t,100))}return!u(o)}async function L(o,e=x,n){const t=n||{pid:o,startTicks:R(o)};if(!u(t))return!0;try{process.kill(o,"SIGTERM")}catch(r){if(r.code!=="ESRCH")throw r}return U(t,e)}async function y(o,e=x){return(await Promise.all(o.map(async t=>{const r=await L(t.pid,e,t);return r?h(t.pid):console.error(`Worker PID ${t.pid} 未在 ${e}ms 内退出,已中止重启,请检查该实例的任务和日志。`),r}))).every(Boolean)}export{Q as createDaemonCommand,S as printStartTips,P as spawnDaemon,L as stopDaemonProcess,y as stopWorkerInstances};
|
package/dist/commands/start.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{openCodeAuthManager as N}from"../core/opencode/worker.js";import{mimoAuthManager as L}from"../core/mimo-auth-worker.js";import{stopProviderInstaller as U}from"../core/provider-installer.js";import{startProviderUpdates as F,stopProviderUpdates as H}from"../core/provider-updates/service.js";import{activateWorkBuddyCredits as B,stopWorkBuddyCredits as K}from"../core/workbuddy-credits/worker.js";import{stopManagedClaims as V}from"../core/management/claims.js";import{workBuddyAuthManager as G}from"../core/workbuddy-auth-worker.js";import{assertManualStartAllowed as J}from"../core/management/manual.js";import{startManagementControl as q}from"../core/management/control-server.js";import{managementActivity as u}from"../core/management/activity.js";import{acquireWorkerLease as z,assertNoWorkerInstances as Q}from"../core/worker-instances.js";import{getAcaHome as X}from"../core/paths.js";import{Command as Y}from"commander";import I from"node:fs";import D from"node:os";import c from"node:path";import t from"node:process";import{randomUUID as Z}from"node:crypto";import{Logger as ee}from"../core/logger.js";import{loadCredentials as te,saveApiKeyCredential as re}from"../core/auth.js";import{getRuntimeStatePath as oe}from"../core/paths.js";import{writeJsonFile as ae}from"../core/fs.js";import{HEARTBEAT_INTERVAL_MS as ie,startHeartbeatLoop as ne}from"../core/heartbeat.js";import{startJobWorkerLoop as se}from"../core/job-worker.js";import{startFileTransferWorkerLoop as me}from"../core/file-transfer-worker.js";import{startTerminalWorker as ce}from"../core/terminal/worker.js";import{startMachineFilesystemWorkerLoop as de}from"../core/machine-filesystem-worker.js";import{cleanupOrphanedAcpProcesses as fe}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 pe,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 le}from"../core/aca-server-client.js";import{restoreProjectsAfterLogin as ue}from"../core/project-recovery.js";import{syncCodexProviderRuntimeConfig as he}from"../core/codex-provider-runtime-config.js";import{ACA_WORKER_INSTANCE_ENV as O,ACA_WORKER_PID_ENV as ge,ACA_WORKER_START_TICKS_ENV as we,readProcessStartTicks as ve}from"../core/process-identity.js";import{printStartTips as Ae,spawnDaemon as Ie}from"./daemon.js";import{startDesktopControl as Se}from"../core/desktop-control.js";function lt(){return new Y("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 ke(e);return}await Pe(e)})}async function ke(e){x(e),Q();const a=d();if(t.platform!=="linux"&&a&&h(a))throw new Error(`Worker 已在后台运行,PID ${a}。`);a&&W(a);const n=Ie(ye());if(n.
|
|
1
|
+
import{openCodeAuthManager as N}from"../core/opencode/worker.js";import{mimoAuthManager as L}from"../core/mimo-auth-worker.js";import{stopProviderInstaller as U}from"../core/provider-installer.js";import{startProviderUpdates as F,stopProviderUpdates as H}from"../core/provider-updates/service.js";import{activateWorkBuddyCredits as B,stopWorkBuddyCredits as K}from"../core/workbuddy-credits/worker.js";import{stopManagedClaims as V}from"../core/management/claims.js";import{workBuddyAuthManager as G}from"../core/workbuddy-auth-worker.js";import{assertManualStartAllowed as J}from"../core/management/manual.js";import{startManagementControl as q}from"../core/management/control-server.js";import{managementActivity as u}from"../core/management/activity.js";import{acquireWorkerLease as z,assertNoWorkerInstances as Q}from"../core/worker-instances.js";import{getAcaHome as X}from"../core/paths.js";import{Command as Y}from"commander";import I from"node:fs";import D from"node:os";import c from"node:path";import t from"node:process";import{randomUUID as Z}from"node:crypto";import{Logger as ee}from"../core/logger.js";import{loadCredentials as te,saveApiKeyCredential as re}from"../core/auth.js";import{getRuntimeStatePath as oe}from"../core/paths.js";import{writeJsonFile as ae}from"../core/fs.js";import{HEARTBEAT_INTERVAL_MS as ie,startHeartbeatLoop as ne}from"../core/heartbeat.js";import{startJobWorkerLoop as se}from"../core/job-worker.js";import{startFileTransferWorkerLoop as me}from"../core/file-transfer-worker.js";import{startTerminalWorker as ce}from"../core/terminal/worker.js";import{startMachineFilesystemWorkerLoop as de}from"../core/machine-filesystem-worker.js";import{cleanupOrphanedAcpProcesses as fe}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 pe,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 le}from"../core/aca-server-client.js";import{restoreProjectsAfterLogin as ue}from"../core/project-recovery.js";import{syncCodexProviderRuntimeConfig as he}from"../core/codex-provider-runtime-config.js";import{ACA_WORKER_INSTANCE_ENV as O,ACA_WORKER_PID_ENV as ge,ACA_WORKER_START_TICKS_ENV as we,readProcessStartTicks as ve}from"../core/process-identity.js";import{printStartTips as Ae,spawnDaemon as Ie}from"./daemon.js";import{startDesktopControl as Se}from"../core/desktop-control.js";function lt(){return new Y("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 ke(e);return}await Pe(e)})}async function ke(e){x(e),Q();const a=d();if(t.platform!=="linux"&&a&&h(a))throw new Error(`Worker 已在后台运行,PID ${a}。`);a&&W(a);const n=await Ie(ye());if(!n.pid)throw new Error("后台 Worker 启动失败:未获得进程 PID。");if(!await Ce(n.pid,3e3)&&!h(n.pid))throw new Error(`后台 Worker 启动失败,请检查 ${c.join(t.env.AICA_HOME||t.env.ACA_HOME||c.join(D.homedir(),".aica"),"logs")}。`);console.log(`Worker 已在后台启动,PID ${n.pid}。`),Ae()}function ye(){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 Ce(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 Pe(e){x(e);const a=await z();try{await Ee(e)}finally{a()}}async function Ee(e){if(e.managedStart||J(),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=ve(t.pid)||"";t.env[O]=a,t.env[ge]=String(t.pid),t.env[we]=n;const o=new ee("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 m=Object.keys(S).length>0?R(S):g;const f=e.machineName?.trim()||m.machineName||D.hostname();if(e.token?.trim()){const r=await le("POST","/api/auth/machine-enroll",{token:e.token.trim(),machineId:m.machineId,machineName:f},{serverUrl:m.serverUrl});m=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 ue();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`),m=$()}catch(i){o.warn(`project recovery after machine enrollment failed: ${i instanceof Error?i.message:String(i)}`)}}const M=e.auth?re(e.auth,f):te(),l=d();if(t.platform!=="linux"&&l&&l!==t.pid&&h(l))throw new Error(`Worker 已在运行,PID ${l}`);pe(t.pid),j({pid:t.pid,workerInstanceId:a,workerStartTicks:n,phase:"running",startupStage:"local-compatible",machineId:m.machineId||M?.machine?.machineId||"local-machine",machineName:f,cliTypes:e.cliTypes??be(),startedAt:new Date().toISOString(),heartbeatIntervalMs:ie,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 fe();w.terminated>0&&o.warn(`terminated ${w.terminated} orphaned ACP process(es): ${w.pids.join(",")}`),he().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 p=async r=>{if(k)return;k=!0,V(),o.info("Shutting down"),v&&clearInterval(v),C?.stop(),E?.stop(),P?.stop(),await H(),await U(),await K(),await G.stop(),await N.stop(),await L.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=c.join(X(),"management");I.mkdirSync(i,{recursive:!0,mode:448}),I.writeFileSync(c.join(i,"manual-stop.json"),JSON.stringify({workerInstanceId:a,at:Date.now()}),{mode:384})}p(r)};t.once("SIGINT",()=>b(130)),t.once("SIGTERM",()=>b(0)),t.once("uncaughtException",r=>{o.error(`uncaught exception: ${r.stack||r.message}`),p(1)}),t.once("unhandledRejection",r=>{const i=r instanceof Error?r.stack||r.message:String(r);o.error(`unhandled rejection: ${i}`),p(1)}),e.heartbeatLog&&setInterval(()=>o.info(`heartbeat: ${new Date().toISOString()}`),5e3);let T=!1;const A=()=>{T||(T=!0,F(),B(),y=se(o),C=me(o),E=de(o),P=ce(o))};try{await q(a,()=>p(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=ne(o),t.env.ACA_DESKTOP_CHILD==="1"&&t.env.ACA_DESKTOP_STAGED==="1"&&(u.paused=!0),u.paused||A(),Se(()=>p(0),A),await new Promise(()=>{})}function be(){return["codex","claude"].filter(e=>Te(e))}function Te(e){const a=t.env.PATH||"";for(const n of a.split(c.delimiter))try{const o=t.platform==="win32"?["",".exe",".cmd",".bat"]:[""];if(n&&o.some(g=>I.existsSync(c.join(n,e+g))))return!0}catch{}return!1}function j(e){ae(oe(),e)}export{lt as createStartCommand};
|
package/dist/core/job-worker.js
CHANGED
|
@@ -1 +1 @@
|
|
|
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{resolveJobExecutionConfig as fe}from"./job-execution-config.js";import{createAcpEventBatcher as he}from"./acp-event-batcher.js";import{createToolLifecycleTrace as we}from"../acp/tool-lifecycle-trace.js";import{shouldBufferAcpEvent as Ie}from"./acp-event-upload-filter.js";import{userFacingAcpError as Se}from"../acp/error-display.js";import{ensureCodexProviderRuntimeRevision as be}from"./codex-provider-runtime-config.js";import{acpSessionInvalidationFromError as ye}from"../acp/session-errors.js";const ve=1500,q=5e3,K=900,Ae=1e4,_e=0,Te=12e3;function Ce(e){return ue(e)}function ge(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 mt(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=ke();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):N(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 Re(f,p,u,k)),!k.signal.aborted&&await(t.runJob??je)(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=Le(l);w=Date.now()+v,e.warn(`job worker failed, retry in ${v}ms: ${u instanceof Error?u.message:String(u)}`)}}finally{S()}},E=()=>{if(n||s)return;const m=_();s=m,m.finally(()=>{s===m&&(s=null)})};E();const c=setInterval(()=>{E()},t.claimIntervalMs??ve);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(()=>{}),Be(Te)])})(),i)}}async function Re(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,Pe(s.signal)])}finally{clearInterval(w)}}function Pe(e){return e.aborted?Promise.resolve():new Promise(t=>e.addEventListener("abort",()=>t(),{once:!0}))}function ke(){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 je(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,...fe(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 N(i,t.machineId,"job input missing message");return}const E=t.projects.find(r=>r.projectId===I),c=Ce(E?.rootPath||e.project_root_path||"");if(!c){await N(i,t.machineId,"job project root is unknown");return}ge(e)&&ae.mkdirSync(c,{recursive:!0}),n.info(`claimed job=${i} session=${l} project=${E?.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=we(r=>n.info(r)),b=he(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 Ue(i,t.machineId,r);const T=Me(a.acpRun);if(await y.initialize(),xe(T.agentType)&&await be(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:x(T.agentType,"codex"),cliType:x(T.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:J(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 an ACP context to fork");await f.sendNow({phase:"forking",label:"正在分叉会话",detail:a.control?.sourceSessionId?`源会话 ${a.control.sourceSessionId}`:void 0}).catch(()=>{});const h=await se({agentType:x(T.agentType,"codex"),cliType:x(T.cliType,"builtin"),sourceProviderSessionId:o,cwd:r.cwd,model:a.model,mode:a.mode,configOptionValues:a.configOptionValues,timeoutMs:a.timeoutMs,signal:s.signal,onStatus:M=>{f.report({phase:M.phase,label:M.label,detail:M.detail,updateType:M.updateType,raw:M})}});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:J(r)}}),n.info(`completed session fork job=${i}`);return}const C=await ne({cwd:r.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:L(C)}}).catch(()=>{}));const O=ee(a,C),P=te(O,C);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:x(T.agentType,"codex"),cliType:x(T.cliType,"builtin"),acpSessionId:e.acp_session_id??null,requireSessionResume:Ee(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:$e(T.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 M of pe(o.raw))$.add(M);const h={turnId:e.user_turn_id??null,eventType:o.type,label:o.label,text:o.text,status:o.status,toolCallId:o.toolCallId,raw:Fe(o.raw,v)};Ie(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(),z=await b.finish();z.complete||g.uncertain.add("job-event-upload"),await f.flush(),await R?.stop();const B=D(V({cwd:r.cwd,evidencePaths:$}),Q),F=Oe(d.promptResponse);if(!(Je(F)||!!d.content)){await N(i,t.machineId,`${d.agentType} stopped: ${F??"unknown"}`,{acpSessionId:d.acpSessionId,stopReason:F,eventUpload:z,files:B,exitCode:d.exitCode,stderr:De(d.stderr,4e3),worktree:J(r)});return}await A("POST",`/api/client/jobs/${encodeURIComponent(i)}/complete`,{machineId:t.machineId,acpSessionId:d.acpSessionId,assistantText:d.content,result:{ok:!0,eventUpload:z,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:J(r),attachments:L(C),files:B}}),n.info(`completed job=${i}`)}catch(r){if(await R?.stop(),We(s.signal)){b.discard(),f.discard(),n.warn(`stopped local job=${i} because ACA Server already finalized it`);return}const T=await W(),C=await b.finish();C.complete||g.uncertain.add("job-event-upload"),await f.flush();const O=s.signal.aborted?"cancelled":r instanceof Error?r.message:String(r),P=ye(r),d=D(k?V({cwd:k.cwd,evidencePaths:$}):[],T);await N(i,t.machineId,Se(O),{rawError:O,eventUpload:C,files:d,providerDiagnostics:U(r,"providerDiagnostics"),failure:U(r,"failure"),statusTimeline:U(r,"statusTimeline"),performance:U(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 Ee(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 $e(e){return typeof e=="number"&&Number.isFinite(e)&&e>0?e:void 0}function x(...e){for(const t of e)if(typeof t=="string"&&t.trim())return t.trim();return""}function Oe(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 Je(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},E=c=>{l||(l=setTimeout(()=>{_()},c),l.unref())};return{report(c){if(I)return;const m=Date.now(),u=a(c)!==i,v=m-s>=Ae;if(!u&&!v)return;n=c;const p=m-s;if(p>=K||s===0){_();return}E(K-p)},sendNow(c){return I?w:(n=c,_())},flush:_,discard(){I=!0,n=null,l&&clearTimeout(l),l=null}}}function Fe(e,t){return e&&typeof e=="object"&&!Array.isArray(e)?{...e,acaSequence:t}:{value:e,acaSequence:t}}async function Ue(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??J(n)})}function J(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=ze(n.item);s&&t.abort(new G(e,s))}function ze(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 We(e){return e.aborted&&e.reason instanceof G}async function N(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 Be(e){return new Promise(t=>setTimeout(t,e))}function Le(e){return Math.min(1e4,500*2**Math.min(5,Math.max(0,e-1)))+Math.floor(Math.random()*250)}function De(e,t){return e.length<=t?e:`${e.slice(0,t)}...`}function U(e,t){return!e||typeof e!="object"||Array.isArray(e)?null:e[t]??null}export{ze as serverFinalizedJobStatus,mt 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{resolveJobExecutionConfig as fe}from"./job-execution-config.js";import{createAcpEventBatcher as he}from"./acp-event-batcher.js";import{createToolLifecycleTrace as we}from"../acp/tool-lifecycle-trace.js";import{shouldBufferAcpEvent as Ie}from"./acp-event-upload-filter.js";import{userFacingAcpError as Se}from"../acp/error-display.js";import{ensureCodexProviderRuntimeRevision as be}from"./codex-provider-runtime-config.js";import{acpSessionInvalidationFromError as ye}from"../acp/session-errors.js";const ve=1500,q=5e3,K=900,Ae=1e4,_e=0,Ce=12e3;function Te(e){return ue(e)}function ge(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 mt(e,t={}){let n=!1,s=null,i=null,l=0,I=0;const S=new Set,a=new Map,w=new Map,_=async()=>{if(n||g.paused||g.stopping||Date.now()<I)return;const m=ke();if(!X(S.size,m))return;const b=g.enter("poll");if(b)try{const u=t.loadConfig?.()??le();if(!u.token)return;for(;!n&&!g.paused&&!g.stopping&&X(S.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,I=0,!v.item)break;const p=v.item;if(n){await(t.cancelClaimedJob?t.cancelClaimedJob(p,u):N(p.job_id,u.machineId,"cancelled")).catch(()=>{});break}const y=p.session_id,f=a.get(y),$=g.track("job");S.add(p.job_id);const k=new AbortController;let j;j=(async()=>{f&&(e.warn(`claimed job=${p.job_id} for already active session=${y}; waiting for the previous run to release its ACP runtime`),await Re(f,p,u,k)),!k.signal.aborted&&await(t.runJob??je)(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(()=>{$(),S.delete(p.job_id),a.get(y)===j&&a.delete(y),w.delete(p.job_id)}),a.set(y,j),w.set(p.job_id,{controller:k,promise:j})}}catch(u){if(!n){l+=1;const v=Le(l);I=Date.now()+v,e.warn(`job worker failed, retry in ${v}ms: ${u instanceof Error?u.message:String(u)}`)}}finally{b()}},E=()=>{if(n||s)return;const m=_();s=m,m.finally(()=>{s===m&&(s=null)})};E();const c=setInterval(()=>{E()},t.claimIntervalMs??ve);return{timer:c,stop:()=>i||(i=(async()=>{if(n)return;n=!0,clearInterval(c);for(const b of w.values())b.controller.abort();await s?.catch(()=>{});for(const b of w.values())b.controller.abort();const m=[...w.values()].map(b=>b.promise);m.length!==0&&await Promise.race([Promise.allSettled(m).then(()=>{}),Be(Ce)])})(),i)}}async function Re(e,t,n,s){let i=!1;const I=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,Pe(s.signal)])}finally{clearInterval(I)}}function Pe(e){return e.aborted?Promise.resolve():new Promise(t=>e.addEventListener("abort",()=>t(),{once:!0}))}function ke(){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 je(e,t,n,s){const i=e.job_id,l=e.session_id,I=e.workspace_id,S=e.project_id??null,a={...e.input,...fe(e.input??{})},w=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 N(i,t.machineId,"job input missing message");return}const E=t.projects.find(r=>r.projectId===S),c=Te(E?.rootPath||e.project_root_path||"");if(!c){await N(i,t.machineId,"job project root is unknown");return}ge(e)&&ae.mkdirSync(c,{recursive:!0}),n.info(`claimed job=${i} session=${l} project=${E?.name??S??"unknown"}`);let m=!1,b=!1;const u=setInterval(()=>{m||(m=!0,A("POST",`/api/client/jobs/${encodeURIComponent(i)}/heartbeat`,{machineId:t.machineId}).catch(()=>{}).finally(()=>{m=!1})),b||(b=!0,H(i,s).catch(()=>{}).finally(()=>{b=!1}))},q);let v=0;const p=we(r=>n.info(r)),y=he(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:S,sessionId:l,projectRoot:c,worktree:e});k=r,await Ue(i,t.machineId,r);const C=Me(a.acpRun);if(await w.initialize(),xe(C.agentType)&&await be(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:x(C.agentType,"codex"),cliType:x(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:J(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 an ACP context to fork");await f.sendNow({phase:"forking",label:"正在分叉会话",detail:a.control?.sourceSessionId?`源会话 ${a.control.sourceSessionId}`:void 0}).catch(()=>{});const h=await se({agentType:x(C.agentType,"codex"),cliType:x(C.cliType,"builtin"),sourceProviderSessionId:o,cwd:r.cwd,model:a.model,mode:a.mode,configOptionValues:a.configOptionValues,timeoutMs:a.timeoutMs,signal:s.signal,onStatus:M=>{f.report({phase:M.phase,label:M.label,detail:M.detail,updateType:M.updateType,raw:M})}});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:J(r)}}),n.info(`completed 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:x(C.agentType,"codex"),cliType:x(C.cliType,"builtin"),acpSessionId:e.acp_session_id??null,requireSessionResume:Ee(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:$e(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:y.uploadRunId,toolCallId:o.toolCallId,status:o.status,sequence:v});for(const M of pe(o.raw))$.add(M);const h={turnId:e.user_turn_id??null,eventType:o.type,label:o.label,text:o.text,status:o.status,toolCallId:o.toolCallId,raw:Fe(o.raw,v)};Ie(h)&&y.push(h)},onPermissionRequest:o=>{const h=me(a,o);return h?Promise.resolve(h):w.permission(o)},onUserInputRequest:o=>w.input(o),elicitation:w.elicitation,onElicitationComplete:o=>{w.complete(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 y.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(),z=await y.finish();z.complete||g.uncertain.add("job-event-upload"),await f.flush(),await R?.stop();const B=D(V({cwd:r.cwd,evidencePaths:$}),Q),F=Oe(d.promptResponse);if(!(Je(F)||!!d.content)){await N(i,t.machineId,`${d.agentType} stopped: ${F??"unknown"}`,{acpSessionId:d.acpSessionId,stopReason:F,eventUpload:z,files:B,exitCode:d.exitCode,stderr:De(d.stderr,4e3),worktree:J(r)});return}await A("POST",`/api/client/jobs/${encodeURIComponent(i)}/complete`,{machineId:t.machineId,acpSessionId:d.acpSessionId,assistantText:d.content,result:{ok:!0,eventUpload:z,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:J(r),attachments:L(T),files:B}}),n.info(`completed job=${i}`)}catch(r){if(await R?.stop(),We(s.signal)){y.discard(),f.discard(),n.warn(`stopped local job=${i} because ACA Server already finalized it`);return}const C=await W(),T=await y.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=ye(r),d=D(k?V({cwd:k.cwd,evidencePaths:$}):[],C);await N(i,t.machineId,Se(O),{rawError:O,eventUpload:T,files:d,providerDiagnostics:U(r,"providerDiagnostics"),failure:U(r,"failure"),statusTimeline:U(r,"statusTimeline"),performance:U(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 w.close(),await R?.stop(),j?.dispose(),y.discard(),clearInterval(u),await f.flush()}}function Ee(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 $e(e){return typeof e=="number"&&Number.isFinite(e)&&e>0?e:void 0}function x(...e){for(const t of e)if(typeof t=="string"&&t.trim())return t.trim();return""}function Oe(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 Je(e){return e?["end_turn","stop","completed","complete"].includes(e):!1}function Ne(e,t){let n=null,s=0,i="",l=null,I=Promise.resolve(),S=!1;const a=c=>JSON.stringify({phase:c.phase??"",label:c.label??"",detail:c.detail??"",updateType:c.updateType??""}),w=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),S)return n=null,I;if(!n)return I;const c=n;return n=null,I=I.then(()=>w(c)).catch(()=>{}),I},E=c=>{l||(l=setTimeout(()=>{_()},c),l.unref())};return{report(c){if(S)return;const m=Date.now(),u=a(c)!==i,v=m-s>=Ae;if(!u&&!v)return;n=c;const p=m-s;if(p>=K||s===0){_();return}E(K-p)},sendNow(c){return S?I:(n=c,_())},flush:_,discard(){S=!0,n=null,l&&clearTimeout(l),l=null}}}function Fe(e,t){return e&&typeof e=="object"&&!Array.isArray(e)?{...e,acaSequence:t}:{value:e,acaSequence:t}}async function Ue(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??J(n)})}function J(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=ze(n.item);s&&t.abort(new G(e,s))}function ze(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 We(e){return e.aborted&&e.reason instanceof G}async function N(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 Be(e){return new Promise(t=>setTimeout(t,e))}function Le(e){return Math.min(1e4,500*2**Math.min(5,Math.max(0,e-1)))+Math.floor(Math.random()*250)}function De(e,t){return e.length<=t?e:`${e.slice(0,t)}...`}function U(e,t){return!e||typeof e!="object"||Array.isArray(e)?null:e[t]??null}export{ze as serverFinalizedJobStatus,mt as startJobWorkerLoop};
|
package/dist/core/logger.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
`)}}export{
|
|
1
|
+
import n from"node:path";import{getLogDir as p}from"./paths.js";import{RotatingLog as u}from"./logging/rotating-log.js";const i={debug:10,info:20,warn:30,error:40},s=new Map;class h{scope;minLevel;output;constructor(t,o="info"){this.scope=t,this.minLevel=o;const r=n.join(p(),"worker.log");let e=s.get(r);e||(e=new u(r,{legacyPattern:/^\d{4}-\d{2}-\d{2}\.log$/}),s.set(r,e)),this.output=e}debug(t){this.write("debug",t)}info(t){this.write("info",t)}warn(t){this.write("warn",t)}error(t){this.write("error",t)}write(t,o){const r=`${new Date().toISOString()} [${t.toUpperCase()}] [${this.scope}] ${o}`;this.output.append(`${r}
|
|
2
|
+
`),i[t]>=i[this.minLevel]&&(t==="error"?process.stderr:process.stdout).write(`${r}
|
|
3
|
+
`)}}export{h as Logger};
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import o from"node:fs";import m from"node:path";import{Writable as u}from"node:stream";import{spawn as p}from"node:child_process";const x=10*1024*1024,w=7,y="--internal-worker-log-collector";class S{file;options;maxBytes;maxFiles;initialized=!1;retryAt=0;constructor(e,t={}){if(this.file=e,this.options=t,this.maxBytes=t.maxBytes??x,this.maxFiles=t.maxFiles??w,!Number.isSafeInteger(this.maxBytes)||this.maxBytes<1||!Number.isSafeInteger(this.maxFiles)||this.maxFiles<1)throw new Error("日志大小和文件数量必须是正整数");this.append("")}append(e){if(Date.now()<this.retryAt)return!1;try{return this.initialize(),this.writeBuffer(Buffer.isBuffer(e)?e:Buffer.from(e)),!0}catch(t){this.initialized=!1,this.retryAt=Date.now()+3e4;try{o.writeSync(2,`Worker 日志写入失败,30 秒后重试:${String(t)}
|
|
2
|
+
`)}catch{}return!1}}initialize(){if(this.initialized)return;const e=m.dirname(this.file);o.mkdirSync(e,{recursive:!0});const t=m.basename(this.file);for(const i of o.readdirSync(e)){if(!i.startsWith(`${t}.`))continue;const s=i.slice(t.length+1);/^\d+$/.test(s)&&Number(s)>=this.maxFiles&&o.rmSync(m.join(e,i),{force:!0})}const r=this.options.legacyPattern?o.readdirSync(e,{withFileTypes:!0}).filter(i=>i.isFile()&&this.options.legacyPattern.test(i.name)).map(i=>({file:m.join(e,i.name),at:o.statSync(m.join(e,i.name)).mtimeMs})).sort((i,s)=>i.at-s.at||i.file.localeCompare(s.file)):[];for(let i=0;i<this.maxFiles;i++)this.limitExisting(this.archive(i));for(const i of r.slice(-this.maxFiles)){const s=o.statSync(i.file).size;this.writeBuffer(this.readTail(i.file,Math.min(s,this.maxBytes)))}for(const i of r)o.unlinkSync(i.file);this.initialized=!0}archive(e){return e?`${this.file}.${e}`:this.file}readTail(e,t){const r=o.openSync(e,"r");try{const i=Math.max(0,o.fstatSync(r).size-t),s=Buffer.alloc(t);let n=0;for(;n<t;){const l=o.readSync(r,s,n,t-n,i+n);if(!l)break;n+=l}return s.subarray(0,n)}finally{o.closeSync(r)}}limitExisting(e){try{o.statSync(e).size>this.maxBytes&&o.writeFileSync(e,this.readTail(e,this.maxBytes),{mode:384})}catch(t){if(t.code!=="ENOENT")throw t}}size(){try{return o.statSync(this.file).size}catch(e){if(e.code==="ENOENT")return 0;throw e}}rotate(){o.rmSync(this.archive(this.maxFiles-1),{force:!0});for(let e=this.maxFiles-2;e>=0;e--)try{o.renameSync(this.archive(e),this.archive(e+1))}catch(t){if(t.code!=="ENOENT")throw t}}writeBuffer(e){let t=this.size();t&&t+e.length>this.maxBytes&&(this.rotate(),t=0);for(let r=0;r<e.length;){t>=this.maxBytes&&(this.rotate(),t=0);const i=Math.min(e.length,r+this.maxBytes-t);o.appendFileSync(this.file,e.subarray(r,i),{mode:384}),t+=i-r,r=i}}}function g(c,e){const t=new S(c,e);return new u({write(r,i,s){t.append(r),s()}})}async function R(c,e,t=process.env){const r=new Set(["ACA_WORKER_INSTANCE_ID","ACA_WORKER_PID","ACA_WORKER_START_TICKS","ACA_ACP_WORKER_INSTANCE_ID","AICA_MANAGER_ID"]),i=Object.fromEntries(Object.entries(t).filter(([n])=>!r.has(n)&&!n.startsWith("ACA_ACP_RUNTIME_"))),s=p(c.command,[...c.args,y],{detached:!0,windowsHide:!0,env:i,stdio:["ignore","ignore","ignore","ipc"]});return new Promise((n,l)=>{let f=!1;const d=setTimeout(()=>h(new Error("日志收集进程启动超时")),15e3),h=a=>{f||(f=!0,clearTimeout(d),s.connected&&s.disconnect(),s.unref(),l(a))};s.once("error",h),s.once("exit",a=>h(new Error(`日志收集进程退出:${a}`))),s.once("message",a=>{if(clearTimeout(d),!a.pid){h(new Error(a.error||"Worker 启动失败"));return}f=!0,s.removeAllListeners("exit"),s.disconnect(),s.unref(),n({pid:a.pid})}),s.send({...e,env:t},a=>{a&&h(a)})})}async function I(c=process.argv){if(c.at(-1)!==y)return!1;const e=await new Promise(n=>process.once("message",n)),t=g(e.file,{legacyPattern:e.legacyPattern?new RegExp(e.legacyPattern):void 0});let r;r=p(e.command,e.args,{windowsHide:!0,env:e.env??process.env,stdio:["ignore",e.stdout===!1?"ignore":"pipe","pipe"]}),r.stdout?.pipe(t,{end:!1}),r.stderr.pipe(t,{end:!1});const i=()=>{r.kill("SIGINT")},s=()=>{r.kill("SIGTERM")};return process.on("SIGINT",i),process.on("SIGTERM",s),r.once("spawn",()=>process.send?.({pid:r.pid},()=>{})),r.once("error",n=>{t.write(`Worker 启动失败:${n.message}
|
|
3
|
+
`),process.send?.({error:n.message},()=>{})}),await new Promise(n=>r.once("close",(l,f)=>{process.exitCode=l??(f?1:0),n()})),t.end(),process.off("SIGINT",i),process.off("SIGTERM",s),!0}export{y as LOG_COLLECTOR_ARGUMENT,S as RotatingLog,x as WORKER_LOG_MAX_BYTES,w as WORKER_LOG_MAX_FILES,g as createLogSink,I as handleLogCollector,R as spawnLoggedProcess};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{randomUUID as v}from"node:crypto";import{setTimeout as y}from"node:timers/promises";import{acaServerRequest as I,AcaServerHttpError as f}from"../core/aca-server-client.js";import{loadAcaConfig as T}from"../core/aca-config.js";function P(m,b){const w=v(),l=T(),S={serverUrl:l.serverUrl,token:l.token},i=new AbortController,a=`/api/client/interactions/connections/${w}`,u=new Set;let o=!1,g,d,p=!1;const r=(t,e,s,n=!1)=>I(t,e,s,{...S,timeoutMs:5e3,...n?{}:{signal:i.signal}}),h=()=>g??=(async()=>{if(!l.token)return;let t;try{t=await r("GET","/api/interactions/capabilities")}catch(e){if(e instanceof f&&e.status===404)return;throw e}!(t.interactionVersions??[t.interactionVersion]).includes(2)||!t.requestScope||(await r("POST",a,{action:"open",provider:m,key:b}),o=!0,d=setInterval(()=>{p||i.signal.aborted||(p=!0,r("POST",a,{action:"heartbeat"}).catch(e=>{e instanceof f&&e.status<500&&i.abort()}).finally(()=>{p=!1}))},1e4),d.unref())})();return{initialize:h,get supported(){return o},async input(t){if(await h(),!o)throw Object.assign(new Error("ACA Server 不支持会话外交互,请先升级 Server。"),{rpcCode:-32602});const e=t.interaction;if(!e||e.scope?.requestId===void 0||e.scope.sessionId!==void 0)throw Object.assign(new Error("Invalid request-scoped elicitation"),{rpcCode:-32602});const s={...e,provider:m,runtimeId:w};let n;for(;!i.signal.aborted&&Date.now()<s.expiresAtMs;){try{if(n=(n?await r("GET",`/api/client/connection-interactions/${n.permission_id}`):await r("POST",`${a}/requests`,{interaction:s})).item,n.status!=="pending")return n.status==="resolved"&&n.response?n.response:{outcome:{outcome:"cancelled"}}}catch(c){if(i.signal.aborted)break;if(c instanceof f&&c.status<500)throw c}await y(Math.min(1e3,Math.max(1,s.expiresAtMs-Date.now())),void 0,{signal:i.signal}).catch(()=>{})}return{outcome:{outcome:"cancelled"}}},async complete(t){if(!o||i.signal.aborted)return;const e=r("POST",`${a}/complete`,t).catch(()=>{});u.add(e),await e,u.delete(e)},async close(){await Promise.allSettled([...u]),i.abort(),clearInterval(d),await g?.catch(()=>{}),o&&await r("POST",a,{action:"close"},!0).catch(()=>{})}}}export{P as createConnectionInteractions};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
class c{pending=new Map;begin(e,t,i,r){const n=e;if(!(n?.mode!=="url"||typeof n.elicitationId!="string")){for(const[s,o]of this.pending)o.expires<=Date.now()&&this.pending.delete(s);if(this.pending.has(n.elicitationId)||this.pending.size>=100)throw Object.assign(new Error("Duplicate or excessive outstanding elicitation IDs"),{rpcCode:-32602});return this.pending.set(n.elicitationId,{requestId:String(t),provider:i,expires:Date.now()+6e5,accepted:!1,completed:!1,notify:r??(()=>{})}),n.elicitationId}}settle(e,t){if(!e)return;const i=this.pending.get(e);if(i){if(t?.action!=="accept"){this.pending.delete(e);return}i.accepted=!0,i.completed&&this.deliver(e)}}complete(e){const t=e?.elicitationId;if(typeof t!="string")return;const i=this.pending.get(t);if(!i||i.expires<=Date.now()){this.pending.delete(t);return}i.completed=!0,i.accepted&&this.deliver(t)}deliver(e){const t=this.pending.get(e);this.pending.delete(e),t.notify({requestId:t.requestId,provider:t.provider,elicitationId:e})}}export{c as ElicitationTracker};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
const d=a=>a&&typeof a=="object"&&!Array.isArray(a)?a:{};function h(a){const u=t=>Object.fromEntries(Object.entries(d(t)).filter(([f,e])=>e!==null&&f!=="_meta")),s=u(a);if(s.type!==void 0&&s.type!=="object")throw new Error("Unsupported elicitation schema: expected an object");const l=new Set(["type","properties","required","title","description","$schema","additionalProperties"]);if(Object.keys(s).some(t=>!l.has(t))||s.additionalProperties!==void 0&&s.additionalProperties!==!1||s.required!==void 0&&!Array.isArray(s.required))throw new Error("Unsupported form root validation");const c=Array.isArray(s.required)?s.required:[];if(c.some(t=>typeof t!="string"||!Object.hasOwn(d(s.properties),t)))throw new Error("Unsupported required field");return Object.entries(d(s.properties)).map(([t,f])=>{if(["__proto__","constructor","prototype"].includes(t))throw new Error("Unsupported field ID");const e=u(f),i=e.type==="array"?"string[]":e.type;if(!["string","number","integer","boolean","string[]"].includes(String(i)))throw new Error(`Unsupported form field type: ${t}`);const g=new Set(["type","title","description","enum","enumNames","oneOf","anyOf","items","default","minLength","maxLength","minimum","maximum","minItems","maxItems","uniqueItems","format","pattern"]);for(const r of Object.keys(e))if(!g.has(r))throw new Error(`Unsupported form validation: ${t}.${r}`);const n=i==="string[]"?u(e.items):e;if(i==="string[]"&&(n.type!=="string"&&!(n.type===void 0&&Array.isArray(n.anyOf))||Object.keys(n).some(r=>!["type","enum","oneOf","anyOf","enumNames"].includes(r))))throw new Error(`Unsupported array item schema: ${t}`);if(["enum","oneOf","anyOf"].filter(r=>n[r]!==void 0).length>1)throw new Error(`Unsupported combined enum: ${t}`);for(const r of["enum","oneOf","anyOf"])if(n[r]!==void 0&&!Array.isArray(n[r]))throw new Error(`Unsupported enum schema: ${t}`);let m;if(Array.isArray(n.enum)){if(!n.enum.every(r=>typeof r=="string"))throw new Error(`Unsupported non-string enum: ${t}`);m=n.enum.map((r,o)=>({value:String(r),label:String(Array.isArray(n.enumNames)?n.enumNames[o]??r:r)}))}else(Array.isArray(n.oneOf)||Array.isArray(n.anyOf))&&(m=(Array.isArray(n.oneOf)?n.oneOf:n.anyOf).map(r=>{const o=u(r);if(typeof o.const!="string"||Object.keys(o).some(p=>!["const","title","description"].includes(p)))throw new Error(`Unsupported enum option: ${t}`);return{value:o.const,label:String(o.title||o.const),...typeof o.description=="string"?{description:o.description}:{}}}));if(i==="string[]"&&!m)throw new Error(`Unsupported free-form array: ${t}`);if(m&&(!m.length||new Set(m.map(r=>r.value)).size!==m.length||!["string","string[]"].includes(String(i))))throw new Error(`Unsupported enum field: ${t}`);if(e.format==="password")throw new Error("Unsupported secret in form mode; use URL mode");if(e.format&&!["email","uri","date","date-time"].includes(String(e.format)))throw new Error(`Unsupported form format: ${t}`);for(const r of["minLength","maxLength","minimum","maximum","minItems","maxItems"]){const o=e[r],p=["minimum","maximum"].includes(r)?["number","integer"].includes(String(i)):r.endsWith("Length")?i==="string":i==="string[]";if(o!==void 0&&(!p||typeof o!="number"||!Number.isFinite(o)||!["minimum","maximum"].includes(r)&&(!Number.isSafeInteger(o)||o<0)))throw new Error(`Unsupported form bound: ${t}.${r}`)}if(e.format!==void 0&&i!=="string"||e.items!==void 0&&i!=="string[]"||e.uniqueItems!==void 0&&(i!=="string[]"||typeof e.uniqueItems!="boolean"))throw new Error(`Unsupported form constraints: ${t}`);for(const[r,o]of[["minimum","maximum"],["minLength","maxLength"],["minItems","maxItems"]]){const p=e[r],y=e[o];if(typeof p=="number"&&typeof y=="number"&&p>y)throw new Error(`Unsupported inverted bounds: ${t}`)}if(e.pattern!==void 0){if(i!=="string"||typeof e.pattern!="string"||e.pattern.length>4096)throw new Error(`Unsupported form pattern: ${t}`);try{new RegExp(e.pattern,"u")}catch{throw new Error(`Unsupported invalid pattern: ${t}`)}}const w=Object.fromEntries(["minLength","maxLength","minimum","maximum","minItems","maxItems"].filter(r=>typeof e[r]=="number").map(r=>[r,e[r]]));return{id:t,label:typeof e.title=="string"&&e.title?e.title:t,description:typeof e.description=="string"?e.description:void 0,type:i,required:c.includes(t),options:m,...typeof e.pattern=="string"?{pattern:e.pattern}:{},...w,...typeof e.format=="string"?{format:e.format}:{},...e.default!==void 0?{defaultValue:e.default}:{}}})}export{h as schemaFields};
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{randomUUID as
|
|
1
|
+
import{randomUUID as I}from"node:crypto";import{setTimeout as x}from"node:timers/promises";import{acaServerRequest as l,AcaServerHttpError as M}from"../core/aca-server-client.js";import{INTERACTION_TIMEOUT_MS as E}from"./types.js";function U(m,s,u,a){const o=I(),h=new AbortController,n=AbortSignal.any([a,h.signal]),f=`/api/client/jobs/${encodeURIComponent(m)}`,S=new Set;let d=!1,p=0,g;const v=()=>g??=(async()=>{try{const t=await l("GET","/api/interactions/capabilities",void 0,{signal:n}),e=t.interactionVersions??[t.interactionVersion];p=e.includes(2)?2:e.includes(1)?1:0,d=p>=1}catch(t){if(!(t instanceof M)||t.status!==404)throw t}d&&await l("POST",`${f}/interactions/runtime`,{machineId:s,runtimeId:o},{signal:n})})();async function A(t,e){if(await v(),e&&!d)throw new Error("ACA Server 不支持结构化回答,请先更新 Server;本次请求未提交给旧接口。");const w=e?t.interaction:void 0;if(w&&w.version>p)throw new Error("ACA Server 不支持此交互版本,请先更新 Server。");const b=w?{...w,runtimeId:o}:{version:1,kind:"approval",provider:u,method:t.method??"session/request_permission",requestId:t.requestId,runtimeId:o,expiresAtMs:t.expiresAtMs??Date.now()+E,prompt:C(t.params),options:t.options},y={machineId:s,request:{...t,...e?{kind:"elicitation"}:{},...d?{interaction:b}:{}}};let i;for(;!n.aborted&&Date.now()<b.expiresAtMs;){try{const r={signal:n,timeoutMs:Math.max(1,Math.min(15e3,b.expiresAtMs-Date.now()))};if(i?i=(await l("GET",`/api/client/permissions/${encodeURIComponent(i.permission_id)}`,void 0,r)).item:i=(await l("POST",`${f}/permissions`,y,r)).item,i.status!=="pending"){const c=i.response?.outcome;return i.status==="resolved"&&c?.outcome==="selected"&&!e&&t.options.some(T=>T.optionId===c.optionId)?{outcome:c}:i.status==="resolved"&&(c?.outcome==="submitted"||c?.outcome==="declined")&&e?{outcome:c}:{outcome:{outcome:"cancelled"}}}}catch(r){if(n.aborted)break;if(r instanceof M&&r.status<500){if([404,409,410].includes(r.status))break;throw r}}await x(Math.min(1e3,Math.max(1,b.expiresAtMs-Date.now())),void 0,{signal:n}).catch(()=>{})}return{outcome:{outcome:"cancelled"}}}return{initialize:v,get elicitation(){return p>=2},permission:t=>A(t,!1),input:t=>A(t,!0),complete:async t=>{if(p<2||n.aborted)return;const e=l("POST",`${f}/interactions/complete`,{...t,runtimeId:o},{timeoutMs:5e3}).catch(()=>{});S.add(e),await e,S.delete(e)},close:async()=>{await Promise.allSettled([...S]),h.abort(),d&&await l("POST",`${f}/interactions/runtime`,{machineId:s,runtimeId:o,closed:!0},{timeoutMs:3e3}).catch(()=>{})}}}function C(m){const s=m&&typeof m=="object"?m:{},u=s.request??s,a=u.params??u;return[a.reason,a.description,a.command,a.title].filter(o=>typeof o=="string"&&o.trim()).join(`
|
|
2
2
|
`)||"Agent 请求继续执行。"}export{U as createJobInteractions};
|