@yuandc/aica 0.1.19 → 0.1.20

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- import{spawn as i}from"node:child_process";import{acpChildEnvironment as s}from"../../core/process-identity.js";function a(e,t,n){const r=i(e,t,{cwd:n,stdio:["pipe","pipe","pipe"],env:s()});return{command:e,args:t,child:r}}async function l(e){e.exitCode!==null||e.signalCode!==null||(e.stdin.end(),!await o(e,2e3)&&(e.kill("SIGTERM"),!await o(e,1e3)&&(e.kill("SIGKILL"),await o(e,1e3))))}function o(e,t){return e.exitCode!==null||e.signalCode!==null?Promise.resolve(!0):new Promise(n=>{const r=setTimeout(()=>n(!1),t);r.unref(),e.once("close",()=>{clearTimeout(r),n(!0)})})}export{l as closeAcpProcess,a as startAcpProcess};
1
+ import{spawn as i}from"node:child_process";import{acpChildEnvironment as s}from"../../core/process-identity.js";function l(e,t,n){const r=i(e,t,{cwd:n,stdio:["pipe","pipe","pipe"],env:s(e,t)});return{command:e,args:t,child:r}}async function a(e){e.exitCode!==null||e.signalCode!==null||(e.stdin.end(),!await o(e,2e3)&&(e.kill("SIGTERM"),!await o(e,1e3)&&(e.kill("SIGKILL"),await o(e,1e3))))}function o(e,t){return e.exitCode!==null||e.signalCode!==null?Promise.resolve(!0):new Promise(n=>{const r=setTimeout(()=>n(!1),t);r.unref(),e.once("close",()=>{clearTimeout(r),n(!0)})})}export{a as closeAcpProcess,l as startAcpProcess};
@@ -0,0 +1 @@
1
+ class n{maxItems;items=[];totalCount=0;constructor(e){this.maxItems=e}push(e){this.totalCount+=1,!(this.maxItems<=0)&&(this.items.push(e),this.items.length>this.maxItems&&this.items.splice(0,this.items.length-this.maxItems))}clear(){this.items.length=0,this.totalCount=0}count(){return this.totalCount}snapshot(){return[...this.items]}}function u(h,e,s){if(!e||s<=0)return s<=0?"":h;const t=h+e;if(Buffer.byteLength(t,"utf8")<=s)return t;let i=Math.max(0,t.length-s);for(;i<t.length&&Buffer.byteLength(t.slice(i),"utf8")>s;)i+=Math.max(1,Math.ceil((t.length-i)/16));return t.slice(i)}export{n as BoundedItemBuffer,u as appendBoundedText};
@@ -1,4 +1,4 @@
1
- class c{child;handlers;nextId=1;lineBuffer="";pending=new Map;peerName;includeJsonRpc;notifications=[];stdout="";stderr="";exitCode=null;signal=null;constructor(e,t={}){this.child=e,this.handlers=t,this.peerName=t.peerName||"JSON-RPC peer",this.includeJsonRpc=t.includeJsonRpc===!0,e.stdout.on("data",i=>this.handleStdout(String(i))),e.stderr.on("data",i=>{this.stderr+=String(i)}),e.on("close",(i,n)=>{this.exitCode=i,this.signal=n;for(const[r,o]of this.pending)clearTimeout(o.timer),o.reject(new Error(`${this.peerName} exited before ${o.method} completed (code=${i}, signal=${n})`)),this.pending.delete(r)}),e.on("error",i=>{for(const[n,r]of this.pending)clearTimeout(r.timer),r.reject(i),this.pending.delete(n)})}request(e,t,i=12e4){const n=this.nextId++;return new Promise((r,o)=>{const s=this.createRequestTimeout(n,e,i,o);s.unref(),this.pending.set(n,{method:e,resolve:r,reject:o,timer:s,timeoutMs:i}),this.write({...this.includeJsonRpc?{jsonrpc:"2.0"}:{},id:n,method:e,...t===void 0?{}:{params:t}})})}notify(e,t){this.write({...this.includeJsonRpc?{jsonrpc:"2.0"}:{},method:e,...t===void 0?{}:{params:t}})}refreshPendingRequestTimeout(e){for(const[t,i]of this.pending)i.method===e&&(clearTimeout(i.timer),i.timer=this.createRequestTimeout(t,i.method,i.timeoutMs,i.reject),i.timer.unref())}respond(e,t){this.write({...this.includeJsonRpc?{jsonrpc:"2.0"}:{},id:e,result:t})}respondError(e,t){this.write({...this.includeJsonRpc?{jsonrpc:"2.0"}:{},id:e,error:{code:-32603,message:t.message}})}createRequestTimeout(e,t,i,n){return setTimeout(()=>{this.pending.delete(e),n(new Error(`${this.peerName} request timed out while waiting for ${t}`))},i)}handleStdout(e){this.stdout+=e,this.lineBuffer+=e;const t=this.lineBuffer.split(`
2
- `);this.lineBuffer=t.pop()??"";for(const i of t){const n=i.trim();if(!n)continue;let r;try{r=JSON.parse(n)}catch{this.stderr+=`
3
- [non-json stdout] ${n}`;continue}this.handleMessage(r)}}async handleMessage(e){if("id"in e&&("result"in e||"error"in e)&&typeof e.method!="string"){const t=Number(e.id),i=this.pending.get(t);if(!i)return;if(this.pending.delete(t),clearTimeout(i.timer),"error"in e){const n=e.error;i.reject(new Error(n?.message||`${this.peerName} error ${n?.code??"unknown"}`))}else i.resolve(e.result);return}if(typeof e.method=="string"){if("id"in e){try{const t=await this.handlers.onRequest?.(e.method,e.params,e);this.respond(e.id,t??null)}catch(t){this.respondError(e.id,t instanceof Error?t:new Error(String(t)))}return}this.notifications.push(e),await this.handlers.onNotification?.(e.method,e.params,e)}}write(e){this.child.stdin.write(`${JSON.stringify(e)}
4
- `)}}export{c as JsonLineRpcClient};
1
+ import{appendBoundedText as u}from"./bounded-buffer.js";class s{child;handlers;static MAX_STDOUT_BYTES=1024*1024;static MAX_STDERR_BYTES=1024*1024;nextId=1;lineBuffer="";pending=new Map;peerName;includeJsonRpc;stdoutBuffer="";stderrBuffer="";notificationCount=0;exitCode=null;signal=null;constructor(t,e={}){this.child=t,this.handlers=e,this.peerName=e.peerName||"JSON-RPC peer",this.includeJsonRpc=e.includeJsonRpc===!0,t.stdout.on("data",r=>this.handleStdout(String(r))),t.stderr.on("data",r=>{this.stderr=this.stderr+String(r)}),t.on("close",(r,i)=>{this.exitCode=r,this.signal=i;for(const[n,o]of this.pending)clearTimeout(o.timer),o.reject(new Error(`${this.peerName} exited before ${o.method} completed (code=${r}, signal=${i})`)),this.pending.delete(n)}),t.on("error",r=>{for(const[i,n]of this.pending)clearTimeout(n.timer),n.reject(r),this.pending.delete(i)})}get stdout(){return this.stdoutBuffer}get stderr(){return this.stderrBuffer}set stderr(t){this.stderrBuffer=u("",t,s.MAX_STDERR_BYTES)}request(t,e,r=12e4){const i=this.nextId++;return new Promise((n,o)=>{const d=this.createRequestTimeout(i,t,r,o);d.unref(),this.pending.set(i,{method:t,resolve:n,reject:o,timer:d,timeoutMs:r}),this.write({...this.includeJsonRpc?{jsonrpc:"2.0"}:{},id:i,method:t,...e===void 0?{}:{params:e}})})}notify(t,e){this.write({...this.includeJsonRpc?{jsonrpc:"2.0"}:{},method:t,...e===void 0?{}:{params:e}})}refreshPendingRequestTimeout(t){for(const[e,r]of this.pending)r.method===t&&(clearTimeout(r.timer),r.timer=this.createRequestTimeout(e,r.method,r.timeoutMs,r.reject),r.timer.unref())}respond(t,e){this.write({...this.includeJsonRpc?{jsonrpc:"2.0"}:{},id:t,result:e})}respondError(t,e){this.write({...this.includeJsonRpc?{jsonrpc:"2.0"}:{},id:t,error:{code:-32603,message:e.message}})}createRequestTimeout(t,e,r,i){return setTimeout(()=>{this.pending.delete(t),i(new Error(`${this.peerName} request timed out while waiting for ${e}`))},r)}handleStdout(t){this.stdoutBuffer=u(this.stdoutBuffer,t,s.MAX_STDOUT_BYTES),this.lineBuffer+=t;const e=this.lineBuffer.split(`
2
+ `);this.lineBuffer=e.pop()??"";for(const r of e){const i=r.trim();if(!i)continue;let n;try{n=JSON.parse(i)}catch{this.stderr+=`
3
+ [non-json stdout] ${i}`;continue}this.handleMessage(n)}}async handleMessage(t){if("id"in t&&("result"in t||"error"in t)&&typeof t.method!="string"){const e=Number(t.id),r=this.pending.get(e);if(!r)return;if(this.pending.delete(e),clearTimeout(r.timer),"error"in t){const i=t.error;r.reject(new Error(i?.message||`${this.peerName} error ${i?.code??"unknown"}`))}else r.resolve(t.result);return}if(typeof t.method=="string"){if("id"in t){try{const e=await this.handlers.onRequest?.(t.method,t.params,t);this.respond(t.id,e??null)}catch(e){this.respondError(t.id,e instanceof Error?e:new Error(String(e)))}return}this.notificationCount+=1,await this.handlers.onNotification?.(t.method,t.params,t)}}write(t){this.child.stdin.write(`${JSON.stringify(t)}
4
+ `)}}export{s as JsonLineRpcClient};
@@ -1 +1 @@
1
- import{spawn as p}from"node:child_process";import{acpChildEnvironment as n}from"../../../core/process-identity.js";import{resolveCodexPath as s}from"./codex-path.js";import{resolveCodexPath as v}from"./codex-path.js";function c(r){const o=s(),e=["app-server","--stdio"],t=p(o,e,{cwd:r,env:n({CODEX_PATH:o}),stdio:["pipe","pipe","pipe"]});return t.stdout.setEncoding("utf8"),t.stderr.setEncoding("utf8"),{command:o,args:e,child:t}}export{v as resolveCodexPath,c as startCodexAppServer};
1
+ import{spawn as p}from"node:child_process";import{acpChildEnvironment as n}from"../../../core/process-identity.js";import{resolveCodexPath as s}from"./codex-path.js";import{resolveCodexPath as v}from"./codex-path.js";function c(r){const o=s(),t=["app-server","--stdio"],e=p(o,t,{cwd:r,env:n(o,t,{CODEX_PATH:o}),stdio:["pipe","pipe","pipe"]});return e.stdout.setEncoding("utf8"),e.stderr.setEncoding("utf8"),{command:o,args:t,child:e}}export{v as resolveCodexPath,c as startCodexAppServer};
@@ -1 +1 @@
1
- import c from"node:fs";import{JsonLineRpcClient as u}from"../../client/json-rpc.js";import{startCodexAppServer as h}from"./codex-process.js";import{extractAssistantTextFromTurn as p,mapCodexNotification as m}from"./events.js";import{CodexPlanDeltaState as f}from"./plan-events.js";import{isCodexApprovalRequest as g,resolveCodexApprovalRequest as C}from"./permissions.js";const w=1500,v={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 k{id="native-codex";name="Native Codex";capabilities=v;async createSession(e){return new x(e.cwd,e.providerSessionId??null,e.timeoutMs)}}class x{cwd;providerSessionId;defaultTimeoutMs;started;rpc;initialized=!1;content="";promptActive=!1;updates=[];statusTimeline=[];statusCallback=null;updateCallback=null;permissionCallback=null;abortHandler=null;completedTurns=new Map;pendingTurnCompletions=new Map;agentMessagePhases=new Map;planDeltas=new f;constructor(e,s,t=M()){this.cwd=e,this.providerSessionId=s,this.defaultTimeoutMs=t,this.started=h(e),this.rpc=new u(this.started.child,{onNotification:(n,r)=>this.handleNotification(n,r),onRequest:(n,r)=>this.handleReverseRequest(n,r)})}async sendPrompt(e){const s=Date.now(),t=l(e.mode);this.statusCallback=e.onStatus??null,this.updateCallback=e.onUpdate??null,this.permissionCallback=e.onPermissionRequest??null,this.attachAbortSignal(e.signal),this.emitStatus("initializing","Codex 初始化中","initialize"),await this.initialize();const n=await this.establishThread(e);this.emitStatus("thinking","思考中"),this.content="",this.updates.length=0,this.agentMessagePhases.clear(),this.planDeltas.reset(),this.promptActive=!0;let r,a;try{r=await this.rpc.request("turn/start",{threadId:n,clientUserMessageId:`aca-${Date.now()}-${Math.random().toString(16).slice(2)}`,input:T(e.prompt,e.promptBlocks),cwd:e.cwd,...e.model?{model:e.model}:{},...S(e.configOptionValues)},e.timeoutMs??this.defaultTimeoutMs);const o=String(r?.turn?.id??"");a=o?await this.waitForTurnCompleted(o,e.timeoutMs??this.defaultTimeoutMs):r?.turn}finally{this.promptActive=!1}const d=this.content.trim()||p(a);return this.emitStatus("completed","已完成"),{providerSessionId:n,cliType:"builtin",agentType:"codex",effectiveConfig:t,command:this.started.command,args:this.started.args,cwd:this.cwd,exitCode:this.rpc.exitCode,signal:this.rpc.signal,durationMs:Date.now()-s,stdout:this.rpc.stdout,stderr:this.rpc.stderr,content:d,updates:this.updates,statusTimeline:this.statusTimeline,promptResponse:r}}async cancel(){try{this.providerSessionId&&this.started.child.exitCode===null&&this.started.child.signalCode===null&&await this.rpc.request("turn/interrupt",{threadId:this.providerSessionId},w).catch(()=>{})}finally{await this.close()}}async close(){this.statusCallback=null,this.updateCallback=null,this.permissionCallback=null;for(const e of this.pendingTurnCompletions.values())clearTimeout(e.timer),e.reject(new Error("Codex app-server session closed"));this.pendingTurnCompletions.clear(),this.abortHandler&&(this.abortHandler=null),await this.closeCodexProcess()}async closeCodexProcess(){this.started.child.exitCode!==null||this.started.child.signalCode!==null||(this.started.child.stdin.end(),!await this.waitForProcessClose(2e3)&&(this.started.child.kill("SIGTERM"),!await this.waitForProcessClose(1e3)&&(this.started.child.kill("SIGKILL"),await this.waitForProcessClose(1e3))))}waitForProcessClose(e){return this.started.child.exitCode!==null||this.started.child.signalCode!==null?Promise.resolve(!0):new Promise(s=>{const t=setTimeout(()=>s(!1),e);t.unref(),this.started.child.once("close",()=>{clearTimeout(t),s(!0)})})}async initialize(){this.initialized||(await this.rpc.request("initialize",{clientInfo:{name:"aca",version:"0.1.0"},capabilities:{experimentalApi:!0,requestAttestation:!1}}),this.initialized=!0)}async establishThread(e){const s=l(e.mode);if(this.providerSessionId)try{this.emitStatus("resuming","Codex 会话恢复中","thread/resume");const r=await this.rpc.request("thread/resume",{threadId:this.providerSessionId,cwd:e.cwd,...e.model?{model:e.model}:{},approvalsReviewer:"user",...s},6e4);return this.providerSessionId=String(r?.thread?.id??this.providerSessionId),this.providerSessionId}catch(r){if(e.requireSessionResume){const a=r instanceof Error?r.message:String(r);throw new Error(`无法恢复导入的 Codex Session ${this.providerSessionId}:${a}`)}this.providerSessionId=null}this.emitStatus("acp","Codex 启动中","thread/start");const n=(await this.rpc.request("thread/start",{cwd:e.cwd,...e.model?{model:e.model}:{},approvalsReviewer:"user",...s,ephemeral:!1,threadSource:"aca"},6e4))?.thread?.id;if(!n)throw new Error("Codex app-server thread/start did not return thread id");return this.providerSessionId=n,n}attachAbortSignal(e){if(e){if(this.abortHandler=()=>{this.cancel()},e.aborted)throw new Error("ACP prompt cancelled");e.addEventListener("abort",this.abortHandler,{once:!0})}}handleNotification(e,s){!this.promptActive&&!I(e)||(this.rpc.refreshPendingRequestTimeout("turn/start"),this.updates.push({method:e,params:s}),this.captureAgentMessagePhase(e,s),this.appendFinalAnswerDelta(e,s),e==="turn/completed"&&this.resolveTurnCompleted(s),m(e,s,{pushUpdate:t=>this.updateCallback?.(t),pushStatus:(t,n,r,a)=>this.emitStatus(t,n,r,a)},{agentMessagePhases:this.agentMessagePhases,planDeltas:this.planDeltas}))}captureAgentMessagePhase(e,s){if(e!=="item/started"&&e!=="item/completed")return;const t=s?.item;t?.type!=="agentMessage"||!t.id||this.agentMessagePhases.set(t.id,t.phase??"")}appendFinalAnswerDelta(e,s){if(e!=="item/agentMessage/delta")return;const t=s;if(!t.itemId||typeof t.delta!="string")return;const n=this.agentMessagePhases.get(t.itemId);(n==="final_answer"||!n)&&(this.content+=t.delta)}waitForTurnCompleted(e,s){const t=this.completedTurns.get(e);return t?Promise.resolve(t):new Promise((n,r)=>{const a=setTimeout(()=>{this.pendingTurnCompletions.delete(e),r(new Error("Codex app-server request timed out while waiting for turn/completed"))},s);a.unref(),this.pendingTurnCompletions.set(e,{resolve:n,reject:r,timer:a})})}resolveTurnCompleted(e){const s=e?.turn,t=s?.id;if(!t)return;this.completedTurns.set(t,s);const n=this.pendingTurnCompletions.get(t);if(n){if(this.pendingTurnCompletions.delete(t),clearTimeout(n.timer),s.status==="failed"){n.reject(new Error(`Codex turn failed: ${JSON.stringify(s.error??{})}`));return}n.resolve(s)}}async handleReverseRequest(e,s){return g(e)?(this.emitStatus("requestPermission","等待授权",y(s),e),C(e,s,this.permissionCallback)):{}}emitStatus(e,s,t,n){const r={phase:e,label:s,...t?{detail:t}:{},...n?{updateType:n}:{},atMs:Date.now()},a=this.statusTimeline[this.statusTimeline.length-1];a&&a.phase===r.phase&&a.label===r.label&&a.detail===r.detail&&a.updateType===r.updateType||(this.statusTimeline.push(r),this.statusCallback?.(r))}}function T(i,e){return(e?.length?e:[{type:"text",text:i}]).map(t=>t.type==="image"?t.uri&&c.existsSync(t.uri)?{type:"localImage",path:t.uri}:{type:"image",url:`data:${t.mimeType};base64,${t.data}`}:{type:"text",text:t.text,text_elements:[]})}function l(i){const e=String(i||"").trim();return e==="read-only"?{approvalPolicy:"on-request",sandbox:"read-only"}:e==="acceptEdits"||e==="accept-edits"?{approvalPolicy:"on-failure",sandbox:"workspace-write"}:["agent-full-access","full-access","bypassPermissions","danger-full-access"].includes(e)?{approvalPolicy:"never",sandbox:"danger-full-access"}:{approvalPolicy:"on-request",sandbox:"workspace-write"}}function S(i){if(!i)return{};const e={},s=i.reasoning_effort??i.reasoningEffort??i.model_reasoning_effort??i.modelReasoningEffort??i.effort;return typeof s=="string"&&s&&(e.effort=s),e}function y(i){if(!i||typeof i!="object"||Array.isArray(i))return;const e=i;for(const s of["command","cwd","reason","grantRoot"]){const t=e[s];if(typeof t=="string"&&t.trim())return P(t)}}function P(i){const e=i.replace(/\s+/g," ").trim();return e.length>120?`${e.slice(0,117)}...`:e}function I(i){return i.startsWith("thread/")?!0:["account/rateLimits/updated","account/updated","configWarning","warning","model/rerouted","thread/compacted"].includes(i)}function M(){const i=Number.parseInt(process.env.ACA_ACP_PROMPT_TIMEOUT_MS??"",10);return Number.isInteger(i)&&i>=3e4?i:360*60*1e3}export{k as NativeCodexProvider,v as nativeCodexCapabilities};
1
+ import c from"node:fs";import{BoundedItemBuffer as u}from"../../client/bounded-buffer.js";import{JsonLineRpcClient as p}from"../../client/json-rpc.js";import{startCodexAppServer as h}from"./codex-process.js";import{extractAssistantTextFromTurn as m,mapCodexNotification as f}from"./events.js";import{CodexPlanDeltaState as g}from"./plan-events.js";import{isCodexApprovalRequest as C,resolveCodexApprovalRequest as w}from"./permissions.js";const v=1500,x={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 z{id="native-codex";name="Native Codex";capabilities=x;async createSession(e){return new T(e.cwd,e.providerSessionId??null,e.timeoutMs)}}class T{cwd;providerSessionId;defaultTimeoutMs;started;rpc;initialized=!1;content="";promptActive=!1;updates=new u(16);statusTimeline=[];statusCallback=null;updateCallback=null;permissionCallback=null;abortHandler=null;completedTurns=new Map;pendingTurnCompletions=new Map;agentMessagePhases=new Map;planDeltas=new g;constructor(e,s,t=b()){this.cwd=e,this.providerSessionId=s,this.defaultTimeoutMs=t,this.started=h(e),this.rpc=new p(this.started.child,{onNotification:(n,r)=>this.handleNotification(n,r),onRequest:(n,r)=>this.handleReverseRequest(n,r)})}async sendPrompt(e){const s=Date.now(),t=l(e.mode);this.statusCallback=e.onStatus??null,this.updateCallback=e.onUpdate??null,this.permissionCallback=e.onPermissionRequest??null,this.attachAbortSignal(e.signal),this.emitStatus("initializing","Codex 初始化中","initialize"),await this.initialize();const n=await this.establishThread(e);this.emitStatus("thinking","思考中"),this.content="",this.updates.clear(),this.agentMessagePhases.clear(),this.planDeltas.reset(),this.promptActive=!0;let r,a;try{r=await this.rpc.request("turn/start",{threadId:n,clientUserMessageId:`aca-${Date.now()}-${Math.random().toString(16).slice(2)}`,input:S(e.prompt,e.promptBlocks),cwd:e.cwd,...e.model?{model:e.model}:{},...y(e.configOptionValues)},e.timeoutMs??this.defaultTimeoutMs);const o=String(r?.turn?.id??"");a=o?await this.waitForTurnCompleted(o,e.timeoutMs??this.defaultTimeoutMs):r?.turn}finally{this.promptActive=!1}const d=this.content.trim()||m(a);return this.emitStatus("completed","已完成"),{providerSessionId:n,cliType:"builtin",agentType:"codex",effectiveConfig:t,command:this.started.command,args:this.started.args,cwd:this.cwd,exitCode:this.rpc.exitCode,signal:this.rpc.signal,durationMs:Date.now()-s,stdout:this.rpc.stdout,stderr:this.rpc.stderr,content:d,updateCount:this.updates.count(),updates:this.updates.snapshot(),statusTimeline:this.statusTimeline,promptResponse:r}}async cancel(){try{this.providerSessionId&&this.started.child.exitCode===null&&this.started.child.signalCode===null&&await this.rpc.request("turn/interrupt",{threadId:this.providerSessionId},v).catch(()=>{})}finally{await this.close()}}async close(){this.statusCallback=null,this.updateCallback=null,this.permissionCallback=null;for(const e of this.pendingTurnCompletions.values())clearTimeout(e.timer),e.reject(new Error("Codex app-server session closed"));this.pendingTurnCompletions.clear(),this.abortHandler&&(this.abortHandler=null),await this.closeCodexProcess()}async closeCodexProcess(){this.started.child.exitCode!==null||this.started.child.signalCode!==null||(this.started.child.stdin.end(),!await this.waitForProcessClose(2e3)&&(this.started.child.kill("SIGTERM"),!await this.waitForProcessClose(1e3)&&(this.started.child.kill("SIGKILL"),await this.waitForProcessClose(1e3))))}waitForProcessClose(e){return this.started.child.exitCode!==null||this.started.child.signalCode!==null?Promise.resolve(!0):new Promise(s=>{const t=setTimeout(()=>s(!1),e);t.unref(),this.started.child.once("close",()=>{clearTimeout(t),s(!0)})})}async initialize(){this.initialized||(await this.rpc.request("initialize",{clientInfo:{name:"aca",version:"0.1.0"},capabilities:{experimentalApi:!0,requestAttestation:!1}}),this.initialized=!0)}async establishThread(e){const s=l(e.mode);if(this.providerSessionId)try{this.emitStatus("resuming","Codex 会话恢复中","thread/resume");const r=await this.rpc.request("thread/resume",{threadId:this.providerSessionId,cwd:e.cwd,...e.model?{model:e.model}:{},approvalsReviewer:"user",...s},6e4);return this.providerSessionId=String(r?.thread?.id??this.providerSessionId),this.providerSessionId}catch(r){if(e.requireSessionResume){const a=r instanceof Error?r.message:String(r);throw new Error(`无法恢复导入的 Codex Session ${this.providerSessionId}:${a}`)}this.providerSessionId=null}this.emitStatus("acp","Codex 启动中","thread/start");const n=(await this.rpc.request("thread/start",{cwd:e.cwd,...e.model?{model:e.model}:{},approvalsReviewer:"user",...s,ephemeral:!1,threadSource:"aca"},6e4))?.thread?.id;if(!n)throw new Error("Codex app-server thread/start did not return thread id");return this.providerSessionId=n,n}attachAbortSignal(e){if(e){if(this.abortHandler=()=>{this.cancel()},e.aborted)throw new Error("ACP prompt cancelled");e.addEventListener("abort",this.abortHandler,{once:!0})}}handleNotification(e,s){!this.promptActive&&!M(e)||(this.rpc.refreshPendingRequestTimeout("turn/start"),this.updates.push({method:e,params:s}),this.captureAgentMessagePhase(e,s),this.appendFinalAnswerDelta(e,s),e==="turn/completed"&&this.resolveTurnCompleted(s),f(e,s,{pushUpdate:t=>this.updateCallback?.(t),pushStatus:(t,n,r,a)=>this.emitStatus(t,n,r,a)},{agentMessagePhases:this.agentMessagePhases,planDeltas:this.planDeltas}))}captureAgentMessagePhase(e,s){if(e!=="item/started"&&e!=="item/completed")return;const t=s?.item;t?.type!=="agentMessage"||!t.id||this.agentMessagePhases.set(t.id,t.phase??"")}appendFinalAnswerDelta(e,s){if(e!=="item/agentMessage/delta")return;const t=s;if(!t.itemId||typeof t.delta!="string")return;const n=this.agentMessagePhases.get(t.itemId);(n==="final_answer"||!n)&&(this.content+=t.delta)}waitForTurnCompleted(e,s){const t=this.completedTurns.get(e);return t?Promise.resolve(t):new Promise((n,r)=>{const a=setTimeout(()=>{this.pendingTurnCompletions.delete(e),r(new Error("Codex app-server request timed out while waiting for turn/completed"))},s);a.unref(),this.pendingTurnCompletions.set(e,{resolve:n,reject:r,timer:a})})}resolveTurnCompleted(e){const s=e?.turn,t=s?.id;if(!t)return;this.completedTurns.set(t,s);const n=this.pendingTurnCompletions.get(t);if(n){if(this.pendingTurnCompletions.delete(t),clearTimeout(n.timer),s.status==="failed"){n.reject(new Error(`Codex turn failed: ${JSON.stringify(s.error??{})}`));return}n.resolve(s)}}async handleReverseRequest(e,s){return C(e)?(this.emitStatus("requestPermission","等待授权",P(s),e),w(e,s,this.permissionCallback)):{}}emitStatus(e,s,t,n){const r={phase:e,label:s,...t?{detail:t}:{},...n?{updateType:n}:{},atMs:Date.now()},a=this.statusTimeline[this.statusTimeline.length-1];a&&a.phase===r.phase&&a.label===r.label&&a.detail===r.detail&&a.updateType===r.updateType||(this.statusTimeline.push(r),this.statusCallback?.(r))}}function S(i,e){return(e?.length?e:[{type:"text",text:i}]).map(t=>t.type==="image"?t.uri&&c.existsSync(t.uri)?{type:"localImage",path:t.uri}:{type:"image",url:`data:${t.mimeType};base64,${t.data}`}:{type:"text",text:t.text,text_elements:[]})}function l(i){const e=String(i||"").trim();return e==="read-only"?{approvalPolicy:"on-request",sandbox:"read-only"}:e==="acceptEdits"||e==="accept-edits"?{approvalPolicy:"on-failure",sandbox:"workspace-write"}:["agent-full-access","full-access","bypassPermissions","danger-full-access"].includes(e)?{approvalPolicy:"never",sandbox:"danger-full-access"}:{approvalPolicy:"on-request",sandbox:"workspace-write"}}function y(i){if(!i)return{};const e={},s=i.reasoning_effort??i.reasoningEffort??i.model_reasoning_effort??i.modelReasoningEffort??i.effort;return typeof s=="string"&&s&&(e.effort=s),e}function P(i){if(!i||typeof i!="object"||Array.isArray(i))return;const e=i;for(const s of["command","cwd","reason","grantRoot"]){const t=e[s];if(typeof t=="string"&&t.trim())return I(t)}}function I(i){const e=i.replace(/\s+/g," ").trim();return e.length>120?`${e.slice(0,117)}...`:e}function M(i){return i.startsWith("thread/")?!0:["account/rateLimits/updated","account/updated","configWarning","warning","model/rerouted","thread/compacted"].includes(i)}function b(){const i=Number.parseInt(process.env.ACA_ACP_PROMPT_TIMEOUT_MS??"",10);return Number.isInteger(i)&&i>=3e4?i:360*60*1e3}export{z as NativeCodexProvider,x as nativeCodexCapabilities};
@@ -1 +1 @@
1
- import d from"node:fs";import{promptBlocksToAcpContent as I}from"../../client/acp-content.js";import{eventFromAcpSessionUpdate as v,statusFromAcpSessionUpdate as A}from"../../client/acp-events.js";import{defaultAcpRuntimePool as w}from"../../client/acp-runtime-pool.js";import{resolveCodexAcpLaunch as S}from"./launch.js";import{CodexPromptPerformanceTracker as x,readCodexContextMaintenancePolicy as P}from"./context-maintenance.js";const k={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 F{id="codex-acp";name="Codex ACP";capabilities=k;async createSession(e){return new M(e.cwd,e.providerSessionId??null,e.timeoutMs)}async forkSession(e){if(!d.existsSync(e.cwd)||!d.statSync(e.cwd).isDirectory())throw new Error(`Project root does not exist or is not a directory: ${e.cwd}`);const s=e.sourceProviderSessionId.trim(),t=await w.acquire({providerId:"codex-acp",cwd:e.cwd,providerSessionId:s,...S(),onSessionUpdate:()=>{}});try{t.initialized||(await t.client.initialize(6e4),t.markInitialized()),e.onStatus?.({phase:"forking",label:"正在分叉 Codex 会话",detail:s,updateType:"session/fork",atMs:Date.now()});const r=await t.client.forkSession({sessionId:s,cwd:e.cwd,config:g(e)},e.timeoutMs??12e4),n=p(r);if(!n)throw new Error("Codex ACP session/fork did not return sessionId");return{providerSessionId:n}}finally{t.release()}}}class M{cwd;providerSessionId;defaultTimeoutMs;lease=null;sessionResponse=null;statusCallback=null;updateCallback=null;permissionCallback=null;userInputCallback=null;abortHandler=null;statusTimeline=[];updates=[];content="";performanceTracker=null;constructor(e,s,t=q()){this.cwd=e,this.providerSessionId=s,this.defaultTimeoutMs=t}async sendPrompt(e){if(!d.existsSync(e.cwd)||!d.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.attachAbortSignal(e.signal),this.content="",this.updates.length=0,this.statusTimeline.length=0,this.performanceTracker=new x(s);let t=null,r=null,n=this.providerSessionId,i,c=null;try{this.emitStatus("initializing","Codex ACP 启动中","codex-acp");const a=await this.acquireRuntime(e.cwd);this.lease=a,t=a.client,r=a.started,await this.initialize(a),n=await this.establishSession(a,e),await this.applyConfig(t,n,e),this.emitStatus("thinking","思考中"),c=this.startSlowStatusTimer(),i=await this.promptWithAbort(t,{sessionId:n,prompt:I(e.prompt,e.promptBlocks),timeoutMs:e.timeoutMs??this.defaultTimeoutMs,signal:e.signal});const u=b(i);u&&u!==n&&(n=u,this.providerSessionId=u,a.setProviderSession({providerSessionId:u,sessionResponse:this.sessionResponse}))}finally{c&&clearInterval(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:"codex",effectiveConfig:{model:e.model??null,mode:e.mode??null,configOptionValues:e.configOptionValues??null,command:r.command,processPid:r.child.pid??null,provider:"codex-acp",protocol:"acp"},command:r.command,args:r.args,cwd:e.cwd,exitCode:r.child.exitCode,signal:r.child.signalCode,durationMs:Date.now()-s,stdout:t.rpc.stdout,stderr:t.rpc.stderr,content:this.content.trim(),updates:[...this.updates],statusTimeline:[...this.statusTimeline],performance:this.performanceTracker?.snapshot(),contextUsage:U(i),promptResponse:i}}async compactContext(e){if(!d.existsSync(e.cwd)||!d.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.attachAbortSignal(e.signal);let t=this.providerSessionId,r;try{this.emitStatus("initializing","Codex ACP 启动中","codex-acp");const i=await this.acquireRuntime(e.cwd);this.lease=i,await this.initialize(i),t=await this.establishSession(i,e),await this.applyConfig(i.client,t,e);const c=E(e.timeoutMs);r=await this.compactWithAbort(i.client,{sessionId:t,allowRollover:e.allowRollover===!0,timeoutMs:c,signal:e.signal});const a=b(r);a&&a!==t&&(t=a,this.providerSessionId=a,i.setProviderSession({providerSessionId:a,sessionResponse:this.sessionResponse}))}catch(i){if(this.lease&&T(i,e.signal)){const c=this.lease;this.lease=null,await c.dispose().catch(()=>{})}throw this.emitStatus("failed","上下文压缩失败",i instanceof Error?i.message:String(i)),i}finally{this.abortHandler&&e.signal&&(e.signal.removeEventListener("abort",this.abortHandler),this.abortHandler=null),this.releaseLease()}const n=l(r)??{};return{providerSessionId:t||this.providerSessionId||"",contextUsage:C(n.contextUsage),maintenance:l(n.maintenance)??{},durationMs:Date.now()-s}}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.abortHandler&&(this.abortHandler=null),this.releaseLease()}async acquireRuntime(e){const s=S();return w.acquire({providerId:"codex-acp",cwd:e,providerSessionId:this.providerSessionId,command:s.command,args:s.args,onSessionUpdate:t=>this.handleSessionUpdate(t),onClientRequest:async(t,r)=>{if(t==="session/request_permission")return this.requestPermission(r);if(t==="elicitation/create"||t==="session/request_user_input")return this.requestUserInput(r);throw new Error(`Unsupported Codex ACP client request: ${t}`)}})}async initialize(e){e.initialized||(await e.client.initialize(6e4),e.markInitialized())}async establishSession(e,s){const t=e.client,r=g(s);if(e.providerSessionId&&e.sessionEstablished)return this.providerSessionId=e.providerSessionId,this.sessionResponse=e.sessionResponse,e.providerSessionId;if(this.providerSessionId)try{this.emitStatus("resuming","Codex ACP 会话恢复中","session/load");const c=await t.loadSession({sessionId:this.providerSessionId,cwd:s.cwd,config:r},6e4);this.sessionResponse=l(c);const a=p(c)||this.providerSessionId;return this.providerSessionId=a,e.setProviderSession({providerSessionId:a,sessionResponse:this.sessionResponse}),a}catch(c){if(s.requireSessionResume){const a=c instanceof Error?c.message:String(c);throw new Error(`无法恢复导入的 Codex ACP Session ${this.providerSessionId}:${a}`)}this.providerSessionId=null}this.emitStatus("acp","Codex ACP 会话创建中","session/new");const n=await t.newSession({cwd:s.cwd,config:r},12e4);this.sessionResponse=l(n);const i=p(n);if(!i)throw new Error("Codex ACP session/new did not return sessionId");return this.providerSessionId=i,e.setProviderSession({providerSessionId:i,sessionResponse:this.sessionResponse}),i}async applyConfig(e,s,t){t.model&&await e.setSessionConfigOption({sessionId:s,configId:"model",value:t.model},6e4).catch(()=>{});const r=f(t.mode);r&&await e.setSessionConfigOption({sessionId:s,configId:"approvalPolicy",value:r},6e4).catch(()=>{});const n=h(t.mode);n&&await e.setSessionConfigOption({sessionId:s,configId:"sandbox",value:n},6e4).catch(()=>{});for(const[i,c]of Object.entries(t.configOptionValues??{}))["model","mode","sandbox","approvalPolicy"].includes(i)||await e.setSessionConfigOption({sessionId:s,configId:i,value:c},6e4).catch(()=>{})}handleSessionUpdate(e){this.performanceTracker?.recordEvent(),this.updates.push({method:"session/update",params:e});const s=v(e);s&&(s.type==="agent_message_chunk"&&(this.content+=s.text??""),this.updateCallback?.(s));const t=A(e);t&&this.emitStatus(t.phase,t.label,t.detail,t.updateType)}startSlowStatusTimer(){const e=P().slowEventThresholdMs,s=setInterval(()=>{const t=this.performanceTracker,r=Date.now();if(!t?.shouldReportSlow(r,e))return;const n=t.snapshot(r),i=Math.max(1,Math.round(n.silentForMs/6e4));this.emitStatus("thinking","模型长时间处理中",`已 ${i} 分钟没有收到新的 ACP 事件`,"aca.performance.slow")},Math.min(3e4,Math.max(1e3,Math.floor(e/4))));return s.unref(),s}attachAbortSignal(e){if(e){if(this.abortHandler=()=>{this.cancel()},e.aborted)throw new Error("Codex ACP prompt cancelled");e.addEventListener("abort",this.abortHandler,{once:!0})}}async promptWithAbort(e,s){const t=e.prompt({sessionId:s.sessionId,prompt:s.prompt},s.timeoutMs);if(!s.signal)return t;if(s.signal.aborted)throw await this.lease?.dispose().catch(()=>{}),new Error("Codex ACP prompt cancelled");let r=null;const n=new Promise((i,c)=>{r=()=>{e.cancel(s.sessionId),this.lease?.dispose().catch(()=>{}),c(new Error("Codex ACP prompt cancelled"))},s.signal?.addEventListener("abort",r,{once:!0})});try{return await Promise.race([t,n])}finally{r&&s.signal.removeEventListener("abort",r)}}async compactWithAbort(e,s){const t=e.compactSession({sessionId:s.sessionId,allowRollover:s.allowRollover,timeoutMs:s.timeoutMs},s.timeoutMs);if(!s.signal)return t;if(s.signal.aborted)throw await this.lease?.dispose().catch(()=>{}),new Error("Codex ACP 上下文压缩已取消");let r=null;const n=new Promise((i,c)=>{r=()=>{e.cancel(s.sessionId),this.lease?.dispose().catch(()=>{}),c(new Error("Codex ACP 上下文压缩已取消"))},s.signal?.addEventListener("abort",r,{once:!0})});try{return await Promise.race([t,n])}finally{r&&s.signal.removeEventListener("abort",r)}}async requestPermission(e){if(!this.permissionCallback)return{outcome:{outcome:"cancelled"}};const s=e&&typeof e=="object"&&!Array.isArray(e)?e:{},t=s.request&&typeof s.request=="object"&&!Array.isArray(s.request)?s.request:s,n=(Array.isArray(t.options)?t.options:[]).map((i,c)=>{const a=i&&typeof i=="object"&&!Array.isArray(i)?i:{};return{optionId:String(a.optionId??a.id??c),kind:String(a.kind??"choice"),...typeof a.label=="string"?{label:a.label}:{}}});return this.emitStatus("requestPermission","等待授权",_(t),"session/request_permission"),this.permissionCallback({requestId:String(t.requestId??t.id??`codex-acp-permission-${Date.now()}`),params:e,options:n})}async requestUserInput(e){if(!this.userInputCallback)return{outcome:{outcome:"cancelled"}};const s=e&&typeof e=="object"&&!Array.isArray(e)?e:{},t=s.request&&typeof s.request=="object"&&!Array.isArray(s.request)?s.request:s;return this.emitStatus("requestPermission","等待输入",R(t),"elicitation/create"),this.userInputCallback({requestId:String(t.requestId??t.id??`codex-acp-input-${Date.now()}`),prompt:String(t.prompt??t.message??t.question??""),params:e,...typeof t.defaultValue=="string"?{defaultValue:t.defaultValue}:{}})}emitStatus(e,s,t,r){const n={phase:e,label:s,...t?{detail:t}:{},...r?{updateType:r}:{},atMs:Date.now()},i=this.statusTimeline[this.statusTimeline.length-1];i&&i.phase===n.phase&&i.label===n.label&&i.detail===n.detail&&i.updateType===n.updateType||(this.statusTimeline.push(n),this.statusCallback?.(n))}releaseLease(){this.lease?.release(),this.lease=null}}function T(o,e){if(e?.aborted)return!0;const s=o instanceof Error?o.message:String(o||"");return/compact.*timed out|request timed out.*compact|adapter closed|aborted|cancelled/i.test(s)}function p(o){if(!o||typeof o!="object"||Array.isArray(o))return null;const e=o.sessionId;return typeof e=="string"&&e.trim()?e.trim():null}function l(o){return o&&typeof o=="object"&&!Array.isArray(o)?o:null}function h(o){const e=String(o||"").trim();return e?e==="read-only"?"read-only":["agent-full-access","full-access","bypassPermissions","danger-full-access"].includes(e)?"danger-full-access":"workspace-write":null}function f(o){const e=String(o||"").trim();return e?["agent-full-access","full-access","bypassPermissions","danger-full-access"].includes(e)?"never":e==="acceptEdits"||e==="accept-edits"?"on-failure":"on-request":null}function g(o){return{...o.model?{model:o.model}:{},...f(o.mode)?{approvalPolicy:f(o.mode)}:{},...h(o.mode)?{sandbox:h(o.mode)}:{},...o.configOptionValues??{}}}function _(o){const e=o.params&&typeof o.params=="object"&&!Array.isArray(o.params)?o.params:o;for(const s of["command","cwd","reason","grantRoot","method"]){const t=e[s];if(typeof t=="string"&&t.trim())return y(t)}}function R(o){for(const e of["prompt","message","question","title"]){const s=o[e];if(typeof s=="string"&&s.trim())return y(s)}}function y(o){const e=o.replace(/\s+/g," ").trim();return e.length>120?`${e.slice(0,117)}...`:e}function q(){const o=Number.parseInt(process.env.ACA_CODEX_ACP_PROMPT_TIMEOUT_MS??process.env.ACA_ACP_PROMPT_TIMEOUT_MS??"",10);return Number.isInteger(o)&&o>=3e4?o:360*60*1e3}function E(o){const e=Number.isInteger(o)?Number(o):Number.parseInt(process.env.ACA_CODEX_ACP_COMPACT_TIMEOUT_MS??"",10);return Number.isFinite(e)&&e>=3e4?Math.min(e,6e5):12e4}function b(o){if(!o||typeof o!="object"||Array.isArray(o))return null;const e=o;return typeof e.sessionId=="string"&&e.sessionId.trim()?e.sessionId:null}function U(o){const e=l(o),s=l(e?._meta);return C(s?.acaContextUsage)}function C(o){const e=l(o);if(!e)return null;const s=m(e.contextWindow),t=m(e.usedTokens),r=m(e.inputTokens),n=String(e.state||"unknown"),i=["active","compacted","new_thread"].includes(n)?n:"unknown";return{usedTokens:t,inputTokens:r,contextWindow:s,ratio:s>0?Math.max(0,m(e.ratio)||t/s):0,observedAtMs:m(e.observedAtMs)||Date.now(),state:i,maintenanceAction:typeof e.maintenanceAction=="string"?e.maintenanceAction:null}}function m(o){return typeof o=="number"&&Number.isFinite(o)&&o>=0?o:0}export{F as CodexAcpProvider,k as codexAcpCapabilities};
1
+ import d from"node:fs";import{BoundedItemBuffer as I}from"../../client/bounded-buffer.js";import{promptBlocksToAcpContent as v}from"../../client/acp-content.js";import{eventFromAcpSessionUpdate as A,statusFromAcpSessionUpdate as x}from"../../client/acp-events.js";import{defaultAcpRuntimePool as w}from"../../client/acp-runtime-pool.js";import{resolveCodexAcpLaunch as S}from"./launch.js";import{CodexPromptPerformanceTracker as P,readCodexContextMaintenancePolicy as k}from"./context-maintenance.js";const M={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 N{id="codex-acp";name="Codex ACP";capabilities=M;async createSession(e){return new T(e.cwd,e.providerSessionId??null,e.timeoutMs)}async forkSession(e){if(!d.existsSync(e.cwd)||!d.statSync(e.cwd).isDirectory())throw new Error(`Project root does not exist or is not a directory: ${e.cwd}`);const s=e.sourceProviderSessionId.trim(),t=await w.acquire({providerId:"codex-acp",cwd:e.cwd,providerSessionId:s,...S(),onSessionUpdate:()=>{}});try{t.initialized||(await t.client.initialize(6e4),t.markInitialized()),e.onStatus?.({phase:"forking",label:"正在分叉 Codex 会话",detail:s,updateType:"session/fork",atMs:Date.now()});const r=await t.client.forkSession({sessionId:s,cwd:e.cwd,config:g(e)},e.timeoutMs??12e4),n=p(r);if(!n)throw new Error("Codex ACP session/fork did not return sessionId");return{providerSessionId:n}}finally{t.release()}}}class T{cwd;providerSessionId;defaultTimeoutMs;lease=null;sessionResponse=null;statusCallback=null;updateCallback=null;permissionCallback=null;userInputCallback=null;abortHandler=null;statusTimeline=[];updates=new I(16);content="";performanceTracker=null;constructor(e,s,t=E()){this.cwd=e,this.providerSessionId=s,this.defaultTimeoutMs=t}async sendPrompt(e){if(!d.existsSync(e.cwd)||!d.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.attachAbortSignal(e.signal),this.content="",this.updates.clear(),this.statusTimeline.length=0,this.performanceTracker=new P(s);let t=null,r=null,n=this.providerSessionId,i,c=null;try{this.emitStatus("initializing","Codex ACP 启动中","codex-acp");const a=await this.acquireRuntime(e.cwd);this.lease=a,t=a.client,r=a.started,await this.initialize(a),n=await this.establishSession(a,e),await this.applyConfig(t,n,e),this.emitStatus("thinking","思考中"),c=this.startSlowStatusTimer(),i=await this.promptWithAbort(t,{sessionId:n,prompt:v(e.prompt,e.promptBlocks),timeoutMs:e.timeoutMs??this.defaultTimeoutMs,signal:e.signal});const u=b(i);u&&u!==n&&(n=u,this.providerSessionId=u,a.setProviderSession({providerSessionId:u,sessionResponse:this.sessionResponse}))}finally{c&&clearInterval(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:"codex",effectiveConfig:{model:e.model??null,mode:e.mode??null,configOptionValues:e.configOptionValues??null,command:r.command,processPid:r.child.pid??null,provider:"codex-acp",protocol:"acp"},command:r.command,args:r.args,cwd:e.cwd,exitCode:r.child.exitCode,signal:r.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],performance:this.performanceTracker?.snapshot(),contextUsage:O(i),promptResponse:i}}async compactContext(e){if(!d.existsSync(e.cwd)||!d.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.attachAbortSignal(e.signal);let t=this.providerSessionId,r;try{this.emitStatus("initializing","Codex ACP 启动中","codex-acp");const i=await this.acquireRuntime(e.cwd);this.lease=i,await this.initialize(i),t=await this.establishSession(i,e),await this.applyConfig(i.client,t,e);const c=U(e.timeoutMs);r=await this.compactWithAbort(i.client,{sessionId:t,allowRollover:e.allowRollover===!0,timeoutMs:c,signal:e.signal});const a=b(r);a&&a!==t&&(t=a,this.providerSessionId=a,i.setProviderSession({providerSessionId:a,sessionResponse:this.sessionResponse}))}catch(i){if(this.lease&&_(i,e.signal)){const c=this.lease;this.lease=null,await c.dispose().catch(()=>{})}throw this.emitStatus("failed","上下文压缩失败",i instanceof Error?i.message:String(i)),i}finally{this.abortHandler&&e.signal&&(e.signal.removeEventListener("abort",this.abortHandler),this.abortHandler=null),this.releaseLease()}const n=l(r)??{};return{providerSessionId:t||this.providerSessionId||"",contextUsage:C(n.contextUsage),maintenance:l(n.maintenance)??{},durationMs:Date.now()-s}}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.abortHandler&&(this.abortHandler=null),this.releaseLease()}async acquireRuntime(e){const s=S();return w.acquire({providerId:"codex-acp",cwd:e,providerSessionId:this.providerSessionId,command:s.command,args:s.args,onSessionUpdate:t=>this.handleSessionUpdate(t),onClientRequest:async(t,r)=>{if(t==="session/request_permission")return this.requestPermission(r);if(t==="elicitation/create"||t==="session/request_user_input")return this.requestUserInput(r);throw new Error(`Unsupported Codex ACP client request: ${t}`)}})}async initialize(e){e.initialized||(await e.client.initialize(6e4),e.markInitialized())}async establishSession(e,s){const t=e.client,r=g(s);if(e.providerSessionId&&e.sessionEstablished)return this.providerSessionId=e.providerSessionId,this.sessionResponse=e.sessionResponse,e.providerSessionId;if(this.providerSessionId)try{this.emitStatus("resuming","Codex ACP 会话恢复中","session/load");const c=await t.loadSession({sessionId:this.providerSessionId,cwd:s.cwd,config:r},6e4);this.sessionResponse=l(c);const a=p(c)||this.providerSessionId;return this.providerSessionId=a,e.setProviderSession({providerSessionId:a,sessionResponse:this.sessionResponse}),a}catch(c){if(s.requireSessionResume){const a=c instanceof Error?c.message:String(c);throw new Error(`无法恢复导入的 Codex ACP Session ${this.providerSessionId}:${a}`)}this.providerSessionId=null}this.emitStatus("acp","Codex ACP 会话创建中","session/new");const n=await t.newSession({cwd:s.cwd,config:r},12e4);this.sessionResponse=l(n);const i=p(n);if(!i)throw new Error("Codex ACP session/new did not return sessionId");return this.providerSessionId=i,e.setProviderSession({providerSessionId:i,sessionResponse:this.sessionResponse}),i}async applyConfig(e,s,t){t.model&&await e.setSessionConfigOption({sessionId:s,configId:"model",value:t.model},6e4).catch(()=>{});const r=f(t.mode);r&&await e.setSessionConfigOption({sessionId:s,configId:"approvalPolicy",value:r},6e4).catch(()=>{});const n=h(t.mode);n&&await e.setSessionConfigOption({sessionId:s,configId:"sandbox",value:n},6e4).catch(()=>{});for(const[i,c]of Object.entries(t.configOptionValues??{}))["model","mode","sandbox","approvalPolicy"].includes(i)||await e.setSessionConfigOption({sessionId:s,configId:i,value:c},6e4).catch(()=>{})}handleSessionUpdate(e){this.performanceTracker?.recordEvent(),this.updates.push({method:"session/update",params:e});const s=A(e);s&&(s.type==="agent_message_chunk"&&(this.content+=s.text??""),this.updateCallback?.(s));const t=x(e);t&&this.emitStatus(t.phase,t.label,t.detail,t.updateType)}startSlowStatusTimer(){const e=k().slowEventThresholdMs,s=setInterval(()=>{const t=this.performanceTracker,r=Date.now();if(!t?.shouldReportSlow(r,e))return;const n=t.snapshot(r),i=Math.max(1,Math.round(n.silentForMs/6e4));this.emitStatus("thinking","模型长时间处理中",`已 ${i} 分钟没有收到新的 ACP 事件`,"aca.performance.slow")},Math.min(3e4,Math.max(1e3,Math.floor(e/4))));return s.unref(),s}attachAbortSignal(e){if(e){if(this.abortHandler=()=>{this.cancel()},e.aborted)throw new Error("Codex ACP prompt cancelled");e.addEventListener("abort",this.abortHandler,{once:!0})}}async promptWithAbort(e,s){const t=e.prompt({sessionId:s.sessionId,prompt:s.prompt},s.timeoutMs);if(!s.signal)return t;if(s.signal.aborted)throw await this.lease?.dispose().catch(()=>{}),new Error("Codex ACP prompt cancelled");let r=null;const n=new Promise((i,c)=>{r=()=>{e.cancel(s.sessionId),this.lease?.dispose().catch(()=>{}),c(new Error("Codex ACP prompt cancelled"))},s.signal?.addEventListener("abort",r,{once:!0})});try{return await Promise.race([t,n])}finally{r&&s.signal.removeEventListener("abort",r)}}async compactWithAbort(e,s){const t=e.compactSession({sessionId:s.sessionId,allowRollover:s.allowRollover,timeoutMs:s.timeoutMs},s.timeoutMs);if(!s.signal)return t;if(s.signal.aborted)throw await this.lease?.dispose().catch(()=>{}),new Error("Codex ACP 上下文压缩已取消");let r=null;const n=new Promise((i,c)=>{r=()=>{e.cancel(s.sessionId),this.lease?.dispose().catch(()=>{}),c(new Error("Codex ACP 上下文压缩已取消"))},s.signal?.addEventListener("abort",r,{once:!0})});try{return await Promise.race([t,n])}finally{r&&s.signal.removeEventListener("abort",r)}}async requestPermission(e){if(!this.permissionCallback)return{outcome:{outcome:"cancelled"}};const s=e&&typeof e=="object"&&!Array.isArray(e)?e:{},t=s.request&&typeof s.request=="object"&&!Array.isArray(s.request)?s.request:s,n=(Array.isArray(t.options)?t.options:[]).map((i,c)=>{const a=i&&typeof i=="object"&&!Array.isArray(i)?i:{};return{optionId:String(a.optionId??a.id??c),kind:String(a.kind??"choice"),...typeof a.label=="string"?{label:a.label}:{}}});return this.emitStatus("requestPermission","等待授权",R(t),"session/request_permission"),this.permissionCallback({requestId:String(t.requestId??t.id??`codex-acp-permission-${Date.now()}`),params:e,options:n})}async requestUserInput(e){if(!this.userInputCallback)return{outcome:{outcome:"cancelled"}};const s=e&&typeof e=="object"&&!Array.isArray(e)?e:{},t=s.request&&typeof s.request=="object"&&!Array.isArray(s.request)?s.request:s;return this.emitStatus("requestPermission","等待输入",q(t),"elicitation/create"),this.userInputCallback({requestId:String(t.requestId??t.id??`codex-acp-input-${Date.now()}`),prompt:String(t.prompt??t.message??t.question??""),params:e,...typeof t.defaultValue=="string"?{defaultValue:t.defaultValue}:{}})}emitStatus(e,s,t,r){const n={phase:e,label:s,...t?{detail:t}:{},...r?{updateType:r}:{},atMs:Date.now()},i=this.statusTimeline[this.statusTimeline.length-1];i&&i.phase===n.phase&&i.label===n.label&&i.detail===n.detail&&i.updateType===n.updateType||(this.statusTimeline.push(n),this.statusCallback?.(n))}releaseLease(){this.lease?.release(),this.lease=null}}function _(o,e){if(e?.aborted)return!0;const s=o instanceof Error?o.message:String(o||"");return/compact.*timed out|request timed out.*compact|adapter closed|aborted|cancelled/i.test(s)}function p(o){if(!o||typeof o!="object"||Array.isArray(o))return null;const e=o.sessionId;return typeof e=="string"&&e.trim()?e.trim():null}function l(o){return o&&typeof o=="object"&&!Array.isArray(o)?o:null}function h(o){const e=String(o||"").trim();return e?e==="read-only"?"read-only":["agent-full-access","full-access","bypassPermissions","danger-full-access"].includes(e)?"danger-full-access":"workspace-write":null}function f(o){const e=String(o||"").trim();return e?["agent-full-access","full-access","bypassPermissions","danger-full-access"].includes(e)?"never":e==="acceptEdits"||e==="accept-edits"?"on-failure":"on-request":null}function g(o){return{...o.model?{model:o.model}:{},...f(o.mode)?{approvalPolicy:f(o.mode)}:{},...h(o.mode)?{sandbox:h(o.mode)}:{},...o.configOptionValues??{}}}function R(o){const e=o.params&&typeof o.params=="object"&&!Array.isArray(o.params)?o.params:o;for(const s of["command","cwd","reason","grantRoot","method"]){const t=e[s];if(typeof t=="string"&&t.trim())return y(t)}}function q(o){for(const e of["prompt","message","question","title"]){const s=o[e];if(typeof s=="string"&&s.trim())return y(s)}}function y(o){const e=o.replace(/\s+/g," ").trim();return e.length>120?`${e.slice(0,117)}...`:e}function E(){const o=Number.parseInt(process.env.ACA_CODEX_ACP_PROMPT_TIMEOUT_MS??process.env.ACA_ACP_PROMPT_TIMEOUT_MS??"",10);return Number.isInteger(o)&&o>=3e4?o:360*60*1e3}function U(o){const e=Number.isInteger(o)?Number(o):Number.parseInt(process.env.ACA_CODEX_ACP_COMPACT_TIMEOUT_MS??"",10);return Number.isFinite(e)&&e>=3e4?Math.min(e,6e5):12e4}function b(o){if(!o||typeof o!="object"||Array.isArray(o))return null;const e=o;return typeof e.sessionId=="string"&&e.sessionId.trim()?e.sessionId:null}function O(o){const e=l(o),s=l(e?._meta);return C(s?.acaContextUsage)}function C(o){const e=l(o);if(!e)return null;const s=m(e.contextWindow),t=m(e.usedTokens),r=m(e.inputTokens),n=String(e.state||"unknown"),i=["active","compacted","new_thread"].includes(n)?n:"unknown";return{usedTokens:t,inputTokens:r,contextWindow:s,ratio:s>0?Math.max(0,m(e.ratio)||t/s):0,observedAtMs:m(e.observedAtMs)||Date.now(),state:i,maintenanceAction:typeof e.maintenanceAction=="string"?e.maintenanceAction:null}}function m(o){return typeof o=="number"&&Number.isFinite(o)&&o>=0?o:0}export{N as CodexAcpProvider,M as codexAcpCapabilities};
@@ -1,5 +1,5 @@
1
- import d from"node:fs";import u from"node:path";import{contextUsageFromAcpSessionUpdate as I}from"../../client/context-usage.js";import{defaultAcpRuntimePool as w}from"../../client/acp-runtime-pool.js";import{providerEventFromAcpSessionUpdate as b,providerStatusFromAcpSessionUpdate as A}from"../standard-acp-events.js";const M={sessionResume:!0,imageInput:!0,fileAttachment:!0,filesystem:!0,terminal:!0,permissionRequest:!0,configOptions:!0,usage:!0,contextUsage:!0,contextCompaction:!1,plan:!0,diff:!1};class V{id="mimocode";name="MimoCode";capabilities=M;async createSession(e){return new v(e.cwd,e.providerSessionId??null,e.timeoutMs)}}class v{cwd;providerSessionId;defaultTimeoutMs;lease=null;acpSessionResponse=null;statusCallback=null;updateCallback=null;permissionCallback=null;userInputCallback=null;canExecuteTools=!0;abortHandler=null;statusTimeline=[];updates=[];content="";latestContextUsage=null;contextWindow=0;constructor(e,t,s=E()){this.cwd=e,this.providerSessionId=t,this.defaultTimeoutMs=s}async sendPrompt(e){if(!d.existsSync(e.cwd)||!d.statSync(e.cwd).isDirectory())throw new Error(`Project root does not exist or is not a directory: ${e.cwd}`);const t=Date.now();this.statusCallback=e.onStatus??null,this.updateCallback=e.onUpdate??null,this.permissionCallback=e.onPermissionRequest??null,this.userInputCallback=e.onUserInputRequest??null,this.canExecuteTools=e.canExecuteTools!==!1,this.attachAbortSignal(e.signal),this.content="",this.updates.length=0,this.statusTimeline.length=0,this.latestContextUsage=null,this.contextWindow=x(e.contextWindow);let s=null,o=null,n=this.providerSessionId,a;try{this.emitStatus("initializing","MimoCode ACP 启动中","mimo acp");const r=await this.acquireRuntime(e.cwd);this.lease=r,s=r.client,o=r.started,await this.initialize(r),n=await this.establishSession(r,e.cwd),await this.applyConfig(s,n,e),this.emitStatus("thinking","思考中"),a=await s.prompt({sessionId:n,prompt:q(e.prompt,e.promptBlocks)},e.timeoutMs??this.defaultTimeoutMs)}finally{this.abortHandler&&e.signal&&(e.signal.removeEventListener("abort",this.abortHandler),this.abortHandler=null),this.releaseLease()}return this.emitStatus("completed","已完成"),{providerSessionId:n,cliType:"builtin",agentType:"mimo",effectiveConfig:{model:e.model||y(),mode:e.mode||null,configOptionValues:e.configOptionValues??null,command:o.command,processPid:o.child.pid??null,provider:"mimocode",protocol:"acp"},command:o.command,args:o.args,cwd:e.cwd,exitCode:o.child.exitCode,signal:o.child.signalCode,durationMs:Date.now()-t,stdout:s.rpc.stdout,stderr:s.rpc.stderr,content:this.content.trim(),updates:[...this.updates],statusTimeline:[...this.statusTimeline],contextUsage:this.latestContextUsage,promptResponse:a}}async cancel(){this.providerSessionId&&this.lease?.client.cancel(this.providerSessionId)}async close(){this.statusCallback=null,this.updateCallback=null,this.permissionCallback=null,this.userInputCallback=null,this.canExecuteTools=!0,this.abortHandler&&(this.abortHandler=null),this.releaseLease()}async acquireRuntime(e){const t=process.env.ACA_MIMO_COMMAND||"mimo",s=["acp","--cwd",e];return w.acquire({providerId:"mimocode",cwd:e,providerSessionId:this.providerSessionId,command:t,args:s,onSessionUpdate:o=>this.handleSessionUpdate(o),onClientRequest:(o,n)=>this.handleClientRequest(o,n)})}async initialize(e){e.initialized||(await e.client.initialize(6e4),e.markInitialized())}async establishSession(e,t){const s=e.client;if(e.providerSessionId&&e.sessionEstablished)return this.providerSessionId=e.providerSessionId,this.acpSessionResponse=e.sessionResponse,e.providerSessionId;if(this.providerSessionId)try{this.emitStatus("resuming","MimoCode 会话恢复中","session/resume");const a=await s.resumeSession({sessionId:this.providerSessionId,cwd:t},6e4);this.acpSessionResponse=f(a);const r=m(a)||this.providerSessionId;return this.providerSessionId=r,e.setProviderSession({providerSessionId:r,sessionResponse:this.acpSessionResponse}),r}catch(a){try{this.emitStatus("resuming","MimoCode 会话加载中","session/load");const r=await s.loadSession({sessionId:this.providerSessionId,cwd:t},6e4);this.acpSessionResponse=f(r);const c=m(r)||this.providerSessionId;return this.providerSessionId=c,e.setProviderSession({providerSessionId:c,sessionResponse:this.acpSessionResponse}),c}catch{this.appendStderr(`MimoCode resume failed, creating new session: ${p(a)}
2
- `),this.providerSessionId=null}}this.emitStatus("acp","MimoCode 会话创建中","session/new");const o=await s.newSession({cwd:t},12e4);this.acpSessionResponse=f(o);const n=m(o);if(!n)throw new Error("MimoCode ACP session/new did not return sessionId");return this.providerSessionId=n,e.setProviderSession({providerSessionId:n,sessionResponse:this.acpSessionResponse}),n}async applyConfig(e,t,s){const o=h(this.acpSessionResponse,"model")||"model",n=h(this.acpSessionResponse,"mode")||"mode",a=_(s.model||y(),s.configOptionValues,this.acpSessionResponse);a&&await e.setSessionConfigOption({sessionId:t,configId:o,value:a},6e4).catch(c=>{this.appendStderr(`MimoCode model config failed: ${p(c)}
3
- `)});const r=P(s.mode);r&&await e.setSessionMode({sessionId:t,modeId:r},6e4).catch(async()=>{await e.setSessionConfigOption({sessionId:t,configId:n,value:r},6e4).catch(c=>{this.appendStderr(`MimoCode mode config failed: ${p(c)}
1
+ import d from"node:fs";import u from"node:path";import{BoundedItemBuffer as I}from"../../client/bounded-buffer.js";import{contextUsageFromAcpSessionUpdate as w}from"../../client/context-usage.js";import{defaultAcpRuntimePool as b}from"../../client/acp-runtime-pool.js";import{providerEventFromAcpSessionUpdate as A,providerStatusFromAcpSessionUpdate as M}from"../standard-acp-events.js";const v={sessionResume:!0,imageInput:!0,fileAttachment:!0,filesystem:!0,terminal:!0,permissionRequest:!0,configOptions:!0,usage:!0,contextUsage:!0,contextCompaction:!1,plan:!0,diff:!1};class H{id="mimocode";name="MimoCode";capabilities=v;async createSession(e){return new x(e.cwd,e.providerSessionId??null,e.timeoutMs)}}class x{cwd;providerSessionId;defaultTimeoutMs;lease=null;acpSessionResponse=null;statusCallback=null;updateCallback=null;permissionCallback=null;userInputCallback=null;canExecuteTools=!0;abortHandler=null;statusTimeline=[];updates=new I(16);content="";latestContextUsage=null;contextWindow=0;constructor(e,t,s=O()){this.cwd=e,this.providerSessionId=t,this.defaultTimeoutMs=s}async sendPrompt(e){if(!d.existsSync(e.cwd)||!d.statSync(e.cwd).isDirectory())throw new Error(`Project root does not exist or is not a directory: ${e.cwd}`);const t=Date.now();this.statusCallback=e.onStatus??null,this.updateCallback=e.onUpdate??null,this.permissionCallback=e.onPermissionRequest??null,this.userInputCallback=e.onUserInputRequest??null,this.canExecuteTools=e.canExecuteTools!==!1,this.attachAbortSignal(e.signal),this.content="",this.updates.clear(),this.statusTimeline.length=0,this.latestContextUsage=null,this.contextWindow=_(e.contextWindow);let s=null,o=null,n=this.providerSessionId,a;try{this.emitStatus("initializing","MimoCode ACP 启动中","mimo acp");const r=await this.acquireRuntime(e.cwd);this.lease=r,s=r.client,o=r.started,await this.initialize(r),n=await this.establishSession(r,e.cwd),await this.applyConfig(s,n,e),this.emitStatus("thinking","思考中"),a=await s.prompt({sessionId:n,prompt:E(e.prompt,e.promptBlocks)},e.timeoutMs??this.defaultTimeoutMs)}finally{this.abortHandler&&e.signal&&(e.signal.removeEventListener("abort",this.abortHandler),this.abortHandler=null),this.releaseLease()}return this.emitStatus("completed","已完成"),{providerSessionId:n,cliType:"builtin",agentType:"mimo",effectiveConfig:{model:e.model||y(),mode:e.mode||null,configOptionValues:e.configOptionValues??null,command:o.command,processPid:o.child.pid??null,provider:"mimocode",protocol:"acp"},command:o.command,args:o.args,cwd:e.cwd,exitCode:o.child.exitCode,signal:o.child.signalCode,durationMs:Date.now()-t,stdout:s.rpc.stdout,stderr:s.rpc.stderr,content:this.content.trim(),updateCount:this.updates.count(),updates:this.updates.snapshot(),statusTimeline:[...this.statusTimeline],contextUsage:this.latestContextUsage,promptResponse:a}}async cancel(){this.providerSessionId&&this.lease?.client.cancel(this.providerSessionId)}async close(){this.statusCallback=null,this.updateCallback=null,this.permissionCallback=null,this.userInputCallback=null,this.canExecuteTools=!0,this.abortHandler&&(this.abortHandler=null),this.releaseLease()}async acquireRuntime(e){const t=process.env.ACA_MIMO_COMMAND||"mimo",s=["acp","--cwd",e];return b.acquire({providerId:"mimocode",cwd:e,providerSessionId:this.providerSessionId,command:t,args:s,onSessionUpdate:o=>this.handleSessionUpdate(o),onClientRequest:(o,n)=>this.handleClientRequest(o,n)})}async initialize(e){e.initialized||(await e.client.initialize(6e4),e.markInitialized())}async establishSession(e,t){const s=e.client;if(e.providerSessionId&&e.sessionEstablished)return this.providerSessionId=e.providerSessionId,this.acpSessionResponse=e.sessionResponse,e.providerSessionId;if(this.providerSessionId)try{this.emitStatus("resuming","MimoCode 会话恢复中","session/resume");const a=await s.resumeSession({sessionId:this.providerSessionId,cwd:t},6e4);this.acpSessionResponse=f(a);const r=m(a)||this.providerSessionId;return this.providerSessionId=r,e.setProviderSession({providerSessionId:r,sessionResponse:this.acpSessionResponse}),r}catch(a){try{this.emitStatus("resuming","MimoCode 会话加载中","session/load");const r=await s.loadSession({sessionId:this.providerSessionId,cwd:t},6e4);this.acpSessionResponse=f(r);const c=m(r)||this.providerSessionId;return this.providerSessionId=c,e.setProviderSession({providerSessionId:c,sessionResponse:this.acpSessionResponse}),c}catch{this.appendStderr(`MimoCode resume failed, creating new session: ${p(a)}
2
+ `),this.providerSessionId=null}}this.emitStatus("acp","MimoCode 会话创建中","session/new");const o=await s.newSession({cwd:t},12e4);this.acpSessionResponse=f(o);const n=m(o);if(!n)throw new Error("MimoCode ACP session/new did not return sessionId");return this.providerSessionId=n,e.setProviderSession({providerSessionId:n,sessionResponse:this.acpSessionResponse}),n}async applyConfig(e,t,s){const o=h(this.acpSessionResponse,"model")||"model",n=h(this.acpSessionResponse,"mode")||"mode",a=T(s.model||y(),s.configOptionValues,this.acpSessionResponse);a&&await e.setSessionConfigOption({sessionId:t,configId:o,value:a},6e4).catch(c=>{this.appendStderr(`MimoCode model config failed: ${p(c)}
3
+ `)});const r=k(s.mode);r&&await e.setSessionMode({sessionId:t,modeId:r},6e4).catch(async()=>{await e.setSessionConfigOption({sessionId:t,configId:n,value:r},6e4).catch(c=>{this.appendStderr(`MimoCode mode config failed: ${p(c)}
4
4
  `)})});for(const[c,g]of Object.entries(s.configOptionValues??{}))["variant","reasoning_effort","effort","model","mode"].includes(c)||await e.setSessionConfigOption({sessionId:t,configId:c,value:g},6e4).catch(C=>{this.appendStderr(`MimoCode config ${c} failed: ${p(C)}
5
- `)})}handleSessionUpdate(e){this.updates.push({method:"session/update",params:e}),this.latestContextUsage=I(e,this.contextWindow)??this.latestContextUsage;const t=b("mimo",e);t&&(t.type==="agent_message_chunk"&&(this.content+=t.text??""),this.updateCallback?.(t));const s=A("mimo",e);s&&this.emitStatus(s.phase,s.label,s.detail,s.updateType)}async handleClientRequest(e,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(t);case"fs/write_text_file":return this.writeTextFile(t);case"session/request_permission":return this.requestPermission(t);case"elicitation/create":case"session/request_user_input":return this.requestUserInput(t);default:throw new Error(`Unsupported ACP client request: ${e}`)}}readTextFile(e){const t=S(this.cwd,l(e,"path")||l(e,"filePath"));return{content:d.readFileSync(t,"utf8")}}writeTextFile(e){const t=S(this.cwd,l(e,"path")||l(e,"filePath")),s=l(e,"content")??l(e,"text")??"";return d.mkdirSync(u.dirname(t),{recursive:!0}),d.writeFileSync(t,s,"utf8"),{}}async requestPermission(e){if(!this.permissionCallback)return{outcome:{outcome:"cancelled"}};const t=e&&typeof e=="object"&&!Array.isArray(e)?e:{},o=(Array.isArray(t.options)?t.options:[]).map((n,a)=>{const r=n&&typeof n=="object"&&!Array.isArray(n)?n:{};return{optionId:String(r.optionId??r.id??a),kind:String(r.kind??r.outcome??"choice"),...typeof r.label=="string"?{label:r.label}:{}}});return this.permissionCallback({requestId:String(t.requestId??t.id??`mimo-permission-${Date.now()}`),params:e,options:o})}async requestUserInput(e){if(!this.userInputCallback)return{outcome:{outcome:"cancelled"}};const t=e&&typeof e=="object"&&!Array.isArray(e)?e:{};return this.userInputCallback({requestId:String(t.requestId??t.id??`mimo-input-${Date.now()}`),prompt:String(t.prompt??t.message??t.question??""),params:e,...typeof t.defaultValue=="string"?{defaultValue:t.defaultValue}:{}})}attachAbortSignal(e){if(e){if(this.abortHandler=()=>{this.cancel()},e.aborted)throw new Error("MimoCode prompt cancelled");e.addEventListener("abort",this.abortHandler,{once:!0})}}emitStatus(e,t,s,o){const n={phase:e,label:t,...s?{detail:s}:{},...o?{updateType:o}:{},atMs:Date.now()},a=this.statusTimeline[this.statusTimeline.length-1];a&&a.phase===n.phase&&a.label===n.label&&a.detail===n.detail&&a.updateType===n.updateType||(this.statusTimeline.push(n),this.statusCallback?.(n))}requireClient(){if(!this.lease)throw new Error("MimoCode ACP client is not started");return this.lease.client}appendStderr(e){const t=this.lease?.client;t&&(t.rpc.stderr+=e)}releaseLease(){this.lease?.release(),this.lease=null}}function m(i){if(!i||typeof i!="object"||Array.isArray(i))return null;const e=i.sessionId;return typeof e=="string"&&e.trim()?e.trim():null}function f(i){return i&&typeof i=="object"&&!Array.isArray(i)?i:null}function x(i){return typeof i=="number"&&Number.isFinite(i)&&i>0?i:0}function h(i,e){const t=i?.configOptions;if(!Array.isArray(t))return null;for(const s of t){if(!s||typeof s!="object"||Array.isArray(s))continue;const o=s;if(o.category===e&&typeof o.id=="string"&&o.id.trim())return o.id.trim()}return null}function _(i,e,t){const s=k(e?.variant,e?.reasoning_effort,e?.effort);if(!s||i.endsWith(`/${s}`))return i;const o=`${i}/${s}`;return T(t,o)?o:i}function T(i,e){const t=i?.configOptions;if(!Array.isArray(t))return!1;for(const s of t){if(!s||typeof s!="object"||Array.isArray(s))continue;const o=s;if(!(o.category!=="model"||!Array.isArray(o.options))&&o.options.some(n=>R(n)===e))return!0}return!1}function R(i){if(typeof i=="string"&&i.trim())return i.trim();if(!i||typeof i!="object"||Array.isArray(i))return null;const e=i;for(const t of["value","id","modelId","model_id"]){const s=e[t];if(typeof s=="string"&&s.trim())return s.trim()}return null}function l(i,e){if(!i||typeof i!="object"||Array.isArray(i))return null;const t=i[e];return typeof t=="string"&&t.trim()?t:null}function S(i,e){if(!e)throw new Error("ACP file request did not include a path");const t=u.resolve(i,e),s=u.resolve(i);if(t!==s&&!t.startsWith(`${s}${u.sep}`))throw new Error(`ACP file request is outside project root: ${e}`);return t}function P(i){const e=String(i||"").trim();return e?["agent","agent-full-access","full-access","bypassPermissions","danger-full-access"].includes(e)?"build":e==="read-only"?"plan":e:null}function k(...i){for(const e of i)if(typeof e=="string"&&e.trim())return e.trim();return null}function p(i){return i instanceof Error?i.message:String(i)}function y(){return process.env.ACA_MIMO_DEFAULT_MODEL||"mimo/mimo-auto"}function q(i,e){return(e?.length?e:[{type:"text",text:i}]).map(s=>s.type==="image"?{type:"image",data:s.data,mimeType:s.mimeType}:{type:"text",text:s.text})}function E(){const i=Number.parseInt(process.env.ACA_MIMO_PROMPT_TIMEOUT_MS??process.env.ACA_ACP_PROMPT_TIMEOUT_MS??"",10);return Number.isInteger(i)&&i>=3e4?i:360*60*1e3}export{V as MimoCodeProvider,M as mimoCodeCapabilities};
5
+ `)})}handleSessionUpdate(e){this.updates.push({method:"session/update",params:e}),this.latestContextUsage=w(e,this.contextWindow)??this.latestContextUsage;const t=A("mimo",e);t&&(t.type==="agent_message_chunk"&&(this.content+=t.text??""),this.updateCallback?.(t));const s=M("mimo",e);s&&this.emitStatus(s.phase,s.label,s.detail,s.updateType)}async handleClientRequest(e,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(t);case"fs/write_text_file":return this.writeTextFile(t);case"session/request_permission":return this.requestPermission(t);case"elicitation/create":case"session/request_user_input":return this.requestUserInput(t);default:throw new Error(`Unsupported ACP client request: ${e}`)}}readTextFile(e){const t=S(this.cwd,l(e,"path")||l(e,"filePath"));return{content:d.readFileSync(t,"utf8")}}writeTextFile(e){const t=S(this.cwd,l(e,"path")||l(e,"filePath")),s=l(e,"content")??l(e,"text")??"";return d.mkdirSync(u.dirname(t),{recursive:!0}),d.writeFileSync(t,s,"utf8"),{}}async requestPermission(e){if(!this.permissionCallback)return{outcome:{outcome:"cancelled"}};const t=e&&typeof e=="object"&&!Array.isArray(e)?e:{},o=(Array.isArray(t.options)?t.options:[]).map((n,a)=>{const r=n&&typeof n=="object"&&!Array.isArray(n)?n:{};return{optionId:String(r.optionId??r.id??a),kind:String(r.kind??r.outcome??"choice"),...typeof r.label=="string"?{label:r.label}:{}}});return this.permissionCallback({requestId:String(t.requestId??t.id??`mimo-permission-${Date.now()}`),params:e,options:o})}async requestUserInput(e){if(!this.userInputCallback)return{outcome:{outcome:"cancelled"}};const t=e&&typeof e=="object"&&!Array.isArray(e)?e:{};return this.userInputCallback({requestId:String(t.requestId??t.id??`mimo-input-${Date.now()}`),prompt:String(t.prompt??t.message??t.question??""),params:e,...typeof t.defaultValue=="string"?{defaultValue:t.defaultValue}:{}})}attachAbortSignal(e){if(e){if(this.abortHandler=()=>{this.cancel()},e.aborted)throw new Error("MimoCode prompt cancelled");e.addEventListener("abort",this.abortHandler,{once:!0})}}emitStatus(e,t,s,o){const n={phase:e,label:t,...s?{detail:s}:{},...o?{updateType:o}:{},atMs:Date.now()},a=this.statusTimeline[this.statusTimeline.length-1];a&&a.phase===n.phase&&a.label===n.label&&a.detail===n.detail&&a.updateType===n.updateType||(this.statusTimeline.push(n),this.statusCallback?.(n))}requireClient(){if(!this.lease)throw new Error("MimoCode ACP client is not started");return this.lease.client}appendStderr(e){const t=this.lease?.client;t&&(t.rpc.stderr+=e)}releaseLease(){this.lease?.release(),this.lease=null}}function m(i){if(!i||typeof i!="object"||Array.isArray(i))return null;const e=i.sessionId;return typeof e=="string"&&e.trim()?e.trim():null}function f(i){return i&&typeof i=="object"&&!Array.isArray(i)?i:null}function _(i){return typeof i=="number"&&Number.isFinite(i)&&i>0?i:0}function h(i,e){const t=i?.configOptions;if(!Array.isArray(t))return null;for(const s of t){if(!s||typeof s!="object"||Array.isArray(s))continue;const o=s;if(o.category===e&&typeof o.id=="string"&&o.id.trim())return o.id.trim()}return null}function T(i,e,t){const s=q(e?.variant,e?.reasoning_effort,e?.effort);if(!s||i.endsWith(`/${s}`))return i;const o=`${i}/${s}`;return R(t,o)?o:i}function R(i,e){const t=i?.configOptions;if(!Array.isArray(t))return!1;for(const s of t){if(!s||typeof s!="object"||Array.isArray(s))continue;const o=s;if(!(o.category!=="model"||!Array.isArray(o.options))&&o.options.some(n=>P(n)===e))return!0}return!1}function P(i){if(typeof i=="string"&&i.trim())return i.trim();if(!i||typeof i!="object"||Array.isArray(i))return null;const e=i;for(const t of["value","id","modelId","model_id"]){const s=e[t];if(typeof s=="string"&&s.trim())return s.trim()}return null}function l(i,e){if(!i||typeof i!="object"||Array.isArray(i))return null;const t=i[e];return typeof t=="string"&&t.trim()?t:null}function S(i,e){if(!e)throw new Error("ACP file request did not include a path");const t=u.resolve(i,e),s=u.resolve(i);if(t!==s&&!t.startsWith(`${s}${u.sep}`))throw new Error(`ACP file request is outside project root: ${e}`);return t}function k(i){const e=String(i||"").trim();return e?["agent","agent-full-access","full-access","bypassPermissions","danger-full-access"].includes(e)?"build":e==="read-only"?"plan":e:null}function q(...i){for(const e of i)if(typeof e=="string"&&e.trim())return e.trim();return null}function p(i){return i instanceof Error?i.message:String(i)}function y(){return process.env.ACA_MIMO_DEFAULT_MODEL||"mimo/mimo-auto"}function E(i,e){return(e?.length?e:[{type:"text",text:i}]).map(s=>s.type==="image"?{type:"image",data:s.data,mimeType:s.mimeType}:{type:"text",text:s.text})}function O(){const i=Number.parseInt(process.env.ACA_MIMO_PROMPT_TIMEOUT_MS??process.env.ACA_ACP_PROMPT_TIMEOUT_MS??"",10);return Number.isInteger(i)&&i>=3e4?i:360*60*1e3}export{H as MimoCodeProvider,v as mimoCodeCapabilities};
@@ -1,4 +1,4 @@
1
- import p from"node:fs";import h from"node:path";import{contextUsageFromAcpSessionUpdate as y}from"../../client/context-usage.js";import{defaultAcpRuntimePool as x}from"../../client/acp-runtime-pool.js";import{providerEventFromAcpSessionUpdate as E,providerStatusFromAcpSessionUpdate as O}from"../standard-acp-events.js";const P={sessionResume:!0,imageInput:!0,fileAttachment:!0,filesystem:!0,terminal:!0,permissionRequest:!0,configOptions:!0,usage:!0,contextUsage:!0,contextCompaction:!1,plan:!0,diff:!0};class V{id="opencode";name="OpenCode";capabilities=P;async createSession(e){return new T(e.cwd,e.providerSessionId??null,e.timeoutMs)}}class T{cwd;providerSessionId;defaultTimeoutMs;lease=null;sessionResponse=null;statusCallback=null;updateCallback=null;permissionCallback=null;userInputCallback=null;canExecuteTools=!0;abortHandler=null;statusTimeline=[];updates=[];content="";latestContextUsage=null;contextWindow=0;resumedExistingSession=!1;constructor(e,t,s=F()){this.cwd=e,this.providerSessionId=t,this.defaultTimeoutMs=s}async sendPrompt(e){if(!p.existsSync(e.cwd)||!p.statSync(e.cwd).isDirectory())throw new Error(`Project root does not exist or is not a directory: ${e.cwd}`);const t=Date.now();this.statusCallback=e.onStatus??null,this.updateCallback=e.onUpdate??null,this.permissionCallback=e.onPermissionRequest??null,this.userInputCallback=e.onUserInputRequest??null,this.canExecuteTools=e.canExecuteTools!==!1,this.attachAbortSignal(e.signal),this.content="",this.updates.length=0,this.statusTimeline.length=0,this.latestContextUsage=null,this.contextWindow=_(e.contextWindow);let s=null,n=null,o=this.providerSessionId,r;try{this.emitStatus("initializing","OpenCode ACP 启动中","opencode acp");const l=await this.acquireRuntime(e.cwd);this.lease=l,s=l.client,n=l.started,await this.initialize(l),o=await this.establishSession(l,e),await this.applyConfig(s,o,e),this.emitStatus("thinking","思考中");const a=U(e.prompt,e.promptBlocks);try{r=await this.promptWithAbort(s,{sessionId:o,prompt:a,timeoutMs:e.timeoutMs??this.defaultTimeoutMs,signal:e.signal})}catch(f){if(!this.resumedExistingSession||!k(f))throw f;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 u=await this.acquireRuntime(e.cwd);this.lease=u,s=u.client,n=u.started,await this.initialize(u),o=await this.establishSession(u,e),await this.applyConfig(s,o,e),this.emitStatus("thinking","思考中"),r=await this.promptWithAbort(s,{sessionId:o,prompt:a,timeoutMs:e.timeoutMs??this.defaultTimeoutMs,signal:e.signal})}}finally{this.abortHandler&&e.signal&&(e.signal.removeEventListener("abort",this.abortHandler),this.abortHandler=null),this.releaseLease()}return this.emitStatus("completed","已完成"),{providerSessionId:o,cliType:"builtin",agentType:"opencode",effectiveConfig:{model:m(e.model)||null,mode:e.mode||null,configOptionValues:e.configOptionValues??null,command:n.command,processPid:n.child.pid??null,provider:"opencode",protocol:"acp"},command:n.command,args:n.args,cwd:e.cwd,exitCode:n.child.exitCode,signal:n.child.signalCode,durationMs:Date.now()-t,stdout:s.rpc.stdout,stderr:s.rpc.stderr,content:this.content.trim(),updates:[...this.updates],statusTimeline:[...this.statusTimeline],contextUsage:this.latestContextUsage,promptResponse:r}}async cancel(){this.providerSessionId&&this.lease?.client.cancel(this.providerSessionId)}async close(){this.statusCallback=null,this.updateCallback=null,this.permissionCallback=null,this.userInputCallback=null,this.canExecuteTools=!0,this.abortHandler&&(this.abortHandler=null),this.releaseLease()}async acquireRuntime(e){const t=process.env.ACA_OPENCODE_COMMAND||"opencode";return x.acquire({providerId:"opencode",cwd:e,providerSessionId:this.providerSessionId,command:t,args:["acp","--cwd",e],onSessionUpdate:s=>this.handleSessionUpdate(s),onClientRequest:(s,n)=>this.handleClientRequest(s,n)})}async initialize(e){e.initialized||(await e.client.initialize(6e4),e.markInitialized())}async establishSession(e,t){const s=e.client,n=q(t);if(this.resumedExistingSession=!1,e.providerSessionId&&e.sessionEstablished){if(S(e.sessionResponse,t.model))return this.providerSessionId=e.providerSessionId,this.sessionResponse=e.sessionResponse,this.resumedExistingSession=!0,e.providerSessionId;this.providerSessionId=null}else if(this.providerSessionId)try{this.emitStatus("resuming","OpenCode 会话恢复中","session/load");const l=await s.loadSession({sessionId:this.providerSessionId,cwd:t.cwd},6e4);if(this.sessionResponse=d(l),S(this.sessionResponse,t.model)){const a=g(l)||this.providerSessionId;return this.providerSessionId=a,e.setProviderSession({providerSessionId:a,sessionResponse:this.sessionResponse}),this.resumedExistingSession=!0,a}this.appendStderr(`OpenCode model changed from ${C(this.sessionResponse)??"unknown"} to ${m(t.model)||"default"}, creating new session
1
+ import p from"node:fs";import m from"node:path";import{BoundedItemBuffer as y}from"../../client/bounded-buffer.js";import{contextUsageFromAcpSessionUpdate as x}from"../../client/context-usage.js";import{defaultAcpRuntimePool as E}from"../../client/acp-runtime-pool.js";import{providerEventFromAcpSessionUpdate as O,providerStatusFromAcpSessionUpdate as P}from"../standard-acp-events.js";const T={sessionResume:!0,imageInput:!0,fileAttachment:!0,filesystem:!0,terminal:!0,permissionRequest:!0,configOptions:!0,usage:!0,contextUsage:!0,contextCompaction:!1,plan:!0,diff:!0};class j{id="opencode";name="OpenCode";capabilities=T;async createSession(e){return new _(e.cwd,e.providerSessionId??null,e.timeoutMs)}}class _{cwd;providerSessionId;defaultTimeoutMs;lease=null;sessionResponse=null;statusCallback=null;updateCallback=null;permissionCallback=null;userInputCallback=null;canExecuteTools=!0;abortHandler=null;statusTimeline=[];updates=new y(16);content="";latestContextUsage=null;contextWindow=0;resumedExistingSession=!1;constructor(e,t,s=$()){this.cwd=e,this.providerSessionId=t,this.defaultTimeoutMs=s}async sendPrompt(e){if(!p.existsSync(e.cwd)||!p.statSync(e.cwd).isDirectory())throw new Error(`Project root does not exist or is not a directory: ${e.cwd}`);const t=Date.now();this.statusCallback=e.onStatus??null,this.updateCallback=e.onUpdate??null,this.permissionCallback=e.onPermissionRequest??null,this.userInputCallback=e.onUserInputRequest??null,this.canExecuteTools=e.canExecuteTools!==!1,this.attachAbortSignal(e.signal),this.content="",this.updates.clear(),this.statusTimeline.length=0,this.latestContextUsage=null,this.contextWindow=q(e.contextWindow);let s=null,n=null,o=this.providerSessionId,r;try{this.emitStatus("initializing","OpenCode ACP 启动中","opencode acp");const l=await this.acquireRuntime(e.cwd);this.lease=l,s=l.client,n=l.started,await this.initialize(l),o=await this.establishSession(l,e),await this.applyConfig(s,o,e),this.emitStatus("thinking","思考中");const a=D(e.prompt,e.promptBlocks);try{r=await this.promptWithAbort(s,{sessionId:o,prompt:a,timeoutMs:e.timeoutMs??this.defaultTimeoutMs,signal:e.signal})}catch(f){if(!this.resumedExistingSession||!U(f))throw f;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 u=await this.acquireRuntime(e.cwd);this.lease=u,s=u.client,n=u.started,await this.initialize(u),o=await this.establishSession(u,e),await this.applyConfig(s,o,e),this.emitStatus("thinking","思考中"),r=await this.promptWithAbort(s,{sessionId:o,prompt:a,timeoutMs:e.timeoutMs??this.defaultTimeoutMs,signal:e.signal})}}finally{this.abortHandler&&e.signal&&(e.signal.removeEventListener("abort",this.abortHandler),this.abortHandler=null),this.releaseLease()}return this.emitStatus("completed","已完成"),{providerSessionId:o,cliType:"builtin",agentType:"opencode",effectiveConfig:{model:h(e.model)||null,mode:e.mode||null,configOptionValues:e.configOptionValues??null,command:n.command,processPid:n.child.pid??null,provider:"opencode",protocol:"acp"},command:n.command,args:n.args,cwd:e.cwd,exitCode:n.child.exitCode,signal:n.child.signalCode,durationMs:Date.now()-t,stdout:s.rpc.stdout,stderr:s.rpc.stderr,content:this.content.trim(),updateCount:this.updates.count(),updates:this.updates.snapshot(),statusTimeline:[...this.statusTimeline],contextUsage:this.latestContextUsage,promptResponse:r}}async cancel(){this.providerSessionId&&this.lease?.client.cancel(this.providerSessionId)}async close(){this.statusCallback=null,this.updateCallback=null,this.permissionCallback=null,this.userInputCallback=null,this.canExecuteTools=!0,this.abortHandler&&(this.abortHandler=null),this.releaseLease()}async acquireRuntime(e){const t=process.env.ACA_OPENCODE_COMMAND||"opencode";return E.acquire({providerId:"opencode",cwd:e,providerSessionId:this.providerSessionId,command:t,args:["acp","--cwd",e],onSessionUpdate:s=>this.handleSessionUpdate(s),onClientRequest:(s,n)=>this.handleClientRequest(s,n)})}async initialize(e){e.initialized||(await e.client.initialize(6e4),e.markInitialized())}async establishSession(e,t){const s=e.client,n=R(t);if(this.resumedExistingSession=!1,e.providerSessionId&&e.sessionEstablished){if(S(e.sessionResponse,t.model))return this.providerSessionId=e.providerSessionId,this.sessionResponse=e.sessionResponse,this.resumedExistingSession=!0,e.providerSessionId;this.providerSessionId=null}else if(this.providerSessionId)try{this.emitStatus("resuming","OpenCode 会话恢复中","session/load");const l=await s.loadSession({sessionId:this.providerSessionId,cwd:t.cwd},6e4);if(this.sessionResponse=d(l),S(this.sessionResponse,t.model)){const a=g(l)||this.providerSessionId;return this.providerSessionId=a,e.setProviderSession({providerSessionId:a,sessionResponse:this.sessionResponse}),this.resumedExistingSession=!0,a}this.appendStderr(`OpenCode model changed from ${C(this.sessionResponse)??"unknown"} to ${h(t.model)||"default"}, creating new session
3
3
  `),this.providerSessionId=null}catch{this.providerSessionId=null}this.emitStatus("acp","OpenCode 会话创建中","session/new");const o=await s.newSession({cwd:t.cwd,config:n},12e4);this.sessionResponse=d(o);const r=g(o);if(!r)throw new Error("OpenCode ACP session/new did not return sessionId");return this.providerSessionId=r,e.setProviderSession({providerSessionId:r,sessionResponse:this.sessionResponse}),r}async applyConfig(e,t,s){const n=w(s.mode);n&&await e.setSessionMode({sessionId:t,modeId:n},6e4).catch(o=>{this.appendStderr(`OpenCode mode config failed: ${v(o)}
4
- `)})}handleSessionUpdate(e){this.updates.push({method:"session/update",params:e}),this.latestContextUsage=y(e,this.contextWindow)??this.latestContextUsage;const t=E("opencode",e);t&&(t.type==="agent_message_chunk"&&(this.content+=t.text??""),this.updateCallback?.(t));const s=O("opencode",e);s&&this.emitStatus(s.phase,s.label,s.detail,s.updateType)}async handleClientRequest(e,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(t);case"fs/write_text_file":return this.writeTextFile(t);case"session/request_permission":return this.requestPermission(t);case"elicitation/create":case"session/request_user_input":return this.requestUserInput(t);default:throw new Error(`Unsupported OpenCode ACP client request: ${e}`)}}readTextFile(e){const t=I(this.cwd,c(e,"path")||c(e,"filePath"));return{content:p.readFileSync(t,"utf8")}}writeTextFile(e){const t=I(this.cwd,c(e,"path")||c(e,"filePath")),s=c(e,"content")??c(e,"text")??"";return p.mkdirSync(h.dirname(t),{recursive:!0}),p.writeFileSync(t,s,"utf8"),{}}attachAbortSignal(e){if(e){if(this.abortHandler=()=>{this.cancel()},e.aborted)throw new Error("OpenCode prompt cancelled");e.addEventListener("abort",this.abortHandler,{once:!0})}}async promptWithAbort(e,t){const s=e.prompt({sessionId:t.sessionId,prompt:t.prompt},t.timeoutMs);if(!t.signal)return s;if(t.signal.aborted)throw await this.lease?.dispose().catch(()=>{}),new Error("OpenCode prompt cancelled");let n=null;const o=new Promise((r,l)=>{n=()=>{e.cancel(t.sessionId),this.lease?.dispose().catch(()=>{}),l(new Error("OpenCode prompt cancelled"))},t.signal?.addEventListener("abort",n,{once:!0})});try{return await Promise.race([s,o])}finally{n&&t.signal.removeEventListener("abort",n)}}async requestPermission(e){if(!this.permissionCallback)return{outcome:{outcome:"cancelled"}};const t=d(e)??{},s=d(t.request)??t,o=(Array.isArray(s.options)?s.options:[]).map((r,l)=>{const a=d(r)??{};return{optionId:String(a.optionId??a.id??l),kind:String(a.kind??a.outcome??"choice"),...typeof a.label=="string"?{label:a.label}:{}}});return this.emitStatus("requestPermission","等待授权",A(s),"session/request_permission"),this.permissionCallback({requestId:String(s.requestId??s.id??`opencode-permission-${Date.now()}`),params:e,options:o})}async requestUserInput(e){if(!this.userInputCallback)return{outcome:{outcome:"cancelled"}};const t=d(e)??{},s=d(t.request)??t;return this.emitStatus("requestPermission","等待输入",M(s),"elicitation/create"),this.userInputCallback({requestId:String(s.requestId??s.id??`opencode-input-${Date.now()}`),prompt:String(s.prompt??s.message??s.question??""),params:e,...typeof s.defaultValue=="string"?{defaultValue:s.defaultValue}:{}})}emitStatus(e,t,s,n){const o={phase:e,label:t,...s?{detail:s}:{},...n?{updateType:n}:{},atMs:Date.now()},r=this.statusTimeline[this.statusTimeline.length-1];r&&r.phase===o.phase&&r.label===o.label&&r.detail===o.detail&&r.updateType===o.updateType||(this.statusTimeline.push(o),this.statusCallback?.(o))}appendStderr(e){const t=this.lease?.client;t&&(t.rpc.stderr+=e)}releaseLease(){this.lease?.release(),this.lease=null}}function g(i){const t=d(i)?.sessionId;return typeof t=="string"&&t.trim()?t.trim():null}function d(i){return i&&typeof i=="object"&&!Array.isArray(i)?i:null}function _(i){return typeof i=="number"&&Number.isFinite(i)&&i>0?i:0}function q(i){const e=m(i.model),t=w(i.mode);return{...R(i.configOptionValues),...e?{model:e}:{},...t?{mode:t}:{}}}function R(i){const e={};for(const[t,s]of Object.entries(i??{}))["model","mode","reasoning_effort","effort","variant"].includes(t)||(e[t]=s);return e}function S(i,e){const t=m(e);if(!t)return!0;const s=C(i);return!s||s===t}function C(i){const t=d(i?.models)?.currentModelId;return typeof t=="string"&&t.trim()?t.trim():null}function w(i){const e=String(i||"").trim();return e?["agent-full-access","full-access","bypassPermissions","danger-full-access"].includes(e)?"build":e==="read-only"?"plan":e:null}function c(i,e){const s=d(i)?.[e];return typeof s=="string"&&s.trim()?s:null}function I(i,e){if(!e)throw new Error("ACP file request did not include a path");const t=h.resolve(i,e),s=h.resolve(i);if(t!==s&&!t.startsWith(`${s}${h.sep}`))throw new Error(`ACP file request is outside project root: ${e}`);return t}function A(i){const e=d(i.params)??i;for(const t of["command","cwd","reason","grantRoot","method"]){const s=e[t];if(typeof s=="string"&&s.trim())return b(s)}}function M(i){for(const e of["prompt","message","question","title"]){const t=i[e];if(typeof t=="string"&&t.trim())return b(t)}}function b(i){const e=i.replace(/\s+/g," ").trim();return e.length>120?`${e.slice(0,117)}...`:e}function v(i){return i instanceof Error?i.message:String(i)}function k(i){return/\binvalid params\b/i.test(v(i))}function U(i,e){return(e?.length?e:[{type:"text",text:i}]).map(s=>s.type==="image"?{type:"image",data:s.data,mimeType:s.mimeType}:{type:"text",text:s.text})}function D(){return process.env.ACA_OPENCODE_DEFAULT_MODEL||""}function m(i){const e=String(i||D()).trim();return e==="default"||e==="opencode-default"?"":e}function F(){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:360*60*1e3}export{V as OpenCodeProvider,P as openCodeCapabilities};
4
+ `)})}handleSessionUpdate(e){this.updates.push({method:"session/update",params:e}),this.latestContextUsage=x(e,this.contextWindow)??this.latestContextUsage;const t=O("opencode",e);t&&(t.type==="agent_message_chunk"&&(this.content+=t.text??""),this.updateCallback?.(t));const s=P("opencode",e);s&&this.emitStatus(s.phase,s.label,s.detail,s.updateType)}async handleClientRequest(e,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(t);case"fs/write_text_file":return this.writeTextFile(t);case"session/request_permission":return this.requestPermission(t);case"elicitation/create":case"session/request_user_input":return this.requestUserInput(t);default:throw new Error(`Unsupported OpenCode ACP client request: ${e}`)}}readTextFile(e){const t=I(this.cwd,c(e,"path")||c(e,"filePath"));return{content:p.readFileSync(t,"utf8")}}writeTextFile(e){const t=I(this.cwd,c(e,"path")||c(e,"filePath")),s=c(e,"content")??c(e,"text")??"";return p.mkdirSync(m.dirname(t),{recursive:!0}),p.writeFileSync(t,s,"utf8"),{}}attachAbortSignal(e){if(e){if(this.abortHandler=()=>{this.cancel()},e.aborted)throw new Error("OpenCode prompt cancelled");e.addEventListener("abort",this.abortHandler,{once:!0})}}async promptWithAbort(e,t){const s=e.prompt({sessionId:t.sessionId,prompt:t.prompt},t.timeoutMs);if(!t.signal)return s;if(t.signal.aborted)throw await this.lease?.dispose().catch(()=>{}),new Error("OpenCode prompt cancelled");let n=null;const o=new Promise((r,l)=>{n=()=>{e.cancel(t.sessionId),this.lease?.dispose().catch(()=>{}),l(new Error("OpenCode prompt cancelled"))},t.signal?.addEventListener("abort",n,{once:!0})});try{return await Promise.race([s,o])}finally{n&&t.signal.removeEventListener("abort",n)}}async requestPermission(e){if(!this.permissionCallback)return{outcome:{outcome:"cancelled"}};const t=d(e)??{},s=d(t.request)??t,o=(Array.isArray(s.options)?s.options:[]).map((r,l)=>{const a=d(r)??{};return{optionId:String(a.optionId??a.id??l),kind:String(a.kind??a.outcome??"choice"),...typeof a.label=="string"?{label:a.label}:{}}});return this.emitStatus("requestPermission","等待授权",M(s),"session/request_permission"),this.permissionCallback({requestId:String(s.requestId??s.id??`opencode-permission-${Date.now()}`),params:e,options:o})}async requestUserInput(e){if(!this.userInputCallback)return{outcome:{outcome:"cancelled"}};const t=d(e)??{},s=d(t.request)??t;return this.emitStatus("requestPermission","等待输入",k(s),"elicitation/create"),this.userInputCallback({requestId:String(s.requestId??s.id??`opencode-input-${Date.now()}`),prompt:String(s.prompt??s.message??s.question??""),params:e,...typeof s.defaultValue=="string"?{defaultValue:s.defaultValue}:{}})}emitStatus(e,t,s,n){const o={phase:e,label:t,...s?{detail:s}:{},...n?{updateType:n}:{},atMs:Date.now()},r=this.statusTimeline[this.statusTimeline.length-1];r&&r.phase===o.phase&&r.label===o.label&&r.detail===o.detail&&r.updateType===o.updateType||(this.statusTimeline.push(o),this.statusCallback?.(o))}appendStderr(e){const t=this.lease?.client;t&&(t.rpc.stderr+=e)}releaseLease(){this.lease?.release(),this.lease=null}}function g(i){const t=d(i)?.sessionId;return typeof t=="string"&&t.trim()?t.trim():null}function d(i){return i&&typeof i=="object"&&!Array.isArray(i)?i:null}function q(i){return typeof i=="number"&&Number.isFinite(i)&&i>0?i:0}function R(i){const e=h(i.model),t=w(i.mode);return{...A(i.configOptionValues),...e?{model:e}:{},...t?{mode:t}:{}}}function A(i){const e={};for(const[t,s]of Object.entries(i??{}))["model","mode","reasoning_effort","effort","variant"].includes(t)||(e[t]=s);return e}function S(i,e){const t=h(e);if(!t)return!0;const s=C(i);return!s||s===t}function C(i){const t=d(i?.models)?.currentModelId;return typeof t=="string"&&t.trim()?t.trim():null}function w(i){const e=String(i||"").trim();return e?["agent-full-access","full-access","bypassPermissions","danger-full-access"].includes(e)?"build":e==="read-only"?"plan":e:null}function c(i,e){const s=d(i)?.[e];return typeof s=="string"&&s.trim()?s:null}function I(i,e){if(!e)throw new Error("ACP file request did not include a path");const t=m.resolve(i,e),s=m.resolve(i);if(t!==s&&!t.startsWith(`${s}${m.sep}`))throw new Error(`ACP file request is outside project root: ${e}`);return t}function M(i){const e=d(i.params)??i;for(const t of["command","cwd","reason","grantRoot","method"]){const s=e[t];if(typeof s=="string"&&s.trim())return b(s)}}function k(i){for(const e of["prompt","message","question","title"]){const t=i[e];if(typeof t=="string"&&t.trim())return b(t)}}function b(i){const e=i.replace(/\s+/g," ").trim();return e.length>120?`${e.slice(0,117)}...`:e}function v(i){return i instanceof Error?i.message:String(i)}function U(i){return/\binvalid params\b/i.test(v(i))}function D(i,e){return(e?.length?e:[{type:"text",text:i}]).map(s=>s.type==="image"?{type:"image",data:s.data,mimeType:s.mimeType}:{type:"text",text:s.text})}function F(){return process.env.ACA_OPENCODE_DEFAULT_MODEL||""}function h(i){const e=String(i||F()).trim();return e==="default"||e==="opencode-default"?"":e}function $(){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:360*60*1e3}export{j as OpenCodeProvider,T as openCodeCapabilities};
@@ -1,3 +1,3 @@
1
- import{Command as a}from"commander";import s from"chalk";import f from"node:fs";import u from"node:path";import{spawn as P}from"node:child_process";import{randomUUID as C}from"node:crypto";import{getLogDir as l,getRuntimeStatePath as $}from"../core/paths.js";import{ensureDir as x,readJsonFile as S}from"../core/fs.js";import{isProcessAlive as r,readPidFile as m,removePidFile as d}from"../core/process.js";import{ACA_WORKER_INSTANCE_ENV as y}from"../core/process-identity.js";import{applyRuntimePathOptions as i}from"../core/runtime-options.js";const g=3e4;function G(){return new a("daemon").description("Run aca as a background daemon service").addCommand(c(new a("start").description("Start aca daemon in the background")).allowUnknownOption(!0).action(e=>{i(e);const o=m();if(o&&r(o)){console.log(`Daemon is already running (PID ${o}).`),process.exitCode=1;return}const n=w(h("start"));n.unref(),console.log(`Daemon started (PID ${n.pid})`),D()})).addCommand(c(new a("stop").description("Stop the running aca daemon")).action(async e=>{i(e);const o=m();if(!o){console.log("No daemon PID file found. Daemon is not running.");return}if(!r(o)){console.log(`Daemon process (PID ${o}) is not running. Cleaning up PID file.`),d();return}await I(o)?(d(),console.log(`Daemon stopped successfully (PID ${o}).`)):(console.log(`Daemon (PID ${o}) is still running. You may need to kill it manually: ${s.yellow(`kill -9 ${o}`)}`),process.exitCode=1)})).addCommand(c(new a("status").description("Show daemon status")).action(e=>{i(e);const o=S($()),n=m();if(n&&r(n)){console.log(s.green("● Daemon is running")),console.log(` PID: ${n}`),o?.phase&&console.log(` Phase: ${o.phase}`),o?.startupStage&&console.log(` Stage: ${o.startupStage}`);return}n?(console.log(s.red("● Daemon is not running (stale PID file)")),d()):console.log(s.red("● Daemon is not running")),process.exitCode=1})).addCommand(c(new a("logs").description("Show daemon logs")).option("-n, --lines <count>","number of lines to show","50").action(e=>{i(e);const o=Number.parseInt(e.lines,10)||50,n=E();if(!n){console.log(`No log files found in ${l()}`),process.exitCode=1;return}const t=f.readFileSync(n,"utf8").trimEnd().split(`
1
+ import{Command as i}from"commander";import s from"chalk";import m from"node:fs";import p from"node:path";import{spawn as S}from"node:child_process";import{randomUUID as $}from"node:crypto";import{getLogDir as c,getRuntimeStatePath as y}from"../core/paths.js";import{ensureDir as g,readJsonFile as C}from"../core/fs.js";import{isProcessAlive as r,readPidFile as u,removePidFile as f}from"../core/process.js";import{ACA_WORKER_INSTANCE_ENV as x}from"../core/process-identity.js";import{applyRuntimePathOptions as l}from"../core/runtime-options.js";const h=3e4;function G(){return new i("daemon").description("Run aca as a background daemon service").addCommand(d(new i("start").description("Start aca daemon in the background")).allowUnknownOption(!0).action(e=>{l(e);const o=u();if(o&&r(o)){console.log(`Daemon is already running (PID ${o}).`),process.exitCode=1;return}const n=D(w("start"));n.unref(),console.log(`Daemon started (PID ${n.pid})`),I()})).addCommand(d(new i("stop").description("Stop the running aca daemon")).action(async e=>{l(e);const o=u();if(!o){console.log("No daemon PID file found. Daemon is not running.");return}if(!r(o)){console.log(`Daemon process (PID ${o}) is not running. Cleaning up PID file.`),f();return}await P(o)?(f(),console.log(`Daemon stopped successfully (PID ${o}).`)):(console.log(`Daemon (PID ${o}) is still running. You may need to kill it manually: ${s.yellow(`kill -9 ${o}`)}`),process.exitCode=1)})).addCommand(d(new i("status").description("Show daemon status")).action(e=>{l(e);const o=C(y()),n=u();if(n&&r(n)){console.log(s.green("● Daemon is running")),console.log(` PID: ${n}`),o?.phase&&console.log(` Phase: ${o.phase}`),o?.startupStage&&console.log(` Stage: ${o.startupStage}`);return}n?(console.log(s.red("● Daemon is not running (stale PID file)")),f()):console.log(s.red("● Daemon is not running")),process.exitCode=1})).addCommand(d(new i("logs").description("Show daemon logs")).option("-n, --lines <count>","number of lines to show","50").action(e=>{l(e);const o=Number.parseInt(e.lines,10)||50,n=E();if(!n){console.log(`No log files found in ${c()}`),process.exitCode=1;return}const t=m.readFileSync(n,"utf8").trimEnd().split(`
2
2
  `);console.log(s.dim(`--- ${n} (last ${o} lines) ---`)),console.log(t.slice(-o).join(`
3
- `))})).addCommand(c(new a("restart").description("Restart the aca daemon")).allowUnknownOption(!0).action(async e=>{i(e);const o=m();if(o&&r(o)&&!await I(o)){console.error(`Daemon restart aborted because PID ${o} did not stop within ${g}ms.`),process.exitCode=1;return}d();const n=w(h("restart"));n.unref(),console.log(`Daemon started (PID ${n.pid})`),D()}))}function c(e){return e.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 h(e){const o=process.argv.findIndex((t,p)=>p>=2&&t==="daemon"),n=process.argv.findIndex((t,p)=>p>o&&t===e);return n>=0?process.argv.slice(n+1):[]}function w(e){const o=v();return P(o.command,[...o.prefixArgs,"start",...e],{detached:!0,stdio:"ignore",env:{...process.env,[y]:C()}})}function v(){if(process.env.ACA_SINGLE_FILE_WORKER==="1")return{command:process.execPath,prefixArgs:[]};const e=process.argv[1];if(!e)throw new Error("Unable to resolve the current ACA CLI entrypoint");return{command:process.execPath,prefixArgs:[u.resolve(e)]}}function D(){const e=A();console.log(`Use ${s.yellow(`${e} daemon status`)} to check status`),console.log(`Use ${s.yellow(`${e} daemon stop`)} to stop`),console.log(`Use ${s.yellow(`${e} daemon logs`)} to view logs`)}function A(){if(process.env.ACA_SINGLE_FILE_WORKER==="1")return process.execPath;const e=u.basename(process.argv[1]||"");return/worker-cli|aica/i.test(e)?"aica":"aca"}function E(){return x(l()),f.readdirSync(l()).filter(o=>o.endsWith(".log")).map(o=>u.join(l(),o)).sort().at(-1)??null}async function R(e,o){const n=Date.now();for(;Date.now()-n<o;){if(!r(e))return!0;await new Promise(t=>setTimeout(t,100))}return!r(e)}async function I(e,o=g){return r(e)?(process.kill(e,"SIGTERM"),R(e,o)):!0}export{G as createDaemonCommand,D as printStartTips,w as spawnDaemon,I as stopDaemonProcess};
3
+ `))})).addCommand(d(new i("restart").description("Restart the aca daemon")).allowUnknownOption(!0).action(async e=>{l(e);const o=u();if(o&&r(o)&&!await P(o)){console.error(`Daemon restart aborted because PID ${o} did not stop within ${h}ms.`),process.exitCode=1;return}f();const n=D(w("restart"));n.unref(),console.log(`Daemon started (PID ${n.pid})`),I()}))}function d(e){return e.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 w(e){const o=process.argv.findIndex((t,a)=>a>=2&&t==="daemon"),n=process.argv.findIndex((t,a)=>a>o&&t===e);return n>=0?process.argv.slice(n+1):[]}function D(e){const o=v();g(c());const n=new Date().toISOString().slice(0,10),t=p.join(c(),`daemon-stderr-${n}.log`),a=m.openSync(t,"a");try{return S(o.command,[...o.prefixArgs,"start",...e],{detached:!0,stdio:["ignore","ignore",a],env:{...process.env,[x]:$()}})}finally{m.closeSync(a)}}function v(){if(process.env.ACA_SINGLE_FILE_WORKER==="1")return{command:process.execPath,prefixArgs:[]};const e=process.argv[1];if(!e)throw new Error("Unable to resolve the current ACA CLI entrypoint");return{command:process.execPath,prefixArgs:[p.resolve(e)]}}function I(){const e=A();console.log(`Use ${s.yellow(`${e} daemon status`)} to check status`),console.log(`Use ${s.yellow(`${e} daemon stop`)} to stop`),console.log(`Use ${s.yellow(`${e} daemon logs`)} to view logs`)}function A(){if(process.env.ACA_SINGLE_FILE_WORKER==="1")return process.execPath;const e=p.basename(process.argv[1]||"");return/worker-cli|aica/i.test(e)?"aica":"aca"}function E(){return g(c()),m.readdirSync(c()).filter(o=>/^\d{4}-\d{2}-\d{2}\.log$/.test(o)).map(o=>p.join(c(),o)).sort().at(-1)??null}async function R(e,o){const n=Date.now();for(;Date.now()-n<o;){if(!r(e))return!0;await new Promise(t=>setTimeout(t,100))}return!r(e)}async function P(e,o=h){return r(e)?(process.kill(e,"SIGTERM"),R(e,o)):!0}export{G as createDaemonCommand,I as printStartTips,D as spawnDaemon,P as stopDaemonProcess};
@@ -1 +1 @@
1
- import{Command as _}from"commander";import $ from"node:fs";import k from"node:os";import p from"node:path";import r from"node:process";import{randomUUID as W}from"node:crypto";import{Logger as N}from"../core/logger.js";import{loadCredentials as O,saveApiKeyCredential as j}from"../core/auth.js";import{getRuntimeStatePath as L}from"../core/paths.js";import{writeJsonFile as x}from"../core/fs.js";import{HEARTBEAT_INTERVAL_MS as M,startHeartbeatLoop as U}from"../core/heartbeat.js";import{startJobWorkerLoop as F}from"../core/job-worker.js";import{startFileTransferWorkerLoop as H}from"../core/file-transfer-worker.js";import{startMachineFilesystemWorkerLoop as K}from"../core/machine-filesystem-worker.js";import{cleanupOrphanedAcpProcesses as V}from"../core/acp-orphan-cleanup.js";import{defaultAcpRuntimePool as S}from"../acp/client/acp-runtime-pool.js";import{isProcessAlive as f,readPidFile as h,writePidFile as B,removePidFile as y}from"../core/process.js";import{applyRuntimePathOptions as b}from"../core/runtime-options.js";import{loadAcaConfig as C,updateAcaConfig as P}from"../core/aca-config.js";import{acaServerRequest as G}from"../core/aca-server-client.js";import{restoreProjectsAfterLogin as J}from"../core/project-recovery.js";import{ACA_WORKER_INSTANCE_ENV as E,ACA_WORKER_PID_ENV as q,ACA_WORKER_START_TICKS_ENV as z,readProcessStartTicks as Q}from"../core/process-identity.js";import{printStartTips as X,spawnDaemon as Y}from"./daemon.js";function Te(){return new _("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("--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 Z(e);return}await re(e)})}async function Z(e){b(e);const a=h();if(a&&f(a))throw new Error(`Worker 已在后台运行,PID ${a}。`);a&&y();const i=Y(ee());if(i.unref(),!i.pid)throw new Error("后台 Worker 启动失败:未获得进程 PID。");if(!await te(i.pid,3e3)&&!f(i.pid))throw new Error(`后台 Worker 启动失败,请检查 ${p.join(r.env.AICA_HOME||r.env.ACA_HOME||p.join(k.homedir(),".aica"),"logs")}。`);console.log(`Worker 已在后台启动,PID ${i.pid}。`),X()}function ee(){const e=r.argv.findIndex((a,i)=>i>=2&&a==="start");return e<0?[]:r.argv.slice(e+1).filter(a=>a!=="--background"&&a!=="-b")}async function te(e,a){const i=Date.now();for(;Date.now()-i<a;){if(h()===e)return!0;if(!f(e))return!1;await new Promise(t=>setTimeout(t,50))}return h()===e}async function re(e){b(e);const a=r.env[E]||W(),i=Q(r.pid)||"";r.env[E]=a,r.env[q]=String(r.pid),r.env[z]=i;const t=new N("start",e.debug?"debug":"info"),D=C(),u={...e.server?.trim()?{serverUrl:e.server.trim()}:{},...e.machineId?{machineId:e.machineId.trim()}:{},...e.machineName?{machineName:e.machineName.trim()}:{}};let c=Object.keys(u).length>0?P(u):D;const m=e.machineName?.trim()||c.machineName||k.hostname();if(e.token?.trim()){const s=await G("POST","/api/auth/machine-enroll",{token:e.token.trim(),machineId:c.machineId,machineName:m},{serverUrl:c.serverUrl});c=P({machineId:s.machineId,machineName:m,token:s.token,username:s.username}),t.info(`Machine enrollment completed for user ${s.username}`);try{const o=await J();o.restored>0&&t.info(`restored ${o.restored} local project(s) after machine enrollment`),o.unavailable>0&&t.warn(`skipped ${o.unavailable} unavailable project path(s) during machine enrollment recovery`),c=C()}catch(o){t.warn(`project recovery after machine enrollment failed: ${o instanceof Error?o.message:String(o)}`)}}const R=e.auth?j(e.auth,m):O();B(r.pid),T({pid:r.pid,workerInstanceId:a,workerStartTicks:i,phase:"running",startupStage:"local-compatible",machineId:c.machineId||R?.machine?.machineId||"local-machine",machineName:m,cliTypes:e.cliTypes??ae(),startedAt:new Date().toISOString(),heartbeatIntervalMs:M,issues:[{severity:"info",message:"aca is running in aca-server compatibility mode; ACP execution happens on this machine."}]}),t.info(`Starting aca local-compatible service on machine ${m}`),t.info(`PID ${r.pid}`);const d=await V();d.terminated>0&&t.warn(`terminated ${d.terminated} orphaned ACP process(es): ${d.pids.join(",")}`);let g=!1,l,w,v,I;const A=async s=>{if(g)return;g=!0,t.info("Shutting down"),l&&clearInterval(l),v?.stop(),I?.stop(),await w?.stop().catch(n=>t.warn(`job worker shutdown failed: ${n instanceof Error?n.message:String(n)}`));const o=S.stats();await S.disposeAll().catch(n=>t.warn(`ACP runtime shutdown failed: ${n instanceof Error?n.message:String(n)}`)),t.info(`ACP runtimes stopped total=${o.total} inUse=${o.inUse} idle=${o.idle}`),T({pid:r.pid,phase:"stopped",stoppedAt:new Date().toISOString(),issues:[]}),y(),r.exit(s)};r.once("SIGINT",()=>{A(130)}),r.once("SIGTERM",()=>{A(0)}),e.heartbeatLog&&setInterval(()=>t.info(`heartbeat: ${new Date().toISOString()}`),5e3),l=U(t),w=F(t),v=H(t),I=K(t),await new Promise(()=>{})}function ae(){return["codex","claude"].filter(e=>ie(e))}function ie(e){const a=r.env.PATH||"";for(const i of a.split(":"))try{if(i&&$.existsSync(p.join(i,e)))return!0}catch{}return!1}function T(e){x(L(),e)}export{Te as createStartCommand};
1
+ import{Command as D}from"commander";import _ from"node:fs";import k from"node:os";import f from"node:path";import r from"node:process";import{randomUUID as W}from"node:crypto";import{Logger as j}from"../core/logger.js";import{loadCredentials as N,saveApiKeyCredential as O}from"../core/auth.js";import{getRuntimeStatePath as x}from"../core/paths.js";import{writeJsonFile as L}from"../core/fs.js";import{HEARTBEAT_INTERVAL_MS as M,startHeartbeatLoop as U}from"../core/heartbeat.js";import{startJobWorkerLoop as F}from"../core/job-worker.js";import{startFileTransferWorkerLoop as H}from"../core/file-transfer-worker.js";import{startMachineFilesystemWorkerLoop as K}from"../core/machine-filesystem-worker.js";import{cleanupOrphanedAcpProcesses as V}from"../core/acp-orphan-cleanup.js";import{defaultAcpRuntimePool as S}from"../acp/client/acp-runtime-pool.js";import{isProcessAlive as u,readPidFile as h,writePidFile as B,removePidFile as y}from"../core/process.js";import{applyRuntimePathOptions as b}from"../core/runtime-options.js";import{loadAcaConfig as C,updateAcaConfig as P}from"../core/aca-config.js";import{acaServerRequest as G}from"../core/aca-server-client.js";import{restoreProjectsAfterLogin as J}from"../core/project-recovery.js";import{ACA_WORKER_INSTANCE_ENV as E,ACA_WORKER_PID_ENV as q,ACA_WORKER_START_TICKS_ENV as z,readProcessStartTicks as Q}from"../core/process-identity.js";import{printStartTips as X,spawnDaemon as Y}from"./daemon.js";function Te(){return new D("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("--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 Z(e);return}await re(e)})}async function Z(e){b(e);const a=h();if(a&&u(a))throw new Error(`Worker 已在后台运行,PID ${a}。`);a&&y();const i=Y(ee());if(i.unref(),!i.pid)throw new Error("后台 Worker 启动失败:未获得进程 PID。");if(!await te(i.pid,3e3)&&!u(i.pid))throw new Error(`后台 Worker 启动失败,请检查 ${f.join(r.env.AICA_HOME||r.env.ACA_HOME||f.join(k.homedir(),".aica"),"logs")}。`);console.log(`Worker 已在后台启动,PID ${i.pid}。`),X()}function ee(){const e=r.argv.findIndex((a,i)=>i>=2&&a==="start");return e<0?[]:r.argv.slice(e+1).filter(a=>a!=="--background"&&a!=="-b")}async function te(e,a){const i=Date.now();for(;Date.now()-i<a;){if(h()===e)return!0;if(!u(e))return!1;await new Promise(t=>setTimeout(t,50))}return h()===e}async function re(e){b(e);const a=r.env[E]||W(),i=Q(r.pid)||"";r.env[E]=a,r.env[q]=String(r.pid),r.env[z]=i;const t=new j("start",e.debug?"debug":"info"),$=C(),g={...e.server?.trim()?{serverUrl:e.server.trim()}:{},...e.machineId?{machineId:e.machineId.trim()}:{},...e.machineName?{machineName:e.machineName.trim()}:{}};let s=Object.keys(g).length>0?P(g):$;const m=e.machineName?.trim()||s.machineName||k.hostname();if(e.token?.trim()){const n=await G("POST","/api/auth/machine-enroll",{token:e.token.trim(),machineId:s.machineId,machineName:m},{serverUrl:s.serverUrl});s=P({machineId:n.machineId,machineName:m,token:n.token,username:n.username}),t.info(`Machine enrollment completed for user ${n.username}`);try{const o=await J();o.restored>0&&t.info(`restored ${o.restored} local project(s) after machine enrollment`),o.unavailable>0&&t.warn(`skipped ${o.unavailable} unavailable project path(s) during machine enrollment recovery`),s=C()}catch(o){t.warn(`project recovery after machine enrollment failed: ${o instanceof Error?o.message:String(o)}`)}}const R=e.auth?O(e.auth,m):N();B(r.pid),T({pid:r.pid,workerInstanceId:a,workerStartTicks:i,phase:"running",startupStage:"local-compatible",machineId:s.machineId||R?.machine?.machineId||"local-machine",machineName:m,cliTypes:e.cliTypes??ae(),startedAt:new Date().toISOString(),heartbeatIntervalMs:M,issues:[{severity:"info",message:"aca is running in aca-server compatibility mode; ACP execution happens on this machine."}]}),t.info(`Starting aca local-compatible service on machine ${m}`),t.info(`PID ${r.pid}`);const l=await V();l.terminated>0&&t.warn(`terminated ${l.terminated} orphaned ACP process(es): ${l.pids.join(",")}`);let w=!1,p,v,I,A;const d=async n=>{if(w)return;w=!0,t.info("Shutting down"),p&&clearInterval(p),I?.stop(),A?.stop(),await v?.stop().catch(c=>t.warn(`job worker shutdown failed: ${c instanceof Error?c.message:String(c)}`));const o=S.stats();await S.disposeAll().catch(c=>t.warn(`ACP runtime shutdown failed: ${c instanceof Error?c.message:String(c)}`)),t.info(`ACP runtimes stopped total=${o.total} inUse=${o.inUse} idle=${o.idle}`),T({pid:r.pid,phase:"stopped",stoppedAt:new Date().toISOString(),issues:[]}),y(),r.exit(n)};r.once("SIGINT",()=>{d(130)}),r.once("SIGTERM",()=>{d(0)}),r.once("uncaughtException",n=>{t.error(`uncaught exception: ${n.stack||n.message}`),d(1)}),r.once("unhandledRejection",n=>{const o=n instanceof Error?n.stack||n.message:String(n);t.error(`unhandled rejection: ${o}`),d(1)}),e.heartbeatLog&&setInterval(()=>t.info(`heartbeat: ${new Date().toISOString()}`),5e3),p=U(t),v=F(t),I=H(t),A=K(t),await new Promise(()=>{})}function ae(){return["codex","claude"].filter(e=>ie(e))}function ie(e){const a=r.env.PATH||"";for(const i of a.split(":"))try{if(i&&_.existsSync(f.join(i,e)))return!0}catch{}return!1}function T(e){L(x(),e)}export{Te as createStartCommand};
@@ -1 +1 @@
1
- import i from"node:fs";import{ACA_ACP_CHILD_ENV as l,ACA_ACP_OWNER_PID_ENV as p,ACA_ACP_OWNER_START_TICKS_ENV as _,ACA_ACP_WORKER_INSTANCE_ENV as A,ACA_WORKER_INSTANCE_ENV as m,readProcessStartTicks as d}from"./process-identity.js";async function y(){if(process.platform!=="linux")return{inspected:0,terminated:0,pids:[]};let r=0;const e=[];for(const n of C("/proc")){if(!/^\d+$/.test(n))continue;const t=Number(n);if(!Number.isInteger(t)||t<=1||t===process.pid)continue;const o=c(t);if(s(o,l)!=="1")continue;r+=1;const u=Number.parseInt(s(o,p),10),f=s(o,_),a=s(o,A);if(!N(u,f,a))try{process.kill(t,"SIGTERM"),e.push(t)}catch{}}if(e.length>0){await E(2e3);for(const n of e)if(h(n))try{process.kill(n,"SIGKILL")}catch{}}return{inspected:r,terminated:e.length,pids:e}}function c(r){try{return i.readFileSync(`/proc/${r}/environ`,"utf8").split("\0").filter(Boolean)}catch{return[]}}function N(r,e,n){if(!Number.isInteger(r)||r<=1||!e)return!1;try{if(process.kill(r,0),d(r)!==e)return!1;const t=s(c(r),m);return!n||!t||t===n}catch{return!1}}function s(r,e){const n=`${e}=`;return r.find(t=>t.startsWith(n))?.slice(n.length)??""}function h(r){try{return process.kill(r,0),!0}catch{return!1}}function C(r){try{return i.readdirSync(r)}catch{return[]}}function E(r){return new Promise(e=>setTimeout(e,r))}export{y as cleanupOrphanedAcpProcesses};
1
+ import l from"node:fs";import{ACA_ACP_COMMAND_ENV as m,ACA_ACP_CHILD_ENV as A,ACA_ACP_OWNER_PID_ENV as _,ACA_ACP_OWNER_START_TICKS_ENV as h,ACA_ACP_WORKER_INSTANCE_ENV as y,ACA_WORKER_INSTANCE_ENV as N,readProcessStartTicks as u}from"./process-identity.js";async function v(){if(process.platform!=="linux")return{inspected:0,terminated:0,pids:[]};let e=0;const n=[];for(const t of R("/proc")){if(!/^\d+$/.test(t))continue;const r=Number(t);if(!Number.isInteger(r)||r<=1||r===process.pid)continue;const s=p(r);if(c(s,A)!=="1")continue;e+=1;const o=Number.parseInt(c(s,_),10),a=c(s,h),f=c(s,y);if(I(o,a,f))continue;const i=u(r);if(g(r,c(s,m)))try{if(!i||u(r)!==i)continue;process.kill(r,"SIGTERM"),n.push({pid:r,startTicks:i})}catch{}}if(n.length>0){await T(2e3);for(const t of n)if(!(!S(t.pid)||u(t.pid)!==t.startTicks))try{process.kill(t.pid,"SIGKILL")}catch{}}return{inspected:e,terminated:n.length,pids:n.map(t=>t.pid)}}function g(e,n){const t=E(e);if(t.length===0)return!1;const r=C(n);if(r&&P(t,r.command,r.args))return!0;const s=t.join(" ");return s.includes("codex-acp/adapter")||s.includes("--internal-acp-adapter")||/(?:^|[/\\])codex\s+app-server\s+--stdio(?:\s|$)/.test(s)||/(?:^|[/\\])mimo\s+acp(?:\s|$)/.test(s)||/(?:^|[/\\])opencode\s+acp(?:\s|$)/.test(s)}function C(e){if(!e)return null;try{const n=JSON.parse(e);return typeof n.command!="string"||!Array.isArray(n.args)||!n.args.every(t=>typeof t=="string")?null:{command:n.command,args:n.args}}catch{return null}}function P(e,n,t){const r=d(n);for(let s=0;s<e.length;s+=1){const o=e[s];if(o!==n&&d(o)!==r)continue;const a=e.slice(s+1);if(a.length===t.length&&t.every((f,i)=>a[i]===f))return!0}return!1}function E(e){try{return l.readFileSync(`/proc/${e}/cmdline`,"utf8").split("\0").filter(Boolean)}catch{return[]}}function d(e){const n=e.replace(/\\/g,"/");return n.slice(n.lastIndexOf("/")+1)}function p(e){try{return l.readFileSync(`/proc/${e}/environ`,"utf8").split("\0").filter(Boolean)}catch{return[]}}function I(e,n,t){if(!Number.isInteger(e)||e<=1||!n)return!1;try{if(process.kill(e,0),u(e)!==n)return!1;const r=c(p(e),N);return!t||!r||r===t}catch{return!1}}function c(e,n){const t=`${n}=`;return e.find(r=>r.startsWith(t))?.slice(t.length)??""}function S(e){try{return process.kill(e,0),!0}catch{return!1}}function R(e){try{return l.readdirSync(e)}catch{return[]}}function T(e){return new Promise(n=>setTimeout(n,e))}export{v as cleanupOrphanedAcpProcesses};
@@ -1,6 +1,6 @@
1
- import L from"node:crypto";import E from"node:fs";import x from"node:path";import{compactAgentContext as W,forkAgentSession as V,sendAgentPrompt as q}from"../acp/agent.js";import{acaServerDownload as D,acaServerRequest as y}from"./aca-server-client.js";import{loadAcaConfig as H}from"./aca-config.js";import{prepareLocalWorktree as G}from"./worktree.js";import{expandWorkerRuntimePath as K}from"./worker-runtime-paths.js";import{collectAffectedFiles as N,extractAffectedFilePathsFromAcpRaw as X}from"./affected-files.js";import{resolveJobPermissionPolicy as Z}from"./job-permission-policy.js";import{appendCoalescedAcpEvent as Q}from"./acp-event-coalescer.js";import{shouldUploadAcpEvent as Y}from"./acp-event-upload-filter.js";import{userFacingAcpError as ee}from"../acp/error-display.js";const te=1500,ne=5e3,M=1e3,oe=200,ae=64,O=900,ie=1e4,re=0,se=12e3;function ce(e){return K(e)}function le(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 Ge(e,t={}){let n=!1,a=null,o=null;const r=new Set,l=new Set,u=new Map,i=async()=>{if(n)return;const s=ue();if(U(r.size,s))try{const p=t.loadConfig?.()??H();if(!p.token)return;for(;!n&&U(r.size,s);){const A=t.claimNextJob?await t.claimNextJob(p):await y("POST","/api/client/jobs/claim",{machineId:p.machineId,workspaceId:p.defaultWorkspaceId});if(!A.item)break;const f=A.item;if(n){await(t.cancelClaimedJob?t.cancelClaimedJob(f,p):P(f.job_id,p.machineId,"cancelled")).catch(()=>{});break}const m=f.session_id;l.has(m)&&e.warn(`claimed job=${f.job_id} for already active session=${m}; running anyway to avoid leaving it stuck`),r.add(f.job_id),l.add(m);const _=new AbortController,C=(t.runJob??de)(f,p,e,_).catch(d=>{e.warn(`job ${f.job_id} failed outside handler: ${d instanceof Error?d.message:String(d)}`)}).finally(()=>{r.delete(f.job_id),l.delete(m),u.delete(f.job_id)});u.set(f.job_id,{controller:_,promise:C})}}catch(p){n||e.warn(`job worker failed: ${p instanceof Error?p.message:String(p)}`)}},h=()=>{if(n||a)return;const s=i();a=s,s.finally(()=>{a===s&&(a=null)})};h();const g=setInterval(()=>{h()},t.claimIntervalMs??te);return{timer:g,stop:()=>o||(o=(async()=>{if(n)return;n=!0,clearInterval(g);for(const p of u.values())p.controller.abort();await a?.catch(()=>{});for(const p of u.values())p.controller.abort();const s=[...u.values()].map(p=>p.promise);s.length!==0&&await Promise.race([Promise.allSettled(s).then(()=>{}),j(se)])})(),o)}}function ue(){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?re:t===0?0:Math.max(1,Math.min(t,8))}function U(e,t){return t===0||e<t}function pe(){const e=Number.parseInt(process.env.ACA_ACP_EVENT_BATCH_INTERVAL_MS||"",10);return Number.isFinite(e)?Math.max(40,Math.min(e,2e3)):oe}async function de(e,t,n,a){const o=e.job_id,r=e.session_id,l=e.workspace_id,u=e.project_id??null,i=e.input??{},h=String(i.control?.type||"").toLowerCase();if(!h&&!i.message?.trim()&&(i.attachments??[]).length===0){await P(o,t.machineId,"job input missing message");return}const g=t.projects.find(d=>d.projectId===u),s=ce(g?.rootPath||e.project_root_path||"");if(!s){await P(o,t.machineId,"job project root is unknown");return}le(e)&&E.mkdirSync(s,{recursive:!0}),n.info(`claimed job=${o} session=${r} project=${g?.name??u??"unknown"}`);const p=setInterval(()=>{y("POST",`/api/client/jobs/${encodeURIComponent(o)}/heartbeat`,{machineId:t.machineId}).catch(()=>{}),je(o,a).catch(()=>{})},ne);let A=0;const f=ye(o,t.machineId),m=_e(o,t.machineId),_=new Set;let C=null;try{await m.sendNow({phase:"initializing",label:"准备工作区",detail:e.worktree_id?"worktree":"project root"}).catch(()=>{});const d=G({projectId:u,sessionId:r,projectRoot:s,worktree:e});C=d,await Re(o,t.machineId,d);const I=fe(i.acpRun);if(h==="compact"){if(!e.acp_session_id)throw new Error("Current session does not have an ACP context to compact");const c=await W({agentType:v(I.agentType,"codex"),cliType:v(I.cliType,"builtin"),acpSessionId:e.acp_session_id,cwd:d.cwd,model:i.model,mode:i.mode,configOptionValues:i.configOptionValues,timeoutMs:i.timeoutMs,allowRollover:i.control?.allowRollover===!0,signal:a.signal,onStatus:b=>{m.report({phase:b.phase,label:b.label,detail:b.detail,updateType:b.updateType,raw:b})}});await m.flush(),await y("POST",`/api/client/jobs/${encodeURIComponent(o)}/complete`,{machineId:t.machineId,acpSessionId:c.acpSessionId,suppressTurn:!0,result:{ok:!0,control:{type:"compact",allowRollover:i.control?.allowRollover===!0},acpSessionId:c.acpSessionId,durationMs:c.durationMs,usage:c.contextUsage,maintenance:c.maintenance,worktree:k(d)}}),n.info(`completed context compact job=${o}`);return}if(h==="fork"){const c=String(i.control?.sourceAcpSessionId||"").trim();if(!c)throw new Error("Source session does not have a Codex ACP context to fork");await m.sendNow({phase:"forking",label:"正在分叉 Codex 会话",detail:i.control?.sourceSessionId?`源会话 ${i.control.sourceSessionId}`:void 0}).catch(()=>{});const b=await V({agentType:v(I.agentType,"codex"),cliType:v(I.cliType,"builtin"),sourceProviderSessionId:c,cwd:d.cwd,model:i.model,mode:i.mode,configOptionValues:i.configOptionValues,timeoutMs:i.timeoutMs,signal:a.signal,onStatus:S=>{m.report({phase:S.phase,label:S.label,detail:S.detail,updateType:S.updateType,raw:S})}});await m.flush(),await y("POST",`/api/client/jobs/${encodeURIComponent(o)}/complete`,{machineId:t.machineId,acpSessionId:b.acpSessionId,suppressTurn:!0,result:{ok:!0,control:{type:"fork",sourceSessionId:i.control?.sourceSessionId||null},acpSessionId:b.acpSessionId,worktree:k(d)}}),n.info(`completed Codex session fork job=${o}`);return}const T=await Ae({cwd:d.cwd,sessionId:r,jobId:o,attachments:i.attachments??[]});T.length>0&&(n.info(`materialized ${T.length} attachment(s) for job=${o}`),await m.sendNow({phase:"initializing",label:"附件已写入本机",detail:T.map(c=>c.relativePath).join(", "),raw:{attachments:z(T)}}).catch(()=>{}));const $=ge(i,T),F=Se($,T),w=await q({agentType:v(I.agentType,"codex"),cliType:v(I.cliType,"builtin"),acpSessionId:e.acp_session_id??null,requireSessionResume:me(e.session_raw),prompt:$,promptBlocks:F,cwd:d.cwd,model:i.model,mode:i.mode,configOptionValues:i.configOptionValues,canExecuteTools:i.canExecuteTools,timeoutMs:i.timeoutMs,contextWindow:he(I.contextWindow),signal:a.signal,onStatus:c=>{m.report({phase:c.phase,label:c.label,detail:c.detail,updateType:c.updateType,raw:c})},onUpdate:c=>{A+=1;for(const S of X(c.raw))_.add(S);const b={turnId:e.user_turn_id??null,eventType:c.type,label:c.label,text:c.text,status:c.status,toolCallId:c.toolCallId,raw:Te(c.raw,A)};Y(b)&&f.push(b)},onPermissionRequest:c=>{const b=Z(i,c);return b?Promise.resolve(b):xe(o,t.machineId,c,a.signal)},onUserInputRequest:c=>Ee(o,t.machineId,c,a.signal)});await f.flush(),await m.flush();const J=N({cwd:d.cwd,evidencePaths:_}),R=we(w.promptResponse);if(!(be(R)||!!w.content)){await P(o,t.machineId,`${w.agentType} stopped: ${R??"unknown"}`,{acpSessionId:w.acpSessionId,stopReason:R,exitCode:w.exitCode,stderr:$e(w.stderr,4e3),worktree:k(d)});return}await y("POST",`/api/client/jobs/${encodeURIComponent(o)}/complete`,{machineId:t.machineId,acpSessionId:w.acpSessionId,assistantText:w.content,result:{ok:!0,acpSessionId:w.acpSessionId,stopReason:R,durationMs:w.durationMs,acpRun:i.acpRun??null,effectiveConfig:w.effectiveConfig??null,performance:w.performance??null,usage:w.contextUsage??null,updateCount:w.updates.length,statusTimeline:w.statusTimeline,worktree:k(d),attachments:z(T),files:J}}),n.info(`completed job=${o}`)}catch(d){await f.flush(),await m.flush();const I=a.signal.aborted?"cancelled":d instanceof Error?d.message:String(d),T=C?N({cwd:C.cwd,evidencePaths:_}):[];await P(o,t.machineId,ee(I),{rawError:I,files:T})}finally{await f.flush(),await m.flush(),clearInterval(p)}}function me(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 fe(e){return!e||typeof e!="object"||Array.isArray(e)?{}:e}function he(e){return typeof e=="number"&&Number.isFinite(e)&&e>0?e:void 0}function v(...e){for(const t of e)if(typeof t=="string"&&t.trim())return t.trim();return""}function we(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 be(e){return e?["end_turn","stop","completed","complete"].includes(e):!1}function ye(e,t){let n=[],a=null,o=Promise.resolve();const r=async i=>{i.length!==0&&await y("POST",`/api/client/jobs/${encodeURIComponent(e)}/events/batch`,{machineId:t,events:i})},l=()=>{a&&(clearTimeout(a),a=null);const i=n;return n=[],o=o.then(()=>r(i)).catch(()=>{}),o},u=()=>{a||(a=setTimeout(()=>{l()},pe()),a.unref())};return{push(i){if(Q(n,i),Ie(i)||n.length>=ae){l();return}u()},flush:l}}function Ie(e){return e.eventType==="tool_call"?!0:e.eventType!=="tool_call_update"?!1:["completed","failed","cancelled"].includes(String(e.status||"").toLowerCase())}function _e(e,t){let n=null,a=0,o="",r=null,l=Promise.resolve();const u=s=>JSON.stringify({phase:s.phase??"",label:s.label??"",detail:s.detail??"",updateType:s.updateType??""}),i=async s=>{await y("POST",`/api/client/jobs/${encodeURIComponent(e)}/presence`,{machineId:t,phase:s.phase,label:s.label,detail:s.detail,updateType:s.updateType,raw:s.raw}),a=Date.now(),o=u(s)},h=()=>{if(r&&(clearTimeout(r),r=null),!n)return l;const s=n;return n=null,l=l.then(()=>i(s)).catch(()=>{}),l},g=s=>{r||(r=setTimeout(()=>{h()},s),r.unref())};return{report(s){const p=Date.now(),f=u(s)!==o,m=p-a>=ie;if(!f&&!m)return;n=s;const _=p-a;if(_>=O||a===0){h();return}g(O-_)},sendNow(s){return n=s,h()},flush:h}}function Te(e,t){return e&&typeof e=="object"&&!Array.isArray(e)?{...e,acaSequence:t}:{value:e,acaSequence:t}}function ge(e,t=[]){const n=e?.message?.trim()??"";if(t.length===0)return n;const o=["用户随消息上传了以下附件,aca 已将附件写入本机工作区。需要读取附件时请直接使用下面的 localPath;图片附件也已作为 ACP image block 随本轮请求传入。",...t.map((r,l)=>`${l+1}. ${r.name} (${r.kind}, ${r.mimeType}, ${Pe(r.size)})
1
+ import L from"node:crypto";import E from"node:fs";import x from"node:path";import{compactAgentContext as W,forkAgentSession as V,sendAgentPrompt as q}from"../acp/agent.js";import{acaServerDownload as D,acaServerRequest as y}from"./aca-server-client.js";import{loadAcaConfig as H}from"./aca-config.js";import{prepareLocalWorktree as G}from"./worktree.js";import{expandWorkerRuntimePath as K}from"./worker-runtime-paths.js";import{collectAffectedFiles as N,extractAffectedFilePathsFromAcpRaw as X}from"./affected-files.js";import{resolveJobPermissionPolicy as Z}from"./job-permission-policy.js";import{appendCoalescedAcpEvent as Q}from"./acp-event-coalescer.js";import{shouldUploadAcpEvent as Y}from"./acp-event-upload-filter.js";import{userFacingAcpError as ee}from"../acp/error-display.js";const te=1500,ne=5e3,M=1e3,oe=200,ae=64,O=900,ie=1e4,re=0,se=12e3;function ce(e){return K(e)}function le(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 Ge(e,t={}){let n=!1,a=null,o=null;const r=new Set,l=new Set,u=new Map,i=async()=>{if(n)return;const s=ue();if(U(r.size,s))try{const p=t.loadConfig?.()??H();if(!p.token)return;for(;!n&&U(r.size,s);){const A=t.claimNextJob?await t.claimNextJob(p):await y("POST","/api/client/jobs/claim",{machineId:p.machineId,workspaceId:p.defaultWorkspaceId});if(!A.item)break;const f=A.item;if(n){await(t.cancelClaimedJob?t.cancelClaimedJob(f,p):P(f.job_id,p.machineId,"cancelled")).catch(()=>{});break}const m=f.session_id;l.has(m)&&e.warn(`claimed job=${f.job_id} for already active session=${m}; running anyway to avoid leaving it stuck`),r.add(f.job_id),l.add(m);const _=new AbortController,v=(t.runJob??de)(f,p,e,_).catch(d=>{e.warn(`job ${f.job_id} failed outside handler: ${d instanceof Error?d.message:String(d)}`)}).finally(()=>{r.delete(f.job_id),l.delete(m),u.delete(f.job_id)});u.set(f.job_id,{controller:_,promise:v})}}catch(p){n||e.warn(`job worker failed: ${p instanceof Error?p.message:String(p)}`)}},h=()=>{if(n||a)return;const s=i();a=s,s.finally(()=>{a===s&&(a=null)})};h();const S=setInterval(()=>{h()},t.claimIntervalMs??te);return{timer:S,stop:()=>o||(o=(async()=>{if(n)return;n=!0,clearInterval(S);for(const p of u.values())p.controller.abort();await a?.catch(()=>{});for(const p of u.values())p.controller.abort();const s=[...u.values()].map(p=>p.promise);s.length!==0&&await Promise.race([Promise.allSettled(s).then(()=>{}),j(se)])})(),o)}}function ue(){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?re:t===0?0:Math.max(1,Math.min(t,8))}function U(e,t){return t===0||e<t}function pe(){const e=Number.parseInt(process.env.ACA_ACP_EVENT_BATCH_INTERVAL_MS||"",10);return Number.isFinite(e)?Math.max(40,Math.min(e,2e3)):oe}async function de(e,t,n,a){const o=e.job_id,r=e.session_id,l=e.workspace_id,u=e.project_id??null,i=e.input??{},h=String(i.control?.type||"").toLowerCase();if(!h&&!i.message?.trim()&&(i.attachments??[]).length===0){await P(o,t.machineId,"job input missing message");return}const S=t.projects.find(d=>d.projectId===u),s=ce(S?.rootPath||e.project_root_path||"");if(!s){await P(o,t.machineId,"job project root is unknown");return}le(e)&&E.mkdirSync(s,{recursive:!0}),n.info(`claimed job=${o} session=${r} project=${S?.name??u??"unknown"}`);const p=setInterval(()=>{y("POST",`/api/client/jobs/${encodeURIComponent(o)}/heartbeat`,{machineId:t.machineId}).catch(()=>{}),je(o,a).catch(()=>{})},ne);let A=0;const f=ye(o,t.machineId),m=_e(o,t.machineId),_=new Set;let v=null;try{await m.sendNow({phase:"initializing",label:"准备工作区",detail:e.worktree_id?"worktree":"project root"}).catch(()=>{});const d=G({projectId:u,sessionId:r,projectRoot:s,worktree:e});v=d,await Re(o,t.machineId,d);const I=fe(i.acpRun);if(h==="compact"){if(!e.acp_session_id)throw new Error("Current session does not have an ACP context to compact");const c=await W({agentType:C(I.agentType,"codex"),cliType:C(I.cliType,"builtin"),acpSessionId:e.acp_session_id,cwd:d.cwd,model:i.model,mode:i.mode,configOptionValues:i.configOptionValues,timeoutMs:i.timeoutMs,allowRollover:i.control?.allowRollover===!0,signal:a.signal,onStatus:b=>{m.report({phase:b.phase,label:b.label,detail:b.detail,updateType:b.updateType,raw:b})}});await m.flush(),await y("POST",`/api/client/jobs/${encodeURIComponent(o)}/complete`,{machineId:t.machineId,acpSessionId:c.acpSessionId,suppressTurn:!0,result:{ok:!0,control:{type:"compact",allowRollover:i.control?.allowRollover===!0},acpSessionId:c.acpSessionId,durationMs:c.durationMs,usage:c.contextUsage,maintenance:c.maintenance,worktree:k(d)}}),n.info(`completed context compact job=${o}`);return}if(h==="fork"){const c=String(i.control?.sourceAcpSessionId||"").trim();if(!c)throw new Error("Source session does not have a Codex ACP context to fork");await m.sendNow({phase:"forking",label:"正在分叉 Codex 会话",detail:i.control?.sourceSessionId?`源会话 ${i.control.sourceSessionId}`:void 0}).catch(()=>{});const b=await V({agentType:C(I.agentType,"codex"),cliType:C(I.cliType,"builtin"),sourceProviderSessionId:c,cwd:d.cwd,model:i.model,mode:i.mode,configOptionValues:i.configOptionValues,timeoutMs:i.timeoutMs,signal:a.signal,onStatus:g=>{m.report({phase:g.phase,label:g.label,detail:g.detail,updateType:g.updateType,raw:g})}});await m.flush(),await y("POST",`/api/client/jobs/${encodeURIComponent(o)}/complete`,{machineId:t.machineId,acpSessionId:b.acpSessionId,suppressTurn:!0,result:{ok:!0,control:{type:"fork",sourceSessionId:i.control?.sourceSessionId||null},acpSessionId:b.acpSessionId,worktree:k(d)}}),n.info(`completed Codex session fork job=${o}`);return}const T=await Ae({cwd:d.cwd,sessionId:r,jobId:o,attachments:i.attachments??[]});T.length>0&&(n.info(`materialized ${T.length} attachment(s) for job=${o}`),await m.sendNow({phase:"initializing",label:"附件已写入本机",detail:T.map(c=>c.relativePath).join(", "),raw:{attachments:z(T)}}).catch(()=>{}));const $=Se(i,T),F=ge($,T),w=await q({agentType:C(I.agentType,"codex"),cliType:C(I.cliType,"builtin"),acpSessionId:e.acp_session_id??null,requireSessionResume:me(e.session_raw),prompt:$,promptBlocks:F,cwd:d.cwd,model:i.model,mode:i.mode,configOptionValues:i.configOptionValues,canExecuteTools:i.canExecuteTools,timeoutMs:i.timeoutMs,contextWindow:he(I.contextWindow),signal:a.signal,onStatus:c=>{m.report({phase:c.phase,label:c.label,detail:c.detail,updateType:c.updateType,raw:c})},onUpdate:c=>{A+=1;for(const g of X(c.raw))_.add(g);const b={turnId:e.user_turn_id??null,eventType:c.type,label:c.label,text:c.text,status:c.status,toolCallId:c.toolCallId,raw:Te(c.raw,A)};Y(b)&&f.push(b)},onPermissionRequest:c=>{const b=Z(i,c);return b?Promise.resolve(b):xe(o,t.machineId,c,a.signal)},onUserInputRequest:c=>Ee(o,t.machineId,c,a.signal)});await f.flush(),await m.flush();const J=N({cwd:d.cwd,evidencePaths:_}),R=we(w.promptResponse);if(!(be(R)||!!w.content)){await P(o,t.machineId,`${w.agentType} stopped: ${R??"unknown"}`,{acpSessionId:w.acpSessionId,stopReason:R,exitCode:w.exitCode,stderr:$e(w.stderr,4e3),worktree:k(d)});return}await y("POST",`/api/client/jobs/${encodeURIComponent(o)}/complete`,{machineId:t.machineId,acpSessionId:w.acpSessionId,assistantText:w.content,result:{ok:!0,acpSessionId:w.acpSessionId,stopReason:R,durationMs:w.durationMs,acpRun:i.acpRun??null,effectiveConfig:w.effectiveConfig??null,performance:w.performance??null,usage:w.contextUsage??null,updateCount:w.updateCount,statusTimeline:w.statusTimeline,worktree:k(d),attachments:z(T),files:J}}),n.info(`completed job=${o}`)}catch(d){await f.flush(),await m.flush();const I=a.signal.aborted?"cancelled":d instanceof Error?d.message:String(d),T=v?N({cwd:v.cwd,evidencePaths:_}):[];await P(o,t.machineId,ee(I),{rawError:I,files:T})}finally{await f.flush(),await m.flush(),clearInterval(p)}}function me(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 fe(e){return!e||typeof e!="object"||Array.isArray(e)?{}:e}function he(e){return typeof e=="number"&&Number.isFinite(e)&&e>0?e:void 0}function C(...e){for(const t of e)if(typeof t=="string"&&t.trim())return t.trim();return""}function we(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 be(e){return e?["end_turn","stop","completed","complete"].includes(e):!1}function ye(e,t){let n=[],a=null,o=Promise.resolve();const r=async i=>{i.length!==0&&await y("POST",`/api/client/jobs/${encodeURIComponent(e)}/events/batch`,{machineId:t,events:i})},l=()=>{a&&(clearTimeout(a),a=null);const i=n;return n=[],o=o.then(()=>r(i)).catch(()=>{}),o},u=()=>{a||(a=setTimeout(()=>{l()},pe()),a.unref())};return{push(i){if(Q(n,i),Ie(i)||n.length>=ae){l();return}u()},flush:l}}function Ie(e){return e.eventType==="tool_call"?!0:e.eventType!=="tool_call_update"?!1:["completed","failed","cancelled"].includes(String(e.status||"").toLowerCase())}function _e(e,t){let n=null,a=0,o="",r=null,l=Promise.resolve();const u=s=>JSON.stringify({phase:s.phase??"",label:s.label??"",detail:s.detail??"",updateType:s.updateType??""}),i=async s=>{await y("POST",`/api/client/jobs/${encodeURIComponent(e)}/presence`,{machineId:t,phase:s.phase,label:s.label,detail:s.detail,updateType:s.updateType,raw:s.raw}),a=Date.now(),o=u(s)},h=()=>{if(r&&(clearTimeout(r),r=null),!n)return l;const s=n;return n=null,l=l.then(()=>i(s)).catch(()=>{}),l},S=s=>{r||(r=setTimeout(()=>{h()},s),r.unref())};return{report(s){const p=Date.now(),f=u(s)!==o,m=p-a>=ie;if(!f&&!m)return;n=s;const _=p-a;if(_>=O||a===0){h();return}S(O-_)},sendNow(s){return n=s,h()},flush:h}}function Te(e,t){return e&&typeof e=="object"&&!Array.isArray(e)?{...e,acaSequence:t}:{value:e,acaSequence:t}}function Se(e,t=[]){const n=e?.message?.trim()??"";if(t.length===0)return n;const o=["用户随消息上传了以下附件,aca 已将附件写入本机工作区。需要读取附件时请直接使用下面的 localPath;图片附件也已作为 ACP image block 随本轮请求传入。",...t.map((r,l)=>`${l+1}. ${r.name} (${r.kind}, ${r.mimeType}, ${Pe(r.size)})
2
2
  localPath: ${r.path}
3
3
  workspacePath: ${r.relativePath}`)].join(`
4
4
  `);return n?`${n}
5
5
 
6
- ${o}`:o}function Se(e,t){const n=[{type:"text",text:e}];for(const a of t)a.kind!=="image"||!a.mimeType.startsWith("image/")||n.push({type:"image",data:a.base64,mimeType:a.mimeType,uri:a.path});return n}async function Ae(e){if(e.attachments.length===0)return[];const t=x.join(e.cwd,".aca","attachments",B(e.sessionId),B(e.jobId));E.mkdirSync(t,{recursive:!0});const n=[];for(const[a,o]of e.attachments.entries()){const r=o.attachmentId?{mimeType:o.mimeType||"application/octet-stream",bytes:await D(`/api/client/attachments/${encodeURIComponent(o.attachmentId)}`)}:ve(o.dataUrl??""),l=o.mimeType||r.mimeType||"application/octet-stream",u=o.kind==="image"||l.startsWith("image/")?"image":"file",i=Ce(o.name||`attachment-${a+1}`,l),h=x.join(t,`${String(a+1).padStart(2,"0")}-${i}`);E.writeFileSync(h,r.bytes),n.push({name:o.name||i,mimeType:l,size:r.bytes.byteLength,kind:u,path:h,relativePath:x.relative(e.cwd,h),base64:r.bytes.toString("base64"),sha256:L.createHash("sha256").update(r.bytes).digest("hex")})}return n}function ve(e){const t=e.indexOf(",");if(!e.startsWith("data:")||t<0)throw new Error("attachment dataUrl is not a data URL");const n=e.slice(5,t),a=e.slice(t+1),o=n.split(";").filter(Boolean),r=o[0]?.includes("/")?o[0]:"application/octet-stream";if(!o.includes("base64"))throw new Error("attachment dataUrl is not base64 encoded");return{mimeType:r,bytes:Buffer.from(a,"base64")}}function Ce(e,t){const n=x.basename(e).replace(/[<>:"/\\|?*\x00-\x1F]/g,"_").trim(),a=`attachment${ke(t)}`;return(!n||n==="."||n===".."?a:n).slice(0,180)}function ke(e){switch(e.toLowerCase()){case"image/png":return".png";case"image/jpeg":return".jpg";case"image/webp":return".webp";case"image/gif":return".gif";case"text/plain":return".txt";case"application/json":return".json";case"text/markdown":case"text/x-markdown":return".md";case"application/pdf":return".pdf";case"application/msword":return".doc";case"application/vnd.openxmlformats-officedocument.wordprocessingml.document":return".docx";case"application/vnd.ms-excel":return".xls";case"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet":return".xlsx";case"application/vnd.ms-powerpoint":return".ppt";case"application/vnd.openxmlformats-officedocument.presentationml.presentation":return".pptx";case"application/zip":return".zip";case"application/x-7z-compressed":return".7z";case"application/x-rar-compressed":case"application/vnd.rar":return".rar";case"application/gzip":return".gz";case"application/x-tar":return".tar";default:return""}}function B(e){return e.replace(/[^a-zA-Z0-9._-]+/g,"-").replace(/^-+|-+$/g,"").slice(0,80)||"item"}function z(e){return e.map(t=>({name:t.name,kind:t.kind,mimeType:t.mimeType,size:t.size,path:t.path,relativePath:t.relativePath,sha256:t.sha256}))}function Pe(e){if(!Number.isFinite(e)||e<=0)return"0 B";const t=["B","KB","MB","GB"];let n=e,a=0;for(;n>=1024&&a<t.length-1;)n/=1024,a+=1;return`${n>=10||a===0?n.toFixed(0):n.toFixed(1)} ${t[a]}`}async function Re(e,t,n){!n.usedWorktree||!n.worktreeId||await y("POST",`/api/client/jobs/${encodeURIComponent(e)}/worktree`,{machineId:t,baseCommit:n.baseCommit??null,worktreePath:n.cwd,status:n.status??"active",raw:n.raw??k(n)})}function k(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 xe(e,t,n,a){const r=(await y("POST",`/api/client/jobs/${encodeURIComponent(e)}/permissions`,{machineId:t,request:n})).item.permission_id;for(;!a.aborted;){await j(M);const l=await y("GET",`/api/client/permissions/${encodeURIComponent(r)}`);if(l.item.status==="resolved"&&l.item.response){const u=l.item.response.outcome;return u.outcome==="selected"?{outcome:u}:{outcome:{outcome:"cancelled"}}}}return{outcome:{outcome:"cancelled"}}}async function Ee(e,t,n,a){const r=(await y("POST",`/api/client/jobs/${encodeURIComponent(e)}/permissions`,{machineId:t,request:{kind:"elicitation",...n}})).item.permission_id;for(;!a.aborted;){await j(M);const l=await y("GET",`/api/client/permissions/${encodeURIComponent(r)}`);if(l.item.status==="resolved"&&l.item.response){const u=l.item.response.outcome;return u.outcome==="submitted"?{outcome:u}:{outcome:{outcome:"cancelled"}}}}return{outcome:{outcome:"cancelled"}}}async function je(e,t){if(t.signal.aborted)return;const n=await y("GET",`/api/client/jobs/${encodeURIComponent(e)}`);Number(n.item.cancel_requested??0)===1&&t.abort()}async function P(e,t,n,a){await y("POST",`/api/client/jobs/${encodeURIComponent(e)}/fail`,{machineId:t,error:n,result:a})}function j(e){return new Promise(t=>setTimeout(t,e))}function $e(e,t){return e.length<=t?e:`${e.slice(0,t)}...`}export{Ge as startJobWorkerLoop};
6
+ ${o}`:o}function ge(e,t){const n=[{type:"text",text:e}];for(const a of t)a.kind!=="image"||!a.mimeType.startsWith("image/")||n.push({type:"image",data:a.base64,mimeType:a.mimeType,uri:a.path});return n}async function Ae(e){if(e.attachments.length===0)return[];const t=x.join(e.cwd,".aca","attachments",B(e.sessionId),B(e.jobId));E.mkdirSync(t,{recursive:!0});const n=[];for(const[a,o]of e.attachments.entries()){const r=o.attachmentId?{mimeType:o.mimeType||"application/octet-stream",bytes:await D(`/api/client/attachments/${encodeURIComponent(o.attachmentId)}`)}:Ce(o.dataUrl??""),l=o.mimeType||r.mimeType||"application/octet-stream",u=o.kind==="image"||l.startsWith("image/")?"image":"file",i=ve(o.name||`attachment-${a+1}`,l),h=x.join(t,`${String(a+1).padStart(2,"0")}-${i}`);E.writeFileSync(h,r.bytes),n.push({name:o.name||i,mimeType:l,size:r.bytes.byteLength,kind:u,path:h,relativePath:x.relative(e.cwd,h),base64:r.bytes.toString("base64"),sha256:L.createHash("sha256").update(r.bytes).digest("hex")})}return n}function Ce(e){const t=e.indexOf(",");if(!e.startsWith("data:")||t<0)throw new Error("attachment dataUrl is not a data URL");const n=e.slice(5,t),a=e.slice(t+1),o=n.split(";").filter(Boolean),r=o[0]?.includes("/")?o[0]:"application/octet-stream";if(!o.includes("base64"))throw new Error("attachment dataUrl is not base64 encoded");return{mimeType:r,bytes:Buffer.from(a,"base64")}}function ve(e,t){const n=x.basename(e).replace(/[<>:"/\\|?*\x00-\x1F]/g,"_").trim(),a=`attachment${ke(t)}`;return(!n||n==="."||n===".."?a:n).slice(0,180)}function ke(e){switch(e.toLowerCase()){case"image/png":return".png";case"image/jpeg":return".jpg";case"image/webp":return".webp";case"image/gif":return".gif";case"text/plain":return".txt";case"application/json":return".json";case"text/markdown":case"text/x-markdown":return".md";case"application/pdf":return".pdf";case"application/msword":return".doc";case"application/vnd.openxmlformats-officedocument.wordprocessingml.document":return".docx";case"application/vnd.ms-excel":return".xls";case"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet":return".xlsx";case"application/vnd.ms-powerpoint":return".ppt";case"application/vnd.openxmlformats-officedocument.presentationml.presentation":return".pptx";case"application/zip":return".zip";case"application/x-7z-compressed":return".7z";case"application/x-rar-compressed":case"application/vnd.rar":return".rar";case"application/gzip":return".gz";case"application/x-tar":return".tar";default:return""}}function B(e){return e.replace(/[^a-zA-Z0-9._-]+/g,"-").replace(/^-+|-+$/g,"").slice(0,80)||"item"}function z(e){return e.map(t=>({name:t.name,kind:t.kind,mimeType:t.mimeType,size:t.size,path:t.path,relativePath:t.relativePath,sha256:t.sha256}))}function Pe(e){if(!Number.isFinite(e)||e<=0)return"0 B";const t=["B","KB","MB","GB"];let n=e,a=0;for(;n>=1024&&a<t.length-1;)n/=1024,a+=1;return`${n>=10||a===0?n.toFixed(0):n.toFixed(1)} ${t[a]}`}async function Re(e,t,n){!n.usedWorktree||!n.worktreeId||await y("POST",`/api/client/jobs/${encodeURIComponent(e)}/worktree`,{machineId:t,baseCommit:n.baseCommit??null,worktreePath:n.cwd,status:n.status??"active",raw:n.raw??k(n)})}function k(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 xe(e,t,n,a){const r=(await y("POST",`/api/client/jobs/${encodeURIComponent(e)}/permissions`,{machineId:t,request:n})).item.permission_id;for(;!a.aborted;){await j(M);const l=await y("GET",`/api/client/permissions/${encodeURIComponent(r)}`);if(l.item.status==="resolved"&&l.item.response){const u=l.item.response.outcome;return u.outcome==="selected"?{outcome:u}:{outcome:{outcome:"cancelled"}}}}return{outcome:{outcome:"cancelled"}}}async function Ee(e,t,n,a){const r=(await y("POST",`/api/client/jobs/${encodeURIComponent(e)}/permissions`,{machineId:t,request:{kind:"elicitation",...n}})).item.permission_id;for(;!a.aborted;){await j(M);const l=await y("GET",`/api/client/permissions/${encodeURIComponent(r)}`);if(l.item.status==="resolved"&&l.item.response){const u=l.item.response.outcome;return u.outcome==="submitted"?{outcome:u}:{outcome:{outcome:"cancelled"}}}}return{outcome:{outcome:"cancelled"}}}async function je(e,t){if(t.signal.aborted)return;const n=await y("GET",`/api/client/jobs/${encodeURIComponent(e)}`);Number(n.item.cancel_requested??0)===1&&t.abort()}async function P(e,t,n,a){await y("POST",`/api/client/jobs/${encodeURIComponent(e)}/fail`,{machineId:t,error:n,result:a})}function j(e){return new Promise(t=>setTimeout(t,e))}function $e(e,t){return e.length<=t?e:`${e.slice(0,t)}...`}export{Ge as startJobWorkerLoop};
@@ -1 +1 @@
1
- import r from"node:fs";const e="ACA_WORKER_INSTANCE_ID",o="ACA_WORKER_PID",s="ACA_WORKER_START_TICKS",C="ACA_ACP_RUNTIME_CHILD",c="ACA_ACP_RUNTIME_OWNER_PID",E="ACA_ACP_RUNTIME_OWNER_START_TICKS",R="ACA_ACP_RUNTIME_WORKER_INSTANCE_ID";function N(A){try{const _=r.readFileSync(`/proc/${A}/stat`,"utf8"),t=_.lastIndexOf(")");return t<0?null:_.slice(t+2).trim().split(/\s+/)[19]||null}catch{return null}}function T(A={}){const _=process.env[o]||String(process.pid),t=process.env[s]||N(Number(_))||"",n=process.env[e]||"";return{...process.env,...A,[C]:"1",[c]:_,[E]:t,[R]:n}}export{C as ACA_ACP_CHILD_ENV,c as ACA_ACP_OWNER_PID_ENV,E as ACA_ACP_OWNER_START_TICKS_ENV,R as ACA_ACP_WORKER_INSTANCE_ENV,e as ACA_WORKER_INSTANCE_ENV,o as ACA_WORKER_PID_ENV,s as ACA_WORKER_START_TICKS_ENV,T as acpChildEnvironment,N as readProcessStartTicks};
1
+ import e from"node:fs";const s="ACA_WORKER_INSTANCE_ID",C="ACA_WORKER_PID",c="ACA_WORKER_START_TICKS",E="ACA_ACP_RUNTIME_CHILD",N="ACA_ACP_RUNTIME_OWNER_PID",R="ACA_ACP_RUNTIME_OWNER_START_TICKS",I="ACA_ACP_RUNTIME_WORKER_INSTANCE_ID",T="ACA_ACP_RUNTIME_COMMAND";function i(t){try{const _=e.readFileSync(`/proc/${t}/stat`,"utf8"),A=_.lastIndexOf(")");return A<0?null:_.slice(A+2).trim().split(/\s+/)[19]||null}catch{return null}}function P(t,_,A={}){const n=process.env[C]||String(process.pid),r=process.env[c]||i(Number(n))||"",o=process.env[s]||"";return{...process.env,...A,[E]:"1",[N]:n,[R]:r,[I]:o,[T]:JSON.stringify({command:t,args:_})}}export{E as ACA_ACP_CHILD_ENV,T as ACA_ACP_COMMAND_ENV,N as ACA_ACP_OWNER_PID_ENV,R as ACA_ACP_OWNER_START_TICKS_ENV,I as ACA_ACP_WORKER_INSTANCE_ENV,s as ACA_WORKER_INSTANCE_ENV,C as ACA_WORKER_PID_ENV,c as ACA_WORKER_START_TICKS_ENV,P as acpChildEnvironment,i as readProcessStartTicks};
@@ -1,2 +1,2 @@
1
1
  #!/usr/bin/env node
2
- import{Command as a}from"commander";import m from"node:path";import{createStartCommand as n}from"./commands/start.js";import{createDaemonCommand as t}from"./commands/daemon.js";import{createWorkerAuthCommand as s}from"./commands/worker-auth.js";import{createWorkerCodexCommand as c}from"./commands/worker-codex.js";import{createWorkerProjectCommand as i}from"./commands/worker-project.js";import{DEFAULT_ACA_SERVER_URL as d}from"./core/aca-config.js";process.env.ACA_DEFAULT_SERVER_URL||=d;async function p(r=process.argv){const o=new a;o.name("aica").description("AICA remote Agent worker").version("0.1.19").enablePositionalOptions(),o.addCommand(n()),o.addCommand(t()),o.addCommand(s()),o.addCommand(i()),o.addCommand(c()),await o.parseAsync(r)}const C=process.argv[1]||"",e=m.basename(C).replace(/\.(?:js|ts|cjs|mjs)$/i,"");(e==="worker-cli"||e==="aica"||e==="aca")&&p().catch(r=>{const o=r instanceof Error?r.message:String(r);console.error(o),process.exitCode=1});export{p as runWorkerCli};
2
+ import{Command as a}from"commander";import m from"node:path";import{createStartCommand as n}from"./commands/start.js";import{createDaemonCommand as t}from"./commands/daemon.js";import{createWorkerAuthCommand as s}from"./commands/worker-auth.js";import{createWorkerCodexCommand as c}from"./commands/worker-codex.js";import{createWorkerProjectCommand as i}from"./commands/worker-project.js";import{DEFAULT_ACA_SERVER_URL as d}from"./core/aca-config.js";process.env.ACA_DEFAULT_SERVER_URL||=d;async function p(r=process.argv){const o=new a;o.name("aica").description("AICA remote Agent worker").version("0.1.20").enablePositionalOptions(),o.addCommand(n()),o.addCommand(t()),o.addCommand(s()),o.addCommand(i()),o.addCommand(c()),await o.parseAsync(r)}const C=process.argv[1]||"",e=m.basename(C).replace(/\.(?:js|ts|cjs|mjs)$/i,"");(e==="worker-cli"||e==="aica"||e==="aca")&&p().catch(r=>{const o=r instanceof Error?r.message:String(r);console.error(o),process.exitCode=1});export{p as runWorkerCli};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yuandc/aica",
3
- "version": "0.1.19",
3
+ "version": "0.1.20",
4
4
  "description": "AICA remote Agent worker",
5
5
  "author": "yuandc",
6
6
  "type": "module",