@yuandc/aica 0.1.58 → 0.1.60

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,4 +1,4 @@
1
- import{JsonLineRpcClient as D}from"../../client/json-rpc.js";import{startCodexAppServer as $}from"../codex/codex-process.js";import{CodexConnectionRecoveryTracker as L}from"../codex/connection-events.js";import{codexAcpSessionUpdatesFromNotification as C}from"../codex/events.js";import{CodexPlanDeltaState as y}from"../codex/plan-events.js";import{isCodexApprovalRequest as O,resolveCodexApprovalRequest as j}from"../codex/permissions.js";import{buildCodexContinuityInstructions as B,codexTokenUsageFromRawResponseCompleted as K,codexTokenUsageFromNotification as W,decideCodexContextMaintenance as k,readCodexContextMaintenancePolicy as v,rolloutFileSize as z}from"./context-maintenance.js";import{codexNotificationBelongsToCurrentTurn as G,codexNotificationRouting as R}from"./notification-routing.js";import{completedSubtaskHistoryUpdates as J}from"./subtask-history.js";import{CodexSubtaskRouter as H}from"./subtask-routing.js";import{decorateSubtaskUpdate as A}from"./subtask-executions.js";import{waitForCancellableTurnStart as V}from"./cancellation.js";import{codexAcpUpdateIsMeaningfulProgress as P,codexRawItemIsMeaningfulProgress as X,withCodexProgressMetadata as _}from"./progress-evidence.js";import{acpPromptText as Q,acpPromptToCodexInput as Y,turnConfigFromConfig as Z}from"./turn-input.js";import{CodexControlToolLoopDetector as ee,codexControlToolLoopAcpUpdate as M,controlToolLoopDetectionThresholds as te,controlToolLoopReady as se,readCodexControlToolLoopPolicy as oe}from"./control-tool-loop.js";import{CodexControlToolSteeringLimiter as ne,codexControlToolSteeringAcpUpdate as q}from"./control-tool-steering.js";class ie{lineBuffer="";started=null;rpc=null;codexStartupPromise=null;initialized=!1;currentSessionId=null;currentCwd=process.cwd();promptActive=!1;currentTurnId=null;turnStartPromise=null;promptCancelled=!1;nextParentRequestId=1;pendingParentRequests=new Map;completedTurns=new Map;pendingTurnCompletions=new Map;agentMessagePhases=new Map;planDeltas=new y;subtaskRouter=new H;subtaskGeneration=0;subtaskAgentMessagePhases=new Map;subtaskPlanDeltas=new Map;subscribedSubtaskThreadIds=new Set;pendingSubtaskSubscriptions=new Map;hydratedSubtaskThreadIds=new Set;seenSubtaskItemIds=new Set;pendingSubtaskNotifications=[];config={};sessionAliases=new Map;currentAffectedFiles=new Set;connectionRecovery=new L;latestTokenUsage=null;currentRolloutPath=null;threadTurnCount=0;currentPromptText="";currentAssistantText="";previousUserText="";previousAssistantText="";previousAffectedFiles=new Set;maintenanceActive=!1;pendingCompaction=null;shutdownPromise=null;managedProvider=null;controlToolLoop;controlToolPolicy=oe();controlToolSteering=new ne(this.controlToolPolicy.steeringMaxAttempts);constructor(){this.controlToolLoop=new ee({hasActiveSubtasks:()=>this.subtaskRouter.hasActiveSubtasks(),shortSleepMaxDurationMs:this.controlToolPolicy.shortSleepMaxDurationMs}),process.stdin.setEncoding("utf8"),process.stdin.on("data",t=>this.handleInput(String(t)));const e=()=>{this.shutdown().finally(()=>process.exit(0))};process.stdin.once("end",e),process.stdin.once("close",e),process.stdin.once("error",e),process.once("SIGTERM",()=>{this.shutdown().finally(()=>process.exit(0))}),process.once("SIGINT",()=>{this.shutdown().finally(()=>process.exit(130))})}handleInput(e){this.lineBuffer+=e;const t=this.lineBuffer.split(`
2
- `);this.lineBuffer=t.pop()??"";for(const s of t){const o=s.trim();if(!o)continue;let n;try{n=JSON.parse(o)}catch{continue}this.handleMessage(n)}}async handleMessage(e){if("id"in e&&("result"in e||"error"in e)&&typeof e.method!="string"){this.handleParentResponse(e);return}const t=typeof e.method=="string"?e.method:"",s=e.id;if(t){if(s===void 0){await this.handleNotification(t,d(e.params));return}try{const o=await this.handleRequest(t,d(e.params));this.write({jsonrpc:"2.0",id:s,result:o})}catch(o){this.write({jsonrpc:"2.0",id:s,error:{code:-32603,message:o instanceof Error?o.message:String(o)}})}}}async handleNotification(e,t){if(e==="session/cancel")try{await this.cancel(t)}catch(s){process.stderr.write(`Codex cancel failed: ${s instanceof Error?s.message:String(s)}
3
- `),await this.shutdown(),process.exit(1)}}async handleRequest(e,t){switch(e){case"initialize":return this.initializeResponse();case"aca/runtime_config":return this.applyRuntimeConfig(t);case"session/new":return this.newSession(t);case"session/resume":case"session/load":return this.resumeSession(t);case"session/fork":return this.forkSession(t);case"session/prompt":return this.prompt(t);case"session/compact":return this.manualCompact(t);case"aca/session_recover":return this.recoverSession(t);case"session/cancel":return await this.cancel(t),{};case"session/close":return await this.closeSession(t),{};case"session/set_config_option":return this.setConfigOption(t);case"session/set_mode":return this.setMode(t);case"fs/read_text_file":case"fs/write_text_file":case"terminal/create":case"terminal/output":case"terminal/wait_for_exit":case"terminal/kill":case"terminal/release":return{};default:throw new Error(`Unsupported ACP method: ${e}`)}}initializeResponse(){return{protocolVersion:1,agentCapabilities:{loadSession:!0,sessionCapabilities:{resume:!0,close:!0}}}}applyRuntimeConfig(e){if(this.started)throw new Error("Codex runtime config must be applied before app-server starts");const t=d(e.codexProvider);if(Object.keys(t).length===0)return this.managedProvider=null,{configured:!1};const s=a(t,"baseUrl"),o=a(t,"apiKey"),n=Number(t.revision||0);if(!s||!o||!Number.isSafeInteger(n)||n<1)throw new Error("Invalid Codex managed provider runtime config");return this.managedProvider={baseUrl:s,apiKey:o,revision:n},{configured:!0,revision:n}}async newSession(e){const t=a(e,"cwd")||process.cwd();this.applyInitialConfig(e),await this.ensureCodex(t);const s=await this.requireRpc().request("thread/start",{cwd:t,approvalsReviewer:"user",...this.codexConfig(),ephemeral:!1,experimentalRawEvents:!0,threadSource:"aca-codex-acp"},6e4),o=x(s);if(!o)throw new Error("Codex thread/start did not return thread id");return this.currentSessionId=o,this.currentCwd=t,this.resetSubtaskState(),this.captureThreadMetadata(s),this.sessionResponse(o)}async resumeSession(e){const t=a(e,"sessionId");if(!t)throw new Error("sessionId is required");const s=a(e,"cwd")||this.currentCwd;this.applyInitialConfig(e),await this.ensureCodex(s);const o=await this.requireRpc().request("thread/resume",{threadId:t,cwd:s,approvalsReviewer:"user",...this.codexConfig()},6e4);return this.currentSessionId=x(o)||t,this.currentCwd=s,this.resetSubtaskState(o),await this.subscribeKnownSubtasks(!1),this.captureThreadMetadata(o),this.sessionResponse(this.currentSessionId)}async forkSession(e){const t=a(e,"sessionId");if(!t)throw new Error("sessionId is required");const s=a(e,"cwd")||this.currentCwd;this.applyInitialConfig(e),await this.ensureCodex(s);const o=await this.requireRpc().request("thread/fork",{threadId:t,cwd:s,approvalsReviewer:"user",ephemeral:!1,threadSource:"aca-codex-acp-fork",...this.codexConfig()},12e4),n=x(o);if(!n)throw new Error("Codex thread/fork did not return thread id");return this.currentSessionId=n,this.currentCwd=s,this.resetSubtaskState(o),this.captureThreadMetadata(o),this.sessionResponse(n)}async prompt(e){this.promptCancelled=!1;const t=a(e,"sessionId")||this.currentSessionId;let s=this.resolveSessionAlias(t);if(!s)throw new Error("sessionId is required");const o=Array.isArray(e.prompt)?e.prompt:[],n=this.requireRpc();this.currentSessionId&&this.currentSessionId!==s&&this.resetSubtaskState(),this.currentSessionId=s;const r=await this.maintainContext("before",{userRequest:this.previousUserText,assistantResponse:this.previousAssistantText,affectedFiles:[...this.previousAffectedFiles]},{allowRollover:!0});if(ue(r),this.promptCancelled||this.shutdownPromise)throw new Error("Codex prompt cancelled");s=this.currentSessionId||s,this.promptActive=!0,this.currentTurnId=null,this.controlToolLoop.reset(),this.controlToolSteering.reset(),this.currentPromptText=Q(o),this.currentAssistantText="",this.currentAffectedFiles.clear(),this.connectionRecovery.reset(),this.agentMessagePhases.clear(),this.planDeltas.reset(),this.pendingSubtaskNotifications=[],this.flushSubtaskStates();let c;try{this.turnStartPromise=n.request("turn/start",{threadId:s,clientUserMessageId:`aca-acp-${Date.now()}-${Math.random().toString(16).slice(2)}`,input:Y(o),cwd:this.currentCwd,...Z(this.config)},N()),c=await this.turnStartPromise;const u=String(c?.turn?.id??"");this.currentTurnId=u||null;const l=u?await this.waitForTurnCompleted(u,N()):c?.turn,h=await this.subtaskRouter.waitForIdle(fe());if(h.timedOut&&h.activeThreadIds.length>0&&this.notifySessionUpdate({sessionUpdate:"aca_codex_subtask_drain",label:"子任务收尾等待超时",detail:`仍有 ${h.activeThreadIds.length} 个子任务未结束`,_meta:{aca:{subtaskDrain:h}}}),this.promptCancelled)throw new Error("Codex prompt cancelled");this.threadTurnCount+=1,this.previousUserText=this.currentPromptText,this.previousAssistantText=this.currentAssistantText,this.previousAffectedFiles.clear();for(const p of this.currentAffectedFiles)this.previousAffectedFiles.add(p);const f=await this.maintainContext("after",{userRequest:this.previousUserText,assistantResponse:this.previousAssistantText,affectedFiles:[...this.previousAffectedFiles]},{allowRollover:!0});return{stopReason:"end_turn",sessionId:this.currentSessionId,turn:l,_meta:{acaContextUsage:U(f)??ce(this.latestTokenUsage),acaContextMaintenance:f,acaSubtaskDrain:h}}}catch(u){const l=this.connectionRecovery.fail(u);throw l&&this.notifySessionUpdate(l),u}finally{this.subtaskRouter.executions.loseObservation(this.promptCancelled?"父任务已中断,子执行状态需重新确认":"本轮观测结束,未确认子执行终态"),this.flushSubtaskStates(),this.controlToolLoop.reset(),this.promptActive=!1,this.currentTurnId=null,this.turnStartPromise=null,this.pendingSubtaskNotifications=[]}}resetSubtaskState(e){this.subtaskGeneration+=1,this.subtaskRouter.reset(),this.subtaskAgentMessagePhases.clear(),this.subtaskPlanDeltas.clear(),this.subscribedSubtaskThreadIds.clear(),this.pendingSubtaskSubscriptions.clear(),this.hydratedSubtaskThreadIds.clear(),this.seenSubtaskItemIds.clear(),this.pendingSubtaskNotifications=[],e!==void 0&&this.subtaskRouter.restoreFromSessionSnapshot(e,this.currentSessionId)}async subscribeKnownSubtasks(e=!0){const t=this.subtaskRouter.routesForSubscription().map(s=>this.subscribeSubtask(s,e));await Promise.allSettled(t)}subscribeSubtask(e,t){const s=e.sourceThreadId;if(!s||s===this.currentSessionId||this.subscribedSubtaskThreadIds.has(s))return Promise.resolve();const o=this.pendingSubtaskSubscriptions.get(s);if(o)return o;const n=this.subtaskGeneration,r=this.requireRpc().request("thread/resume",{threadId:s,approvalsReviewer:"user",excludeTurns:!1},6e4).then(c=>{if(n!==this.subtaskGeneration)return;this.subscribedSubtaskThreadIds.add(s);const u=this.subtaskRouter.restoreFromSessionSnapshot(c,this.currentSessionId);t&&this.hydrateSubtaskHistory(c,e),u>0&&this.subscribeKnownSubtasks(t)}).catch(()=>{}).finally(()=>{this.pendingSubtaskSubscriptions.get(s)===r&&this.pendingSubtaskSubscriptions.delete(s)});return this.pendingSubtaskSubscriptions.set(s,r),r}async cancel(e){const t=this.resolveSessionAlias(a(e,"sessionId")||this.currentSessionId);if(!t||!this.rpc)return;if(t!==this.currentSessionId)throw new Error("Cannot cancel a different Codex session");this.promptCancelled=!0,this.subtaskRouter.stopWaiting();const s=this.turnStartPromise?await V(this.turnStartPromise):null,o=this.currentTurnId||a(w(s,"turn"),"id");if(await this.rpc.request("turn/interrupt",{threadId:t,turnId:o},5e3),o){const n=this.pendingTurnCompletions.get(o);n&&(this.pendingTurnCompletions.delete(o),clearTimeout(n.timer),n.reject(new Error("Codex prompt cancelled")))}}async manualCompact(e){const t=a(e,"sessionId")||this.currentSessionId,s=this.resolveSessionAlias(t);if(!s)throw new Error("sessionId is required");if(this.promptActive||this.maintenanceActive)throw new Error("Codex session is busy");this.currentSessionId=s;const o=v(),n=await this.maintainContext("manual",{userRequest:this.previousUserText,assistantResponse:this.previousAssistantText,affectedFiles:[...this.previousAffectedFiles]},{force:!0,allowRollover:e.allowRollover===!0,compactTimeoutMs:ae(e,o.compactTimeoutMs)}),r=String(n.action||"");if(r==="compact_failed"||r==="rollover_failed"){const c=String(n.compactError||"Codex 上下文压缩未完成");throw new Error(`Codex 上下文压缩失败:${c}`)}return{sessionId:this.currentSessionId,maintenance:n,contextUsage:U(n)}}async recoverSession(e){const t=a(e,"sessionId")||this.currentSessionId,s=this.resolveSessionAlias(t);if(!s)throw new Error("sessionId is required");const o=a(e,"reason");if(o!=="low_output_loop"&&o!=="control_tool_loop")throw new Error("Unsupported Codex recovery reason");if(this.promptActive||this.maintenanceActive)throw new Error("Codex session is busy");if(s!==this.currentSessionId)throw new Error("Cannot recover a different Codex session");const n=new Set([...this.previousAffectedFiles,...this.currentAffectedFiles]);return await this.rotateThread(s,{userRequest:this.currentPromptText||this.previousUserText,assistantResponse:this.currentAssistantText||this.previousAssistantText,affectedFiles:[...n]}),{...d(this.sessionResponse(this.currentSessionId)),previousSessionId:s,recoveryReason:o}}async closeSession(e){await this.shutdown()}async ensureCodex(e){if(this.rpc&&this.started&&this.started.child.exitCode===null&&this.started.child.signalCode===null)return;if(this.codexStartupPromise){await this.codexStartupPromise;return}const t=(async()=>{if(this.rpc&&this.started&&this.started.child.exitCode===null&&this.started.child.signalCode===null)return;const s=$(e,this.managedProvider),o=new D(s.child,{peerName:"Codex app-server",onNotification:(n,r)=>this.handleCodexNotification(n,r),onRequest:async(n,r)=>this.handleCodexRequest(n,r)});this.started=s,this.rpc=o,s.child.once("exit",()=>{this.started===s&&(this.subtaskRouter.executions.loseObservation("执行连接断开,未确认子代理终态"),this.flushSubtaskStates(),this.subtaskRouter.stopWaiting())}),this.initialized=!1,await o.request("initialize",{clientInfo:{name:"aca-codex-acp",version:"0.1.0"},capabilities:{experimentalApi:!0,requestAttestation:!1}},6e4),this.initialized=!0})();this.codexStartupPromise=t;try{await t}catch(s){throw this.rpc&&this.started&&!this.initialized&&await T(this.started.child,1e3),this.rpc=null,this.started=null,s}finally{this.codexStartupPromise===t&&(this.codexStartupPromise=null)}}handleCodexNotification(e,t){const s=R(t);if(this.maintenanceActive&&(!s.threadId||s.threadId===this.currentSessionId)){this.captureContextNotification(e,t);return}if(!G(t,this.currentSessionId,this.currentTurnId)){if(!this.promptActive||!s.threadId||s.threadId===this.currentSessionId)return;const u=this.subtaskRouter.routeFor(s.threadId);if(!u){this.bufferPendingSubtaskNotification(e,t,s.threadId);return}this.handleSubtaskNotification(e,t,u),this.flushPendingSubtaskNotifications();return}const o=this.subtaskRouter.observe(t,this.currentSessionId);if(this.flushSubtaskStates(),o>0&&this.subscribeKnownSubtasks(!0),this.captureContextNotification(e,t),this.maintenanceActive||!this.promptActive&&e!=="turn/completed")return;this.rpc?.refreshPendingRequestTimeout("turn/start"),this.captureAgentMessagePhase(e,t);const n=this.connectionRecovery.beforeNotification(e,t);e==="turn/completed"&&this.resolveTurnCompleted(t);const r=e==="rawResponseItem/completed"?le(this.controlToolLoop,t):[],c=e==="rawResponse/completed"?de(t):e==="rawResponseItem/completed"?r.map(({update:u})=>u):C(e,t,{agentMessagePhases:this.agentMessagePhases,planDeltas:this.planDeltas});for(const u of r)this.maybeSteerControlToolLoop(u.event);for(const u of[...n,...c]){const l=_(u,e,t,this.agentMessagePhases);P(l)&&this.notifyControlToolProgress(),this.captureUpdateEvidence(l),this.notifySessionUpdate(l)}o>0&&this.flushPendingSubtaskNotifications()}handleSubtaskNotification(e,t,s){if(!this.promptActive)return;const o=this.subtaskRouter.acceptNotification(s,e,t);if(this.flushSubtaskStates(),!o)return;(e==="rawResponseItem/completed"||e==="item/completed")&&X(d(d(t).item))&&this.notifyControlToolProgress(),this.rememberSubtaskItem(s.sourceThreadId,t),this.rpc?.refreshPendingRequestTimeout("turn/start");const n=this.subtaskRouter.executions.get(s.sourceThreadId)?.runId||s.sourceThreadId,r=this.subtaskAgentMessagePhases.get(n)??new Map;this.subtaskAgentMessagePhases.set(n,r);const c=this.subtaskPlanDeltas.get(n)??new y;this.subtaskPlanDeltas.set(n,c),this.captureAgentMessagePhase(e,t,r);const u=this.subtaskRouter.observe(t,this.currentSessionId);this.flushSubtaskStates(),u>0&&this.subscribeKnownSubtasks(!0);const l=C(e,t,{agentMessagePhases:r,planDeltas:c}),h=R(t).turnId;for(const f of l){const p=_(f,e,t,r);if(!he(p))continue;a(p,"title")&&this.subtaskRouter.executions.action(s.sourceThreadId,a(p,"title")),P(p)&&(this.notifyControlToolProgress(),this.subtaskRouter.executions.progress(s.sourceThreadId,a(p,"title")||"收到执行结果"));const g=A(p,s,h,this.subtaskRouter.executions.get(s.sourceThreadId));b(g,this.currentAffectedFiles),this.notifySessionUpdate(g)}}hydrateSubtaskHistory(e,t){if(!(!this.promptActive||this.hydratedSubtaskThreadIds.has(t.sourceThreadId))){this.hydratedSubtaskThreadIds.add(t.sourceThreadId);for(const s of J(e)){const o=F(t.sourceThreadId,s.itemId);if(this.seenSubtaskItemIds.has(o))continue;this.seenSubtaskItemIds.add(o);const n=A(s.update,t,s.sourceTurnId,{runId:`history.${t.sourceThreadId}.${s.sourceTurnId}`,state:"unknown",outputIncomplete:!0,reason:"历史过程回放,不代表当前执行状态"}),r=d(n._meta);n._meta={...r,aca:{...d(r.aca),historyReplay:!0}},b(n,this.currentAffectedFiles),this.notifySessionUpdate(n)}}}rememberSubtaskItem(e,t){const s=d(t),o=d(s.item),n=a(o,"id")||a(s,"itemId");n&&this.seenSubtaskItemIds.add(F(e,n))}bufferPendingSubtaskNotification(e,t,s){this.pendingSubtaskNotifications.push({method:e,params:t,threadId:s}),this.pendingSubtaskNotifications.length>200&&this.pendingSubtaskNotifications.shift()}flushPendingSubtaskNotifications(){if(this.pendingSubtaskNotifications.length===0)return;let e=!0;for(;e&&this.pendingSubtaskNotifications.length>0;){e=!1;const t=[];for(const s of this.pendingSubtaskNotifications){const o=this.subtaskRouter.routeFor(s.threadId);if(!o){t.push(s);continue}e=!0,this.handleSubtaskNotification(s.method,s.params,o)}this.pendingSubtaskNotifications=t}}notifySessionUpdate(e){this.notify("session/update",{sessionId:this.currentSessionId,update:e})}flushSubtaskStates(){if(this.promptActive)for(const e of this.subtaskRouter.executions.drainUpdates())this.notifySessionUpdate(e)}async handleCodexRequest(e,t){if(O(e))return j(e,t,async s=>{const o=await this.requestParent("session/request_permission",{sessionId:this.currentSessionId,request:{...s,params:t,method:e}}),n=d(o),r=d(n.outcome);return r.outcome==="selected"&&typeof r.optionId=="string"?{outcome:{outcome:"selected",optionId:r.optionId}}:{outcome:{outcome:"cancelled"}}});if(pe(e)){const s=await this.requestParent("elicitation/create",{sessionId:this.currentSessionId,request:{...d(t),method:e}}),o=d(s),n=d(o.outcome);return n.outcome==="submitted"?{outcome:{outcome:"submitted",value:typeof n.value=="string"?n.value:""}}:{outcome:{outcome:"cancelled"}}}return{}}captureAgentMessagePhase(e,t,s=this.agentMessagePhases){if(e!=="item/started"&&e!=="item/completed")return;const o=w(t,"item");if(a(o,"type")!=="agentMessage")return;const n=a(o,"id");n&&s.set(n,a(o,"phase"))}waitForTurnCompleted(e,t){const s=this.completedTurns.get(e);return s?Promise.resolve(s):new Promise((o,n)=>{const r=setTimeout(()=>{this.pendingTurnCompletions.delete(e),n(new Error("Codex app-server request timed out while waiting for turn/completed"))},t);r.unref(),this.pendingTurnCompletions.set(e,{resolve:o,reject:n,timer:r})})}resolveTurnCompleted(e){const t=w(e,"turn"),s=a(t,"id");if(!s)return;this.completedTurns.set(s,t);const o=this.pendingTurnCompletions.get(s);if(o){if(this.pendingTurnCompletions.delete(s),clearTimeout(o.timer),a(t,"status")==="failed"){o.reject(new Error(`Codex turn failed: ${JSON.stringify(t.error??{})}`));return}o.resolve(t)}}async shutdown(){return this.shutdownPromise?this.shutdownPromise:(this.shutdownPromise=this.performShutdown(),this.shutdownPromise)}async performShutdown(){this.subtaskRouter.executions.loseObservation("执行连接关闭,未确认子代理终态"),this.flushSubtaskStates(),this.subtaskRouter.stopWaiting(),this.pendingCompaction&&(clearTimeout(this.pendingCompaction.timer),this.pendingCompaction.reject(new Error("Codex ACP adapter closed")),this.pendingCompaction=null);for(const t of this.pendingParentRequests.values())clearTimeout(t.timer),t.reject(new Error("Codex ACP adapter closed"));this.pendingParentRequests.clear();for(const t of this.pendingTurnCompletions.values())clearTimeout(t.timer),t.reject(new Error("Codex ACP adapter closed"));this.pendingTurnCompletions.clear();const e=this.started?.child;!e||e.exitCode!==null||e.signalCode!==null||(e.stdin.end(),!await T(e,2e3)&&(e.kill("SIGTERM"),!await T(e,1e3)&&(e.kill("SIGKILL"),await T(e,1e3))))}sessionResponse(e){return{sessionId:e,modes:{currentModeId:"workspace-write",availableModes:[{id:"read-only",name:"Read Only"},{id:"workspace-write",name:"Workspace Write"},{id:"danger-full-access",name:"Full Access"}]},configOptions:[{id:"model",category:"model",type:"string"},{id:"approvalPolicy",category:"approval",type:"string"},{id:"sandbox",category:"mode",type:"string"}]}}requireRpc(){if(!this.rpc)throw new Error("Codex app-server is not started");return this.rpc}notify(e,t){this.write({jsonrpc:"2.0",method:e,params:t})}requestParent(e,t,s=600*1e3){const o=this.nextParentRequestId++;return new Promise((n,r)=>{const c=setTimeout(()=>{this.pendingParentRequests.delete(o),r(new Error(`ACP client request timed out: ${e}`))},s);c.unref(),this.pendingParentRequests.set(o,{resolve:n,reject:r,timer:c}),this.write({jsonrpc:"2.0",id:o,method:e,params:t})})}handleParentResponse(e){const t=Number(e.id),s=this.pendingParentRequests.get(t);if(s){if(this.pendingParentRequests.delete(t),clearTimeout(s.timer),"error"in e){const o=d(e.error);s.reject(new Error(a(o,"message")||"ACP client request failed"));return}s.resolve(e.result)}}setConfigOption(e){const t=a(e,"configId"),s=e.value;if(!t)throw new Error("configId is required");if(typeof s!="string"&&typeof s!="boolean")throw new Error("config value must be string or boolean");return this.config[t]=s,this.notify("session/update",{sessionId:this.currentSessionId,update:{sessionUpdate:"config_option_update",configId:t,value:s}}),{}}setMode(e){const t=a(e,"modeId");if(!t)throw new Error("modeId is required");return this.config.sandbox=t,this.notify("session/update",{sessionId:this.currentSessionId,update:{sessionUpdate:"current_mode_update",modeId:t}}),{}}applyInitialConfig(e){const t=w(e,"config");for(const[s,o]of Object.entries(t))(typeof o=="string"||typeof o=="boolean")&&(this.config[s]=o)}codexConfig(){return{...typeof this.config.model=="string"&&this.config.model?{model:this.config.model}:{},approvalPolicy:typeof this.config.approvalPolicy=="string"?this.config.approvalPolicy:"on-request",sandbox:typeof this.config.sandbox=="string"?this.config.sandbox:"workspace-write"}}captureContextNotification(e,t){if(e==="thread/tokenUsage/updated"&&(this.latestTokenUsage=W(t)??this.latestTokenUsage),e==="thread/compacted"&&this.pendingCompaction&&this.resolveCompaction(),e==="item/completed"){const s=w(t,"item"),o=a(d(t),"threadId");a(s,"type")==="contextCompaction"&&(!o||o===this.currentSessionId)&&this.resolveCompaction(),a(s,"type")==="agentMessage"&&a(s,"phase")==="final_answer"&&(this.currentAssistantText=S(s)||this.currentAssistantText)}}resolveCompaction(){if(!this.pendingCompaction)return;const e=this.pendingCompaction;this.pendingCompaction=null,clearTimeout(e.timer),e.resolve()}captureUpdateEvidence(e){String(e.sessionUpdate||"")==="agent_message_chunk"&&(this.currentAssistantText+=S(e.content)),b(e,this.currentAffectedFiles)}captureThreadMetadata(e){const t=w(e,"thread");this.currentRolloutPath=a(t,"path")||null,this.threadTurnCount=Array.isArray(t.turns)?t.turns.length:0;const s=re(t.turns);s.userRequest&&(this.previousUserText=s.userRequest),s.assistantResponse&&(this.previousAssistantText=s.assistantResponse),this.previousAffectedFiles.clear();for(const o of s.affectedFiles)this.previousAffectedFiles.add(o);this.latestTokenUsage=null}async maintainContext(e,t,s={}){const o=v(),n=this.contextState(),r=k(n,o);if(!r.compact&&!s.force||!this.currentSessionId)return this.maintenanceResult({stage:e,action:"none",reasons:r.reasons,beforeState:n});const c=this.currentSessionId;this.notifyContextStatus("正在整理会话上下文",E(n)),this.maintenanceActive=!0;let u=!1,l="",h=n,f=r,p=!1,g="";try{const m=n.usage?.observedAtMs??0;await this.compactThread(c,s.compactTimeoutMs??o.compactTimeoutMs),await this.waitForTokenUsageAfter(m,2e3)}catch(m){u=!0,l=m instanceof Error?m.message:String(m)}try{if(e!=="manual"&&this.promptCancelled||this.shutdownPromise)throw new Error("Codex prompt cancelled");if(h=this.contextState(),f=k(h,o,{compactAttempted:!0,compactFailed:u}),f.rollover&&s.allowRollover!==!1){this.notifyContextStatus("正在创建轻量会话",f.reasons.join(", "));try{await this.rotateThread(c,t),p=!0}catch(m){g=m instanceof Error?m.message:String(m),this.notifyContextStatus("轻量会话创建失败",g)}}else u||this.notifyContextStatus("会话上下文已压缩",E(h))}finally{this.maintenanceActive=!1}return this.maintenanceResult({action:p?"rollover":g?"rollover_failed":u?"compact_failed":"compact",stage:e,reasons:f.reasons,beforeState:n,afterState:h,compactError:[l,g].filter(Boolean).join("; "),previousThreadId:c,rolloverRecommended:f.rollover&&s.allowRollover===!1})}compactThread(e,t){return this.pendingCompaction?Promise.reject(new Error("Codex context compaction is already running")):new Promise((s,o)=>{const n=setTimeout(()=>{this.pendingCompaction?.timer===n&&(this.pendingCompaction=null),o(new Error("Codex thread compaction timed out"))},t);n.unref(),this.pendingCompaction={resolve:s,reject:o,timer:n},this.requireRpc().request("thread/compact/start",{threadId:e},t).catch(r=>{this.pendingCompaction?.timer===n&&(this.pendingCompaction=null,clearTimeout(n),o(r instanceof Error?r:new Error(String(r))))})})}async rotateThread(e,t){const s=B({previousThreadId:e,cwd:this.currentCwd,userRequest:t.userRequest,assistantResponse:t.assistantResponse,affectedFiles:t.affectedFiles}),o=await this.requireRpc().request("thread/start",{cwd:this.currentCwd,approvalsReviewer:"user",...this.codexConfig(),developerInstructions:s,ephemeral:!1,experimentalRawEvents:!0,threadSource:"aca-codex-acp-rollover"},6e4),n=x(o);if(!n)throw new Error("Codex rollover thread/start did not return thread id");this.sessionAliases.set(e,n),this.currentSessionId=n,this.resetSubtaskState(o),this.captureThreadMetadata(o)}async waitForTokenUsageAfter(e,t){const s=Date.now()+t;for(;Date.now()<s;){if((this.latestTokenUsage?.observedAtMs??0)>e)return;await me(50)}}resolveSessionAlias(e){if(!e)return null;let t=e;const s=new Set;for(;this.sessionAliases.has(t)&&!s.has(t);)s.add(t),t=this.sessionAliases.get(t)??t;return t}contextState(){return{usage:this.latestTokenUsage,rolloutBytes:z(this.currentRolloutPath),turnCount:this.threadTurnCount}}maintenanceResult(e){return{stage:e.stage,action:e.action,reasons:e.reasons,previousThreadId:e.previousThreadId??null,sessionId:this.currentSessionId,before:e.beforeState,after:e.afterState??null,compactError:e.compactError||null,rolloverRecommended:e.rolloverRecommended===!0}}notifyContextStatus(e,t){this.notify("session/update",{sessionId:this.currentSessionId,update:{sessionUpdate:"aca_context_maintenance",label:e,detail:t}})}notifyControlToolProgress(){const e=this.controlToolLoop.recordProgress();e&&this.notifySessionUpdate(M(e))}maybeSteerControlToolLoop(e){if(!se(e.detection,this.controlToolPolicy))return;const t=te(e.detection,this.controlToolPolicy),s=this.controlToolSteering.next(e,t.warningCalls);if(!s)return;const{threadId:o,turnId:n}=e.detection;!o||!n||this.requireRpc().request("turn/steer",{threadId:o,expectedTurnId:n,clientUserMessageId:`aca-loop-steer-${Date.now()}-${Math.random().toString(16).slice(2)}`,input:[{type:"text",text:s.text,text_elements:[]}]},6e4).then(()=>{this.notifySessionUpdate(q(e,s,{succeeded:!0}))}).catch(r=>{this.notifySessionUpdate(q(e,s,{succeeded:!1,error:r}))})}write(e){process.stdout.write(`${JSON.stringify(e)}
4
- `)}}function x(i){const e=w(i,"thread");return a(e,"id")||null}function w(i,e){const t=d(i);return d(t[e])}function d(i){return i&&typeof i=="object"&&!Array.isArray(i)?i:{}}function S(i){if(typeof i=="string")return i;if(Array.isArray(i))return i.map(S).filter(Boolean).join("");if(!i||typeof i!="object")return"";const e=d(i);for(const t of["text","message","content","output_text"]){const s=S(e[t]);if(s)return s}return""}function b(i,e){if(Array.isArray(i)){for(const n of i)b(n,e);return}const t=d(i),s=a(t,"type").toLowerCase(),o=a(t,"path");if(o&&["diff","resource_link","content"].includes(s)&&e.add(o),Array.isArray(t.locations))for(const n of t.locations){const r=a(d(n),"path");r&&e.add(r)}for(const n of[t.content,t.update,t.rawInput,t.rawOutput])n&&n!==i&&b(n,e)}function re(i){if(!Array.isArray(i)||i.length===0)return{userRequest:"",assistantResponse:"",affectedFiles:[]};const e=i.at(-1),t=new Set;b(e,t);let s="",o="";const n=r=>{if(Array.isArray(r)){for(const l of r)n(l);return}if(!r||typeof r!="object")return;const c=d(r),u=a(c,"type").toLowerCase();u==="usermessage"&&(s=S(c)||s),u==="agentmessage"&&(o=S(c)||o);for(const l of["items","content","input"])c[l]&&n(c[l])};return n(e),{userRequest:s,assistantResponse:o,affectedFiles:[...t]}}function ae(i,e){const t=I(i,"timeoutMs");return!Number.isFinite(t)||t<=0?e:Math.max(3e4,Math.min(59e4,t-1e4))}function U(i){const e=String(i.action||"none"),t=d(i.after),s=d(i.before),o=d(t.usage),n=d(s.usage),r=Object.keys(o).length>0?o:n,c=I(r,"contextWindow");if(e==="rollover")return{usedTokens:0,inputTokens:0,contextWindow:c,ratio:0,observedAtMs:Date.now(),state:"new_thread",maintenanceAction:e};if(Object.keys(r).length===0)return null;const u=I(r,"totalTokens"),l=I(r,"inputTokens");return{usedTokens:u||l,inputTokens:l,contextWindow:c,ratio:c>0?(u||l)/c:0,observedAtMs:I(r,"observedAtMs")||Date.now(),state:e==="compact"?"compacted":"active",maintenanceAction:e}}function ce(i){return i?{usedTokens:i.totalTokens||i.inputTokens,inputTokens:i.inputTokens,contextWindow:i.contextWindow,ratio:i.contextWindow>0?(i.totalTokens||i.inputTokens)/i.contextWindow:0,observedAtMs:i.observedAtMs,state:"active",maintenanceAction:null}:null}function E(i){const e=i.usage?`${i.usage.inputTokens} tokens`:"token unknown",t=i.rolloutBytes>0?`${Math.round(i.rolloutBytes/1024/1024)}MB rollout`:"rollout unknown";return`${e}, ${t}, ${i.turnCount} turns`}function ue(i){const e=String(i.action||"none");if(e!=="compact_failed"&&e!=="rollover_failed")return;const t=String(i.compactError||"Codex 上下文维护未完成");throw new Error(`Codex 自动上下文维护失败:${t}`)}function I(i,e){const t=d(i)[e];return typeof t=="number"&&Number.isFinite(t)?t:0}function de(i){return K(i)?[{sessionUpdate:"aca_codex_raw_response_usage",_meta:{codex:{rawResponse:i}}}]:[]}function le(i,e){const t=i.observeRawResponseItem(e);return t?[{event:t,update:M(t)}]:[]}function he(i){return["agent_message_chunk","agent_progress_chunk","agent_thought_chunk","tool_call","tool_call_update","plan","plan_update","plan_removed"].includes(String(i.sessionUpdate||""))}function F(i,e){return`${i}:${e}`}function a(i,e){const s=d(i)[e];return typeof s=="string"?s:""}function pe(i){const e=i.toLowerCase();return e.includes("elicitation")||e.includes("requestuserinput")||e.includes("request_user_input")||e.includes("input/request")}function N(){const i=Number.parseInt(process.env.ACA_ACP_PROMPT_TIMEOUT_MS??"",10);return Number.isInteger(i)&&i>=3e4?i:360*60*1e3}function fe(){const i=Number.parseInt(process.env.ACA_CODEX_SUBTASK_DRAIN_TIMEOUT_MS??"",10);return Number.isInteger(i)&&i>=1e3?Math.min(i,600*1e3):600*1e3}function T(i,e){return i.exitCode!==null||i.signalCode!==null?Promise.resolve(!0):new Promise(t=>{const s=setTimeout(()=>t(!1),e);s.unref(),i.once("close",()=>{clearTimeout(s),t(!0)})})}function me(i){return new Promise(e=>setTimeout(e,i))}function ge(){new ie}const we=process.argv[1]||"";/(?:^|[\\/])adapter\.(?:js|ts)$/.test(we)&&ge();export{ge as runCodexAcpAdapter};
1
+ import{createToolLifecycleTrace as x}from"../../tool-lifecycle-trace.js";import{JsonLineRpcClient as L}from"../../client/json-rpc.js";import{startCodexAppServer as $}from"../codex/codex-process.js";import{CodexConnectionRecoveryTracker as O}from"../codex/connection-events.js";import{codexAcpSessionUpdatesFromNotification as y}from"../codex/events.js";import{CodexPlanDeltaState as k}from"../codex/plan-events.js";import{isCodexApprovalRequest as j,resolveCodexApprovalRequest as B}from"../codex/permissions.js";import{buildCodexContinuityInstructions as K,codexTokenUsageFromRawResponseCompleted as W,codexTokenUsageFromNotification as z,decideCodexContextMaintenance as v,readCodexContextMaintenancePolicy as R,rolloutFileSize as G}from"./context-maintenance.js";import{codexNotificationBelongsToCurrentTurn as J,codexNotificationRouting as A}from"./notification-routing.js";import{completedSubtaskHistoryUpdates as H}from"./subtask-history.js";import{CodexSubtaskRouter as V}from"./subtask-routing.js";import{decorateSubtaskUpdate as P}from"./subtask-executions.js";import{waitForCancellableTurnStart as X}from"./cancellation.js";import{codexAcpUpdateIsMeaningfulProgress as _,codexRawItemIsMeaningfulProgress as Q,withCodexProgressMetadata as M}from"./progress-evidence.js";import{acpPromptText as Y,acpPromptToCodexInput as Z,turnConfigFromConfig as tt}from"./turn-input.js";import{CodexControlToolLoopDetector as et,codexControlToolLoopAcpUpdate as q,controlToolLoopDetectionThresholds as st,controlToolLoopReady as ot,readCodexControlToolLoopPolicy as it}from"./control-tool-loop.js";import{CodexControlToolSteeringLimiter as nt,codexControlToolSteeringAcpUpdate as U}from"./control-tool-steering.js";class rt{toolTrace=x();lineBuffer="";started=null;rpc=null;codexStartupPromise=null;initialized=!1;currentSessionId=null;currentCwd=process.cwd();promptActive=!1;currentTurnId=null;turnStartPromise=null;promptCancelled=!1;nextParentRequestId=1;pendingParentRequests=new Map;completedTurns=new Map;pendingTurnCompletions=new Map;agentMessagePhases=new Map;planDeltas=new k;subtaskRouter=new V;subtaskGeneration=0;subtaskAgentMessagePhases=new Map;subtaskPlanDeltas=new Map;subscribedSubtaskThreadIds=new Set;pendingSubtaskSubscriptions=new Map;hydratedSubtaskThreadIds=new Set;seenSubtaskItemIds=new Set;pendingSubtaskNotifications=[];config={};sessionAliases=new Map;currentAffectedFiles=new Set;connectionRecovery=new O;latestTokenUsage=null;currentRolloutPath=null;threadTurnCount=0;currentPromptText="";currentAssistantText="";previousUserText="";previousAssistantText="";previousAffectedFiles=new Set;maintenanceActive=!1;pendingCompaction=null;shutdownPromise=null;managedProvider=null;controlToolLoop;controlToolPolicy=it();controlToolSteering=new nt(this.controlToolPolicy.steeringMaxAttempts);constructor(){this.controlToolLoop=new et({hasActiveSubtasks:()=>this.subtaskRouter.hasActiveSubtasks(),shortSleepMaxDurationMs:this.controlToolPolicy.shortSleepMaxDurationMs}),process.stdin.setEncoding("utf8"),process.stdin.on("data",e=>this.handleInput(String(e)));const t=()=>{this.shutdown().finally(()=>process.exit(0))};process.stdin.once("end",t),process.stdin.once("close",t),process.stdin.once("error",t),process.once("SIGTERM",()=>{this.shutdown().finally(()=>process.exit(0))}),process.once("SIGINT",()=>{this.shutdown().finally(()=>process.exit(130))})}handleInput(t){this.lineBuffer+=t;const e=this.lineBuffer.split(`
2
+ `);this.lineBuffer=e.pop()??"";for(const s of e){const o=s.trim();if(!o)continue;let i;try{i=JSON.parse(o)}catch{continue}this.handleMessage(i)}}async handleMessage(t){if("id"in t&&("result"in t||"error"in t)&&typeof t.method!="string"){this.handleParentResponse(t);return}const e=typeof t.method=="string"?t.method:"",s=t.id;if(e){if(s===void 0){await this.handleNotification(e,a(t.params));return}try{const o=await this.handleRequest(e,a(t.params));this.write({jsonrpc:"2.0",id:s,result:o})}catch(o){this.write({jsonrpc:"2.0",id:s,error:{code:-32603,message:o instanceof Error?o.message:String(o)}})}}}async handleNotification(t,e){if(t==="session/cancel")try{await this.cancel(e)}catch(s){process.stderr.write(`Codex cancel failed: ${s instanceof Error?s.message:String(s)}
3
+ `),await this.shutdown(),process.exit(1)}}async handleRequest(t,e){switch(t){case"initialize":return this.initializeResponse();case"aca/runtime_config":return this.applyRuntimeConfig(e);case"session/new":return this.newSession(e);case"session/resume":case"session/load":return this.resumeSession(e);case"session/fork":return this.forkSession(e);case"session/prompt":return this.prompt(e);case"session/compact":return this.manualCompact(e);case"aca/session_recover":return this.recoverSession(e);case"session/cancel":return await this.cancel(e),{};case"session/close":return await this.closeSession(e),{};case"session/set_config_option":return this.setConfigOption(e);case"session/set_mode":return this.setMode(e);case"fs/read_text_file":case"fs/write_text_file":case"terminal/create":case"terminal/output":case"terminal/wait_for_exit":case"terminal/kill":case"terminal/release":return{};default:throw new Error(`Unsupported ACP method: ${t}`)}}initializeResponse(){return{protocolVersion:1,agentCapabilities:{loadSession:!0,sessionCapabilities:{resume:!0,close:!0}}}}applyRuntimeConfig(t){if(this.started)throw new Error("Codex runtime config must be applied before app-server starts");const e=a(t.codexProvider);if(Object.keys(e).length===0)return this.managedProvider=null,{configured:!1};const s=c(e,"baseUrl"),o=c(e,"apiKey"),i=Number(e.revision||0);if(!s||!o||!Number.isSafeInteger(i)||i<1)throw new Error("Invalid Codex managed provider runtime config");return this.managedProvider={baseUrl:s,apiKey:o,revision:i},{configured:!0,revision:i}}async newSession(t){const e=c(t,"cwd")||process.cwd();this.applyInitialConfig(t),await this.ensureCodex(e);const s=await this.requireRpc().request("thread/start",{cwd:e,approvalsReviewer:"user",...this.codexConfig(),ephemeral:!1,experimentalRawEvents:!0,threadSource:"aca-codex-acp"},6e4),o=T(s);if(!o)throw new Error("Codex thread/start did not return thread id");return this.currentSessionId=o,this.currentCwd=e,this.resetSubtaskState(),this.captureThreadMetadata(s),this.sessionResponse(o)}async resumeSession(t){const e=c(t,"sessionId");if(!e)throw new Error("sessionId is required");const s=c(t,"cwd")||this.currentCwd;this.applyInitialConfig(t),await this.ensureCodex(s);const o=await this.requireRpc().request("thread/resume",{threadId:e,cwd:s,approvalsReviewer:"user",...this.codexConfig()},6e4);return this.currentSessionId=T(o)||e,this.currentCwd=s,this.resetSubtaskState(o),await this.subscribeKnownSubtasks(!1),this.captureThreadMetadata(o),this.sessionResponse(this.currentSessionId)}async forkSession(t){const e=c(t,"sessionId");if(!e)throw new Error("sessionId is required");const s=c(t,"cwd")||this.currentCwd;this.applyInitialConfig(t),await this.ensureCodex(s);const o=await this.requireRpc().request("thread/fork",{threadId:e,cwd:s,approvalsReviewer:"user",ephemeral:!1,threadSource:"aca-codex-acp-fork",...this.codexConfig()},12e4),i=T(o);if(!i)throw new Error("Codex thread/fork did not return thread id");return this.currentSessionId=i,this.currentCwd=s,this.resetSubtaskState(o),this.captureThreadMetadata(o),this.sessionResponse(i)}async prompt(t){this.promptCancelled=!1;const e=c(t,"sessionId")||this.currentSessionId;let s=this.resolveSessionAlias(e);if(!s)throw new Error("sessionId is required");const o=Array.isArray(t.prompt)?t.prompt:[],i=this.requireRpc();this.currentSessionId&&this.currentSessionId!==s&&this.resetSubtaskState(),this.currentSessionId=s;const r=await this.maintainContext("before",{userRequest:this.previousUserText,assistantResponse:this.previousAssistantText,affectedFiles:[...this.previousAffectedFiles]},{allowRollover:!0});if(dt(r),this.promptCancelled||this.shutdownPromise)throw new Error("Codex prompt cancelled");s=this.currentSessionId||s,this.toolTrace=x(),this.promptActive=!0,this.currentTurnId=null,this.controlToolLoop.reset(),this.controlToolSteering.reset(),this.currentPromptText=Y(o),this.currentAssistantText="",this.currentAffectedFiles.clear(),this.connectionRecovery.reset(),this.agentMessagePhases.clear(),this.planDeltas.reset(),this.pendingSubtaskNotifications=[],this.flushSubtaskStates();let d;try{this.turnStartPromise=i.request("turn/start",{threadId:s,clientUserMessageId:`aca-acp-${Date.now()}-${Math.random().toString(16).slice(2)}`,input:Z(o),cwd:this.currentCwd,...tt(this.config)},D()),d=await this.turnStartPromise;const u=String(d?.turn?.id??"");this.currentTurnId=u||null;const l=u?await this.waitForTurnCompleted(u,D()):d?.turn,h=await this.subtaskRouter.waitForIdle(mt());if(h.timedOut&&h.activeThreadIds.length>0&&this.notifySessionUpdate({sessionUpdate:"aca_codex_subtask_drain",label:"子任务收尾等待超时",detail:`仍有 ${h.activeThreadIds.length} 个子任务未结束`,_meta:{aca:{subtaskDrain:h}}}),this.promptCancelled)throw new Error("Codex prompt cancelled");this.threadTurnCount+=1,this.previousUserText=this.currentPromptText,this.previousAssistantText=this.currentAssistantText,this.previousAffectedFiles.clear();for(const p of this.currentAffectedFiles)this.previousAffectedFiles.add(p);const f=await this.maintainContext("after",{userRequest:this.previousUserText,assistantResponse:this.previousAssistantText,affectedFiles:[...this.previousAffectedFiles]},{allowRollover:!0});return{stopReason:"end_turn",sessionId:this.currentSessionId,turn:l,_meta:{acaContextUsage:E(f)??ut(this.latestTokenUsage),acaContextMaintenance:f,acaSubtaskDrain:h}}}catch(u){const l=this.connectionRecovery.fail(u);throw l&&this.notifySessionUpdate(l),u}finally{this.subtaskRouter.executions.loseObservation(this.promptCancelled?"父任务已中断,子执行状态需重新确认":"本轮观测结束,未确认子执行终态"),this.flushSubtaskStates(),this.controlToolLoop.reset(),this.promptActive=!1,this.currentTurnId=null,this.turnStartPromise=null,this.pendingSubtaskNotifications=[]}}resetSubtaskState(t){this.subtaskGeneration+=1,this.subtaskRouter.reset(),this.subtaskAgentMessagePhases.clear(),this.subtaskPlanDeltas.clear(),this.subscribedSubtaskThreadIds.clear(),this.pendingSubtaskSubscriptions.clear(),this.hydratedSubtaskThreadIds.clear(),this.seenSubtaskItemIds.clear(),this.pendingSubtaskNotifications=[],t!==void 0&&this.subtaskRouter.restoreFromSessionSnapshot(t,this.currentSessionId)}async subscribeKnownSubtasks(t=!0){const e=this.subtaskRouter.routesForSubscription().map(s=>this.subscribeSubtask(s,t));await Promise.allSettled(e)}subscribeSubtask(t,e){const s=t.sourceThreadId;if(!s||s===this.currentSessionId||this.subscribedSubtaskThreadIds.has(s))return Promise.resolve();const o=this.pendingSubtaskSubscriptions.get(s);if(o)return o;const i=this.subtaskGeneration,r=this.requireRpc().request("thread/resume",{threadId:s,approvalsReviewer:"user",excludeTurns:!1},6e4).then(d=>{if(i!==this.subtaskGeneration)return;this.subscribedSubtaskThreadIds.add(s);const u=this.subtaskRouter.restoreFromSessionSnapshot(d,this.currentSessionId);e&&this.hydrateSubtaskHistory(d,t),u>0&&this.subscribeKnownSubtasks(e)}).catch(()=>{}).finally(()=>{this.pendingSubtaskSubscriptions.get(s)===r&&this.pendingSubtaskSubscriptions.delete(s)});return this.pendingSubtaskSubscriptions.set(s,r),r}async cancel(t){const e=this.resolveSessionAlias(c(t,"sessionId")||this.currentSessionId);if(!e||!this.rpc)return;if(e!==this.currentSessionId)throw new Error("Cannot cancel a different Codex session");this.promptCancelled=!0,this.subtaskRouter.stopWaiting();const s=this.turnStartPromise?await X(this.turnStartPromise):null,o=this.currentTurnId||c(w(s,"turn"),"id");if(await this.rpc.request("turn/interrupt",{threadId:e,turnId:o},5e3),o){const i=this.pendingTurnCompletions.get(o);i&&(this.pendingTurnCompletions.delete(o),clearTimeout(i.timer),i.reject(new Error("Codex prompt cancelled")))}}async manualCompact(t){const e=c(t,"sessionId")||this.currentSessionId,s=this.resolveSessionAlias(e);if(!s)throw new Error("sessionId is required");if(this.promptActive||this.maintenanceActive)throw new Error("Codex session is busy");this.currentSessionId=s;const o=R(),i=await this.maintainContext("manual",{userRequest:this.previousUserText,assistantResponse:this.previousAssistantText,affectedFiles:[...this.previousAffectedFiles]},{force:!0,allowRollover:t.allowRollover===!0,compactTimeoutMs:ct(t,o.compactTimeoutMs)}),r=String(i.action||"");if(r==="compact_failed"||r==="rollover_failed"){const d=String(i.compactError||"Codex 上下文压缩未完成");throw new Error(`Codex 上下文压缩失败:${d}`)}return{sessionId:this.currentSessionId,maintenance:i,contextUsage:E(i)}}async recoverSession(t){const e=c(t,"sessionId")||this.currentSessionId,s=this.resolveSessionAlias(e);if(!s)throw new Error("sessionId is required");const o=c(t,"reason");if(o!=="low_output_loop"&&o!=="control_tool_loop")throw new Error("Unsupported Codex recovery reason");if(this.promptActive||this.maintenanceActive)throw new Error("Codex session is busy");if(s!==this.currentSessionId)throw new Error("Cannot recover a different Codex session");const i=new Set([...this.previousAffectedFiles,...this.currentAffectedFiles]);return await this.rotateThread(s,{userRequest:this.currentPromptText||this.previousUserText,assistantResponse:this.currentAssistantText||this.previousAssistantText,affectedFiles:[...i]}),{...a(this.sessionResponse(this.currentSessionId)),previousSessionId:s,recoveryReason:o}}async closeSession(t){await this.shutdown()}async ensureCodex(t){if(this.rpc&&this.started&&this.started.child.exitCode===null&&this.started.child.signalCode===null)return;if(this.codexStartupPromise){await this.codexStartupPromise;return}const e=(async()=>{if(this.rpc&&this.started&&this.started.child.exitCode===null&&this.started.child.signalCode===null)return;const s=$(t,this.managedProvider),o=new L(s.child,{peerName:"Codex app-server",onNotification:(i,r)=>this.handleCodexNotification(i,r),onRequest:async(i,r)=>this.handleCodexRequest(i,r)});this.started=s,this.rpc=o,s.child.once("exit",()=>{this.started===s&&(this.subtaskRouter.executions.loseObservation("执行连接断开,未确认子代理终态"),this.flushSubtaskStates(),this.subtaskRouter.stopWaiting())}),this.initialized=!1,await o.request("initialize",{clientInfo:{name:"aca-codex-acp",version:"0.1.0"},capabilities:{experimentalApi:!0,requestAttestation:!1}},6e4),this.initialized=!0})();this.codexStartupPromise=e;try{await e}catch(s){throw this.rpc&&this.started&&!this.initialized&&await C(this.started.child,1e3),this.rpc=null,this.started=null,s}finally{this.codexStartupPromise===e&&(this.codexStartupPromise=null)}}handleCodexNotification(t,e){const s=A(e);if(t==="item/started"||t==="item/completed"){const u=a(a(e).item);this.toolTrace("provider_notification",{method:t,threadId:s.threadId,turnId:s.turnId,toolCallId:u.id,itemType:u.type,status:u.status})}if(this.maintenanceActive&&(!s.threadId||s.threadId===this.currentSessionId)){(t==="item/started"||t==="item/completed")&&this.toolTrace("notification_filtered",{method:t,toolCallId:a(a(e).item).id,reason:"context_maintenance"}),this.captureContextNotification(t,e);return}if(!J(e,this.currentSessionId,this.currentTurnId)){if(!this.promptActive||!s.threadId||s.threadId===this.currentSessionId){(t==="item/started"||t==="item/completed")&&this.toolTrace("notification_filtered",{method:t,toolCallId:a(a(e).item).id,reason:"outside_current_turn",threadId:s.threadId,turnId:s.turnId});return}const u=this.subtaskRouter.routeFor(s.threadId);if(!u){(t==="item/started"||t==="item/completed")&&this.toolTrace("notification_buffered",{method:t,toolCallId:a(a(e).item).id,threadId:s.threadId,reason:"subtask_route_pending"}),this.bufferPendingSubtaskNotification(t,e,s.threadId);return}this.handleSubtaskNotification(t,e,u),this.flushPendingSubtaskNotifications();return}const o=this.subtaskRouter.observe(e,this.currentSessionId);if(this.flushSubtaskStates(),o>0&&this.subscribeKnownSubtasks(!0),this.captureContextNotification(t,e),this.maintenanceActive)return;if(!this.promptActive&&t!=="turn/completed"){(t==="item/started"||t==="item/completed")&&this.toolTrace("notification_filtered",{method:t,toolCallId:a(a(e).item).id,reason:"prompt_inactive"});return}this.rpc?.refreshPendingRequestTimeout("turn/start"),this.captureAgentMessagePhase(t,e);const i=this.connectionRecovery.beforeNotification(t,e);t==="turn/completed"&&this.resolveTurnCompleted(e);const r=t==="rawResponseItem/completed"?ht(this.controlToolLoop,e):[],d=t==="rawResponse/completed"?lt(e):t==="rawResponseItem/completed"?r.map(({update:u})=>u):y(t,e,{agentMessagePhases:this.agentMessagePhases,planDeltas:this.planDeltas});for(const u of r)this.maybeSteerControlToolLoop(u.event);for(const u of[...i,...d]){const l=M(u,t,e,this.agentMessagePhases);_(l)&&this.notifyControlToolProgress(),this.captureUpdateEvidence(l),this.notifySessionUpdate(l)}o>0&&this.flushPendingSubtaskNotifications()}handleSubtaskNotification(t,e,s){if(!this.promptActive)return;const o=this.subtaskRouter.acceptNotification(s,t,e);if(this.flushSubtaskStates(),!o){(t==="item/started"||t==="item/completed")&&this.toolTrace("notification_filtered",{method:t,toolCallId:a(a(e).item).id,threadId:s.sourceThreadId,reason:"subtask_not_current"});return}(t==="rawResponseItem/completed"||t==="item/completed")&&Q(a(a(e).item))&&this.notifyControlToolProgress(),this.rememberSubtaskItem(s.sourceThreadId,e),this.rpc?.refreshPendingRequestTimeout("turn/start");const i=this.subtaskRouter.executions.get(s.sourceThreadId)?.runId||s.sourceThreadId,r=this.subtaskAgentMessagePhases.get(i)??new Map;this.subtaskAgentMessagePhases.set(i,r);const d=this.subtaskPlanDeltas.get(i)??new k;this.subtaskPlanDeltas.set(i,d),this.captureAgentMessagePhase(t,e,r);const u=this.subtaskRouter.observe(e,this.currentSessionId);this.flushSubtaskStates(),u>0&&this.subscribeKnownSubtasks(!0);const l=y(t,e,{agentMessagePhases:r,planDeltas:d}),h=A(e).turnId;for(const f of l){const p=M(f,t,e,r);if(!pt(p))continue;c(p,"title")&&this.subtaskRouter.executions.action(s.sourceThreadId,c(p,"title")),_(p)&&(this.notifyControlToolProgress(),this.subtaskRouter.executions.progress(s.sourceThreadId,c(p,"title")||"收到执行结果"));const g=P(p,s,h,this.subtaskRouter.executions.get(s.sourceThreadId));b(g,this.currentAffectedFiles),this.notifySessionUpdate(g)}}hydrateSubtaskHistory(t,e){if(!(!this.promptActive||this.hydratedSubtaskThreadIds.has(e.sourceThreadId))){this.hydratedSubtaskThreadIds.add(e.sourceThreadId);for(const s of H(t)){const o=N(e.sourceThreadId,s.itemId);if(this.seenSubtaskItemIds.has(o))continue;this.seenSubtaskItemIds.add(o);const i=P(s.update,e,s.sourceTurnId,{runId:`history.${e.sourceThreadId}.${s.sourceTurnId}`,state:"unknown",outputIncomplete:!0,reason:"历史过程回放,不代表当前执行状态"}),r=a(i._meta);i._meta={...r,aca:{...a(r.aca),historyReplay:!0}},b(i,this.currentAffectedFiles),this.notifySessionUpdate(i)}}}rememberSubtaskItem(t,e){const s=a(e),o=a(s.item),i=c(o,"id")||c(s,"itemId");i&&this.seenSubtaskItemIds.add(N(t,i))}bufferPendingSubtaskNotification(t,e,s){this.pendingSubtaskNotifications.push({method:t,params:e,threadId:s}),this.pendingSubtaskNotifications.length>200&&this.pendingSubtaskNotifications.shift()}flushPendingSubtaskNotifications(){if(this.pendingSubtaskNotifications.length===0)return;let t=!0;for(;t&&this.pendingSubtaskNotifications.length>0;){t=!1;const e=[];for(const s of this.pendingSubtaskNotifications){const o=this.subtaskRouter.routeFor(s.threadId);if(!o){e.push(s);continue}t=!0,this.handleSubtaskNotification(s.method,s.params,o)}this.pendingSubtaskNotifications=e}}notifySessionUpdate(t){(t.sessionUpdate==="tool_call"||t.sessionUpdate==="tool_call_update"&&["completed","failed","cancelled"].includes(String(t.status)))&&this.toolTrace("acp_update",{toolCallId:t.toolCallId,status:t.status,threadId:this.currentSessionId,turnId:this.currentTurnId}),this.notify("session/update",{sessionId:this.currentSessionId,update:t})}flushSubtaskStates(){if(this.promptActive)for(const t of this.subtaskRouter.executions.drainUpdates())this.notifySessionUpdate(t)}async handleCodexRequest(t,e){if(j(t))return B(t,e,async s=>{const o=await this.requestParent("session/request_permission",{sessionId:this.currentSessionId,request:{...s,params:e,method:t}}),i=a(o),r=a(i.outcome);return r.outcome==="selected"&&typeof r.optionId=="string"?{outcome:{outcome:"selected",optionId:r.optionId}}:{outcome:{outcome:"cancelled"}}});if(ft(t)){const s=await this.requestParent("elicitation/create",{sessionId:this.currentSessionId,request:{...a(e),method:t}}),o=a(s),i=a(o.outcome);return i.outcome==="submitted"?{outcome:{outcome:"submitted",value:typeof i.value=="string"?i.value:""}}:{outcome:{outcome:"cancelled"}}}return{}}captureAgentMessagePhase(t,e,s=this.agentMessagePhases){if(t!=="item/started"&&t!=="item/completed")return;const o=w(e,"item");if(c(o,"type")!=="agentMessage")return;const i=c(o,"id");i&&s.set(i,c(o,"phase"))}waitForTurnCompleted(t,e){const s=this.completedTurns.get(t);return s?Promise.resolve(s):new Promise((o,i)=>{const r=setTimeout(()=>{this.pendingTurnCompletions.delete(t),i(new Error("Codex app-server request timed out while waiting for turn/completed"))},e);r.unref(),this.pendingTurnCompletions.set(t,{resolve:o,reject:i,timer:r})})}resolveTurnCompleted(t){const e=w(t,"turn"),s=c(e,"id");if(!s)return;this.completedTurns.set(s,e);const o=this.pendingTurnCompletions.get(s);if(o){if(this.pendingTurnCompletions.delete(s),clearTimeout(o.timer),c(e,"status")==="failed"){o.reject(new Error(`Codex turn failed: ${JSON.stringify(e.error??{})}`));return}o.resolve(e)}}async shutdown(){return this.shutdownPromise?this.shutdownPromise:(this.shutdownPromise=this.performShutdown(),this.shutdownPromise)}async performShutdown(){this.subtaskRouter.executions.loseObservation("执行连接关闭,未确认子代理终态"),this.flushSubtaskStates(),this.subtaskRouter.stopWaiting(),this.pendingCompaction&&(clearTimeout(this.pendingCompaction.timer),this.pendingCompaction.reject(new Error("Codex ACP adapter closed")),this.pendingCompaction=null);for(const e of this.pendingParentRequests.values())clearTimeout(e.timer),e.reject(new Error("Codex ACP adapter closed"));this.pendingParentRequests.clear();for(const e of this.pendingTurnCompletions.values())clearTimeout(e.timer),e.reject(new Error("Codex ACP adapter closed"));this.pendingTurnCompletions.clear();const t=this.started?.child;!t||t.exitCode!==null||t.signalCode!==null||(t.stdin.end(),!await C(t,2e3)&&(t.kill("SIGTERM"),!await C(t,1e3)&&(t.kill("SIGKILL"),await C(t,1e3))))}sessionResponse(t){return{sessionId:t,modes:{currentModeId:"workspace-write",availableModes:[{id:"read-only",name:"Read Only"},{id:"workspace-write",name:"Workspace Write"},{id:"danger-full-access",name:"Full Access"}]},configOptions:[{id:"model",category:"model",type:"string"},{id:"approvalPolicy",category:"approval",type:"string"},{id:"sandbox",category:"mode",type:"string"}]}}requireRpc(){if(!this.rpc)throw new Error("Codex app-server is not started");return this.rpc}notify(t,e){this.write({jsonrpc:"2.0",method:t,params:e})}requestParent(t,e,s=600*1e3){const o=this.nextParentRequestId++;return new Promise((i,r)=>{const d=setTimeout(()=>{this.pendingParentRequests.delete(o),r(new Error(`ACP client request timed out: ${t}`))},s);d.unref(),this.pendingParentRequests.set(o,{resolve:i,reject:r,timer:d}),this.write({jsonrpc:"2.0",id:o,method:t,params:e})})}handleParentResponse(t){const e=Number(t.id),s=this.pendingParentRequests.get(e);if(s){if(this.pendingParentRequests.delete(e),clearTimeout(s.timer),"error"in t){const o=a(t.error);s.reject(new Error(c(o,"message")||"ACP client request failed"));return}s.resolve(t.result)}}setConfigOption(t){const e=c(t,"configId"),s=t.value;if(!e)throw new Error("configId is required");if(typeof s!="string"&&typeof s!="boolean")throw new Error("config value must be string or boolean");return this.config[e]=s,this.notify("session/update",{sessionId:this.currentSessionId,update:{sessionUpdate:"config_option_update",configId:e,value:s}}),{}}setMode(t){const e=c(t,"modeId");if(!e)throw new Error("modeId is required");return this.config.sandbox=e,this.notify("session/update",{sessionId:this.currentSessionId,update:{sessionUpdate:"current_mode_update",modeId:e}}),{}}applyInitialConfig(t){const e=w(t,"config");for(const[s,o]of Object.entries(e))(typeof o=="string"||typeof o=="boolean")&&(this.config[s]=o)}codexConfig(){return{...typeof this.config.model=="string"&&this.config.model?{model:this.config.model}:{},approvalPolicy:typeof this.config.approvalPolicy=="string"?this.config.approvalPolicy:"on-request",sandbox:typeof this.config.sandbox=="string"?this.config.sandbox:"workspace-write"}}captureContextNotification(t,e){if(t==="thread/tokenUsage/updated"&&(this.latestTokenUsage=z(e)??this.latestTokenUsage),t==="thread/compacted"&&this.pendingCompaction&&this.resolveCompaction(),t==="item/completed"){const s=w(e,"item"),o=c(a(e),"threadId");c(s,"type")==="contextCompaction"&&(!o||o===this.currentSessionId)&&this.resolveCompaction(),c(s,"type")==="agentMessage"&&c(s,"phase")==="final_answer"&&(this.currentAssistantText=S(s)||this.currentAssistantText)}}resolveCompaction(){if(!this.pendingCompaction)return;const t=this.pendingCompaction;this.pendingCompaction=null,clearTimeout(t.timer),t.resolve()}captureUpdateEvidence(t){String(t.sessionUpdate||"")==="agent_message_chunk"&&(this.currentAssistantText+=S(t.content)),b(t,this.currentAffectedFiles)}captureThreadMetadata(t){const e=w(t,"thread");this.currentRolloutPath=c(e,"path")||null,this.threadTurnCount=Array.isArray(e.turns)?e.turns.length:0;const s=at(e.turns);s.userRequest&&(this.previousUserText=s.userRequest),s.assistantResponse&&(this.previousAssistantText=s.assistantResponse),this.previousAffectedFiles.clear();for(const o of s.affectedFiles)this.previousAffectedFiles.add(o);this.latestTokenUsage=null}async maintainContext(t,e,s={}){const o=R(),i=this.contextState(),r=v(i,o);if(!r.compact&&!s.force||!this.currentSessionId)return this.maintenanceResult({stage:t,action:"none",reasons:r.reasons,beforeState:i});const d=this.currentSessionId;this.notifyContextStatus("正在整理会话上下文",F(i)),this.maintenanceActive=!0;let u=!1,l="",h=i,f=r,p=!1,g="";try{const m=i.usage?.observedAtMs??0;await this.compactThread(d,s.compactTimeoutMs??o.compactTimeoutMs),await this.waitForTokenUsageAfter(m,2e3)}catch(m){u=!0,l=m instanceof Error?m.message:String(m)}try{if(t!=="manual"&&this.promptCancelled||this.shutdownPromise)throw new Error("Codex prompt cancelled");if(h=this.contextState(),f=v(h,o,{compactAttempted:!0,compactFailed:u}),f.rollover&&s.allowRollover!==!1){this.notifyContextStatus("正在创建轻量会话",f.reasons.join(", "));try{await this.rotateThread(d,e),p=!0}catch(m){g=m instanceof Error?m.message:String(m),this.notifyContextStatus("轻量会话创建失败",g)}}else u||this.notifyContextStatus("会话上下文已压缩",F(h))}finally{this.maintenanceActive=!1}return this.maintenanceResult({action:p?"rollover":g?"rollover_failed":u?"compact_failed":"compact",stage:t,reasons:f.reasons,beforeState:i,afterState:h,compactError:[l,g].filter(Boolean).join("; "),previousThreadId:d,rolloverRecommended:f.rollover&&s.allowRollover===!1})}compactThread(t,e){return this.pendingCompaction?Promise.reject(new Error("Codex context compaction is already running")):new Promise((s,o)=>{const i=setTimeout(()=>{this.pendingCompaction?.timer===i&&(this.pendingCompaction=null),o(new Error("Codex thread compaction timed out"))},e);i.unref(),this.pendingCompaction={resolve:s,reject:o,timer:i},this.requireRpc().request("thread/compact/start",{threadId:t},e).catch(r=>{this.pendingCompaction?.timer===i&&(this.pendingCompaction=null,clearTimeout(i),o(r instanceof Error?r:new Error(String(r))))})})}async rotateThread(t,e){const s=K({previousThreadId:t,cwd:this.currentCwd,userRequest:e.userRequest,assistantResponse:e.assistantResponse,affectedFiles:e.affectedFiles}),o=await this.requireRpc().request("thread/start",{cwd:this.currentCwd,approvalsReviewer:"user",...this.codexConfig(),developerInstructions:s,ephemeral:!1,experimentalRawEvents:!0,threadSource:"aca-codex-acp-rollover"},6e4),i=T(o);if(!i)throw new Error("Codex rollover thread/start did not return thread id");this.sessionAliases.set(t,i),this.currentSessionId=i,this.resetSubtaskState(o),this.captureThreadMetadata(o)}async waitForTokenUsageAfter(t,e){const s=Date.now()+e;for(;Date.now()<s;){if((this.latestTokenUsage?.observedAtMs??0)>t)return;await gt(50)}}resolveSessionAlias(t){if(!t)return null;let e=t;const s=new Set;for(;this.sessionAliases.has(e)&&!s.has(e);)s.add(e),e=this.sessionAliases.get(e)??e;return e}contextState(){return{usage:this.latestTokenUsage,rolloutBytes:G(this.currentRolloutPath),turnCount:this.threadTurnCount}}maintenanceResult(t){return{stage:t.stage,action:t.action,reasons:t.reasons,previousThreadId:t.previousThreadId??null,sessionId:this.currentSessionId,before:t.beforeState,after:t.afterState??null,compactError:t.compactError||null,rolloverRecommended:t.rolloverRecommended===!0}}notifyContextStatus(t,e){this.notify("session/update",{sessionId:this.currentSessionId,update:{sessionUpdate:"aca_context_maintenance",label:t,detail:e}})}notifyControlToolProgress(){const t=this.controlToolLoop.recordProgress();t&&this.notifySessionUpdate(q(t))}maybeSteerControlToolLoop(t){if(!ot(t.detection,this.controlToolPolicy))return;const e=st(t.detection,this.controlToolPolicy),s=this.controlToolSteering.next(t,e.warningCalls);if(!s)return;const{threadId:o,turnId:i}=t.detection;!o||!i||this.requireRpc().request("turn/steer",{threadId:o,expectedTurnId:i,clientUserMessageId:`aca-loop-steer-${Date.now()}-${Math.random().toString(16).slice(2)}`,input:[{type:"text",text:s.text,text_elements:[]}]},6e4).then(()=>{this.notifySessionUpdate(U(t,s,{succeeded:!0}))}).catch(r=>{this.notifySessionUpdate(U(t,s,{succeeded:!1,error:r}))})}write(t){process.stdout.write(`${JSON.stringify(t)}
4
+ `)}}function T(n){const t=w(n,"thread");return c(t,"id")||null}function w(n,t){const e=a(n);return a(e[t])}function a(n){return n&&typeof n=="object"&&!Array.isArray(n)?n:{}}function S(n){if(typeof n=="string")return n;if(Array.isArray(n))return n.map(S).filter(Boolean).join("");if(!n||typeof n!="object")return"";const t=a(n);for(const e of["text","message","content","output_text"]){const s=S(t[e]);if(s)return s}return""}function b(n,t){if(Array.isArray(n)){for(const i of n)b(i,t);return}const e=a(n),s=c(e,"type").toLowerCase(),o=c(e,"path");if(o&&["diff","resource_link","content"].includes(s)&&t.add(o),Array.isArray(e.locations))for(const i of e.locations){const r=c(a(i),"path");r&&t.add(r)}for(const i of[e.content,e.update,e.rawInput,e.rawOutput])i&&i!==n&&b(i,t)}function at(n){if(!Array.isArray(n)||n.length===0)return{userRequest:"",assistantResponse:"",affectedFiles:[]};const t=n.at(-1),e=new Set;b(t,e);let s="",o="";const i=r=>{if(Array.isArray(r)){for(const l of r)i(l);return}if(!r||typeof r!="object")return;const d=a(r),u=c(d,"type").toLowerCase();u==="usermessage"&&(s=S(d)||s),u==="agentmessage"&&(o=S(d)||o);for(const l of["items","content","input"])d[l]&&i(d[l])};return i(t),{userRequest:s,assistantResponse:o,affectedFiles:[...e]}}function ct(n,t){const e=I(n,"timeoutMs");return!Number.isFinite(e)||e<=0?t:Math.max(3e4,Math.min(59e4,e-1e4))}function E(n){const t=String(n.action||"none"),e=a(n.after),s=a(n.before),o=a(e.usage),i=a(s.usage),r=Object.keys(o).length>0?o:i,d=I(r,"contextWindow");if(t==="rollover")return{usedTokens:0,inputTokens:0,contextWindow:d,ratio:0,observedAtMs:Date.now(),state:"new_thread",maintenanceAction:t};if(Object.keys(r).length===0)return null;const u=I(r,"totalTokens"),l=I(r,"inputTokens");return{usedTokens:u||l,inputTokens:l,contextWindow:d,ratio:d>0?(u||l)/d:0,observedAtMs:I(r,"observedAtMs")||Date.now(),state:t==="compact"?"compacted":"active",maintenanceAction:t}}function ut(n){return n?{usedTokens:n.totalTokens||n.inputTokens,inputTokens:n.inputTokens,contextWindow:n.contextWindow,ratio:n.contextWindow>0?(n.totalTokens||n.inputTokens)/n.contextWindow:0,observedAtMs:n.observedAtMs,state:"active",maintenanceAction:null}:null}function F(n){const t=n.usage?`${n.usage.inputTokens} tokens`:"token unknown",e=n.rolloutBytes>0?`${Math.round(n.rolloutBytes/1024/1024)}MB rollout`:"rollout unknown";return`${t}, ${e}, ${n.turnCount} turns`}function dt(n){const t=String(n.action||"none");if(t!=="compact_failed"&&t!=="rollover_failed")return;const e=String(n.compactError||"Codex 上下文维护未完成");throw new Error(`Codex 自动上下文维护失败:${e}`)}function I(n,t){const e=a(n)[t];return typeof e=="number"&&Number.isFinite(e)?e:0}function lt(n){return W(n)?[{sessionUpdate:"aca_codex_raw_response_usage",_meta:{codex:{rawResponse:n}}}]:[]}function ht(n,t){const e=n.observeRawResponseItem(t);return e?[{event:e,update:q(e)}]:[]}function pt(n){return["agent_message_chunk","agent_progress_chunk","agent_thought_chunk","tool_call","tool_call_update","plan","plan_update","plan_removed"].includes(String(n.sessionUpdate||""))}function N(n,t){return`${n}:${t}`}function c(n,t){const s=a(n)[t];return typeof s=="string"?s:""}function ft(n){const t=n.toLowerCase();return t.includes("elicitation")||t.includes("requestuserinput")||t.includes("request_user_input")||t.includes("input/request")}function D(){const n=Number.parseInt(process.env.ACA_ACP_PROMPT_TIMEOUT_MS??"",10);return Number.isInteger(n)&&n>=3e4?n:360*60*1e3}function mt(){const n=Number.parseInt(process.env.ACA_CODEX_SUBTASK_DRAIN_TIMEOUT_MS??"",10);return Number.isInteger(n)&&n>=1e3?Math.min(n,600*1e3):600*1e3}function C(n,t){return n.exitCode!==null||n.signalCode!==null?Promise.resolve(!0):new Promise(e=>{const s=setTimeout(()=>e(!1),t);s.unref(),n.once("close",()=>{clearTimeout(s),e(!0)})})}function gt(n){return new Promise(t=>setTimeout(t,n))}function wt(){new rt}const St=process.argv[1]||"";/(?:^|[\\/])adapter\.(?:js|ts)$/.test(St)&&wt();export{wt as runCodexAcpAdapter};
@@ -0,0 +1,2 @@
1
+ import n from"node:fs";import f from"node:path";import{getLogDir as u}from"../core/paths.js";const d=new Set(["jobId","threadId","turnId","toolCallId","itemType","status","sequence","method","reason","uploadRunId","batchNo","attempt","firstSequence","lastSequence","eventCount"]);function S(i){let r=0;const s=process.env.ACA_TOOL_LIFECYCLE_TRACE==="1";return(l,a)=>{if(!s||r++>=1e3)return;const c={};for(const[t,e]of Object.entries(a))d.has(t)&&(typeof e=="string"?c[t]=e.slice(0,160):typeof e=="number"&&Number.isFinite(e)&&(c[t]=e));try{const t=`[tool-lifecycle] ${JSON.stringify({at:Date.now(),stage:l,...c})}`;if(i){i(t);return}const e=u();n.mkdirSync(e,{recursive:!0});const o=f.join(e,`tool-lifecycle-${process.pid}.log`);n.existsSync(o)&&n.statSync(o).size>1024*1024&&n.renameSync(o,`${o}.1`),n.appendFileSync(o,`${t}
2
+ `,{mode:384})}catch{}}}export{S as createToolLifecycleTrace};
@@ -1 +1 @@
1
- import{Agent as S}from"undici";import $ from"node:crypto";import E from"node:fs";import{Readable as p,Transform as v}from"node:stream";import{pipeline as U}from"node:stream/promises";import{loadAcaConfig as g}from"./aca-config.js";const _=new S({connect:{rejectUnauthorized:!1}}),b=15e3,y=12e4,x=12e4;async function C(e,t,r,n={}){const i=g(),a=(n.serverUrl||i.serverUrl).replace(/\/$/,""),o=n.token??i.token,l={...r===void 0?{}:{"content-type":"application/json"},...o?{authorization:`Bearer ${o}`}:{}},u=h(n,b);try{const s=await fetch(`${a}${t}`,{method:e,headers:l,signal:u.signal,...r===void 0?{}:{body:JSON.stringify(r)},...d(a)}),f=await s.text(),c=f?JSON.parse(f):null;if(!s.ok){const m=c&&typeof c=="object"&&"message"in c?String(c.message):`HTTP ${s.status}`;throw new Error(m)}return c}catch(s){throw w(s,e,t,u)}finally{u.dispose()}}async function q(e,t={}){const r=g(),n=(t.serverUrl||r.serverUrl).replace(/\/$/,""),i=t.token??r.token,a=h(t,y);try{const o=await fetch(`${n}${e}`,{method:"GET",headers:i?{authorization:`Bearer ${i}`}:{},signal:a.signal,...d(n)});if(!o.ok)throw new Error(`附件下载失败 HTTP ${o.status}`);return Buffer.from(await o.arrayBuffer())}catch(o){throw w(o,"GET",e,a)}finally{a.dispose()}}async function z(e,t,r={}){const n=g(),i=(r.serverUrl||n.serverUrl).replace(/\/$/,""),a=r.token??n.token,o=h(r,y);let l=0;const u=$.createHash("sha256");try{const s=await fetch(`${i}${e}`,{method:"GET",headers:a?{authorization:`Bearer ${a}`}:{},signal:o.signal,...d(i)});if(!s.ok)throw new Error(`项目文件下载失败 HTTP ${s.status}`);if(!s.body)throw new Error("项目文件下载响应为空");const f=new v({transform(c,m,T){if(l+=c.byteLength,Number.isFinite(r.maxBytes)&&l>Number(r.maxBytes)){T(new Error("project_upload_too_large"));return}u.update(c),T(null,c)}});return await U(p.fromWeb(s.body),f,E.createWriteStream(t,{flags:"wx",mode:384})),{sizeBytes:l,sha256:u.digest("hex")}}catch(s){throw u.digest(),E.rmSync(t,{force:!0}),w(s,"GET",e,o)}finally{o.dispose()}}async function D(e,t,r,n={},i={}){const a=g(),o=(i.serverUrl||a.serverUrl).replace(/\/$/,""),l=i.token??a.token,u=h(i,x);try{const s=await fetch(`${o}${t}`,{method:e,headers:{...n,...l?{authorization:`Bearer ${l}`}:{}},body:r,duplex:"half",signal:u.signal,...d(o)}),f=await s.text(),c=f?JSON.parse(f):null;if(!s.ok){const m=c&&typeof c=="object"&&"message"in c?String(c.message):`HTTP ${s.status}`;throw new Error(m)}return c}catch(s){throw w(s,e,t,u)}finally{u.dispose()}}function h(e,t){const r=A(e.timeoutMs,t),n=new AbortController;let i=!1;const a=()=>n.abort(e.signal?.reason);e.signal?.aborted?a():e.signal?.addEventListener("abort",a,{once:!0});const o=setTimeout(()=>{i=!0,n.abort(new Error(`ACA Server request timed out after ${r}ms`))},r);return o.unref(),{signal:n.signal,timeoutMs:r,timedOut:()=>i,dispose:()=>{clearTimeout(o),e.signal?.removeEventListener("abort",a)}}}function w(e,t,r,n){return n.timedOut()?new Error(`ACA Server 请求超时(${n.timeoutMs}ms):${t} ${r}`,{cause:e}):e instanceof Error?e:new Error(String(e))}function A(e,t){return!Number.isFinite(e)||Number(e)<=0?t:Math.max(250,Math.min(Number(e),30*6e4))}function d(e){return!/^https:/i.test(e)||!k(process.env.ACA_SERVER_INSECURE_TLS)?{}:{dispatcher:_}}function k(e){return e==="1"||e?.toLowerCase()==="true"||e?.toLowerCase()==="yes"}export{q as acaServerDownload,z as acaServerDownloadToFile,C as acaServerRequest,D as acaServerStreamRequest};
1
+ import{Agent as y}from"undici";import S from"node:crypto";import T from"node:fs";import{Readable as $,Transform as v}from"node:stream";import{pipeline as U}from"node:stream/promises";import{loadAcaConfig as g}from"./aca-config.js";const _=new y({connect:{rejectUnauthorized:!1}}),b=15e3,p=12e4,x=12e4;class A extends Error{status;constructor(t,r){super(r),this.status=t}}async function q(e,t,r,s={}){const i=g(),a=(s.serverUrl||i.serverUrl).replace(/\/$/,""),o=s.token??i.token,l={...r===void 0?{}:{"content-type":"application/json"},...o?{authorization:`Bearer ${o}`}:{}},u=h(s,b);try{const n=await fetch(`${a}${t}`,{method:e,headers:l,signal:u.signal,...r===void 0?{}:{body:JSON.stringify(r)},...w(a)}),f=await n.text(),c=f?JSON.parse(f):null;if(!n.ok){const m=c&&typeof c=="object"&&"message"in c?String(c.message):`HTTP ${n.status}`;throw new A(n.status,m)}return c}catch(n){throw d(n,e,t,u)}finally{u.dispose()}}async function z(e,t={}){const r=g(),s=(t.serverUrl||r.serverUrl).replace(/\/$/,""),i=t.token??r.token,a=h(t,p);try{const o=await fetch(`${s}${e}`,{method:"GET",headers:i?{authorization:`Bearer ${i}`}:{},signal:a.signal,...w(s)});if(!o.ok)throw new Error(`附件下载失败 HTTP ${o.status}`);return Buffer.from(await o.arrayBuffer())}catch(o){throw d(o,"GET",e,a)}finally{a.dispose()}}async function D(e,t,r={}){const s=g(),i=(r.serverUrl||s.serverUrl).replace(/\/$/,""),a=r.token??s.token,o=h(r,p);let l=0;const u=S.createHash("sha256");try{const n=await fetch(`${i}${e}`,{method:"GET",headers:a?{authorization:`Bearer ${a}`}:{},signal:o.signal,...w(i)});if(!n.ok)throw new Error(`项目文件下载失败 HTTP ${n.status}`);if(!n.body)throw new Error("项目文件下载响应为空");const f=new v({transform(c,m,E){if(l+=c.byteLength,Number.isFinite(r.maxBytes)&&l>Number(r.maxBytes)){E(new Error("project_upload_too_large"));return}u.update(c),E(null,c)}});return await U($.fromWeb(n.body),f,T.createWriteStream(t,{flags:"wx",mode:384})),{sizeBytes:l,sha256:u.digest("hex")}}catch(n){throw u.digest(),T.rmSync(t,{force:!0}),d(n,"GET",e,o)}finally{o.dispose()}}async function F(e,t,r,s={},i={}){const a=g(),o=(i.serverUrl||a.serverUrl).replace(/\/$/,""),l=i.token??a.token,u=h(i,x);try{const n=await fetch(`${o}${t}`,{method:e,headers:{...s,...l?{authorization:`Bearer ${l}`}:{}},body:r,duplex:"half",signal:u.signal,...w(o)}),f=await n.text(),c=f?JSON.parse(f):null;if(!n.ok){const m=c&&typeof c=="object"&&"message"in c?String(c.message):`HTTP ${n.status}`;throw new Error(m)}return c}catch(n){throw d(n,e,t,u)}finally{u.dispose()}}function h(e,t){const r=k(e.timeoutMs,t),s=new AbortController;let i=!1;const a=()=>s.abort(e.signal?.reason);e.signal?.aborted?a():e.signal?.addEventListener("abort",a,{once:!0});const o=setTimeout(()=>{i=!0,s.abort(new Error(`ACA Server request timed out after ${r}ms`))},r);return o.unref(),{signal:s.signal,timeoutMs:r,timedOut:()=>i,dispose:()=>{clearTimeout(o),e.signal?.removeEventListener("abort",a)}}}function d(e,t,r,s){return s.timedOut()?new Error(`ACA Server 请求超时(${s.timeoutMs}ms):${t} ${r}`,{cause:e}):e instanceof Error?e:new Error(String(e))}function k(e,t){return!Number.isFinite(e)||Number(e)<=0?t:Math.max(250,Math.min(Number(e),30*6e4))}function w(e){return!/^https:/i.test(e)||!M(process.env.ACA_SERVER_INSECURE_TLS)?{}:{dispatcher:_}}function M(e){return e==="1"||e?.toLowerCase()==="true"||e?.toLowerCase()==="yes"}export{A as AcaServerHttpError,z as acaServerDownload,D as acaServerDownloadToFile,q as acaServerRequest,F as acaServerStreamRequest};
@@ -0,0 +1 @@
1
+ import z from"node:crypto";import{setTimeout as D}from"node:timers/promises";import{acaServerRequest as H,AcaServerHttpError as A}from"./aca-server-client.js";import{appendCoalescedAcpEvent as I}from"./acp-event-coalescer.js";import{shouldUploadAcpEvent as J}from"./acp-event-upload-filter.js";function K(d,R,r={}){const C=r.request??H,b=z.randomUUID(),n=[],g=new AbortController,M=`/api/client/jobs/${encodeURIComponent(d)}/events`,$=Number.parseInt(process.env.ACA_ACP_EVENT_BATCH_INTERVAL_MS||"",10),O=r.intervalMs??(Number.isFinite($)?Math.max(40,Math.min($,2e3)):200);let h=[],l,u,y,v,m,i=!1,q=!1,c=!1,w=!1,U=0,_=0,N=0,E=0,s;const k=()=>{const e=n.length===0&&N===0&&!h.length;return{complete:e,acknowledgedBatches:E,pendingBatches:n.length,droppedEvents:N,...!e&&s?{reason:s}:{}}},S=()=>{l&&clearTimeout(l),l=void 0;const e=h.filter(J);if(h=[],!e.length)return;const f=JSON.stringify(e),a=Buffer.byteLength(f);if(n.length>=256||_+a>(r.maxBytes??8*1024*1024)){N+=e.length,s!=="queue_limit"&&r.warn?.(`ACP event upload job=${d}: queue_limit`),s="queue_limit";return}const T=e[0]?.raw,o=e.at(-1)?.raw,t={batchNo:++U,firstSequence:Number(T?.acaSequence),lastSequence:Number(o?.acaSequenceEnd??o?.acaSequence),eventCount:e.length};n.push({...t,json:f,bytes:a}),r.trace?.("upload_queued",{jobId:d,uploadRunId:b,...t}),_+=a},x=async()=>{for(;n.length&&!i;){const e=n[0];let f=!1;for(let a=0;a<5&&!i;a++){let T=!1;try{if(m===void 0)try{m=(await C("GET",`${M}/capabilities`,void 0,{signal:g.signal,timeoutMs:5e3})).reliableBatches===1}catch(t){if(t instanceof A&&t.status===404)m=!1;else throw t}T=!0;const o=await C("POST",`${M}/batch`,{machineId:R,events:JSON.parse(e.json),...m?{uploadRunId:b,batchNo:e.batchNo,ackOnly:!0}:{}},{signal:g.signal,timeoutMs:5e3});if(m&&(o?.ack?.uploadRunId!==b||o.ack.batchNo!==e.batchNo))throw new A(409,"invalid_batch_ack");r.trace?.("upload_ack",{jobId:d,uploadRunId:b,batchNo:e.batchNo,attempt:a,firstSequence:e.firstSequence,lastSequence:e.lastSequence,eventCount:e.eventCount}),n.shift(),_-=e.bytes,E++,f=!0;break}catch(o){if(i)return;const t=o instanceof A?o.status:void 0;if(w=T&&m===!1||t!==void 0&&t<500&&![408,429].includes(t),s=t?`http_${t}`:"network_error",r.warn?.(`ACP event upload job=${d} batch=${e.batchNo} attempt=${a+1} reason=${s}`),w||a===4)break;const P=(r.retryBaseMs??1e3)*2**Math.min(a,2);await D(P+Math.floor(Math.random()*P*.2),void 0,{signal:g.signal}).catch(()=>{})}}if(!f){c=!0,!w&&!q&&!i&&(u=setTimeout(()=>{c=!1,u=void 0,p()},r.recoveryDelayMs??3e4),u.unref());return}}},p=()=>{y||i||c||!n.length||(y=x().catch(()=>{c=!0,s="upload_internal_error"}).finally(()=>{y=void 0,!c&&n.length&&!i&&p()}))},B=async()=>{S(),p();let e;const f=async()=>{for(;n.length&&!c&&!i;)p(),await y};return await Promise.race([f(),new Promise(a=>{e=setTimeout(a,r.finishTimeoutMs??1e4)})]),e&&clearTimeout(e),k()};return{uploadRunId:b,push(e){i||(I(h,e),q||e.eventType==="tool_call"||e.eventType==="tool_call_update"&&["completed","failed","cancelled"].includes(e.status||"")||h.length>=64?(S(),p()):l||(l=setTimeout(()=>{S(),p()},O),l.unref()))},flush:B,finish(){return v||(q=!0,u&&clearTimeout(u),c&&!w&&(c=!1),v=(async()=>{await B(),n.length&&!s&&(s="finish_timeout"),i=!0,g.abort(),await y;const e=k();return e.complete||r.warn?.(`ACP event upload incomplete job=${d} pending=${e.pendingBatches} dropped=${e.droppedEvents}`),e})(),v)},discard(){i=!0,l&&clearTimeout(l),u&&clearTimeout(u),g.abort(),h=[],n.length=0,_=0}}}export{K as createAcpEventBatcher};
@@ -1,6 +1,6 @@
1
- import G from"node:crypto";import{appendSessionReferenceContext as K}from"./session-reference-prompt.js";import M from"node:fs";import j from"node:path";import{compactAgentContext as X,forkAgentSession as Z,sendAgentPrompt as Q}from"../acp/agent.js";import{acaServerDownload as Y,acaServerRequest as y}from"./aca-server-client.js";import{loadAcaConfig as ee}from"./aca-config.js";import{prepareLocalWorktree as te}from"./worktree.js";import{expandWorkerRuntimePath as ne}from"./worker-runtime-paths.js";import{collectAffectedFiles as F,extractAffectedFilePathsFromAcpRaw as oe}from"./affected-files.js";import{resolveJobPermissionPolicy as ie}from"./job-permission-policy.js";import{appendCoalescedAcpEvent as ae}from"./acp-event-coalescer.js";import{shouldBufferAcpEvent as re,shouldUploadAcpEvent as se}from"./acp-event-upload-filter.js";import{userFacingAcpError as ce}from"../acp/error-display.js";import{ensureCodexProviderRuntimeRevision as le}from"./codex-provider-runtime-config.js";import{acpSessionInvalidationFromError as de}from"../acp/session-errors.js";const ue=1500,U=5e3,z=1e3,pe=200,me=64,B=900,fe=1e4,he=0,we=12e3;function Ie(e){return ne(e)}function be(e){const t=e.project_metadata;return t&&typeof t=="object"&&!Array.isArray(t)&&t.kind==="chat"?!0:typeof e.project_root_path=="string"&&(e.project_root_path.startsWith("~/.aica/chats/")||e.project_root_path.startsWith("~/.aca/chats/")||/(^|\/)\.aica\/chats\//.test(e.project_root_path)||/(^|\/)\.aca\/chats\//.test(e.project_root_path))}function ut(e,t={}){let n=!1,i=null,o=null,r=0,c=0;const u=new Set,a=new Map,p=new Map,g=async()=>{if(n||Date.now()<c)return;const w=ve();if(J(u.size,w))try{const f=t.loadConfig?.()??ee();if(!f.token)return;for(;!n&&J(u.size,w);){const _=t.claimNextJob?await t.claimNextJob(f,[...a.keys()]):await y("POST","/api/client/jobs/claim",{machineId:f.machineId,workspaceId:f.defaultWorkspaceId,activeSessionIds:[...a.keys()]});if(r=0,c=0,!_.item)break;const h=_.item;if(n){await(t.cancelClaimedJob?t.cancelClaimedJob(h,f):k(h.job_id,f.machineId,"cancelled")).catch(()=>{});break}const m=h.session_id,P=a.get(m);u.add(h.job_id);const A=new AbortController;let d;d=(async()=>{P&&(e.warn(`claimed job=${h.job_id} for already active session=${m}; waiting for the previous run to release its ACP runtime`),await ye(P,h,f,A)),!A.signal.aborted&&await(t.runJob??_e)(h,f,e,A)})().catch(S=>{e.warn(`job ${h.job_id} failed outside handler: ${S instanceof Error?S.message:String(S)}`)}).finally(()=>{u.delete(h.job_id),a.get(m)===d&&a.delete(m),p.delete(h.job_id)}),a.set(m,d),p.set(h.job_id,{controller:A,promise:d})}}catch(f){if(!n){r+=1;const _=qe(r);c=Date.now()+_,e.warn(`job worker failed, retry in ${_}ms: ${f instanceof Error?f.message:String(f)}`)}}},T=()=>{if(n||i)return;const w=g();i=w,w.finally(()=>{i===w&&(i=null)})};T();const l=setInterval(()=>{T()},t.claimIntervalMs??ue);return{timer:l,stop:()=>o||(o=(async()=>{if(n)return;n=!0,clearInterval(l);for(const f of p.values())f.controller.abort();await i?.catch(()=>{});for(const f of p.values())f.controller.abort();const w=[...p.values()].map(f=>f.promise);w.length!==0&&await Promise.race([Promise.allSettled(w).then(()=>{}),N(we)])})(),o)}}async function ye(e,t,n,i){let o=!1;const c=setInterval(()=>{o||i.signal.aborted||(o=!0,Promise.allSettled([y("POST",`/api/client/jobs/${encodeURIComponent(t.job_id)}/heartbeat`,{machineId:n.machineId}),V(t.job_id,i)]).finally(()=>{o=!1}))},U);try{await Promise.race([e,Se(i.signal)])}finally{clearInterval(c)}}function Se(e){return e.aborted?Promise.resolve():new Promise(t=>e.addEventListener("abort",()=>t(),{once:!0}))}function ve(){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?he:t===0?0:Math.max(1,Math.min(t,8))}function J(e,t){return t===0||e<t}function ge(){const e=Number.parseInt(process.env.ACA_ACP_EVENT_BATCH_INTERVAL_MS||"",10);return Number.isFinite(e)?Math.max(40,Math.min(e,2e3)):pe}async function _e(e,t,n,i){const o=e.job_id,r=e.session_id,c=e.workspace_id,u=e.project_id??null,a=e.input??{},p=String(a.control?.type||"").toLowerCase();if(!p&&!a.message?.trim()&&(a.attachments??[]).length===0&&!a.sessionReferences?.length){await k(o,t.machineId,"job input missing message");return}const g=t.projects.find(d=>d.projectId===u),T=Ie(g?.rootPath||e.project_root_path||"");if(!T){await k(o,t.machineId,"job project root is unknown");return}be(e)&&M.mkdirSync(T,{recursive:!0}),n.info(`claimed job=${o} session=${r} project=${g?.name??u??"unknown"}`);let l=!1,w=!1;const f=setInterval(()=>{l||(l=!0,y("POST",`/api/client/jobs/${encodeURIComponent(o)}/heartbeat`,{machineId:t.machineId}).catch(()=>{}).finally(()=>{l=!1})),w||(w=!0,V(o,i).catch(()=>{}).finally(()=>{w=!1}))},U);let _=0;const h=ke(o,t.machineId),m=$e(o,t.machineId),P=new Set;let A=null;try{await m.sendNow({phase:"initializing",label:"准备工作区",detail:e.worktree_id?"worktree":"project root"}).catch(()=>{});const d=te({projectId:u,sessionId:r,projectRoot:T,worktree:e});A=d,await Je(o,t.machineId,d);const S=Ae(a.acpRun);if(Ce(S.agentType)&&await le(e.codex_provider_revision),p==="compact"){if(!e.acp_session_id)throw new Error("Current session does not have an ACP context to compact");const s=await X({agentType:x(S.agentType,"codex"),cliType:x(S.cliType,"builtin"),acpSessionId:e.acp_session_id,cwd:d.cwd,model:a.model,mode:a.mode,configOptionValues:a.configOptionValues,timeoutMs:a.timeoutMs,allowRollover:a.control?.allowRollover===!0,signal:i.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:s.acpSessionId,suppressTurn:!0,result:{ok:!0,control:{type:"compact",allowRollover:a.control?.allowRollover===!0},acpSessionId:s.acpSessionId,durationMs:s.durationMs,usage:s.contextUsage,maintenance:s.maintenance,worktree:R(d)}}),n.info(`completed context compact job=${o}`);return}if(p==="fork"){const s=String(a.control?.sourceAcpSessionId||"").trim();if(!s)throw new Error("Source session does not have a Codex ACP context to fork");await m.sendNow({phase:"forking",label:"正在分叉 Codex 会话",detail:a.control?.sourceSessionId?`源会话 ${a.control.sourceSessionId}`:void 0}).catch(()=>{});const b=await Z({agentType:x(S.agentType,"codex"),cliType:x(S.cliType,"builtin"),sourceProviderSessionId:s,cwd:d.cwd,model:a.model,mode:a.mode,configOptionValues:a.configOptionValues,timeoutMs:a.timeoutMs,signal:i.signal,onStatus:C=>{m.report({phase:C.phase,label:C.label,detail:C.detail,updateType:C.updateType,raw:C})}});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:a.control?.sourceSessionId||null},acpSessionId:b.acpSessionId,worktree:R(d)}}),n.info(`completed Codex session fork job=${o}`);return}const v=await Oe({cwd:d.cwd,sessionId:r,jobId:o,attachments:a.attachments??[]});v.length>0&&(n.info(`materialized ${v.length} attachment(s) for job=${o}`),await m.sendNow({phase:"initializing",label:"附件已写入本机",detail:v.map(s=>s.relativePath).join(", "),raw:{attachments:W(v)}}).catch(()=>{}));const E=Me(a,v),q=Ne(E,v),I=await Q({agentType:x(S.agentType,"codex"),cliType:x(S.cliType,"builtin"),acpSessionId:e.acp_session_id??null,requireSessionResume:Te(e.session_raw),prompt:E,promptBlocks:q,cwd:d.cwd,model:a.model,mode:a.mode,configOptionValues:a.configOptionValues,canExecuteTools:a.canExecuteTools,timeoutMs:a.timeoutMs,contextWindow:Pe(S.contextWindow),signal:i.signal,onStatus:s=>{m.report({phase:s.phase,label:s.label,detail:s.detail,updateType:s.updateType,raw:s})},onUpdate:s=>{_+=1;for(const C of oe(s.raw))P.add(C);const b={turnId:e.user_turn_id??null,eventType:s.type,label:s.label,text:s.text,status:s.status,toolCallId:s.toolCallId,raw:je(s.raw,_)};re(b)&&h.push(b)},onPermissionRequest:s=>{const b=ie(a,s);return b?Promise.resolve(b):Le(o,t.machineId,s,i.signal)},onUserInputRequest:s=>We(o,t.machineId,s,i.signal),onProviderSessionMigration:async s=>{const b=await y("POST",`/api/client/jobs/${encodeURIComponent(o)}/acp-session`,{machineId:t.machineId,previousAcpSessionId:s.previousProviderSessionId,acpSessionId:s.providerSessionId,reason:s.reason,migratedAtMs:s.migratedAtMs});e.acp_session_id=s.providerSessionId,n.info(`migrated ACP session for job=${o} ${s.previousProviderSessionId} -> ${s.providerSessionId}`),await h.flush().catch(()=>{}),await m.sendNow({phase:"resuming",label:"Codex 会话已迁移",detail:s.providerSessionId,updateType:"aca.performance.provider_session_migrated",raw:{migration:s,server:{migrated:b.migrated===!0}}}).catch(()=>{})}});await h.flush(),await m.flush();const H=F({cwd:d.cwd,evidencePaths:P}),$=xe(I.promptResponse);if(!(Re($)||!!I.content)){await k(o,t.machineId,`${I.agentType} stopped: ${$??"unknown"}`,{acpSessionId:I.acpSessionId,stopReason:$,exitCode:I.exitCode,stderr:He(I.stderr,4e3),worktree:R(d)});return}await y("POST",`/api/client/jobs/${encodeURIComponent(o)}/complete`,{machineId:t.machineId,acpSessionId:I.acpSessionId,assistantText:I.content,result:{ok:!0,acpSessionId:I.acpSessionId,stopReason:$,durationMs:I.durationMs,acpRun:a.acpRun??null,effectiveConfig:I.effectiveConfig??null,performance:I.performance??null,usage:I.contextUsage??null,providerDiagnostics:I.providerDiagnostics??null,updateCount:I.updateCount,statusTimeline:I.statusTimeline,worktree:R(d),attachments:W(v),files:H}}),n.info(`completed job=${o}`)}catch(d){if(De(i.signal)){h.discard(),m.discard(),n.warn(`stopped local job=${o} because ACA Server already finalized it`);return}await h.flush(),await m.flush();const S=i.signal.aborted?"cancelled":d instanceof Error?d.message:String(d),v=de(d),E=A?F({cwd:A.cwd,evidencePaths:P}):[];await k(o,t.machineId,ce(S),{rawError:S,files:E,providerDiagnostics:O(d,"providerDiagnostics"),statusTimeline:O(d,"statusTimeline"),performance:O(d,"performance"),...v?{acpSessionInvalidated:!0,invalidatedAcpSessionId:v.providerSessionId,failure:{category:v.category,actions:v.actions,details:v.details}}:{}},v?{invalidateAcpSession:!0,invalidatedAcpSessionId:v.providerSessionId}:void 0)}finally{await h.flush(),await m.flush(),clearInterval(f)}}function Te(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 Ae(e){return!e||typeof e!="object"||Array.isArray(e)?{}:e}function Ce(e){const t=String(e||"codex").toLowerCase().replace(/[^a-z0-9]+/g,"");return t==="codex"||t==="codexacp"||t==="codexstandard"}function Pe(e){return typeof e=="number"&&Number.isFinite(e)&&e>0?e:void 0}function x(...e){for(const t of e)if(typeof t=="string"&&t.trim())return t.trim();return""}function xe(e){if(!e||typeof e!="object"||Array.isArray(e))return;const t=e;if(typeof t.stopReason=="string")return t.stopReason;if(typeof t.reason=="string")return t.reason;const n=t.part;if(n&&typeof n=="object"&&!Array.isArray(n)&&typeof n.reason=="string")return n.reason}function Re(e){return e?["end_turn","stop","completed","complete"].includes(e):!1}function ke(e,t){let n=[],i=null,o=Promise.resolve(),r=!1;const c=async p=>{const g=p.filter(se);g.length!==0&&await y("POST",`/api/client/jobs/${encodeURIComponent(e)}/events/batch`,{machineId:t,events:g})},u=()=>{if(i&&(clearTimeout(i),i=null),r)return n=[],o;const p=n;return n=[],o=o.then(()=>c(p)).catch(()=>{}),o},a=()=>{i||(i=setTimeout(()=>{u()},ge()),i.unref())};return{push(p){if(!r){if(ae(n,p),Ee(p)||n.length>=me){u();return}a()}},flush:u,discard(){r=!0,n=[],i&&clearTimeout(i),i=null}}}function Ee(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,i=0,o="",r=null,c=Promise.resolve(),u=!1;const a=l=>JSON.stringify({phase:l.phase??"",label:l.label??"",detail:l.detail??"",updateType:l.updateType??""}),p=async l=>{await y("POST",`/api/client/jobs/${encodeURIComponent(e)}/presence`,{machineId:t,phase:l.phase,label:l.label,detail:l.detail,updateType:l.updateType,raw:l.raw}),i=Date.now(),o=a(l)},g=()=>{if(r&&(clearTimeout(r),r=null),u)return n=null,c;if(!n)return c;const l=n;return n=null,c=c.then(()=>p(l)).catch(()=>{}),c},T=l=>{r||(r=setTimeout(()=>{g()},l),r.unref())};return{report(l){if(u)return;const w=Date.now(),_=a(l)!==o,h=w-i>=fe;if(!_&&!h)return;n=l;const m=w-i;if(m>=B||i===0){g();return}T(B-m)},sendNow(l){return u?c:(n=l,g())},flush:g,discard(){u=!0,n=null,r&&clearTimeout(r),r=null}}}function je(e,t){return e&&typeof e=="object"&&!Array.isArray(e)?{...e,acaSequence:t}:{value:e,acaSequence:t}}function Me(e,t=[]){const n=K(e?.message?.trim()??"",e?.sessionReferences);if(t.length===0)return n;const o=["用户随消息上传了以下附件,aca 已将附件写入本机工作区。需要读取附件时请直接使用下面的 localPath;图片附件也已作为 ACP image block 随本轮请求传入。",...t.map((r,c)=>`${c+1}. ${r.name} (${r.kind}, ${r.mimeType}, ${Be(r.size)})
2
- localPath: ${r.path}
3
- workspacePath: ${r.relativePath}`)].join(`
1
+ import X from"node:crypto";import{appendSessionReferenceContext as Z}from"./session-reference-prompt.js";import N from"node:fs";import M from"node:path";import{compactAgentContext as Q,forkAgentSession as Y,sendAgentPrompt as ee}from"../acp/agent.js";import{acaServerDownload as te,acaServerRequest as S}from"./aca-server-client.js";import{loadAcaConfig as ne}from"./aca-config.js";import{prepareLocalWorktree as oe}from"./worktree.js";import{expandWorkerRuntimePath as ie}from"./worker-runtime-paths.js";import{collectAffectedFiles as J,extractAffectedFilePathsFromAcpRaw as ae}from"./affected-files.js";import{resolveJobPermissionPolicy as re}from"./job-permission-policy.js";import{createAcpEventBatcher as se}from"./acp-event-batcher.js";import{createToolLifecycleTrace as ce}from"../acp/tool-lifecycle-trace.js";import{shouldBufferAcpEvent as le}from"./acp-event-upload-filter.js";import{userFacingAcpError as de}from"../acp/error-display.js";import{ensureCodexProviderRuntimeRevision as ue}from"./codex-provider-runtime-config.js";import{acpSessionInvalidationFromError as pe}from"../acp/session-errors.js";const me=1500,B=5e3,L=1e3,W=900,fe=1e4,he=0,we=12e3;function Ie(e){return ie(e)}function ye(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 lt(e,t={}){let n=!1,i=null,o=null,s=0,l=0;const u=new Set,r=new Map,b=new Map,A=async()=>{if(n||Date.now()<l)return;const m=ve();if(D(u.size,m))try{const p=t.loadConfig?.()??ne();if(!p.token)return;for(;!n&&D(u.size,m);){const v=t.claimNextJob?await t.claimNextJob(p,[...r.keys()]):await S("POST","/api/client/jobs/claim",{machineId:p.machineId,workspaceId:p.defaultWorkspaceId,activeSessionIds:[...r.keys()]});if(s=0,l=0,!v.item)break;const f=v.item;if(n){await(t.cancelClaimedJob?t.cancelClaimedJob(f,p):j(f.job_id,p.machineId,"cancelled")).catch(()=>{});break}const h=f.session_id,w=r.get(h);u.add(f.job_id);const P=new AbortController;let x;x=(async()=>{w&&(e.warn(`claimed job=${f.job_id} for already active session=${h}; waiting for the previous run to release its ACP runtime`),await be(w,f,p,P)),!P.signal.aborted&&await(t.runJob??ge)(f,p,e,P)})().catch(c=>{e.warn(`job ${f.job_id} failed outside handler: ${c instanceof Error?c.message:String(c)}`)}).finally(()=>{u.delete(f.job_id),r.get(h)===x&&r.delete(h),b.delete(f.job_id)}),r.set(h,x),b.set(f.job_id,{controller:P,promise:x})}}catch(p){if(!n){s+=1;const v=We(s);l=Date.now()+v,e.warn(`job worker failed, retry in ${v}ms: ${p instanceof Error?p.message:String(p)}`)}}},C=()=>{if(n||i)return;const m=A();i=m,m.finally(()=>{i===m&&(i=null)})};C();const d=setInterval(()=>{C()},t.claimIntervalMs??me);return{timer:d,stop:()=>o||(o=(async()=>{if(n)return;n=!0,clearInterval(d);for(const p of b.values())p.controller.abort();await i?.catch(()=>{});for(const p of b.values())p.controller.abort();const m=[...b.values()].map(p=>p.promise);m.length!==0&&await Promise.race([Promise.allSettled(m).then(()=>{}),F(we)])})(),o)}}async function be(e,t,n,i){let o=!1;const l=setInterval(()=>{o||i.signal.aborted||(o=!0,Promise.allSettled([S("POST",`/api/client/jobs/${encodeURIComponent(t.job_id)}/heartbeat`,{machineId:n.machineId}),G(t.job_id,i)]).finally(()=>{o=!1}))},B);try{await Promise.race([e,Se(i.signal)])}finally{clearInterval(l)}}function Se(e){return e.aborted?Promise.resolve():new Promise(t=>e.addEventListener("abort",()=>t(),{once:!0}))}function ve(){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?he:t===0?0:Math.max(1,Math.min(t,8))}function D(e,t){return t===0||e<t}async function ge(e,t,n,i){const o=e.job_id,s=e.session_id,l=e.workspace_id,u=e.project_id??null,r=e.input??{},b=String(r.control?.type||"").toLowerCase();if(!b&&!r.message?.trim()&&(r.attachments??[]).length===0&&!r.sessionReferences?.length){await j(o,t.machineId,"job input missing message");return}const A=t.projects.find(c=>c.projectId===u),C=Ie(A?.rootPath||e.project_root_path||"");if(!C){await j(o,t.machineId,"job project root is unknown");return}ye(e)&&N.mkdirSync(C,{recursive:!0}),n.info(`claimed job=${o} session=${s} project=${A?.name??u??"unknown"}`);let d=!1,m=!1;const p=setInterval(()=>{d||(d=!0,S("POST",`/api/client/jobs/${encodeURIComponent(o)}/heartbeat`,{machineId:t.machineId}).catch(()=>{}).finally(()=>{d=!1})),m||(m=!0,G(o,i).catch(()=>{}).finally(()=>{m=!1}))},B);let v=0;const f=ce(c=>n.info(c)),h=se(o,t.machineId,{warn:c=>n.warn(c),trace:f}),w=Re(o,t.machineId),P=new Set;let x=null;try{await w.sendNow({phase:"initializing",label:"准备工作区",detail:e.worktree_id?"worktree":"project root"}).catch(()=>{});const c=oe({projectId:u,sessionId:s,projectRoot:C,worktree:e});x=c,await ze(o,t.machineId,c);const g=Te(r.acpRun);if(Ae(g.agentType)&&await ue(e.codex_provider_revision),b==="compact"){if(!e.acp_session_id)throw new Error("Current session does not have an ACP context to compact");const a=await Q({agentType:k(g.agentType,"codex"),cliType:k(g.cliType,"builtin"),acpSessionId:e.acp_session_id,cwd:c.cwd,model:r.model,mode:r.mode,configOptionValues:r.configOptionValues,timeoutMs:r.timeoutMs,allowRollover:r.control?.allowRollover===!0,signal:i.signal,onStatus:y=>{w.report({phase:y.phase,label:y.label,detail:y.detail,updateType:y.updateType,raw:y})}});await w.flush(),await S("POST",`/api/client/jobs/${encodeURIComponent(o)}/complete`,{machineId:t.machineId,acpSessionId:a.acpSessionId,suppressTurn:!0,result:{ok:!0,control:{type:"compact",allowRollover:r.control?.allowRollover===!0},acpSessionId:a.acpSessionId,durationMs:a.durationMs,usage:a.contextUsage,maintenance:a.maintenance,worktree:$(c)}}),n.info(`completed context compact job=${o}`);return}if(b==="fork"){const a=String(r.control?.sourceAcpSessionId||"").trim();if(!a)throw new Error("Source session does not have a Codex ACP context to fork");await w.sendNow({phase:"forking",label:"正在分叉 Codex 会话",detail:r.control?.sourceSessionId?`源会话 ${r.control.sourceSessionId}`:void 0}).catch(()=>{});const y=await Y({agentType:k(g.agentType,"codex"),cliType:k(g.cliType,"builtin"),sourceProviderSessionId:a,cwd:c.cwd,model:r.model,mode:r.mode,configOptionValues:r.configOptionValues,timeoutMs:r.timeoutMs,signal:i.signal,onStatus:R=>{w.report({phase:R.phase,label:R.label,detail:R.detail,updateType:R.updateType,raw:R})}});await w.flush(),await S("POST",`/api/client/jobs/${encodeURIComponent(o)}/complete`,{machineId:t.machineId,acpSessionId:y.acpSessionId,suppressTurn:!0,result:{ok:!0,control:{type:"fork",sourceSessionId:r.control?.sourceSessionId||null},acpSessionId:y.acpSessionId,worktree:$(c)}}),n.info(`completed Codex session fork job=${o}`);return}const _=await Ee({cwd:c.cwd,sessionId:s,jobId:o,attachments:r.attachments??[]});_.length>0&&(n.info(`materialized ${_.length} attachment(s) for job=${o}`),await w.sendNow({phase:"initializing",label:"附件已写入本机",detail:_.map(a=>a.relativePath).join(", "),raw:{attachments:q(_)}}).catch(()=>{}));const T=$e(r,_),O=je(T,_),I=await ee({agentType:k(g.agentType,"codex"),cliType:k(g.cliType,"builtin"),acpSessionId:e.acp_session_id??null,requireSessionResume:_e(e.session_raw),prompt:T,promptBlocks:O,cwd:c.cwd,model:r.model,mode:r.mode,configOptionValues:r.configOptionValues,canExecuteTools:r.canExecuteTools,timeoutMs:r.timeoutMs,contextWindow:Ce(g.contextWindow),signal:i.signal,onStatus:a=>{w.report({phase:a.phase,label:a.label,detail:a.detail,updateType:a.updateType,raw:a})},onUpdate:a=>{v+=1,(a.type==="tool_call"||a.type==="tool_call_update"&&["completed","failed","cancelled"].includes(a.status||""))&&f("worker_event",{jobId:o,uploadRunId:h.uploadRunId,toolCallId:a.toolCallId,status:a.status,sequence:v});for(const R of ae(a.raw))P.add(R);const y={turnId:e.user_turn_id??null,eventType:a.type,label:a.label,text:a.text,status:a.status,toolCallId:a.toolCallId,raw:ke(a.raw,v)};le(y)&&h.push(y)},onPermissionRequest:a=>{const y=re(r,a);return y?Promise.resolve(y):Ue(o,t.machineId,a,i.signal)},onUserInputRequest:a=>Je(o,t.machineId,a,i.signal),onProviderSessionMigration:async a=>{const y=await S("POST",`/api/client/jobs/${encodeURIComponent(o)}/acp-session`,{machineId:t.machineId,previousAcpSessionId:a.previousProviderSessionId,acpSessionId:a.providerSessionId,reason:a.reason,migratedAtMs:a.migratedAtMs});e.acp_session_id=a.providerSessionId,n.info(`migrated ACP session for job=${o} ${a.previousProviderSessionId} -> ${a.providerSessionId}`),await h.flush().catch(()=>{}),await w.sendNow({phase:"resuming",label:"Codex 会话已迁移",detail:a.providerSessionId,updateType:"aca.performance.provider_session_migrated",raw:{migration:a,server:{migrated:y.migrated===!0}}}).catch(()=>{})}}),U=await h.finish();await w.flush();const H=J({cwd:c.cwd,evidencePaths:P}),E=Pe(I.promptResponse);if(!(xe(E)||!!I.content)){await j(o,t.machineId,`${I.agentType} stopped: ${E??"unknown"}`,{acpSessionId:I.acpSessionId,stopReason:E,eventUpload:U,exitCode:I.exitCode,stderr:De(I.stderr,4e3),worktree:$(c)});return}await S("POST",`/api/client/jobs/${encodeURIComponent(o)}/complete`,{machineId:t.machineId,acpSessionId:I.acpSessionId,assistantText:I.content,result:{ok:!0,eventUpload:U,acpSessionId:I.acpSessionId,stopReason:E,durationMs:I.durationMs,acpRun:r.acpRun??null,effectiveConfig:I.effectiveConfig??null,performance:I.performance??null,usage:I.contextUsage??null,providerDiagnostics:I.providerDiagnostics??null,updateCount:I.updateCount,statusTimeline:I.statusTimeline,worktree:$(c),attachments:q(_),files:H}}),n.info(`completed job=${o}`)}catch(c){if(Le(i.signal)){h.discard(),w.discard(),n.warn(`stopped local job=${o} because ACA Server already finalized it`);return}const g=await h.finish();await w.flush();const _=i.signal.aborted?"cancelled":c instanceof Error?c.message:String(c),T=pe(c),O=x?J({cwd:x.cwd,evidencePaths:P}):[];await j(o,t.machineId,de(_),{rawError:_,eventUpload:g,files:O,providerDiagnostics:z(c,"providerDiagnostics"),statusTimeline:z(c,"statusTimeline"),performance:z(c,"performance"),...T?{acpSessionInvalidated:!0,invalidatedAcpSessionId:T.providerSessionId,failure:{category:T.category,actions:T.actions,details:T.details}}:{}},T?{invalidateAcpSession:!0,invalidatedAcpSessionId:T.providerSessionId}:void 0)}finally{h.discard(),clearInterval(p),await w.flush()}}function _e(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 Te(e){return!e||typeof e!="object"||Array.isArray(e)?{}:e}function Ae(e){const t=String(e||"codex").toLowerCase().replace(/[^a-z0-9]+/g,"");return t==="codex"||t==="codexacp"||t==="codexstandard"}function Ce(e){return typeof e=="number"&&Number.isFinite(e)&&e>0?e:void 0}function k(...e){for(const t of e)if(typeof t=="string"&&t.trim())return t.trim();return""}function Pe(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 xe(e){return e?["end_turn","stop","completed","complete"].includes(e):!1}function Re(e,t){let n=null,i=0,o="",s=null,l=Promise.resolve(),u=!1;const r=d=>JSON.stringify({phase:d.phase??"",label:d.label??"",detail:d.detail??"",updateType:d.updateType??""}),b=async d=>{await S("POST",`/api/client/jobs/${encodeURIComponent(e)}/presence`,{machineId:t,phase:d.phase,label:d.label,detail:d.detail,updateType:d.updateType,raw:d.raw}),i=Date.now(),o=r(d)},A=()=>{if(s&&(clearTimeout(s),s=null),u)return n=null,l;if(!n)return l;const d=n;return n=null,l=l.then(()=>b(d)).catch(()=>{}),l},C=d=>{s||(s=setTimeout(()=>{A()},d),s.unref())};return{report(d){if(u)return;const m=Date.now(),v=r(d)!==o,f=m-i>=fe;if(!v&&!f)return;n=d;const h=m-i;if(h>=W||i===0){A();return}C(W-h)},sendNow(d){return u?l:(n=d,A())},flush:A,discard(){u=!0,n=null,s&&clearTimeout(s),s=null}}}function ke(e,t){return e&&typeof e=="object"&&!Array.isArray(e)?{...e,acaSequence:t}:{value:e,acaSequence:t}}function $e(e,t=[]){const n=Z(e?.message?.trim()??"",e?.sessionReferences);if(t.length===0)return n;const o=["用户随消息上传了以下附件,aca 已将附件写入本机工作区。需要读取附件时请直接使用下面的 localPath;图片附件也已作为 ACP image block 随本轮请求传入。",...t.map((s,l)=>`${l+1}. ${s.name} (${s.kind}, ${s.mimeType}, ${Fe(s.size)})
2
+ localPath: ${s.path}
3
+ workspacePath: ${s.relativePath}`)].join(`
4
4
  `);return n?`${n}
5
5
 
6
- ${o}`:o}function Ne(e,t){const n=[{type:"text",text:e}];for(const i of t)i.kind!=="image"||!i.mimeType.startsWith("image/")||n.push({type:"image",data:i.base64,mimeType:i.mimeType,uri:i.path});return n}async function Oe(e){if(e.attachments.length===0)return[];const t=j.join(e.cwd,".aca","attachments",L(e.sessionId),L(e.jobId));M.mkdirSync(t,{recursive:!0});const n=[];for(const[i,o]of e.attachments.entries()){const r=o.attachmentId?{mimeType:o.mimeType||"application/octet-stream",bytes:await Y(`/api/client/attachments/${encodeURIComponent(o.attachmentId)}`)}:Fe(o.dataUrl??""),c=o.mimeType||r.mimeType||"application/octet-stream",u=o.kind==="image"||c.startsWith("image/")?"image":"file",a=Ue(o.name||`attachment-${i+1}`,c),p=j.join(t,`${String(i+1).padStart(2,"0")}-${a}`);M.writeFileSync(p,r.bytes),n.push({name:o.name||a,mimeType:c,size:r.bytes.byteLength,kind:u,path:p,relativePath:j.relative(e.cwd,p),base64:r.bytes.toString("base64"),sha256:G.createHash("sha256").update(r.bytes).digest("hex")})}return n}function Fe(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),i=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(i,"base64")}}function Ue(e,t){const n=j.basename(e).replace(/[<>:"/\\|?*\x00-\x1F]/g,"_").trim(),i=`attachment${ze(t)}`;return(!n||n==="."||n===".."?i:n).slice(0,180)}function ze(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 L(e){return e.replace(/[^a-zA-Z0-9._-]+/g,"-").replace(/^-+|-+$/g,"").slice(0,80)||"item"}function W(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 Be(e){if(!Number.isFinite(e)||e<=0)return"0 B";const t=["B","KB","MB","GB"];let n=e,i=0;for(;n>=1024&&i<t.length-1;)n/=1024,i+=1;return`${n>=10||i===0?n.toFixed(0):n.toFixed(1)} ${t[i]}`}async function Je(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??R(n)})}function R(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 Le(e,t,n,i){const r=(await y("POST",`/api/client/jobs/${encodeURIComponent(e)}/permissions`,{machineId:t,request:n})).item.permission_id;for(;!i.aborted;){await N(z);const c=await y("GET",`/api/client/permissions/${encodeURIComponent(r)}`);if(c.item.status==="resolved"&&c.item.response){const u=c.item.response.outcome;return u.outcome==="selected"?{outcome:u}:{outcome:{outcome:"cancelled"}}}}return{outcome:{outcome:"cancelled"}}}async function We(e,t,n,i){const r=(await y("POST",`/api/client/jobs/${encodeURIComponent(e)}/permissions`,{machineId:t,request:{kind:"elicitation",...n}})).item.permission_id;for(;!i.aborted;){await N(z);const c=await y("GET",`/api/client/permissions/${encodeURIComponent(r)}`);if(c.item.status==="resolved"&&c.item.response){const u=c.item.response.outcome;return u.outcome==="submitted"?{outcome:u}:{outcome:{outcome:"cancelled"}}}}return{outcome:{outcome:"cancelled"}}}async function V(e,t){if(t.signal.aborted)return;const n=await y("GET",`/api/client/jobs/${encodeURIComponent(e)}`);if(Number(n.item.cancel_requested??0)===1){t.abort();return}const i=Ve(n.item);i&&t.abort(new D(e,i))}function Ve(e){const t=String(e.status||"").toLowerCase();return t&&!["claimed","running"].includes(t)?t:null}class D extends Error{constructor(t,n){super(`ACA Server already finalized job=${t} status=${n}`),this.name="ServerFinalizedJobError"}}function De(e){return e.aborted&&e.reason instanceof D}async function k(e,t,n,i,o){await y("POST",`/api/client/jobs/${encodeURIComponent(e)}/fail`,{machineId:t,error:n,result:i,...o?.invalidateAcpSession?{invalidateAcpSession:!0,invalidatedAcpSessionId:o.invalidatedAcpSessionId}:{}})}function N(e){return new Promise(t=>setTimeout(t,e))}function qe(e){return Math.min(1e4,500*2**Math.min(5,Math.max(0,e-1)))+Math.floor(Math.random()*250)}function He(e,t){return e.length<=t?e:`${e.slice(0,t)}...`}function O(e,t){return!e||typeof e!="object"||Array.isArray(e)?null:e[t]??null}export{Ve as serverFinalizedJobStatus,ut as startJobWorkerLoop};
6
+ ${o}`:o}function je(e,t){const n=[{type:"text",text:e}];for(const i of t)i.kind!=="image"||!i.mimeType.startsWith("image/")||n.push({type:"image",data:i.base64,mimeType:i.mimeType,uri:i.path});return n}async function Ee(e){if(e.attachments.length===0)return[];const t=M.join(e.cwd,".aca","attachments",V(e.sessionId),V(e.jobId));N.mkdirSync(t,{recursive:!0});const n=[];for(const[i,o]of e.attachments.entries()){const s=o.attachmentId?{mimeType:o.mimeType||"application/octet-stream",bytes:await te(`/api/client/attachments/${encodeURIComponent(o.attachmentId)}`)}:Me(o.dataUrl??""),l=o.mimeType||s.mimeType||"application/octet-stream",u=o.kind==="image"||l.startsWith("image/")?"image":"file",r=Oe(o.name||`attachment-${i+1}`,l),b=M.join(t,`${String(i+1).padStart(2,"0")}-${r}`);N.writeFileSync(b,s.bytes),n.push({name:o.name||r,mimeType:l,size:s.bytes.byteLength,kind:u,path:b,relativePath:M.relative(e.cwd,b),base64:s.bytes.toString("base64"),sha256:X.createHash("sha256").update(s.bytes).digest("hex")})}return n}function Me(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),i=e.slice(t+1),o=n.split(";").filter(Boolean),s=o[0]?.includes("/")?o[0]:"application/octet-stream";if(!o.includes("base64"))throw new Error("attachment dataUrl is not base64 encoded");return{mimeType:s,bytes:Buffer.from(i,"base64")}}function Oe(e,t){const n=M.basename(e).replace(/[<>:"/\\|?*\x00-\x1F]/g,"_").trim(),i=`attachment${Ne(t)}`;return(!n||n==="."||n===".."?i:n).slice(0,180)}function Ne(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 V(e){return e.replace(/[^a-zA-Z0-9._-]+/g,"-").replace(/^-+|-+$/g,"").slice(0,80)||"item"}function q(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 Fe(e){if(!Number.isFinite(e)||e<=0)return"0 B";const t=["B","KB","MB","GB"];let n=e,i=0;for(;n>=1024&&i<t.length-1;)n/=1024,i+=1;return`${n>=10||i===0?n.toFixed(0):n.toFixed(1)} ${t[i]}`}async function ze(e,t,n){!n.usedWorktree||!n.worktreeId||await S("POST",`/api/client/jobs/${encodeURIComponent(e)}/worktree`,{machineId:t,baseCommit:n.baseCommit??null,worktreePath:n.cwd,status:n.status??"active",raw:n.raw??$(n)})}function $(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 Ue(e,t,n,i){const s=(await S("POST",`/api/client/jobs/${encodeURIComponent(e)}/permissions`,{machineId:t,request:n})).item.permission_id;for(;!i.aborted;){await F(L);const l=await S("GET",`/api/client/permissions/${encodeURIComponent(s)}`);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 Je(e,t,n,i){const s=(await S("POST",`/api/client/jobs/${encodeURIComponent(e)}/permissions`,{machineId:t,request:{kind:"elicitation",...n}})).item.permission_id;for(;!i.aborted;){await F(L);const l=await S("GET",`/api/client/permissions/${encodeURIComponent(s)}`);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 G(e,t){if(t.signal.aborted)return;const n=await S("GET",`/api/client/jobs/${encodeURIComponent(e)}`);if(Number(n.item.cancel_requested??0)===1){t.abort();return}const i=Be(n.item);i&&t.abort(new K(e,i))}function Be(e){const t=String(e.status||"").toLowerCase();return t&&!["claimed","running"].includes(t)?t:null}class K extends Error{constructor(t,n){super(`ACA Server already finalized job=${t} status=${n}`),this.name="ServerFinalizedJobError"}}function Le(e){return e.aborted&&e.reason instanceof K}async function j(e,t,n,i,o){await S("POST",`/api/client/jobs/${encodeURIComponent(e)}/fail`,{machineId:t,error:n,result:i,...o?.invalidateAcpSession?{invalidateAcpSession:!0,invalidatedAcpSessionId:o.invalidatedAcpSessionId}:{}})}function F(e){return new Promise(t=>setTimeout(t,e))}function We(e){return Math.min(1e4,500*2**Math.min(5,Math.max(0,e-1)))+Math.floor(Math.random()*250)}function De(e,t){return e.length<=t?e:`${e.slice(0,t)}...`}function z(e,t){return!e||typeof e!="object"||Array.isArray(e)?null:e[t]??null}export{Be as serverFinalizedJobStatus,lt as startJobWorkerLoop};
package/dist/version.js CHANGED
@@ -1 +1 @@
1
- const o="0.1.58";export{o as AICA_VERSION};
1
+ const o="0.1.60";export{o as AICA_VERSION};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yuandc/aica",
3
- "version": "0.1.58",
3
+ "version": "0.1.60",
4
4
  "description": "AICA remote Agent worker",
5
5
  "author": "yuandc",
6
6
  "type": "module",
@@ -26,7 +26,7 @@
26
26
  "worker"
27
27
  ],
28
28
  "dependencies": {
29
- "@openai/codex": "0.156.1",
29
+ "@openai/codex": "0.159.3",
30
30
  "@xterm/addon-serialize": "^0.14.0",
31
31
  "@xterm/headless": "^6.0.0",
32
32
  "chalk": "^5.4.1",