@yuandc/aica 0.1.53 → 0.1.54
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,6 +1,4 @@
|
|
|
1
|
-
import{JsonLineRpcClient as $}from"../../client/json-rpc.js";import{startCodexAppServer as j}from"../codex/codex-process.js";import{CodexConnectionRecoveryTracker as L}from"../codex/connection-events.js";import{codexAcpSessionUpdatesFromNotification as x}from"../codex/events.js";import{CodexPlanDeltaState as
|
|
2
|
-
`);this.lineBuffer=t.pop()??"";for(const s of t){const
|
|
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"),r=Number(t.revision||0);if(!s||!o||!Number.isSafeInteger(r)||r<1)throw new Error("Invalid Codex managed provider runtime config");return this.managedProvider={baseUrl:s,apiKey:o,revision:r},{configured:!0,revision:r}}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=y(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=y(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),r=y(o);if(!r)throw new Error("Codex thread/fork did not return thread id");return this.currentSessionId=r,this.currentCwd=s,this.resetSubtaskState(o),this.captureThreadMetadata(o),this.sessionResponse(r)}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:[],r=this.requireRpc();this.currentSessionId&&this.currentSessionId!==s&&this.resetSubtaskState(),this.currentSessionId=s;const i=await this.maintainContext("before",{userRequest:this.previousUserText,assistantResponse:this.previousAssistantText,affectedFiles:[...this.previousAffectedFiles]},{allowRollover:!0});if(ue(i),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=ne(o),this.currentAssistantText="",this.currentAffectedFiles.clear(),this.connectionRecovery.reset(),this.agentMessagePhases.clear(),this.planDeltas.reset(),this.pendingSubtaskNotifications=[];let c;try{this.turnStartPromise=r.request("turn/start",{threadId:s,clientUserMessageId:`aca-acp-${Date.now()}-${Math.random().toString(16).slice(2)}`,input:oe(o),cwd:this.currentCwd,...re(this.config)},D()),c=await this.turnStartPromise;const u=String(c?.turn?.id??"");this.currentTurnId=u||null;const l=u?await this.waitForTurnCompleted(u,D()):c?.turn,h=await this.subtaskRouter.waitForIdle(me());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 I of this.currentAffectedFiles)this.previousAffectedFiles.add(I);const p=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:q(p)??de(this.latestTokenUsage),acaContextMaintenance:p,acaSubtaskDrain:h}}}catch(u){const l=this.connectionRecovery.fail(u);throw l&&this.notifySessionUpdate(l),u}finally{this.controlToolLoop.reset(),this.promptActive=!1,this.currentTurnId=null,this.turnStartPromise=null,this.pendingSubtaskNotifications=[]}}resetSubtaskState(e){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 r=this.requireRpc().request("thread/resume",{threadId:s,approvalsReviewer:"user",excludeTurns:!1},6e4).then(i=>{this.subscribedSubtaskThreadIds.add(s);const c=this.subtaskRouter.restoreFromSessionSnapshot(i,this.currentSessionId);t&&this.hydrateSubtaskHistory(i,e),c>0&&this.subscribeKnownSubtasks(t)}).catch(()=>{}).finally(()=>{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 X(this.turnStartPromise):null,o=this.currentTurnId||a(m(s,"turn"),"id");if(await this.rpc.request("turn/interrupt",{threadId:t,turnId:o},5e3),o){const r=this.pendingTurnCompletions.get(o);r&&(this.pendingTurnCompletions.delete(o),clearTimeout(r.timer),r.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=k(),r=await this.maintainContext("manual",{userRequest:this.previousUserText,assistantResponse:this.previousAssistantText,affectedFiles:[...this.previousAffectedFiles]},{force:!0,allowRollover:e.allowRollover===!0,compactTimeoutMs:ce(e,o.compactTimeoutMs)}),i=String(r.action||"");if(i==="compact_failed"||i==="rollover_failed"){const c=String(r.compactError||"Codex 上下文压缩未完成");throw new Error(`Codex 上下文压缩失败:${c}`)}return{sessionId:this.currentSessionId,maintenance:r,contextUsage:q(r)}}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 r=new Set([...this.previousAffectedFiles,...this.currentAffectedFiles]);return await this.rotateThread(s,{userRequest:this.currentPromptText||this.previousUserText,assistantResponse:this.currentAssistantText||this.previousAssistantText,affectedFiles:[...r]}),{...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=j(e,this.managedProvider),o=new $(s.child,{peerName:"Codex app-server",onNotification:(r,i)=>this.handleCodexNotification(r,i),onRequest:async(r,i)=>this.handleCodexRequest(r,i)});this.started=s,this.rpc=o,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(!J(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(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 r=this.connectionRecovery.beforeNotification(e,t);e==="turn/completed"&&this.resolveTurnCompleted(t);const i=e==="rawResponseItem/completed"?he(this.controlToolLoop,t):[],c=e==="rawResponse/completed"?le(t):e==="rawResponseItem/completed"?i.map(({update:u})=>u):x(e,t,{agentMessagePhases:this.agentMessagePhases,planDeltas:this.planDeltas});for(const u of i)this.maybeSteerControlToolLoop(u.event);for(const u of[...r,...c]){const l=P(u,e,t,this.agentMessagePhases);A(l)&&this.notifyControlToolProgress(),this.captureUpdateEvidence(l),this.notifySessionUpdate(l)}o>0&&this.flushPendingSubtaskNotifications()}handleSubtaskNotification(e,t,s){if(!this.promptActive)return;(e==="rawResponseItem/completed"||e==="item/completed")&&Q(d(d(t).item))&&this.notifyControlToolProgress(),this.rememberSubtaskItem(s.sourceThreadId,t),this.rpc?.refreshPendingRequestTimeout("turn/start");const o=this.subtaskAgentMessagePhases.get(s.sourceThreadId)??new Map;this.subtaskAgentMessagePhases.set(s.sourceThreadId,o);const r=this.subtaskPlanDeltas.get(s.sourceThreadId)??new C;this.subtaskPlanDeltas.set(s.sourceThreadId,r),this.captureAgentMessagePhase(e,t,o),this.subtaskRouter.observe(t,this.currentSessionId)>0&&this.subscribeKnownSubtasks(!0);const c=x(e,t,{agentMessagePhases:o,planDeltas:r}),u=R(t).turnId;for(const l of c){const h=P(l,e,t,o);if(!pe(h))continue;A(h)&&this.notifyControlToolProgress();const p=E(h,s,u);w(p,this.currentAffectedFiles),this.notifySessionUpdate(p)}}hydrateSubtaskHistory(e,t){if(!(!this.promptActive||this.hydratedSubtaskThreadIds.has(t.sourceThreadId))){this.hydratedSubtaskThreadIds.add(t.sourceThreadId);for(const s of V(e)){const o=N(t.sourceThreadId,s.itemId);if(this.seenSubtaskItemIds.has(o))continue;this.seenSubtaskItemIds.add(o);const r=E(s.update,t,s.sourceTurnId),i=d(r._meta);r._meta={...i,aca:{...d(i.aca),historyReplay:!0}},w(r,this.currentAffectedFiles),this.notifySessionUpdate(r)}}}rememberSubtaskItem(e,t){const s=d(t),o=d(s.item),r=a(o,"id")||a(s,"itemId");r&&this.seenSubtaskItemIds.add(N(e,r))}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})}async handleCodexRequest(e,t){if(O(e))return B(e,t,async s=>{const o=await this.requestParent("session/request_permission",{sessionId:this.currentSessionId,request:{...s,params:t,method:e}}),r=d(o),i=d(r.outcome);return i.outcome==="selected"&&typeof i.optionId=="string"?{outcome:{outcome:"selected",optionId:i.optionId}}:{outcome:{outcome:"cancelled"}}});if(fe(e)){const s=await this.requestParent("elicitation/create",{sessionId:this.currentSessionId,request:{...d(t),method:e}}),o=d(s),r=d(o.outcome);return r.outcome==="submitted"?{outcome:{outcome:"submitted",value:typeof r.value=="string"?r.value:""}}:{outcome:{outcome:"cancelled"}}}return{}}captureAgentMessagePhase(e,t,s=this.agentMessagePhases){if(e!=="item/started"&&e!=="item/completed")return;const o=m(t,"item");if(a(o,"type")!=="agentMessage")return;const r=a(o,"id");r&&s.set(r,a(o,"phase"))}waitForTurnCompleted(e,t){const s=this.completedTurns.get(e);return s?Promise.resolve(s):new Promise((o,r)=>{const i=setTimeout(()=>{this.pendingTurnCompletions.delete(e),r(new Error("Codex app-server request timed out while waiting for turn/completed"))},t);i.unref(),this.pendingTurnCompletions.set(e,{resolve:o,reject:r,timer:i})})}resolveTurnCompleted(e){const t=m(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.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((r,i)=>{const c=setTimeout(()=>{this.pendingParentRequests.delete(o),i(new Error(`ACP client request timed out: ${e}`))},s);c.unref(),this.pendingParentRequests.set(o,{resolve:r,reject:i,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=m(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=m(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=g(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+=g(e.content)),w(e,this.currentAffectedFiles)}captureThreadMetadata(e){const t=m(e,"thread");this.currentRolloutPath=a(t,"path")||null,this.threadTurnCount=Array.isArray(t.turns)?t.turns.length:0;const s=ae(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=k(),r=this.contextState(),i=v(r,o);if(!i.compact&&!s.force||!this.currentSessionId)return this.maintenanceResult({stage:e,action:"none",reasons:i.reasons,beforeState:r});const c=this.currentSessionId;this.notifyContextStatus("正在整理会话上下文",U(r)),this.maintenanceActive=!0;let u=!1,l="",h=r,p=i,I=!1,b="";try{const f=r.usage?.observedAtMs??0;await this.compactThread(c,s.compactTimeoutMs??o.compactTimeoutMs),await this.waitForTokenUsageAfter(f,2e3)}catch(f){u=!0,l=f instanceof Error?f.message:String(f)}try{if(e!=="manual"&&this.promptCancelled||this.shutdownPromise)throw new Error("Codex prompt cancelled");if(h=this.contextState(),p=v(h,o,{compactAttempted:!0,compactFailed:u}),p.rollover&&s.allowRollover!==!1){this.notifyContextStatus("正在创建轻量会话",p.reasons.join(", "));try{await this.rotateThread(c,t),I=!0}catch(f){b=f instanceof Error?f.message:String(f),this.notifyContextStatus("轻量会话创建失败",b)}}else u||this.notifyContextStatus("会话上下文已压缩",U(h))}finally{this.maintenanceActive=!1}return this.maintenanceResult({action:I?"rollover":b?"rollover_failed":u?"compact_failed":"compact",stage:e,reasons:p.reasons,beforeState:r,afterState:h,compactError:[l,b].filter(Boolean).join("; "),previousThreadId:c,rolloverRecommended:p.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 r=setTimeout(()=>{this.pendingCompaction?.timer===r&&(this.pendingCompaction=null),o(new Error("Codex thread compaction timed out"))},t);r.unref(),this.pendingCompaction={resolve:s,reject:o,timer:r},this.requireRpc().request("thread/compact/start",{threadId:e},t).catch(i=>{this.pendingCompaction?.timer===r&&(this.pendingCompaction=null,clearTimeout(r),o(i instanceof Error?i:new Error(String(i))))})})}async rotateThread(e,t){const s=z({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),r=y(o);if(!r)throw new Error("Codex rollover thread/start did not return thread id");this.sessionAliases.set(e,r),this.currentSessionId=r,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 ge(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:G(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(_(e))}maybeSteerControlToolLoop(e){const t=Z(e.detection.kind,this.controlToolPolicy),s=this.controlToolSteering.next(e,t.warningCalls);if(!s)return;const{threadId:o,turnId:r}=e.detection;!o||!r||this.requireRpc().request("turn/steer",{threadId:o,expectedTurnId:r,clientUserMessageId:`aca-loop-steer-${Date.now()}-${Math.random().toString(16).slice(2)}`,input:[{type:"text",text:s.text,text_elements:[]}]},6e4).then(()=>{this.notifySessionUpdate(M(e,s,{succeeded:!0}))}).catch(i=>{this.notifySessionUpdate(M(e,s,{succeeded:!1,error:i}))})}write(e){process.stdout.write(`${JSON.stringify(e)}
|
|
4
|
-
`)}}function
|
|
5
|
-
|
|
6
|
-
`)}function re(n){const e={};typeof n.model=="string"&&n.model&&(e.model=n.model),typeof n.approvalPolicy=="string"&&n.approvalPolicy&&(e.approvalPolicy=n.approvalPolicy),typeof n.sandbox=="string"&&n.sandbox&&(e.sandboxPolicy=ie(n.sandbox));const t=n.reasoning_effort??n.reasoningEffort??n.model_reasoning_effort??n.modelReasoningEffort??n.effort;return typeof t=="string"&&t&&(e.effort=t),e}function ie(n){switch(n){case"read-only":return{type:"readOnly",networkAccess:!1};case"workspace-write":return{type:"workspaceWrite",writableRoots:[],networkAccess:!1,excludeTmpdirEnvVar:!1,excludeSlashTmp:!1};case"danger-full-access":return{type:"dangerFullAccess"};default:throw new Error(`Unsupported Codex sandbox mode: ${n}`)}}function y(n){const e=m(n,"thread");return a(e,"id")||null}function m(n,e){const t=d(n);return d(t[e])}function d(n){return n&&typeof n=="object"&&!Array.isArray(n)?n:{}}function g(n){if(typeof n=="string")return n;if(Array.isArray(n))return n.map(g).filter(Boolean).join("");if(!n||typeof n!="object")return"";const e=d(n);for(const t of["text","message","content","output_text"]){const s=g(e[t]);if(s)return s}return""}function w(n,e){if(Array.isArray(n)){for(const r of n)w(r,e);return}const t=d(n),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 r of t.locations){const i=a(d(r),"path");i&&e.add(i)}for(const r of[t.content,t.update,t.rawInput,t.rawOutput])r&&r!==n&&w(r,e)}function ae(n){if(!Array.isArray(n)||n.length===0)return{userRequest:"",assistantResponse:"",affectedFiles:[]};const e=n.at(-1),t=new Set;w(e,t);let s="",o="";const r=i=>{if(Array.isArray(i)){for(const l of i)r(l);return}if(!i||typeof i!="object")return;const c=d(i),u=a(c,"type").toLowerCase();u==="usermessage"&&(s=g(c)||s),u==="agentmessage"&&(o=g(c)||o);for(const l of["items","content","input"])c[l]&&r(c[l])};return r(e),{userRequest:s,assistantResponse:o,affectedFiles:[...t]}}function ce(n,e){const t=S(n,"timeoutMs");return!Number.isFinite(t)||t<=0?e:Math.max(3e4,Math.min(59e4,t-1e4))}function q(n){const e=String(n.action||"none"),t=d(n.after),s=d(n.before),o=d(t.usage),r=d(s.usage),i=Object.keys(o).length>0?o:r,c=S(i,"contextWindow");if(e==="rollover")return{usedTokens:0,inputTokens:0,contextWindow:c,ratio:0,observedAtMs:Date.now(),state:"new_thread",maintenanceAction:e};if(Object.keys(i).length===0)return null;const u=S(i,"totalTokens"),l=S(i,"inputTokens");return{usedTokens:u||l,inputTokens:l,contextWindow:c,ratio:c>0?(u||l)/c:0,observedAtMs:S(i,"observedAtMs")||Date.now(),state:e==="compact"?"compacted":"active",maintenanceAction:e}}function de(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 U(n){const e=n.usage?`${n.usage.inputTokens} tokens`:"token unknown",t=n.rolloutBytes>0?`${Math.round(n.rolloutBytes/1024/1024)}MB rollout`:"rollout unknown";return`${e}, ${t}, ${n.turnCount} turns`}function ue(n){const e=String(n.action||"none");if(e!=="compact_failed"&&e!=="rollover_failed")return;const t=String(n.compactError||"Codex 上下文维护未完成");throw new Error(`Codex 自动上下文维护失败:${t}`)}function S(n,e){const t=d(n)[e];return typeof t=="number"&&Number.isFinite(t)?t:0}function le(n){return K(n)?[{sessionUpdate:"aca_codex_raw_response_usage",_meta:{codex:{rawResponse:n}}}]:[]}function he(n,e){const t=n.observeRawResponseItem(e);return t?[{event:t,update:_(t)}]:[]}function pe(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 E(n,e,t){const s=String(n.sessionUpdate||""),o={...n};if(s==="tool_call"||s==="tool_call_update"){const c=a(n,"toolCallId");c&&(o.toolCallId=F(e.sourceThreadId,c))}if(s==="plan"||s==="plan_update"||s==="plan_removed"){const c=a(n,"planId")||a(n,"plan_id")||a(n,"id")||"current";o.planId=F(e.sourceThreadId,c),delete o.plan_id}const r=d(n._meta),i=d(r.aca);return o._meta={...r,aca:{...i,subtask:{parentToolCallId:e.parentToolCallId,sourceThreadId:e.sourceThreadId,sourceTurnId:t,depth:e.depth}}},o}function F(n,e){return`subtask.${n}.${e}`}function N(n,e){return`${n}:${e}`}function a(n,e){const s=d(n)[e];return typeof s=="string"?s:""}function fe(n){const e=n.toLowerCase();return e.includes("elicitation")||e.includes("requestuserinput")||e.includes("request_user_input")||e.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 me(){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 T(n,e){return n.exitCode!==null||n.signalCode!==null?Promise.resolve(!0):new Promise(t=>{const s=setTimeout(()=>t(!1),e);s.unref(),n.once("close",()=>{clearTimeout(s),t(!0)})})}function ge(n){return new Promise(e=>setTimeout(e,n))}function we(){new se}const Se=process.argv[1]||"";/(?:^|[\\/])adapter\.(?:js|ts)$/.test(Se)&&we();export{we as runCodexAcpAdapter};
|
|
1
|
+
import{JsonLineRpcClient as $}from"../../client/json-rpc.js";import{startCodexAppServer as j}from"../codex/codex-process.js";import{CodexConnectionRecoveryTracker as L}from"../codex/connection-events.js";import{codexAcpSessionUpdatesFromNotification as x}from"../codex/events.js";import{CodexPlanDeltaState as y}from"../codex/plan-events.js";import{isCodexApprovalRequest as O,resolveCodexApprovalRequest as B}from"../codex/permissions.js";import{buildCodexContinuityInstructions as z,codexTokenUsageFromRawResponseCompleted as K,codexTokenUsageFromNotification as W,decideCodexContextMaintenance as v,readCodexContextMaintenancePolicy as k,rolloutFileSize as G}from"./context-maintenance.js";import{codexNotificationBelongsToCurrentTurn as J,codexNotificationRouting as R}from"./notification-routing.js";import{completedSubtaskHistoryUpdates as H}from"./subtask-history.js";import{CodexSubtaskRouter as V}from"./subtask-routing.js";import{waitForCancellableTurnStart as X}from"./cancellation.js";import{codexAcpUpdateIsMeaningfulProgress as A,codexRawItemIsMeaningfulProgress as Q,withCodexProgressMetadata as P}from"./progress-evidence.js";import{acpPromptText as Y,acpPromptToCodexInput as Z,turnConfigFromConfig as ee}from"./turn-input.js";import{CodexControlToolLoopDetector as te,codexControlToolLoopAcpUpdate as _,controlToolLoopThresholds as se,readCodexControlToolLoopPolicy as ne}from"./control-tool-loop.js";import{CodexControlToolSteeringLimiter as oe,codexControlToolSteeringAcpUpdate as M}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 V;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=ne();controlToolSteering=new oe(this.controlToolPolicy.steeringMaxAttempts);constructor(){this.controlToolLoop=new te({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 n=s.trim();if(!n)continue;let o;try{o=JSON.parse(n)}catch{continue}this.handleMessage(o)}}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,u(e.params));return}try{const n=await this.handleRequest(t,u(e.params));this.write({jsonrpc:"2.0",id:s,result:n})}catch(n){this.write({jsonrpc:"2.0",id:s,error:{code:-32603,message:n instanceof Error?n.message:String(n)}})}}}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=u(e.codexProvider);if(Object.keys(t).length===0)return this.managedProvider=null,{configured:!1};const s=a(t,"baseUrl"),n=a(t,"apiKey"),o=Number(t.revision||0);if(!s||!n||!Number.isSafeInteger(o)||o<1)throw new Error("Invalid Codex managed provider runtime config");return this.managedProvider={baseUrl:s,apiKey:n,revision:o},{configured:!0,revision:o}}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),n=b(s);if(!n)throw new Error("Codex thread/start did not return thread id");return this.currentSessionId=n,this.currentCwd=t,this.resetSubtaskState(),this.captureThreadMetadata(s),this.sessionResponse(n)}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 n=await this.requireRpc().request("thread/resume",{threadId:t,cwd:s,approvalsReviewer:"user",...this.codexConfig()},6e4);return this.currentSessionId=b(n)||t,this.currentCwd=s,this.resetSubtaskState(n),await this.subscribeKnownSubtasks(!1),this.captureThreadMetadata(n),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 n=await this.requireRpc().request("thread/fork",{threadId:t,cwd:s,approvalsReviewer:"user",ephemeral:!1,threadSource:"aca-codex-acp-fork",...this.codexConfig()},12e4),o=b(n);if(!o)throw new Error("Codex thread/fork did not return thread id");return this.currentSessionId=o,this.currentCwd=s,this.resetSubtaskState(n),this.captureThreadMetadata(n),this.sessionResponse(o)}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 n=Array.isArray(e.prompt)?e.prompt:[],o=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=Y(n),this.currentAssistantText="",this.currentAffectedFiles.clear(),this.connectionRecovery.reset(),this.agentMessagePhases.clear(),this.planDeltas.reset(),this.pendingSubtaskNotifications=[];let c;try{this.turnStartPromise=o.request("turn/start",{threadId:s,clientUserMessageId:`aca-acp-${Date.now()}-${Math.random().toString(16).slice(2)}`,input:Z(n),cwd:this.currentCwd,...ee(this.config)},D()),c=await this.turnStartPromise;const d=String(c?.turn?.id??"");this.currentTurnId=d||null;const l=d?await this.waitForTurnCompleted(d,D()):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 I of this.currentAffectedFiles)this.previousAffectedFiles.add(I);const p=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:q(p)??ce(this.latestTokenUsage),acaContextMaintenance:p,acaSubtaskDrain:h}}}catch(d){const l=this.connectionRecovery.fail(d);throw l&&this.notifySessionUpdate(l),d}finally{this.controlToolLoop.reset(),this.promptActive=!1,this.currentTurnId=null,this.turnStartPromise=null,this.pendingSubtaskNotifications=[]}}resetSubtaskState(e){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 n=this.pendingSubtaskSubscriptions.get(s);if(n)return n;const o=this.requireRpc().request("thread/resume",{threadId:s,approvalsReviewer:"user",excludeTurns:!1},6e4).then(r=>{this.subscribedSubtaskThreadIds.add(s);const c=this.subtaskRouter.restoreFromSessionSnapshot(r,this.currentSessionId);t&&this.hydrateSubtaskHistory(r,e),c>0&&this.subscribeKnownSubtasks(t)}).catch(()=>{}).finally(()=>{this.pendingSubtaskSubscriptions.delete(s)});return this.pendingSubtaskSubscriptions.set(s,o),o}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 X(this.turnStartPromise):null,n=this.currentTurnId||a(m(s,"turn"),"id");if(await this.rpc.request("turn/interrupt",{threadId:t,turnId:n},5e3),n){const o=this.pendingTurnCompletions.get(n);o&&(this.pendingTurnCompletions.delete(n),clearTimeout(o.timer),o.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 n=k(),o=await this.maintainContext("manual",{userRequest:this.previousUserText,assistantResponse:this.previousAssistantText,affectedFiles:[...this.previousAffectedFiles]},{force:!0,allowRollover:e.allowRollover===!0,compactTimeoutMs:ae(e,n.compactTimeoutMs)}),r=String(o.action||"");if(r==="compact_failed"||r==="rollover_failed"){const c=String(o.compactError||"Codex 上下文压缩未完成");throw new Error(`Codex 上下文压缩失败:${c}`)}return{sessionId:this.currentSessionId,maintenance:o,contextUsage:q(o)}}async recoverSession(e){const t=a(e,"sessionId")||this.currentSessionId,s=this.resolveSessionAlias(t);if(!s)throw new Error("sessionId is required");const n=a(e,"reason");if(n!=="low_output_loop"&&n!=="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 o=new Set([...this.previousAffectedFiles,...this.currentAffectedFiles]);return await this.rotateThread(s,{userRequest:this.currentPromptText||this.previousUserText,assistantResponse:this.currentAssistantText||this.previousAssistantText,affectedFiles:[...o]}),{...u(this.sessionResponse(this.currentSessionId)),previousSessionId:s,recoveryReason:n}}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=j(e,this.managedProvider),n=new $(s.child,{peerName:"Codex app-server",onNotification:(o,r)=>this.handleCodexNotification(o,r),onRequest:async(o,r)=>this.handleCodexRequest(o,r)});this.started=s,this.rpc=n,this.initialized=!1,await n.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(!J(t,this.currentSessionId,this.currentTurnId)){if(!this.promptActive||!s.threadId||s.threadId===this.currentSessionId)return;const d=this.subtaskRouter.routeFor(s.threadId);if(!d){this.bufferPendingSubtaskNotification(e,t,s.threadId);return}this.handleSubtaskNotification(e,t,d),this.flushPendingSubtaskNotifications();return}const n=this.subtaskRouter.observe(t,this.currentSessionId);if(n>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 o=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:d})=>d):x(e,t,{agentMessagePhases:this.agentMessagePhases,planDeltas:this.planDeltas});for(const d of r)this.maybeSteerControlToolLoop(d.event);for(const d of[...o,...c]){const l=P(d,e,t,this.agentMessagePhases);A(l)&&this.notifyControlToolProgress(),this.captureUpdateEvidence(l),this.notifySessionUpdate(l)}n>0&&this.flushPendingSubtaskNotifications()}handleSubtaskNotification(e,t,s){if(!this.promptActive)return;(e==="rawResponseItem/completed"||e==="item/completed")&&Q(u(u(t).item))&&this.notifyControlToolProgress(),this.rememberSubtaskItem(s.sourceThreadId,t),this.rpc?.refreshPendingRequestTimeout("turn/start");const n=this.subtaskAgentMessagePhases.get(s.sourceThreadId)??new Map;this.subtaskAgentMessagePhases.set(s.sourceThreadId,n);const o=this.subtaskPlanDeltas.get(s.sourceThreadId)??new y;this.subtaskPlanDeltas.set(s.sourceThreadId,o),this.captureAgentMessagePhase(e,t,n),this.subtaskRouter.observe(t,this.currentSessionId)>0&&this.subscribeKnownSubtasks(!0);const c=x(e,t,{agentMessagePhases:n,planDeltas:o}),d=R(t).turnId;for(const l of c){const h=P(l,e,t,n);if(!he(h))continue;A(h)&&this.notifyControlToolProgress();const p=E(h,s,d);w(p,this.currentAffectedFiles),this.notifySessionUpdate(p)}}hydrateSubtaskHistory(e,t){if(!(!this.promptActive||this.hydratedSubtaskThreadIds.has(t.sourceThreadId))){this.hydratedSubtaskThreadIds.add(t.sourceThreadId);for(const s of H(e)){const n=N(t.sourceThreadId,s.itemId);if(this.seenSubtaskItemIds.has(n))continue;this.seenSubtaskItemIds.add(n);const o=E(s.update,t,s.sourceTurnId),r=u(o._meta);o._meta={...r,aca:{...u(r.aca),historyReplay:!0}},w(o,this.currentAffectedFiles),this.notifySessionUpdate(o)}}}rememberSubtaskItem(e,t){const s=u(t),n=u(s.item),o=a(n,"id")||a(s,"itemId");o&&this.seenSubtaskItemIds.add(N(e,o))}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 n=this.subtaskRouter.routeFor(s.threadId);if(!n){t.push(s);continue}e=!0,this.handleSubtaskNotification(s.method,s.params,n)}this.pendingSubtaskNotifications=t}}notifySessionUpdate(e){this.notify("session/update",{sessionId:this.currentSessionId,update:e})}async handleCodexRequest(e,t){if(O(e))return B(e,t,async s=>{const n=await this.requestParent("session/request_permission",{sessionId:this.currentSessionId,request:{...s,params:t,method:e}}),o=u(n),r=u(o.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:{...u(t),method:e}}),n=u(s),o=u(n.outcome);return o.outcome==="submitted"?{outcome:{outcome:"submitted",value:typeof o.value=="string"?o.value:""}}:{outcome:{outcome:"cancelled"}}}return{}}captureAgentMessagePhase(e,t,s=this.agentMessagePhases){if(e!=="item/started"&&e!=="item/completed")return;const n=m(t,"item");if(a(n,"type")!=="agentMessage")return;const o=a(n,"id");o&&s.set(o,a(n,"phase"))}waitForTurnCompleted(e,t){const s=this.completedTurns.get(e);return s?Promise.resolve(s):new Promise((n,o)=>{const r=setTimeout(()=>{this.pendingTurnCompletions.delete(e),o(new Error("Codex app-server request timed out while waiting for turn/completed"))},t);r.unref(),this.pendingTurnCompletions.set(e,{resolve:n,reject:o,timer:r})})}resolveTurnCompleted(e){const t=m(e,"turn"),s=a(t,"id");if(!s)return;this.completedTurns.set(s,t);const n=this.pendingTurnCompletions.get(s);if(n){if(this.pendingTurnCompletions.delete(s),clearTimeout(n.timer),a(t,"status")==="failed"){n.reject(new Error(`Codex turn failed: ${JSON.stringify(t.error??{})}`));return}n.resolve(t)}}async shutdown(){return this.shutdownPromise?this.shutdownPromise:(this.shutdownPromise=this.performShutdown(),this.shutdownPromise)}async performShutdown(){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 n=this.nextParentRequestId++;return new Promise((o,r)=>{const c=setTimeout(()=>{this.pendingParentRequests.delete(n),r(new Error(`ACP client request timed out: ${e}`))},s);c.unref(),this.pendingParentRequests.set(n,{resolve:o,reject:r,timer:c}),this.write({jsonrpc:"2.0",id:n,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 n=u(e.error);s.reject(new Error(a(n,"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=m(e,"config");for(const[s,n]of Object.entries(t))(typeof n=="string"||typeof n=="boolean")&&(this.config[s]=n)}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=m(t,"item"),n=a(u(t),"threadId");a(s,"type")==="contextCompaction"&&(!n||n===this.currentSessionId)&&this.resolveCompaction(),a(s,"type")==="agentMessage"&&a(s,"phase")==="final_answer"&&(this.currentAssistantText=g(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+=g(e.content)),w(e,this.currentAffectedFiles)}captureThreadMetadata(e){const t=m(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 n of s.affectedFiles)this.previousAffectedFiles.add(n);this.latestTokenUsage=null}async maintainContext(e,t,s={}){const n=k(),o=this.contextState(),r=v(o,n);if(!r.compact&&!s.force||!this.currentSessionId)return this.maintenanceResult({stage:e,action:"none",reasons:r.reasons,beforeState:o});const c=this.currentSessionId;this.notifyContextStatus("正在整理会话上下文",U(o)),this.maintenanceActive=!0;let d=!1,l="",h=o,p=r,I=!1,C="";try{const f=o.usage?.observedAtMs??0;await this.compactThread(c,s.compactTimeoutMs??n.compactTimeoutMs),await this.waitForTokenUsageAfter(f,2e3)}catch(f){d=!0,l=f instanceof Error?f.message:String(f)}try{if(e!=="manual"&&this.promptCancelled||this.shutdownPromise)throw new Error("Codex prompt cancelled");if(h=this.contextState(),p=v(h,n,{compactAttempted:!0,compactFailed:d}),p.rollover&&s.allowRollover!==!1){this.notifyContextStatus("正在创建轻量会话",p.reasons.join(", "));try{await this.rotateThread(c,t),I=!0}catch(f){C=f instanceof Error?f.message:String(f),this.notifyContextStatus("轻量会话创建失败",C)}}else d||this.notifyContextStatus("会话上下文已压缩",U(h))}finally{this.maintenanceActive=!1}return this.maintenanceResult({action:I?"rollover":C?"rollover_failed":d?"compact_failed":"compact",stage:e,reasons:p.reasons,beforeState:o,afterState:h,compactError:[l,C].filter(Boolean).join("; "),previousThreadId:c,rolloverRecommended:p.rollover&&s.allowRollover===!1})}compactThread(e,t){return this.pendingCompaction?Promise.reject(new Error("Codex context compaction is already running")):new Promise((s,n)=>{const o=setTimeout(()=>{this.pendingCompaction?.timer===o&&(this.pendingCompaction=null),n(new Error("Codex thread compaction timed out"))},t);o.unref(),this.pendingCompaction={resolve:s,reject:n,timer:o},this.requireRpc().request("thread/compact/start",{threadId:e},t).catch(r=>{this.pendingCompaction?.timer===o&&(this.pendingCompaction=null,clearTimeout(o),n(r instanceof Error?r:new Error(String(r))))})})}async rotateThread(e,t){const s=z({previousThreadId:e,cwd:this.currentCwd,userRequest:t.userRequest,assistantResponse:t.assistantResponse,affectedFiles:t.affectedFiles}),n=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),o=b(n);if(!o)throw new Error("Codex rollover thread/start did not return thread id");this.sessionAliases.set(e,o),this.currentSessionId=o,this.resetSubtaskState(n),this.captureThreadMetadata(n)}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:G(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(_(e))}maybeSteerControlToolLoop(e){const t=se(e.detection.kind,this.controlToolPolicy),s=this.controlToolSteering.next(e,t.warningCalls);if(!s)return;const{threadId:n,turnId:o}=e.detection;!n||!o||this.requireRpc().request("turn/steer",{threadId:n,expectedTurnId:o,clientUserMessageId:`aca-loop-steer-${Date.now()}-${Math.random().toString(16).slice(2)}`,input:[{type:"text",text:s.text,text_elements:[]}]},6e4).then(()=>{this.notifySessionUpdate(M(e,s,{succeeded:!0}))}).catch(r=>{this.notifySessionUpdate(M(e,s,{succeeded:!1,error:r}))})}write(e){process.stdout.write(`${JSON.stringify(e)}
|
|
4
|
+
`)}}function b(i){const e=m(i,"thread");return a(e,"id")||null}function m(i,e){const t=u(i);return u(t[e])}function u(i){return i&&typeof i=="object"&&!Array.isArray(i)?i:{}}function g(i){if(typeof i=="string")return i;if(Array.isArray(i))return i.map(g).filter(Boolean).join("");if(!i||typeof i!="object")return"";const e=u(i);for(const t of["text","message","content","output_text"]){const s=g(e[t]);if(s)return s}return""}function w(i,e){if(Array.isArray(i)){for(const o of i)w(o,e);return}const t=u(i),s=a(t,"type").toLowerCase(),n=a(t,"path");if(n&&["diff","resource_link","content"].includes(s)&&e.add(n),Array.isArray(t.locations))for(const o of t.locations){const r=a(u(o),"path");r&&e.add(r)}for(const o of[t.content,t.update,t.rawInput,t.rawOutput])o&&o!==i&&w(o,e)}function re(i){if(!Array.isArray(i)||i.length===0)return{userRequest:"",assistantResponse:"",affectedFiles:[]};const e=i.at(-1),t=new Set;w(e,t);let s="",n="";const o=r=>{if(Array.isArray(r)){for(const l of r)o(l);return}if(!r||typeof r!="object")return;const c=u(r),d=a(c,"type").toLowerCase();d==="usermessage"&&(s=g(c)||s),d==="agentmessage"&&(n=g(c)||n);for(const l of["items","content","input"])c[l]&&o(c[l])};return o(e),{userRequest:s,assistantResponse:n,affectedFiles:[...t]}}function ae(i,e){const t=S(i,"timeoutMs");return!Number.isFinite(t)||t<=0?e:Math.max(3e4,Math.min(59e4,t-1e4))}function q(i){const e=String(i.action||"none"),t=u(i.after),s=u(i.before),n=u(t.usage),o=u(s.usage),r=Object.keys(n).length>0?n:o,c=S(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 d=S(r,"totalTokens"),l=S(r,"inputTokens");return{usedTokens:d||l,inputTokens:l,contextWindow:c,ratio:c>0?(d||l)/c:0,observedAtMs:S(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 U(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 S(i,e){const t=u(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:_(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 E(i,e,t){const s=String(i.sessionUpdate||""),n={...i};if(s==="tool_call"||s==="tool_call_update"){const c=a(i,"toolCallId");c&&(n.toolCallId=F(e.sourceThreadId,c))}if(s==="plan"||s==="plan_update"||s==="plan_removed"){const c=a(i,"planId")||a(i,"plan_id")||a(i,"id")||"current";n.planId=F(e.sourceThreadId,c),delete n.plan_id}const o=u(i._meta),r=u(o.aca);return n._meta={...o,aca:{...r,subtask:{parentToolCallId:e.parentToolCallId,sourceThreadId:e.sourceThreadId,sourceTurnId:t,depth:e.depth}}},n}function F(i,e){return`subtask.${i}.${e}`}function N(i,e){return`${i}:${e}`}function a(i,e){const s=u(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 D(){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};
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
function l(e){return e.map(t=>{const r=a(t);if(r.type==="image"){const n=o(r,"uri");if(n)return{type:"localImage",path:n};const s=o(r,"data");return{type:"image",url:`data:${o(r,"mimeType")||"image/png"};base64,${s}`}}return{type:"text",text:o(r,"text"),text_elements:[]}})}function d(e){return e.map(t=>o(a(t),"text")).filter(Boolean).join(`
|
|
2
|
+
|
|
3
|
+
`)}function f(e){const t={};typeof e.model=="string"&&e.model&&(t.model=e.model),typeof e.approvalPolicy=="string"&&e.approvalPolicy&&(t.approvalPolicy=e.approvalPolicy),typeof e.sandbox=="string"&&e.sandbox&&(t.sandboxPolicy=p(e.sandbox));const r=e.reasoning_effort??e.reasoningEffort??e.model_reasoning_effort??e.modelReasoningEffort??e.effort;return typeof r=="string"&&r&&(t.effort=r),t}function p(e){switch(e){case"read-only":return{type:"readOnly",networkAccess:!1};case"workspace-write":return{type:"workspaceWrite",writableRoots:[],networkAccess:!1,excludeTmpdirEnvVar:!1,excludeSlashTmp:!1};case"danger-full-access":return{type:"dangerFullAccess"};default:throw new Error(`Unsupported Codex sandbox mode: ${e}`)}}function a(e){return e&&typeof e=="object"&&!Array.isArray(e)?e:{}}function o(e,t){return typeof e[t]=="string"?e[t]:""}export{d as acpPromptText,l as acpPromptToCodexInput,f as turnConfigFromConfig};
|
package/dist/version.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
const o="0.1.
|
|
1
|
+
const o="0.1.54";export{o as AICA_VERSION};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yuandc/aica",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.54",
|
|
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.
|
|
29
|
+
"@openai/codex": "0.156.1",
|
|
30
30
|
"chalk": "^5.4.1",
|
|
31
31
|
"commander": "^12.1.0",
|
|
32
32
|
"diff": "^9.0.0",
|