@yuandc/aica 0.1.46 → 0.1.47

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- import{spawn as i}from"node:child_process";import{acpChildEnvironment as s}from"../../../core/process-identity.js";import{resolveCodexPath as _}from"./codex-path.js";import{resolveCodexPath as A}from"./codex-path.js";const d=["The `/root` entry returned by `list_agents` is the current main agent, not a child agent to wait for.","Call `wait_agent` only after spawning a child agent and only while at least one child agent is still running.","If no child agents are running, continue the task or answer the user; do not poll `list_agents` or `wait_agent`."].join(" "),o=["-c","features.sleep_tool=false","-c",`features.multi_agent_v2.multi_agent_mode_hint_text=${JSON.stringify(d)}`];function m(e,n=null){const t=_(),a=l(n),r=i(t,a,{cwd:e,env:s(t,a,{CODEX_PATH:t,...n?{ACA_CODEX_PROVIDER_KEY:n.apiKey}:{}}),stdio:["pipe","pipe","pipe"],windowsHide:!0});return r.stdout.setEncoding("utf8"),r.stderr.setEncoding("utf8"),{command:t,args:a,child:r}}function l(e){return e?[...o,"-c",'model_provider="aca_managed"',"-c",'model_providers.aca_managed.name="ACA Managed"',"-c",`model_providers.aca_managed.base_url=${JSON.stringify(e.baseUrl)}`,"-c",'model_providers.aca_managed.env_key="ACA_CODEX_PROVIDER_KEY"',"-c",'model_providers.aca_managed.wire_api="responses"',"-c","model_providers.aca_managed.requires_openai_auth=false","app-server","--stdio"]:[...o,"app-server","--stdio"]}export{l as codexAppServerArgs,A as resolveCodexPath,m as startCodexAppServer};
1
+ import{spawn as p}from"node:child_process";import{acpChildEnvironment as n}from"../../../core/process-identity.js";import{resolveCodexPath as d}from"./codex-path.js";import{resolveCodexPath as f}from"./codex-path.js";const t=["-c","features.sleep_tool=false"];function l(e,o=null){const r=d(),a=i(o),s=p(r,a,{cwd:e,env:n(r,a,{CODEX_PATH:r,...o?{ACA_CODEX_PROVIDER_KEY:o.apiKey}:{}}),stdio:["pipe","pipe","pipe"],windowsHide:!0});return s.stdout.setEncoding("utf8"),s.stderr.setEncoding("utf8"),{command:r,args:a,child:s}}function i(e){return e?[...t,"-c",'model_provider="aca_managed"',"-c",'model_providers.aca_managed.name="ACA Managed"',"-c",`model_providers.aca_managed.base_url=${JSON.stringify(e.baseUrl)}`,"-c",'model_providers.aca_managed.env_key="ACA_CODEX_PROVIDER_KEY"',"-c",'model_providers.aca_managed.wire_api="responses"',"-c","model_providers.aca_managed.requires_openai_auth=false","app-server","--stdio"]:[...t,"app-server","--stdio"]}export{i as codexAppServerArgs,f as resolveCodexPath,l as startCodexAppServer};
@@ -1,6 +1,6 @@
1
- import{JsonLineRpcClient as F}from"../../client/json-rpc.js";import{startCodexAppServer as N}from"../codex/codex-process.js";import{CodexConnectionRecoveryTracker as $}from"../codex/connection-events.js";import{codexAcpSessionUpdatesFromNotification as x}from"../codex/events.js";import{CodexPlanDeltaState as T}from"../codex/plan-events.js";import{isCodexApprovalRequest as j,resolveCodexApprovalRequest as D}from"../codex/permissions.js";import{buildCodexContinuityInstructions as O,codexTokenUsageFromRawResponseCompleted as L,codexTokenUsageFromNotification as B,decideCodexContextMaintenance as v,readCodexContextMaintenancePolicy as k,rolloutFileSize as z}from"./context-maintenance.js";import{codexNotificationBelongsToCurrentTurn as K,codexNotificationRouting as R}from"./notification-routing.js";import{completedSubtaskHistoryUpdates as W}from"./subtask-history.js";import{CodexSubtaskRouter as G}from"./subtask-routing.js";import{waitForCancellableTurnStart as J}from"./cancellation.js";import{CodexControlToolLoopDetector as V,codexControlToolLoopAcpUpdate as A}from"./control-tool-loop.js";class H{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 T;subtaskRouter=new G;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 $;latestTokenUsage=null;currentRolloutPath=null;threadTurnCount=0;currentPromptText="";currentAssistantText="";previousUserText="";previousAssistantText="";previousAffectedFiles=new Set;maintenanceActive=!1;pendingCompaction=null;shutdownPromise=null;managedProvider=null;controlToolLoop=new V;constructor(){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"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=y(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=y(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=y(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 i=await this.maintainContext("before",{userRequest:this.previousUserText,assistantResponse:this.previousAssistantText,affectedFiles:[...this.previousAffectedFiles]},{allowRollover:!0});if(ne(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.currentPromptText=X(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:Q(n),cwd:this.currentCwd,...Y(this.config)},E()),c=await this.turnStartPromise;const d=String(c?.turn?.id??"");this.currentTurnId=d||null;const l=d?await this.waitForTurnCompleted(d,E()):c?.turn;if(this.promptCancelled)throw new Error("Codex prompt cancelled");this.threadTurnCount+=1,this.previousUserText=this.currentPromptText,this.previousAssistantText=this.currentAssistantText,this.previousAffectedFiles.clear();for(const f of this.currentAffectedFiles)this.previousAffectedFiles.add(f);const h=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:P(h)??se(this.latestTokenUsage),acaContextMaintenance: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(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,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;const s=this.turnStartPromise?await J(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:te(e,n.compactTimeoutMs)}),i=String(o.action||"");if(i==="compact_failed"||i==="rollover_failed"){const c=String(o.compactError||"Codex 上下文压缩未完成");throw new Error(`Codex 上下文压缩失败:${c}`)}return{sessionId:this.currentSessionId,maintenance:o,contextUsage:P(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=N(e,this.managedProvider),n=new F(s.child,{peerName:"Codex app-server",onNotification:(o,i)=>this.handleCodexNotification(o,i),onRequest:async(o,i)=>this.handleCodexRequest(o,i)});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 C(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(!K(t,this.currentSessionId,this.currentTurnId)){if(!this.promptActive||!s.threadId||s.threadId===this.currentSessionId)return;const c=this.subtaskRouter.routeFor(s.threadId);if(!c){this.bufferPendingSubtaskNotification(e,t,s.threadId);return}this.handleSubtaskNotification(e,t,c),this.flushPendingSubtaskNotifications();return}const n=this.subtaskRouter.observe(t,this.currentSessionId);if(n>0&&this.notifyControlToolProgress(),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 i=e==="rawResponse/completed"?re(t):e==="rawResponseItem/completed"?oe(this.controlToolLoop,t):x(e,t,{agentMessagePhases:this.agentMessagePhases,planDeltas:this.planDeltas});for(const c of[...o,...i])this.captureUpdateEvidence(c),this.notifySessionUpdate(c);n>0&&this.flushPendingSubtaskNotifications()}handleSubtaskNotification(e,t,s){if(!this.promptActive)return;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 T;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){if(!ie(l))continue;const h=q(l,s,d);w(h,this.currentAffectedFiles),this.notifySessionUpdate(h)}}hydrateSubtaskHistory(e,t){if(!(!this.promptActive||this.hydratedSubtaskThreadIds.has(t.sourceThreadId))){this.hydratedSubtaskThreadIds.add(t.sourceThreadId);for(const s of W(e)){const n=U(t.sourceThreadId,s.itemId);if(this.seenSubtaskItemIds.has(n))continue;this.seenSubtaskItemIds.add(n);const o=q(s.update,t,s.sourceTurnId);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(U(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(j(e))return D(e,t,async s=>{const n=await this.requestParent("session/request_permission",{sessionId:this.currentSessionId,request:{...s,params:t,method:e}}),o=u(n),i=u(o.outcome);return i.outcome==="selected"&&typeof i.optionId=="string"?{outcome:{outcome:"selected",optionId:i.optionId}}:{outcome:{outcome:"cancelled"}}});if(ae(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 i=setTimeout(()=>{this.pendingTurnCompletions.delete(e),o(new Error("Codex app-server request timed out while waiting for turn/completed"))},t);i.unref(),this.pendingTurnCompletions.set(e,{resolve:n,reject:o,timer:i})})}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 C(e,2e3)&&(e.kill("SIGTERM"),!await C(e,1e3)&&(e.kill("SIGKILL"),await C(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,i)=>{const c=setTimeout(()=>{this.pendingParentRequests.delete(n),i(new Error(`ACP client request timed out: ${e}`))},s);c.unref(),this.pendingParentRequests.set(n,{resolve:o,reject:i,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=B(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=ee(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(),i=v(o,n);if(!i.compact&&!s.force||!this.currentSessionId)return this.maintenanceResult({stage:e,action:"none",reasons:i.reasons,beforeState:o});const c=this.currentSessionId;this.notifyContextStatus("正在整理会话上下文",_(o)),this.maintenanceActive=!0;let d=!1,l="",h=o,f=i,I=!1,b="";try{const p=o.usage?.observedAtMs??0;await this.compactThread(c,s.compactTimeoutMs??n.compactTimeoutMs),await this.waitForTokenUsageAfter(p,2e3)}catch(p){d=!0,l=p instanceof Error?p.message:String(p)}try{if(e!=="manual"&&this.promptCancelled||this.shutdownPromise)throw new Error("Codex prompt cancelled");if(h=this.contextState(),f=v(h,n,{compactAttempted:!0,compactFailed:d}),f.rollover&&s.allowRollover!==!1){this.notifyContextStatus("正在创建轻量会话",f.reasons.join(", "));try{await this.rotateThread(c,t),I=!0}catch(p){b=p instanceof Error?p.message:String(p),this.notifyContextStatus("轻量会话创建失败",b)}}else d||this.notifyContextStatus("会话上下文已压缩",_(h))}finally{this.maintenanceActive=!1}return this.maintenanceResult({action:I?"rollover":b?"rollover_failed":d?"compact_failed":"compact",stage:e,reasons:f.reasons,beforeState:o,afterState:h,compactError:[l,b].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,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(i=>{this.pendingCompaction?.timer===o&&(this.pendingCompaction=null,clearTimeout(o),n(i instanceof Error?i:new Error(String(i))))})})}async rotateThread(e,t){const s=O({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=y(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 ce(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(A(e))}write(e){process.stdout.write(`${JSON.stringify(e)}
4
- `)}}function Q(r){return r.map(e=>{const t=u(e);if(t.type==="image"){const s=a(t,"uri");if(s)return{type:"localImage",path:s};const n=a(t,"data");return{type:"image",url:`data:${a(t,"mimeType")||"image/png"};base64,${n}`}}return{type:"text",text:a(t,"text"),text_elements:[]}})}function X(r){return r.map(e=>a(u(e),"text")).filter(Boolean).join(`
1
+ import{JsonLineRpcClient as F}from"../../client/json-rpc.js";import{startCodexAppServer as N}from"../codex/codex-process.js";import{CodexConnectionRecoveryTracker as D}from"../codex/connection-events.js";import{codexAcpSessionUpdatesFromNotification as x}from"../codex/events.js";import{CodexPlanDeltaState as T}from"../codex/plan-events.js";import{isCodexApprovalRequest as $,resolveCodexApprovalRequest as j}from"../codex/permissions.js";import{buildCodexContinuityInstructions as O,codexTokenUsageFromRawResponseCompleted as L,codexTokenUsageFromNotification as B,decideCodexContextMaintenance as v,readCodexContextMaintenancePolicy as k,rolloutFileSize as z}from"./context-maintenance.js";import{codexNotificationBelongsToCurrentTurn as K,codexNotificationRouting as R}from"./notification-routing.js";import{completedSubtaskHistoryUpdates as W}from"./subtask-history.js";import{CodexSubtaskRouter as G}from"./subtask-routing.js";import{waitForCancellableTurnStart as J}from"./cancellation.js";import{CodexControlToolLoopDetector as V,codexControlToolLoopAcpUpdate as A,readCodexControlToolLoopPolicy as H}from"./control-tool-loop.js";class 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 T;subtaskRouter=new G;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 D;latestTokenUsage=null;currentRolloutPath=null;threadTurnCount=0;currentPromptText="";currentAssistantText="";previousUserText="";previousAssistantText="";previousAffectedFiles=new Set;maintenanceActive=!1;pendingCompaction=null;shutdownPromise=null;managedProvider=null;controlToolLoop;constructor(){const e=H();this.controlToolLoop=new V({hasActiveSubtasks:()=>this.subtaskRouter.hasActiveSubtasks(),shortSleepMaxDurationMs:e.shortSleepMaxDurationMs}),process.stdin.setEncoding("utf8"),process.stdin.on("data",s=>this.handleInput(String(s)));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(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 r;try{r=JSON.parse(n)}catch{continue}this.handleMessage(r)}}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"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"),r=Number(t.revision||0);if(!s||!n||!Number.isSafeInteger(r)||r<1)throw new Error("Invalid Codex managed provider runtime config");return this.managedProvider={baseUrl:s,apiKey:n,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),n=C(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=C(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),r=C(n);if(!r)throw new Error("Codex thread/fork did not return thread id");return this.currentSessionId=r,this.currentCwd=s,this.resetSubtaskState(n),this.captureThreadMetadata(n),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 n=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(oe(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.currentPromptText=Y(n),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:Q(n),cwd:this.currentCwd,...Z(this.config)},E()),c=await this.turnStartPromise;const d=String(c?.turn?.id??"");this.currentTurnId=d||null;const l=d?await this.waitForTurnCompleted(d,E()):c?.turn,h=await this.subtaskRouter.waitForIdle(ue());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 b of this.currentAffectedFiles)this.previousAffectedFiles.add(b);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:P(f)??ne(this.latestTokenUsage),acaContextMaintenance:f,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 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 J(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 r=this.pendingTurnCompletions.get(n);r&&(this.pendingTurnCompletions.delete(n),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 n=k(),r=await this.maintainContext("manual",{userRequest:this.previousUserText,assistantResponse:this.previousAssistantText,affectedFiles:[...this.previousAffectedFiles]},{force:!0,allowRollover:e.allowRollover===!0,compactTimeoutMs:se(e,n.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:P(r)}}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=N(e,this.managedProvider),n=new F(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=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 y(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(!K(t,this.currentSessionId,this.currentTurnId)){if(!this.promptActive||!s.threadId||s.threadId===this.currentSessionId)return;const c=this.subtaskRouter.routeFor(s.threadId);if(!c){this.bufferPendingSubtaskNotification(e,t,s.threadId);return}this.handleSubtaskNotification(e,t,c),this.flushPendingSubtaskNotifications();return}const n=this.subtaskRouter.observe(t,this.currentSessionId);if(n>0&&this.notifyControlToolProgress(),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 r=this.connectionRecovery.beforeNotification(e,t);e==="turn/completed"&&this.resolveTurnCompleted(t);const i=e==="rawResponse/completed"?re(t):e==="rawResponseItem/completed"?ie(this.controlToolLoop,t):x(e,t,{agentMessagePhases:this.agentMessagePhases,planDeltas:this.planDeltas});for(const c of[...r,...i])this.captureUpdateEvidence(c),this.notifySessionUpdate(c);n>0&&this.flushPendingSubtaskNotifications()}handleSubtaskNotification(e,t,s){if(!this.promptActive)return;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 r=this.subtaskPlanDeltas.get(s.sourceThreadId)??new T;this.subtaskPlanDeltas.set(s.sourceThreadId,r),this.captureAgentMessagePhase(e,t,n),this.subtaskRouter.observe(t,this.currentSessionId)>0&&this.subscribeKnownSubtasks(!0);const c=x(e,t,{agentMessagePhases:n,planDeltas:r}),d=R(t).turnId;for(const l of c){if(!ae(l))continue;const h=M(l,s,d);w(h,this.currentAffectedFiles),this.notifySessionUpdate(h)}}hydrateSubtaskHistory(e,t){if(!(!this.promptActive||this.hydratedSubtaskThreadIds.has(t.sourceThreadId))){this.hydratedSubtaskThreadIds.add(t.sourceThreadId);for(const s of W(e)){const n=U(t.sourceThreadId,s.itemId);if(this.seenSubtaskItemIds.has(n))continue;this.seenSubtaskItemIds.add(n);const r=M(s.update,t,s.sourceTurnId);w(r,this.currentAffectedFiles),this.notifySessionUpdate(r)}}}rememberSubtaskItem(e,t){const s=u(t),n=u(s.item),r=a(n,"id")||a(s,"itemId");r&&this.seenSubtaskItemIds.add(U(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 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($(e))return j(e,t,async s=>{const n=await this.requestParent("session/request_permission",{sessionId:this.currentSessionId,request:{...s,params:t,method:e}}),r=u(n),i=u(r.outcome);return i.outcome==="selected"&&typeof i.optionId=="string"?{outcome:{outcome:"selected",optionId:i.optionId}}:{outcome:{outcome:"cancelled"}}});if(ce(e)){const s=await this.requestParent("elicitation/create",{sessionId:this.currentSessionId,request:{...u(t),method:e}}),n=u(s),r=u(n.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 n=m(t,"item");if(a(n,"type")!=="agentMessage")return;const r=a(n,"id");r&&s.set(r,a(n,"phase"))}waitForTurnCompleted(e,t){const s=this.completedTurns.get(e);return s?Promise.resolve(s):new Promise((n,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:n,reject:r,timer:i})})}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 y(e,2e3)&&(e.kill("SIGTERM"),!await y(e,1e3)&&(e.kill("SIGKILL"),await y(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((r,i)=>{const c=setTimeout(()=>{this.pendingParentRequests.delete(n),i(new Error(`ACP client request timed out: ${e}`))},s);c.unref(),this.pendingParentRequests.set(n,{resolve:r,reject:i,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=B(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=te(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(),r=this.contextState(),i=v(r,n);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("正在整理会话上下文",_(r)),this.maintenanceActive=!0;let d=!1,l="",h=r,f=i,b=!1,I="";try{const p=r.usage?.observedAtMs??0;await this.compactThread(c,s.compactTimeoutMs??n.compactTimeoutMs),await this.waitForTokenUsageAfter(p,2e3)}catch(p){d=!0,l=p instanceof Error?p.message:String(p)}try{if(e!=="manual"&&this.promptCancelled||this.shutdownPromise)throw new Error("Codex prompt cancelled");if(h=this.contextState(),f=v(h,n,{compactAttempted:!0,compactFailed:d}),f.rollover&&s.allowRollover!==!1){this.notifyContextStatus("正在创建轻量会话",f.reasons.join(", "));try{await this.rotateThread(c,t),b=!0}catch(p){I=p instanceof Error?p.message:String(p),this.notifyContextStatus("轻量会话创建失败",I)}}else d||this.notifyContextStatus("会话上下文已压缩",_(h))}finally{this.maintenanceActive=!1}return this.maintenanceResult({action:b?"rollover":I?"rollover_failed":d?"compact_failed":"compact",stage:e,reasons:f.reasons,beforeState:r,afterState:h,compactError:[l,I].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,n)=>{const r=setTimeout(()=>{this.pendingCompaction?.timer===r&&(this.pendingCompaction=null),n(new Error("Codex thread compaction timed out"))},t);r.unref(),this.pendingCompaction={resolve:s,reject:n,timer:r},this.requireRpc().request("thread/compact/start",{threadId:e},t).catch(i=>{this.pendingCompaction?.timer===r&&(this.pendingCompaction=null,clearTimeout(r),n(i instanceof Error?i:new Error(String(i))))})})}async rotateThread(e,t){const s=O({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),r=C(n);if(!r)throw new Error("Codex rollover thread/start did not return thread id");this.sessionAliases.set(e,r),this.currentSessionId=r,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 de(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(A(e))}write(e){process.stdout.write(`${JSON.stringify(e)}
4
+ `)}}function Q(o){return o.map(e=>{const t=u(e);if(t.type==="image"){const s=a(t,"uri");if(s)return{type:"localImage",path:s};const n=a(t,"data");return{type:"image",url:`data:${a(t,"mimeType")||"image/png"};base64,${n}`}}return{type:"text",text:a(t,"text"),text_elements:[]}})}function Y(o){return o.map(e=>a(u(e),"text")).filter(Boolean).join(`
5
5
 
6
- `)}function Y(r){const e={};typeof r.model=="string"&&r.model&&(e.model=r.model),typeof r.approvalPolicy=="string"&&r.approvalPolicy&&(e.approvalPolicy=r.approvalPolicy),typeof r.sandbox=="string"&&r.sandbox&&(e.sandboxPolicy=Z(r.sandbox));const t=r.reasoning_effort??r.reasoningEffort??r.model_reasoning_effort??r.modelReasoningEffort??r.effort;return typeof t=="string"&&t&&(e.effort=t),e}function Z(r){switch(r){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: ${r}`)}}function y(r){const e=m(r,"thread");return a(e,"id")||null}function m(r,e){const t=u(r);return u(t[e])}function u(r){return r&&typeof r=="object"&&!Array.isArray(r)?r:{}}function g(r){if(typeof r=="string")return r;if(Array.isArray(r))return r.map(g).filter(Boolean).join("");if(!r||typeof r!="object")return"";const e=u(r);for(const t of["text","message","content","output_text"]){const s=g(e[t]);if(s)return s}return""}function w(r,e){if(Array.isArray(r)){for(const o of r)w(o,e);return}const t=u(r),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 i=a(u(o),"path");i&&e.add(i)}for(const o of[t.content,t.update,t.rawInput,t.rawOutput])o&&o!==r&&w(o,e)}function ee(r){if(!Array.isArray(r)||r.length===0)return{userRequest:"",assistantResponse:"",affectedFiles:[]};const e=r.at(-1),t=new Set;w(e,t);let s="",n="";const o=i=>{if(Array.isArray(i)){for(const l of i)o(l);return}if(!i||typeof i!="object")return;const c=u(i),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 te(r,e){const t=S(r,"timeoutMs");return!Number.isFinite(t)||t<=0?e:Math.max(3e4,Math.min(59e4,t-1e4))}function P(r){const e=String(r.action||"none"),t=u(r.after),s=u(r.before),n=u(t.usage),o=u(s.usage),i=Object.keys(n).length>0?n:o,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 d=S(i,"totalTokens"),l=S(i,"inputTokens");return{usedTokens:d||l,inputTokens:l,contextWindow:c,ratio:c>0?(d||l)/c:0,observedAtMs:S(i,"observedAtMs")||Date.now(),state:e==="compact"?"compacted":"active",maintenanceAction:e}}function se(r){return r?{usedTokens:r.totalTokens||r.inputTokens,inputTokens:r.inputTokens,contextWindow:r.contextWindow,ratio:r.contextWindow>0?(r.totalTokens||r.inputTokens)/r.contextWindow:0,observedAtMs:r.observedAtMs,state:"active",maintenanceAction:null}:null}function _(r){const e=r.usage?`${r.usage.inputTokens} tokens`:"token unknown",t=r.rolloutBytes>0?`${Math.round(r.rolloutBytes/1024/1024)}MB rollout`:"rollout unknown";return`${e}, ${t}, ${r.turnCount} turns`}function ne(r){const e=String(r.action||"none");if(e!=="compact_failed"&&e!=="rollover_failed")return;const t=String(r.compactError||"Codex 上下文维护未完成");throw new Error(`Codex 自动上下文维护失败:${t}`)}function S(r,e){const t=u(r)[e];return typeof t=="number"&&Number.isFinite(t)?t:0}function re(r){return L(r)?[{sessionUpdate:"aca_codex_raw_response_usage",_meta:{codex:{rawResponse:r}}}]:[]}function oe(r,e){const t=r.observeRawResponseItem(e);return t?[A(t)]:[]}function ie(r){return["agent_message_chunk","agent_progress_chunk","agent_thought_chunk","tool_call","tool_call_update","plan","plan_update","plan_removed"].includes(String(r.sessionUpdate||""))}function q(r,e,t){const s=String(r.sessionUpdate||""),n={...r};if(s==="tool_call"||s==="tool_call_update"){const c=a(r,"toolCallId");c&&(n.toolCallId=M(e.sourceThreadId,c))}if(s==="plan"||s==="plan_update"||s==="plan_removed"){const c=a(r,"planId")||a(r,"plan_id")||a(r,"id")||"current";n.planId=M(e.sourceThreadId,c),delete n.plan_id}const o=u(r._meta),i=u(o.aca);return n._meta={...o,aca:{...i,subtask:{parentToolCallId:e.parentToolCallId,sourceThreadId:e.sourceThreadId,sourceTurnId:t,depth:e.depth}}},n}function M(r,e){return`subtask.${r}.${e}`}function U(r,e){return`${r}:${e}`}function a(r,e){const s=u(r)[e];return typeof s=="string"?s:""}function ae(r){const e=r.toLowerCase();return e.includes("elicitation")||e.includes("requestuserinput")||e.includes("request_user_input")||e.includes("input/request")}function E(){const r=Number.parseInt(process.env.ACA_ACP_PROMPT_TIMEOUT_MS??"",10);return Number.isInteger(r)&&r>=3e4?r:360*60*1e3}function C(r,e){return r.exitCode!==null||r.signalCode!==null?Promise.resolve(!0):new Promise(t=>{const s=setTimeout(()=>t(!1),e);s.unref(),r.once("close",()=>{clearTimeout(s),t(!0)})})}function ce(r){return new Promise(e=>setTimeout(e,r))}function ue(){new H}const de=process.argv[1]||"";/(?:^|[\\/])adapter\.(?:js|ts)$/.test(de)&&ue();export{ue as runCodexAcpAdapter};
6
+ `)}function Z(o){const e={};typeof o.model=="string"&&o.model&&(e.model=o.model),typeof o.approvalPolicy=="string"&&o.approvalPolicy&&(e.approvalPolicy=o.approvalPolicy),typeof o.sandbox=="string"&&o.sandbox&&(e.sandboxPolicy=ee(o.sandbox));const t=o.reasoning_effort??o.reasoningEffort??o.model_reasoning_effort??o.modelReasoningEffort??o.effort;return typeof t=="string"&&t&&(e.effort=t),e}function ee(o){switch(o){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: ${o}`)}}function C(o){const e=m(o,"thread");return a(e,"id")||null}function m(o,e){const t=u(o);return u(t[e])}function u(o){return o&&typeof o=="object"&&!Array.isArray(o)?o:{}}function g(o){if(typeof o=="string")return o;if(Array.isArray(o))return o.map(g).filter(Boolean).join("");if(!o||typeof o!="object")return"";const e=u(o);for(const t of["text","message","content","output_text"]){const s=g(e[t]);if(s)return s}return""}function w(o,e){if(Array.isArray(o)){for(const r of o)w(r,e);return}const t=u(o),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 r of t.locations){const i=a(u(r),"path");i&&e.add(i)}for(const r of[t.content,t.update,t.rawInput,t.rawOutput])r&&r!==o&&w(r,e)}function te(o){if(!Array.isArray(o)||o.length===0)return{userRequest:"",assistantResponse:"",affectedFiles:[]};const e=o.at(-1),t=new Set;w(e,t);let s="",n="";const r=i=>{if(Array.isArray(i)){for(const l of i)r(l);return}if(!i||typeof i!="object")return;const c=u(i),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]&&r(c[l])};return r(e),{userRequest:s,assistantResponse:n,affectedFiles:[...t]}}function se(o,e){const t=S(o,"timeoutMs");return!Number.isFinite(t)||t<=0?e:Math.max(3e4,Math.min(59e4,t-1e4))}function P(o){const e=String(o.action||"none"),t=u(o.after),s=u(o.before),n=u(t.usage),r=u(s.usage),i=Object.keys(n).length>0?n: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 d=S(i,"totalTokens"),l=S(i,"inputTokens");return{usedTokens:d||l,inputTokens:l,contextWindow:c,ratio:c>0?(d||l)/c:0,observedAtMs:S(i,"observedAtMs")||Date.now(),state:e==="compact"?"compacted":"active",maintenanceAction:e}}function ne(o){return o?{usedTokens:o.totalTokens||o.inputTokens,inputTokens:o.inputTokens,contextWindow:o.contextWindow,ratio:o.contextWindow>0?(o.totalTokens||o.inputTokens)/o.contextWindow:0,observedAtMs:o.observedAtMs,state:"active",maintenanceAction:null}:null}function _(o){const e=o.usage?`${o.usage.inputTokens} tokens`:"token unknown",t=o.rolloutBytes>0?`${Math.round(o.rolloutBytes/1024/1024)}MB rollout`:"rollout unknown";return`${e}, ${t}, ${o.turnCount} turns`}function oe(o){const e=String(o.action||"none");if(e!=="compact_failed"&&e!=="rollover_failed")return;const t=String(o.compactError||"Codex 上下文维护未完成");throw new Error(`Codex 自动上下文维护失败:${t}`)}function S(o,e){const t=u(o)[e];return typeof t=="number"&&Number.isFinite(t)?t:0}function re(o){return L(o)?[{sessionUpdate:"aca_codex_raw_response_usage",_meta:{codex:{rawResponse:o}}}]:[]}function ie(o,e){const t=o.observeRawResponseItem(e);return t?[A(t)]:[]}function ae(o){return["agent_message_chunk","agent_progress_chunk","agent_thought_chunk","tool_call","tool_call_update","plan","plan_update","plan_removed"].includes(String(o.sessionUpdate||""))}function M(o,e,t){const s=String(o.sessionUpdate||""),n={...o};if(s==="tool_call"||s==="tool_call_update"){const c=a(o,"toolCallId");c&&(n.toolCallId=q(e.sourceThreadId,c))}if(s==="plan"||s==="plan_update"||s==="plan_removed"){const c=a(o,"planId")||a(o,"plan_id")||a(o,"id")||"current";n.planId=q(e.sourceThreadId,c),delete n.plan_id}const r=u(o._meta),i=u(r.aca);return n._meta={...r,aca:{...i,subtask:{parentToolCallId:e.parentToolCallId,sourceThreadId:e.sourceThreadId,sourceTurnId:t,depth:e.depth}}},n}function q(o,e){return`subtask.${o}.${e}`}function U(o,e){return`${o}:${e}`}function a(o,e){const s=u(o)[e];return typeof s=="string"?s:""}function ce(o){const e=o.toLowerCase();return e.includes("elicitation")||e.includes("requestuserinput")||e.includes("request_user_input")||e.includes("input/request")}function E(){const o=Number.parseInt(process.env.ACA_ACP_PROMPT_TIMEOUT_MS??"",10);return Number.isInteger(o)&&o>=3e4?o:360*60*1e3}function ue(){const o=Number.parseInt(process.env.ACA_CODEX_SUBTASK_DRAIN_TIMEOUT_MS??"",10);return Number.isInteger(o)&&o>=1e3?Math.min(o,600*1e3):600*1e3}function y(o,e){return o.exitCode!==null||o.signalCode!==null?Promise.resolve(!0):new Promise(t=>{const s=setTimeout(()=>t(!1),e);s.unref(),o.once("close",()=>{clearTimeout(s),t(!0)})})}function de(o){return new Promise(e=>setTimeout(e,o))}function le(){new X}const he=process.argv[1]||"";/(?:^|[\\/])adapter\.(?:js|ts)$/.test(he)&&le();export{le as runCodexAcpAdapter};
@@ -1,5 +1,5 @@
1
- import T from"node:fs";const f=1024*1024;function x(t=process.env){const e=a(t.ACA_CODEX_USAGE_LOOP_MIN_SAMPLES,4);return{compactTokenThreshold:a(t.ACA_CODEX_COMPACT_TOKEN_THRESHOLD,12e4),compactContextRatio:h(t.ACA_CODEX_COMPACT_CONTEXT_RATIO,.45),rolloverTokenThreshold:a(t.ACA_CODEX_ROLLOVER_TOKEN_THRESHOLD,18e4),rolloverContextRatio:h(t.ACA_CODEX_ROLLOVER_CONTEXT_RATIO,.6),rolloutMaxBytes:a(t.ACA_CODEX_ROLLOUT_MAX_BYTES,50*f),threadMaxTurns:a(t.ACA_CODEX_THREAD_MAX_TURNS,40),compactTimeoutMs:a(t.ACA_CODEX_COMPACT_TIMEOUT_MS,600*1e3),slowEventThresholdMs:a(t.ACA_CODEX_SLOW_EVENT_THRESHOLD_MS,300*1e3),usageLoopWindowMs:a(t.ACA_CODEX_USAGE_LOOP_WINDOW_MS,120*1e3),usageLoopMinSamples:e,usageLoopInterruptSamples:Math.max(e+1,a(t.ACA_CODEX_USAGE_LOOP_INTERRUPT_SAMPLES,8)),usageLoopMinInputTokens:a(t.ACA_CODEX_USAGE_LOOP_MIN_INPUT_TOKENS,5e4),usageLoopMaxOutputTokens:M(t.ACA_CODEX_USAGE_LOOP_MAX_OUTPUT_TOKENS,128)}}function m(t,e=Date.now()){const o=i(t),s=i(o.tokenUsage),n=i(s.last),r=l(n.inputTokens),c=l(n.outputTokens),p=l(n.totalTokens),u=l(s.modelContextWindow);return r===0&&p===0&&u===0?null:{source:"thread_usage",inputTokens:r,outputTokens:c,hasOutputTokens:typeof n.outputTokens=="number"&&Number.isFinite(n.outputTokens),totalTokens:p,contextWindow:u,contextRatio:u>0?r/u:0,observedAtMs:e,threadId:d(o.threadId),turnId:d(o.turnId),responseId:null}}function _(t,e=Date.now()){const o=i(t),s=i(o.usage);if(Object.keys(s).length===0)return null;const n=l(s.inputTokens),r=l(s.outputTokens),c=l(s.totalTokens);return n===0&&r===0&&c===0?null:{source:"raw_response",inputTokens:n,outputTokens:r,hasOutputTokens:typeof s.outputTokens=="number"&&Number.isFinite(s.outputTokens),totalTokens:c,contextWindow:0,contextRatio:0,observedAtMs:e,threadId:d(o.threadId),turnId:d(o.turnId),responseId:d(o.responseId)}}function C(t,e=Date.now()){const o=i(t),s=i(o.update);if(String(s.sessionUpdate||"")!=="usage_update")return null;const n=i(s._meta),r=i(n.codex);return Object.keys(r).length===0?null:m(r,e)}function E(t,e=Date.now()){const o=i(t),s=i(o.update);if(String(s.sessionUpdate||"")!=="aca_codex_raw_response_usage")return null;const n=i(s._meta),r=i(n.codex);return _(r.rawResponse,e)}function k(t,e,o={}){const s=[],n=[];return t.usage&&t.usage.inputTokens>=e.compactTokenThreshold&&s.push("token-threshold"),t.usage&&t.usage.contextRatio>=e.compactContextRatio&&s.push("context-ratio"),t.rolloutBytes>=e.rolloutMaxBytes&&s.push("rollout-size"),t.turnCount>=e.threadMaxTurns&&s.push("turn-count"),o.compactAttempted&&(o.compactFailed&&n.push("compact-failed"),t.usage&&t.usage.inputTokens>=e.rolloverTokenThreshold&&n.push("token-still-high"),t.usage&&t.usage.contextRatio>=e.rolloverContextRatio&&n.push("context-ratio-still-high"),t.rolloutBytes>=e.rolloutMaxBytes&&n.push("rollout-size"),t.turnCount>=e.threadMaxTurns&&n.push("turn-count")),{compact:s.length>0,rollover:n.length>0,reasons:[...new Set([...s,...n])]}}function I(t){if(!t)return 0;try{const e=T.statSync(t);return e.isFile()?e.size:0}catch{return 0}}function R(t){const e=[...new Set(t.affectedFiles.map(o=>o.trim()).filter(Boolean))].slice(0,30);return["ACA automatically rotated a long Codex thread to keep future turns responsive.","Treat the following as continuity context, not as a new user request.","Repository files on disk are authoritative; inspect them before making further edits.","",`Previous thread: ${t.previousThreadId}`,`Working directory: ${t.cwd}`,"","Latest user request:",A(t.userRequest,3e3)||"(not available)","","Latest completed assistant result:",A(t.assistantResponse,7e3)||"(not available)","","Recently affected files:",e.length>0?e.map(o=>`- ${o}`).join(`
1
+ import f from"node:fs";const A=1024*1024;function I(e=process.env){const t=c(e.ACA_CODEX_USAGE_LOOP_MIN_SAMPLES,4);return{compactTokenThreshold:c(e.ACA_CODEX_COMPACT_TOKEN_THRESHOLD,12e4),compactContextRatio:h(e.ACA_CODEX_COMPACT_CONTEXT_RATIO,.45),rolloverTokenThreshold:c(e.ACA_CODEX_ROLLOVER_TOKEN_THRESHOLD,18e4),rolloverContextRatio:h(e.ACA_CODEX_ROLLOVER_CONTEXT_RATIO,.6),rolloutMaxBytes:c(e.ACA_CODEX_ROLLOUT_MAX_BYTES,50*A),threadMaxTurns:c(e.ACA_CODEX_THREAD_MAX_TURNS,40),compactTimeoutMs:c(e.ACA_CODEX_COMPACT_TIMEOUT_MS,600*1e3),slowEventThresholdMs:c(e.ACA_CODEX_SLOW_EVENT_THRESHOLD_MS,300*1e3),usageLoopMinSamples:t,usageLoopInterruptSamples:Math.max(t+1,c(e.ACA_CODEX_USAGE_LOOP_INTERRUPT_SAMPLES,6)),usageLoopMinInputTokens:c(e.ACA_CODEX_USAGE_LOOP_MIN_INPUT_TOKENS,5e4),usageLoopMaxOutputTokens:M(e.ACA_CODEX_USAGE_LOOP_MAX_OUTPUT_TOKENS,128)}}function m(e,t=Date.now()){const s=u(e),n=u(s.tokenUsage),o=u(n.last),r=i(o.inputTokens),l=i(o.outputTokens),d=i(o.totalTokens),a=i(n.modelContextWindow);return r===0&&d===0&&a===0?null:{source:"thread_usage",inputTokens:r,outputTokens:l,cachedInputTokens:i(o.cachedInputTokens),cacheWriteInputTokens:i(o.cacheWriteInputTokens),reasoningOutputTokens:i(o.reasoningOutputTokens),hasOutputTokens:typeof o.outputTokens=="number"&&Number.isFinite(o.outputTokens),totalTokens:d,contextWindow:a,contextRatio:a>0?r/a:0,observedAtMs:t,threadId:p(s.threadId),turnId:p(s.turnId),responseId:null}}function _(e,t=Date.now()){const s=u(e),n=u(s.usage);if(Object.keys(n).length===0)return null;const o=i(n.inputTokens),r=i(n.outputTokens),l=i(n.totalTokens);return o===0&&r===0&&l===0?null:{source:"raw_response",inputTokens:o,outputTokens:r,cachedInputTokens:i(n.cachedInputTokens),cacheWriteInputTokens:i(n.cacheWriteInputTokens),reasoningOutputTokens:i(n.reasoningOutputTokens),hasOutputTokens:typeof n.outputTokens=="number"&&Number.isFinite(n.outputTokens),totalTokens:l,contextWindow:0,contextRatio:0,observedAtMs:t,threadId:p(s.threadId),turnId:p(s.turnId),responseId:p(s.responseId)}}function O(e,t=Date.now()){const s=u(e),n=u(s.update);if(String(n.sessionUpdate||"")!=="usage_update")return null;const o=u(n._meta),r=u(o.codex);return Object.keys(r).length===0?null:m(r,t)}function x(e,t=Date.now()){const s=u(e),n=u(s.update);if(String(n.sessionUpdate||"")!=="aca_codex_raw_response_usage")return null;const o=u(n._meta),r=u(o.codex);return _(r.rawResponse,t)}function C(e,t,s={}){const n=[],o=[];return e.usage&&e.usage.inputTokens>=t.compactTokenThreshold&&n.push("token-threshold"),e.usage&&e.usage.contextRatio>=t.compactContextRatio&&n.push("context-ratio"),e.rolloutBytes>=t.rolloutMaxBytes&&n.push("rollout-size"),e.turnCount>=t.threadMaxTurns&&n.push("turn-count"),s.compactAttempted&&(s.compactFailed&&o.push("compact-failed"),e.usage&&e.usage.inputTokens>=t.rolloverTokenThreshold&&o.push("token-still-high"),e.usage&&e.usage.contextRatio>=t.rolloverContextRatio&&o.push("context-ratio-still-high"),e.rolloutBytes>=t.rolloutMaxBytes&&o.push("rollout-size"),e.turnCount>=t.threadMaxTurns&&o.push("turn-count")),{compact:n.length>0,rollover:o.length>0,reasons:[...new Set([...n,...o])]}}function E(e){if(!e)return 0;try{const t=f.statSync(e);return t.isFile()?t.size:0}catch{return 0}}function R(e){const t=[...new Set(e.affectedFiles.map(s=>s.trim()).filter(Boolean))].slice(0,30);return["ACA automatically rotated a long Codex thread to keep future turns responsive.","Treat the following as continuity context, not as a new user request.","Repository files on disk are authoritative; inspect them before making further edits.","",`Previous thread: ${e.previousThreadId}`,`Working directory: ${e.cwd}`,"","Latest user request:",T(e.userRequest,3e3)||"(not available)","","Latest completed assistant result:",T(e.assistantResponse,7e3)||"(not available)","","Recently affected files:",t.length>0?t.map(s=>`- ${s}`).join(`
2
2
  `):"- (not recorded)"].join(`
3
- `)}class g{startedAtMs;firstEventAtMs=null;lastEventAtMs=null;eventCount=0;lastSlowReportAtMs=0;constructor(e=Date.now()){this.startedAtMs=e}recordEvent(e=Date.now()){this.firstEventAtMs===null&&(this.firstEventAtMs=e),this.lastEventAtMs=e,this.eventCount+=1}shouldReportSlow(e,o){const s=this.lastEventAtMs??this.startedAtMs;return e-s<o||this.lastSlowReportAtMs>0&&e-this.lastSlowReportAtMs<o?!1:(this.lastSlowReportAtMs=e,!0)}snapshot(e=Date.now()){const o=this.lastEventAtMs??this.startedAtMs;return{startedAtMs:this.startedAtMs,firstEventAtMs:this.firstEventAtMs,lastEventAtMs:this.lastEventAtMs,eventCount:this.eventCount,firstEventLatencyMs:this.firstEventAtMs===null?null:this.firstEventAtMs-this.startedAtMs,silentForMs:Math.max(0,e-o),durationMs:Math.max(0,e-this.startedAtMs)}}}class S{policy;samples=[];detection=null;rawResponseObserved=!1;constructor(e){this.policy=e}recordMeaningfulEvent(){const e=this.detection;return this.samples=[],this.detection=null,e}recordUsage(e){if(e.source==="raw_response"&&!this.rawResponseObserved)this.samples=[],this.rawResponseObserved=!0;else if(e.source==="thread_usage"&&this.rawResponseObserved)return null;if(!e.hasOutputTokens||e.inputTokens<this.policy.usageLoopMinInputTokens||e.outputTokens>this.policy.usageLoopMaxOutputTokens)return this.samples=[],this.detection=null,null;const o=this.samples.at(-1);if(o&&(o.source!==e.source||o.threadId&&e.threadId&&o.threadId!==e.threadId||o.turnId&&e.turnId&&o.turnId!==e.turnId)&&(this.samples=[],this.detection=null),this.samples.push(e),this.samples.length<this.policy.usageLoopMinSamples)return null;const s=this.samples,n=s.map(u=>u.inputTokens),r=Math.min(...n),c=Math.max(...n),p=Math.max(2e3,Math.floor(c*.05));return c-r>p?(this.samples=[e],this.detection=null,null):(this.detection={source:e.source,sampleCount:s.length,windowMs:s.at(-1).observedAtMs-s[0].observedAtMs,minInputTokens:r,maxInputTokens:c,maxOutputTokens:Math.max(...s.map(u=>u.outputTokens)),detectedAtMs:e.observedAtMs,threadId:e.threadId,turnId:e.turnId,responseIds:s.map(u=>u.responseId).filter(u=>!!u)},this.detection)}}function a(t,e){const o=Number.parseInt(t??"",10);return Number.isInteger(o)&&o>0?o:e}function M(t,e){const o=Number.parseInt(t??"",10);return Number.isInteger(o)&&o>=0?o:e}function h(t,e){const o=Number.parseFloat(t??"");return Number.isFinite(o)&&o>0&&o<=1?o:e}function l(t){return typeof t=="number"&&Number.isFinite(t)&&t>=0?Math.floor(t):0}function A(t,e){const o=t.replace(/\r\n/g,`
4
- `).trim();return o.length<=e?o:`${o.slice(0,e-16).trimEnd()}
5
- ...[truncated]`}function i(t){return t&&typeof t=="object"&&!Array.isArray(t)?t:{}}function d(t){return typeof t=="string"&&t.trim()?t.trim():null}export{g as CodexPromptPerformanceTracker,S as CodexUsageLoopDetector,R as buildCodexContinuityInstructions,E as codexRawResponseUsageFromAcpSessionUpdate,C as codexTokenUsageFromAcpSessionUpdate,m as codexTokenUsageFromNotification,_ as codexTokenUsageFromRawResponseCompleted,k as decideCodexContextMaintenance,x as readCodexContextMaintenancePolicy,I as rolloutFileSize};
3
+ `)}class g{startedAtMs;firstEventAtMs=null;lastEventAtMs=null;eventCount=0;lastSlowReportAtMs=0;constructor(t=Date.now()){this.startedAtMs=t}recordEvent(t=Date.now()){this.firstEventAtMs===null&&(this.firstEventAtMs=t),this.lastEventAtMs=t,this.eventCount+=1}shouldReportSlow(t,s){const n=this.lastEventAtMs??this.startedAtMs;return t-n<s||this.lastSlowReportAtMs>0&&t-this.lastSlowReportAtMs<s?!1:(this.lastSlowReportAtMs=t,!0)}snapshot(t=Date.now()){const s=this.lastEventAtMs??this.startedAtMs;return{startedAtMs:this.startedAtMs,firstEventAtMs:this.firstEventAtMs,lastEventAtMs:this.lastEventAtMs,eventCount:this.eventCount,firstEventLatencyMs:this.firstEventAtMs===null?null:this.firstEventAtMs-this.startedAtMs,silentForMs:Math.max(0,t-s),durationMs:Math.max(0,t-this.startedAtMs)}}}class S{policy;samples=[];detection=null;rawResponseObserved=!1;seenResponseIds=new Set;constructor(t){this.policy=t}recordMeaningfulEvent(){const t=this.detection;return this.samples=[],this.detection=null,t}recordUsage(t){if(t.source==="raw_response"&&(!t.threadId||!t.turnId))return null;if(t.source==="raw_response"&&t.responseId){if(this.seenResponseIds.has(t.responseId))return null;this.seenResponseIds.add(t.responseId)}if(t.source==="raw_response"&&!this.rawResponseObserved)this.samples=[],this.rawResponseObserved=!0;else if(t.source==="thread_usage"&&this.rawResponseObserved)return null;if(!t.hasOutputTokens||t.inputTokens<this.policy.usageLoopMinInputTokens||t.outputTokens>this.policy.usageLoopMaxOutputTokens)return this.samples=[],this.detection=null,null;const s=this.samples.at(-1);if(s&&(s.source!==t.source||s.threadId&&t.threadId&&s.threadId!==t.threadId||s.turnId&&t.turnId&&s.turnId!==t.turnId)&&(this.samples=[],this.detection=null),this.samples.push(t),this.samples.length<this.policy.usageLoopMinSamples)return null;const n=this.samples,o=n.map(a=>a.inputTokens),r=Math.min(...o),l=Math.max(...o),d=Math.max(2e3,Math.floor(l*.05));return l-r>d?(this.samples=[t],this.detection=null,null):(this.detection={source:t.source,sampleCount:n.length,windowMs:n.at(-1).observedAtMs-n[0].observedAtMs,minInputTokens:r,maxInputTokens:l,maxOutputTokens:Math.max(...n.map(a=>a.outputTokens)),detectedAtMs:t.observedAtMs,threadId:t.threadId,turnId:t.turnId,responseIds:n.map(a=>a.responseId).filter(a=>!!a)},this.detection)}}function c(e,t){const s=Number.parseInt(e??"",10);return Number.isInteger(s)&&s>0?s:t}function M(e,t){const s=Number.parseInt(e??"",10);return Number.isInteger(s)&&s>=0?s:t}function h(e,t){const s=Number.parseFloat(e??"");return Number.isFinite(s)&&s>0&&s<=1?s:t}function i(e){return typeof e=="number"&&Number.isFinite(e)&&e>=0?Math.floor(e):0}function T(e,t){const s=e.replace(/\r\n/g,`
4
+ `).trim();return s.length<=t?s:`${s.slice(0,t-16).trimEnd()}
5
+ ...[truncated]`}function u(e){return e&&typeof e=="object"&&!Array.isArray(e)?e:{}}function p(e){return typeof e=="string"&&e.trim()?e.trim():null}export{g as CodexPromptPerformanceTracker,S as CodexUsageLoopDetector,R as buildCodexContinuityInstructions,x as codexRawResponseUsageFromAcpSessionUpdate,O as codexTokenUsageFromAcpSessionUpdate,m as codexTokenUsageFromNotification,_ as codexTokenUsageFromRawResponseCompleted,C as decideCodexContextMaintenance,I as readCodexContextMaintenancePolicy,E as rolloutFileSize};
@@ -1 +1 @@
1
- const C="aca_codex_control_tool_loop";class M{pending=new Map;latest=null;observeRawResponseItem(n,e=Date.now()){const r=a(n),o=a(r.item),s=u(o,"type"),i=_(r.threadId),c=_(r.turnId);if(s==="function_call"||s==="custom_tool_call"){const l=S(o,i,c);return l?(this.pending.set(l.callId,l),null):this.recordProgress()}if(s==="function_call_output"||s==="custom_tool_call_output"){const l=u(o,"call_id")||u(o,"callId"),d=l?this.pending.get(l):void 0;return d?(this.pending.delete(l),d.kind==="list_agents_self"&&!L(o.output)?this.recordProgress():{state:"observed",detection:this.recordControlCall(d,e)}):null}return s==="reasoning"?null:s?this.recordProgress():null}recordProgress(){this.pending.clear();const n=this.latest;return this.latest=null,n?{state:"recovered",detection:n}:null}reset(){this.pending.clear(),this.latest=null}recordControlCall(n,e){const r=this.latest,o=r&&r.signature===n.signature&&h(r.threadId,n.threadId)&&h(r.turnId,n.turnId),s={kind:n.kind,toolName:n.toolName,arguments:n.arguments,signature:n.signature,sampleCount:o?r.sampleCount+1:1,firstObservedAtMs:o?r.firstObservedAtMs:e,detectedAtMs:e,elapsedMs:o?Math.max(0,e-r.firstObservedAtMs):0,threadId:n.threadId,turnId:n.turnId};return this.latest=s,s}}function N(t=process.env){const n=p(t.ACA_CODEX_LIST_AGENTS_LOOP_WARNING_CALLS,3),e=p(t.ACA_CODEX_SHORT_SLEEP_LOOP_WARNING_CALLS,5);return{listAgentsWarningCalls:n,listAgentsInterruptCalls:Math.max(n+1,p(t.ACA_CODEX_LIST_AGENTS_LOOP_INTERRUPT_CALLS,5)),shortSleepWarningCalls:e,shortSleepInterruptCalls:Math.max(e+1,p(t.ACA_CODEX_SHORT_SLEEP_LOOP_INTERRUPT_CALLS,8)),shortSleepMaxDurationMs:p(t.ACA_CODEX_SHORT_SLEEP_MAX_DURATION_MS,1e3)}}function T(t,n){return t==="list_agents_self"?{warningCalls:n.listAgentsWarningCalls,interruptCalls:n.listAgentsInterruptCalls}:{warningCalls:n.shortSleepWarningCalls,interruptCalls:n.shortSleepInterruptCalls}}function x(t){return{sessionUpdate:C,_meta:{codex:{controlToolLoop:t}}}}function E(t){const n=a(t),e=a(n.update);if(u(e,"sessionUpdate")!==C)return null;const r=a(e._meta),o=a(r.codex),s=a(o.controlToolLoop),i=a(s.detection),c=u(s,"state"),l=u(i,"kind");return c!=="observed"&&c!=="recovered"||l!=="list_agents_self"&&l!=="short_sleep"?null:{state:c,detection:{kind:l,toolName:u(i,"toolName"),arguments:i.arguments,signature:u(i,"signature"),sampleCount:g(i.sampleCount),firstObservedAtMs:g(i.firstObservedAtMs),detectedAtMs:g(i.detectedAtMs),elapsedMs:g(i.elapsedMs),threadId:_(i.threadId),turnId:_(i.turnId)}}}function S(t,n,e){const r=u(t,"call_id")||u(t,"callId");if(!r)return null;const o=u(t,"name"),s=u(t,"namespace"),i=s?`${s}.${o}`:o,c=b(t.arguments??t.input),l=o.split(".").at(-1)||o;let d=null;if(l==="list_agents"&&(!s||s==="collaboration"))d="list_agents_self";else if(l==="sleep"&&(!s||s==="clock")){const A=y(a(c).duration_ms),O=N().shortSleepMaxDurationMs;A>=0&&A<=O&&(d="short_sleep")}return d?{callId:r,kind:d,toolName:i,arguments:c,signature:`${d}:${m(c)}`,threadId:n,turnId:e}:null}function L(t){const n=f(t,0);if(!n||n.length!==1)return!1;const e=a(n[0]),r=String(e.agent_name??e.agentName??"").trim(),o=String(e.agent_status??e.agentStatus??"").trim().toLowerCase();return r==="/root"&&o==="running"}function f(t,n){if(n>4)return null;if(typeof t=="string"){const r=I(t);return r===null?null:f(r,n+1)}if(Array.isArray(t)){for(const r of t){const o=a(r),s=f(o.text??o.content??r,n+1);if(s)return s}return null}const e=a(t);if(Array.isArray(e.agents))return e.agents;for(const r of["structuredContent","output","content","result"]){if(!(r in e))continue;const o=f(e[r],n+1);if(o)return o}return null}function b(t){return typeof t!="string"?t??{}:I(t)??t.trim()}function I(t){try{return JSON.parse(t)}catch{return null}}function m(t){return Array.isArray(t)?`[${t.map(m).join(",")}]`:t&&typeof t=="object"?`{${Object.entries(t).sort(([n],[e])=>n.localeCompare(e)).map(([n,e])=>`${JSON.stringify(n)}:${m(e)}`).join(",")}}`:JSON.stringify(t)??"null"}function h(t,n){return!t||!n||t===n}function p(t,n){const e=Number.parseInt(t??"",10);return Number.isInteger(e)&&e>0?e:n}function g(t){return typeof t=="number"&&Number.isFinite(t)&&t>=0?Math.floor(t):0}function y(t){return typeof t=="number"&&Number.isFinite(t)?t:Number.NaN}function a(t){return t&&typeof t=="object"&&!Array.isArray(t)?t:{}}function u(t,n){const e=t[n];return typeof e=="string"?e:""}function _(t){return typeof t=="string"&&t.trim()?t.trim():null}export{M as CodexControlToolLoopDetector,x as codexControlToolLoopAcpUpdate,E as codexControlToolLoopFromAcpSessionUpdate,T as controlToolLoopThresholds,N as readCodexControlToolLoopPolicy};
1
+ const N="aca_codex_control_tool_loop";class x{options;pending=new Map;latest=null;constructor(n={}){this.options=n}observeRawResponseItem(n,r=Date.now()){const e=u(n),s=u(e.item),o=c(s,"type"),i=A(e.threadId),a=A(e.turnId);if(o==="function_call"||o==="custom_tool_call"){const l=b(s,i,a,this.options.shortSleepMaxDurationMs??L().shortSleepMaxDurationMs,this.options.hasActiveSubtasks?.()===!0);return l?(this.pending.set(l.callId,l),null):this.recordProgress()}if(o==="function_call_output"||o==="custom_tool_call_output"){const l=c(s,"call_id")||c(s,"callId"),d=l?this.pending.get(l):void 0;return d?(this.pending.delete(l),d.kind==="list_agents_no_live"&&!T(s.output)?this.recordProgress():d.kind==="wait_agent_no_live"&&(d.activeSubtasksAtCall||this.options.hasActiveSubtasks?.())?this.recordProgress():{state:"observed",detection:this.recordControlCall(d,r)}):null}return o==="reasoning"?null:o?this.recordProgress():null}recordProgress(){this.pending.clear();const n=this.latest;return this.latest=null,n?{state:"recovered",detection:n}:null}reset(){this.pending.clear(),this.latest=null}recordControlCall(n,r){const e=this.latest,s=e&&e.signature===n.signature&&S(e.threadId,n.threadId)&&S(e.turnId,n.turnId),o={kind:n.kind,toolName:n.toolName,arguments:n.arguments,signature:n.signature,sampleCount:s?e.sampleCount+1:1,firstObservedAtMs:s?e.firstObservedAtMs:r,detectedAtMs:r,elapsedMs:s?Math.max(0,r-e.firstObservedAtMs):0,threadId:n.threadId,turnId:n.turnId};return this.latest=o,o}}function L(t=process.env){const n=p(t.ACA_CODEX_LIST_AGENTS_LOOP_WARNING_CALLS,3),r=p(t.ACA_CODEX_WAIT_AGENT_LOOP_WARNING_CALLS,3),e=p(t.ACA_CODEX_SHORT_SLEEP_LOOP_WARNING_CALLS,5);return{listAgentsWarningCalls:n,listAgentsInterruptCalls:Math.max(n+1,p(t.ACA_CODEX_LIST_AGENTS_LOOP_INTERRUPT_CALLS,5)),waitAgentWarningCalls:r,waitAgentInterruptCalls:Math.max(r+1,p(t.ACA_CODEX_WAIT_AGENT_LOOP_INTERRUPT_CALLS,5)),shortSleepWarningCalls:e,shortSleepInterruptCalls:Math.max(e+1,p(t.ACA_CODEX_SHORT_SLEEP_LOOP_INTERRUPT_CALLS,8)),shortSleepMaxDurationMs:p(t.ACA_CODEX_SHORT_SLEEP_MAX_DURATION_MS,1e3)}}function y(t,n){return t==="list_agents_no_live"?{warningCalls:n.listAgentsWarningCalls,interruptCalls:n.listAgentsInterruptCalls}:t==="wait_agent_no_live"?{warningCalls:n.waitAgentWarningCalls,interruptCalls:n.waitAgentInterruptCalls}:{warningCalls:n.shortSleepWarningCalls,interruptCalls:n.shortSleepInterruptCalls}}function P(t){return{sessionUpdate:N,_meta:{codex:{controlToolLoop:t}}}}function w(t){const n=u(t),r=u(n.update);if(c(r,"sessionUpdate")!==N)return null;const e=u(r._meta),s=u(e.codex),o=u(s.controlToolLoop),i=u(o.detection),a=c(o,"state"),l=c(i,"kind");return a!=="observed"&&a!=="recovered"||l!=="list_agents_no_live"&&l!=="wait_agent_no_live"&&l!=="short_sleep"?null:{state:a,detection:{kind:l,toolName:c(i,"toolName"),arguments:i.arguments,signature:c(i,"signature"),sampleCount:f(i.sampleCount),firstObservedAtMs:f(i.firstObservedAtMs),detectedAtMs:f(i.detectedAtMs),elapsedMs:f(i.elapsedMs),threadId:A(i.threadId),turnId:A(i.turnId)}}}function b(t,n,r,e,s){const o=c(t,"call_id")||c(t,"callId");if(!o)return null;const i=c(t,"name"),a=c(t,"namespace"),l=a?`${a}.${i}`:i,d=M(t.arguments??t.input),h=i.split(".").at(-1)||i,C=h.replace(/[^a-z0-9]/gi,"").toLowerCase();let _=null;if(C==="listagents"&&(!a||a==="collaboration"))_="list_agents_no_live";else if((C==="waitagent"||C==="wait")&&(!a||a==="collaboration"))_="wait_agent_no_live";else if(h==="sleep"&&(!a||a==="clock")){const I=E(u(d).duration_ms);I>=0&&I<=e&&(_="short_sleep")}return _?{callId:o,kind:_,toolName:l,arguments:d,signature:_==="short_sleep"?`${_}:${m(d)}`:_,threadId:n,turnId:r,activeSubtasksAtCall:s}:null}function T(t){const n=g(t,0);return n?!n.some(r=>{const e=u(r),s=String(e.agent_name??e.agentName??"").trim(),o=String(e.agent_status??e.agentStatus??e.status??"").trim().toLowerCase();return s==="/root"?!1:["pending","pendinginit","running","active","in_progress","inprogress"].includes(o)}):!1}function g(t,n){if(n>4)return null;if(typeof t=="string"){const e=O(t);return e===null?null:g(e,n+1)}if(Array.isArray(t)){for(const e of t){const s=u(e),o=g(s.text??s.content??e,n+1);if(o)return o}return null}const r=u(t);if(Array.isArray(r.agents))return r.agents;for(const e of["structuredContent","output","content","result"]){if(!(e in r))continue;const s=g(r[e],n+1);if(s)return s}return null}function M(t){return typeof t!="string"?t??{}:O(t)??t.trim()}function O(t){try{return JSON.parse(t)}catch{return null}}function m(t){return Array.isArray(t)?`[${t.map(m).join(",")}]`:t&&typeof t=="object"?`{${Object.entries(t).sort(([n],[r])=>n.localeCompare(r)).map(([n,r])=>`${JSON.stringify(n)}:${m(r)}`).join(",")}}`:JSON.stringify(t)??"null"}function S(t,n){return!t||!n||t===n}function p(t,n){const r=Number.parseInt(t??"",10);return Number.isInteger(r)&&r>0?r:n}function f(t){return typeof t=="number"&&Number.isFinite(t)&&t>=0?Math.floor(t):0}function E(t){return typeof t=="number"&&Number.isFinite(t)?t:Number.NaN}function u(t){return t&&typeof t=="object"&&!Array.isArray(t)?t:{}}function c(t,n){const r=t[n];return typeof r=="string"?r:""}function A(t){return typeof t=="string"&&t.trim()?t.trim():null}export{x as CodexControlToolLoopDetector,P as codexControlToolLoopAcpUpdate,w as codexControlToolLoopFromAcpSessionUpdate,y as controlToolLoopThresholds,L as readCodexControlToolLoopPolicy};
@@ -1 +1 @@
1
- import g from"node:fs";import{runAbortableRequest as P}from"../../client/abortable-request.js";import{BoundedItemBuffer as F}from"../../client/bounded-buffer.js";import{promptBlocksToAcpContent as j}from"../../client/acp-content.js";import{acpSessionUpdateFromParams as z,eventFromAcpSessionUpdate as N,statusFromAcpSessionUpdate as H,subtaskContextFromAcpSessionUpdate as V}from"../../client/acp-events.js";import{defaultAcpRuntimePool as T}from"../../client/acp-runtime-pool.js";import{resolveCodexAcpLaunch as R}from"./launch.js";import{cancelCodexRuntime as B}from"./cancellation.js";import{CodexPromptPerformanceTracker as K,CodexUsageLoopDetector as X,codexRawResponseUsageFromAcpSessionUpdate as G,codexTokenUsageFromAcpSessionUpdate as Y,readCodexContextMaintenancePolicy as M}from"./context-maintenance.js";import{ensureCodexProviderRuntimeConfig as E}from"../../../core/codex-provider-runtime-config.js";import{codexControlToolLoopFromAcpSessionUpdate as J,controlToolLoopThresholds as Q,readCodexControlToolLoopPolicy as Z}from"./control-tool-loop.js";const ee={sessionResume:!0,imageInput:!0,fileAttachment:!0,filesystem:!0,terminal:!0,permissionRequest:!0,configOptions:!0,usage:!0,contextUsage:!0,contextCompaction:!0,plan:!0,diff:!0};class ve{id="codex-acp";name="Codex ACP";capabilities=ee;async createSession(e){return new te(e.cwd,e.providerSessionId??null,e.timeoutMs)}async forkSession(e){if(!g.existsSync(e.cwd)||!g.statSync(e.cwd).isDirectory())throw new Error(`Project root does not exist or is not a directory: ${e.cwd}`);const t=e.sourceProviderSessionId.trim(),s=await E(),r=await T.acquire({providerId:"codex-acp",cwd:e.cwd,providerSessionId:t,runtimeIdentity:s?.runtimeIdentity||"default",env:U(s),...R(),onSessionUpdate:()=>{}});try{await q(r,s),r.initialized||(await r.client.initialize(6e4),r.markInitialized()),e.onStatus?.({phase:"forking",label:"正在分叉 Codex 会话",detail:t,updateType:"session/fork",atMs:Date.now()});const n=await r.client.forkSession({sessionId:t,cwd:e.cwd,config:L(e)},e.timeoutMs??12e4),i=x(n);if(!i)throw new Error("Codex ACP session/fork did not return sessionId");return{providerSessionId:i}}finally{r.release()}}}class te{cwd;providerSessionId;defaultTimeoutMs;lease=null;sessionResponse=null;statusCallback=null;updateCallback=null;permissionCallback=null;userInputCallback=null;abortHandler=null;statusTimeline=[];updates=new F(16);content="";performanceTracker=null;usageLoopDetector=null;usagePatternReport=null;controlToolPatternReport=null;usageLoopWarningActive=!1;controlToolLoopWarningActive=!1;cancellation=null;constructor(e,t,s=ae()){this.cwd=e,this.providerSessionId=t,this.defaultTimeoutMs=s}async sendPrompt(e){if(!g.existsSync(e.cwd)||!g.statSync(e.cwd).isDirectory())throw new Error(`Project root does not exist or is not a directory: ${e.cwd}`);const t=Date.now();this.statusCallback=e.onStatus??null,this.updateCallback=e.onUpdate??null,this.permissionCallback=e.onPermissionRequest??null,this.userInputCallback=e.onUserInputRequest??null,this.attachAbortSignal(e.signal),this.content="",this.updates.clear(),this.statusTimeline.length=0;const s=M(),r=Z();this.performanceTracker=new K(t),this.usageLoopDetector=new X(s),this.usageLoopWarningActive=!1,this.controlToolLoopWarningActive=!1;const n={error:null,stopping:null};let i=null,a=null,c=this.providerSessionId,v,b=null;try{this.emitStatus("initializing","Codex ACP 启动中","codex-acp");const u=await this.acquireRuntime(e.cwd);this.lease=u,i=u.client,a=u.started,await this.initialize(u),c=await this.establishSession(u,e),await this.applyConfig(i,c,e);const W=i,I=c;this.emitStatus("thinking","思考中"),b=this.startSlowStatusTimer(),this.usagePatternReport=d=>{if(n.error)return;const S=Math.round(d.maxInputTokens/1e3),f=`同一 Turn 已连续请求至少 ${d.sampleCount} 次,单次输入约 ${S}k tokens`;if(d.sampleCount<s.usageLoopInterruptSamples){this.usageLoopWarningActive=!0,this.emitStatus("thinking","Codex 出现低产出内部循环",f,"aca.performance.low_output_loop");return}const C=`Codex 低产出内部循环:同一 Turn 已连续请求至少 ${d.sampleCount} 次,单次输入约 ${S}k tokens,且未观察到有效进展`;n.error=new Error(C),this.emitStatus("thinking","正在中断 Codex 低产出内部循环",f,"aca.performance.low_output_loop_interrupting"),n.stopping=this.stopRuntime(u,I).then(l=>{const p=l.forced?`${f};取消请求失败:${l.reason};已关闭运行实例`:f;l.forced&&(n.error=new Error(`${C};取消请求失败:${l.reason};已关闭运行实例`)),this.emitStatus("failed","Codex 已中断低产出内部循环",p,"aca.performance.low_output_loop_interrupted")}).catch(l=>{const p=l instanceof Error?l.message:String(l);n.error=new Error(`${C};${p}`),this.emitStatus("failed","Codex 循环中断失败",p,"aca.performance.low_output_loop_interrupt_failed")})},this.controlToolPatternReport=d=>{if(n.error)return;const S=Q(d.kind,r);if(d.sampleCount<S.warningCalls)return;const f=d.kind==="list_agents_self"?"list_agents 查询仅返回根线程自身":"短时 sleep",C=Math.max(1,Math.round(d.elapsedMs/6e4)),l=`同一 Turn 已连续 ${d.sampleCount} 次执行 ${f},持续约 ${C} 分钟`;if(d.sampleCount<S.interruptCalls){this.controlToolLoopWarningActive=!0,this.emitStatus("thinking","Codex 出现无进展控制工具循环",l,"aca.performance.control_tool_loop");return}const p=`Codex 无进展控制工具循环:${l}`;n.error=new Error(p),this.emitStatus("thinking","正在中断 Codex 无进展控制工具循环",l,"aca.performance.control_tool_loop_interrupting"),n.stopping=this.stopRuntime(u,I).then(m=>{const _=m.forced?`${l};取消请求失败:${m.reason};已关闭运行实例`:l;m.forced&&(n.error=new Error(`${p};取消请求失败:${m.reason};已关闭运行实例`)),this.emitStatus("failed","Codex 已中断无进展控制工具循环",_,"aca.performance.control_tool_loop_interrupted")}).catch(m=>{const _=m instanceof Error?m.message:String(m);n.error=new Error(`${p};${_}`),this.emitStatus("failed","Codex 控制工具循环中断失败",_,"aca.performance.control_tool_loop_interrupt_failed")})};try{v=await this.promptWithAbort(W,{sessionId:I,prompt:j(e.prompt,e.promptBlocks),timeoutMs:e.timeoutMs??this.defaultTimeoutMs,signal:e.signal})}catch(d){throw await n.stopping,n.error?n.error:d}if(await n.stopping,n.error)throw n.error;const w=D(v);w&&w!==c&&(c=w,this.providerSessionId=w,u.setProviderSession({providerSessionId:w,sessionResponse:this.sessionResponse}))}finally{await n.stopping,await this.cancellation?.catch(()=>{}),b&&clearInterval(b),this.usagePatternReport=null,this.controlToolPatternReport=null,this.usageLoopDetector=null,this.usageLoopWarningActive=!1,this.controlToolLoopWarningActive=!1,this.abortHandler&&e.signal&&(e.signal.removeEventListener("abort",this.abortHandler),this.abortHandler=null),this.releaseLease()}return this.emitStatus("completed","已完成"),{providerSessionId:c,cliType:"builtin",agentType:"codex",effectiveConfig:{model:e.model??null,mode:e.mode??null,configOptionValues:e.configOptionValues??null,command:a.command,processPid:a.child.pid??null,provider:"codex-acp",protocol:"acp"},command:a.command,args:a.args,cwd:e.cwd,exitCode:a.child.exitCode,signal:a.child.signalCode,durationMs:Date.now()-t,stdout:i.rpc.stdout,stderr:i.rpc.stderr,content:this.content.trim(),updateCount:this.updates.count(),updates:this.updates.snapshot(),statusTimeline:[...this.statusTimeline],performance:this.performanceTracker?.snapshot(),contextUsage:le(v),promptResponse:v}}async compactContext(e){if(!g.existsSync(e.cwd)||!g.statSync(e.cwd).isDirectory())throw new Error(`Project root does not exist or is not a directory: ${e.cwd}`);const t=Date.now();this.statusCallback=e.onStatus??null,this.updateCallback=e.onUpdate??null,this.attachAbortSignal(e.signal);let s=this.providerSessionId,r;try{this.emitStatus("initializing","Codex ACP 启动中","codex-acp");const i=await this.acquireRuntime(e.cwd);this.lease=i,await this.initialize(i),s=await this.establishSession(i,e),await this.applyConfig(i.client,s,e);const a=ce(e.timeoutMs);r=await this.compactWithAbort(i.client,{sessionId:s,allowRollover:e.allowRollover===!0,timeoutMs:a,signal:e.signal});const c=D(r);c&&c!==s&&(s=c,this.providerSessionId=c,i.setProviderSession({providerSessionId:c,sessionResponse:this.sessionResponse}))}catch(i){if(this.lease&&ne(i,e.signal)){const a=this.lease;this.lease=null,await a.dispose().catch(()=>{})}throw this.emitStatus("failed","上下文压缩失败",i instanceof Error?i.message:String(i)),i}finally{this.abortHandler&&e.signal&&(e.signal.removeEventListener("abort",this.abortHandler),this.abortHandler=null),this.releaseLease()}const n=h(r)??{};return{providerSessionId:s||this.providerSessionId||"",contextUsage:O(n.contextUsage),maintenance:h(n.maintenance)??{},durationMs:Date.now()-t}}async cancel(){this.providerSessionId&&this.lease&&await this.stopRuntime(this.lease,this.providerSessionId)}stopRuntime(e,t){if(this.cancellation)return this.cancellation;const s=B(e,t);return this.cancellation=s,s.finally(()=>{this.cancellation===s&&(this.cancellation=null)}).catch(()=>{}),s}async close(){await this.cancellation?.catch(()=>{}),this.statusCallback=null,this.updateCallback=null,this.permissionCallback=null,this.userInputCallback=null,this.abortHandler&&(this.abortHandler=null),this.releaseLease()}async acquireRuntime(e){const t=R(),s=await E(),r=await T.acquire({providerId:"codex-acp",cwd:e,providerSessionId:this.providerSessionId,runtimeIdentity:s?.runtimeIdentity||"default",env:U(s),command:t.command,args:t.args,onSessionUpdate:n=>this.handleSessionUpdate(n),onClientRequest:async(n,i)=>{if(n==="session/request_permission")return this.requestPermission(i);if(n==="elicitation/create"||n==="session/request_user_input")return this.requestUserInput(i);throw new Error(`Unsupported Codex ACP client request: ${n}`)}});try{return await q(r,s),r}catch(n){throw await r.dispose().catch(()=>{}),n}}async initialize(e){e.initialized||(await e.client.initialize(6e4),e.markInitialized())}async establishSession(e,t){const s=e.client,r=L(t);if(e.providerSessionId&&e.sessionEstablished)return this.providerSessionId=e.providerSessionId,this.sessionResponse=e.sessionResponse,e.providerSessionId;if(this.providerSessionId)try{this.emitStatus("resuming","Codex ACP 会话恢复中","session/load");const a=await s.loadSession({sessionId:this.providerSessionId,cwd:t.cwd,config:r},6e4);this.sessionResponse=h(a);const c=x(a)||this.providerSessionId;return this.providerSessionId=c,e.setProviderSession({providerSessionId:c,sessionResponse:this.sessionResponse}),c}catch(a){if(t.requireSessionResume){const c=a instanceof Error?a.message:String(a);throw new Error(`无法恢复导入的 Codex ACP Session ${this.providerSessionId}:${c}`)}this.providerSessionId=null}this.emitStatus("acp","Codex ACP 会话创建中","session/new");const n=await s.newSession({cwd:t.cwd,config:r},12e4);this.sessionResponse=h(n);const i=x(n);if(!i)throw new Error("Codex ACP session/new did not return sessionId");return this.providerSessionId=i,e.setProviderSession({providerSessionId:i,sessionResponse:this.sessionResponse}),i}async applyConfig(e,t,s){s.model&&await e.setSessionConfigOption({sessionId:t,configId:"model",value:s.model},6e4).catch(()=>{});const r=k(s.mode);r&&await e.setSessionConfigOption({sessionId:t,configId:"approvalPolicy",value:r},6e4).catch(()=>{});const n=A(s.mode);n&&await e.setSessionConfigOption({sessionId:t,configId:"sandbox",value:n},6e4).catch(()=>{});for(const[i,a]of Object.entries(s.configOptionValues??{}))["model","mode","sandbox","approvalPolicy"].includes(i)||await e.setSessionConfigOption({sessionId:t,configId:i,value:a},6e4).catch(()=>{})}handleSessionUpdate(e){const t=z(e),s=String(t?.update.sessionUpdate||""),r=V(e);if(!r&&s==="aca_codex_control_tool_loop"){const a=J(e);a?.state==="observed"&&this.controlToolPatternReport?.(a.detection),a?.state==="recovered"&&this.controlToolLoopWarningActive&&(this.controlToolLoopWarningActive=!1,this.emitStatus("thinking","Codex 已恢复执行","无进展控制工具循环已结束","aca.performance.control_tool_loop_recovered"))}else if(!r&&se(s))this.performanceTracker?.recordEvent(),this.usageLoopDetector?.recordMeaningfulEvent(),this.usageLoopWarningActive&&(this.usageLoopWarningActive=!1,this.emitStatus("thinking","Codex 已恢复执行","已重新执行工具或产生有效输出","aca.performance.loop_recovered"));else if(!r&&s==="aca_codex_raw_response_usage"){const a=G(e),c=a?this.usageLoopDetector?.recordUsage(a):null;c&&this.usagePatternReport?.(c)}else if(!r&&s==="usage_update"){const a=Y(e),c=a?this.usageLoopDetector?.recordUsage(a):null;c&&this.usagePatternReport?.(c)}this.updates.push({method:"session/update",params:e});const n=N(e);n&&(n.type==="agent_message_chunk"&&!r&&(this.content+=n.text??""),this.updateCallback?.(n));const i=H(e);i&&this.emitStatus(i.phase,i.label,i.detail,i.updateType)}startSlowStatusTimer(){const e=M().slowEventThresholdMs,t=setInterval(()=>{const s=this.performanceTracker,r=Date.now();if(!s?.shouldReportSlow(r,e))return;const n=s.snapshot(r),i=Math.max(1,Math.round(n.silentForMs/6e4)),a=this.usageLoopWarningActive||this.controlToolLoopWarningActive?"Codex 可能卡在内部循环":"Codex 长时间没有有效进展";this.emitStatus("thinking",a,`已 ${i} 分钟没有执行工具或产生有效输出`,"aca.performance.slow")},Math.min(3e4,Math.max(1e3,Math.floor(e/4))));return t.unref(),t}attachAbortSignal(e){if(e){if(this.abortHandler=()=>{this.cancel().catch(()=>{})},e.aborted)throw new Error("Codex ACP prompt cancelled");e.addEventListener("abort",this.abortHandler,{once:!0})}}async promptWithAbort(e,t){return P({signal:t.signal,cancelledMessage:"Codex ACP prompt cancelled",startRequest:()=>e.prompt({sessionId:t.sessionId,prompt:t.prompt},t.timeoutMs),onAbort:async()=>{await this.lease?.dispose()}})}async compactWithAbort(e,t){return P({signal:t.signal,cancelledMessage:"Codex ACP 上下文压缩已取消",startRequest:()=>e.compactSession({sessionId:t.sessionId,allowRollover:t.allowRollover,timeoutMs:t.timeoutMs},t.timeoutMs),onAbort:async()=>{await this.lease?.dispose()}})}async requestPermission(e){if(!this.permissionCallback)return{outcome:{outcome:"cancelled"}};const t=e&&typeof e=="object"&&!Array.isArray(e)?e:{},s=t.request&&typeof t.request=="object"&&!Array.isArray(t.request)?t.request:t,n=(Array.isArray(s.options)?s.options:[]).map((i,a)=>{const c=i&&typeof i=="object"&&!Array.isArray(i)?i:{};return{optionId:String(c.optionId??c.id??a),kind:String(c.kind??"choice"),...typeof c.label=="string"?{label:c.label}:{}}});return this.emitStatus("requestPermission","等待授权",ie(s),"session/request_permission"),this.permissionCallback({requestId:String(s.requestId??s.id??`codex-acp-permission-${Date.now()}`),params:e,options:n})}async requestUserInput(e){if(!this.userInputCallback)return{outcome:{outcome:"cancelled"}};const t=e&&typeof e=="object"&&!Array.isArray(e)?e:{},s=t.request&&typeof t.request=="object"&&!Array.isArray(t.request)?t.request:t;return this.emitStatus("requestPermission","等待输入",re(s),"elicitation/create"),this.userInputCallback({requestId:String(s.requestId??s.id??`codex-acp-input-${Date.now()}`),prompt:String(s.prompt??s.message??s.question??""),params:e,...typeof s.defaultValue=="string"?{defaultValue:s.defaultValue}:{}})}emitStatus(e,t,s,r){const n={phase:e,label:t,...s?{detail:s}:{},...r?{updateType:r}:{},atMs:Date.now()},i=this.statusTimeline[this.statusTimeline.length-1];i&&i.phase===n.phase&&i.label===n.label&&i.detail===n.detail&&i.updateType===n.updateType||(this.statusTimeline.push(n),this.statusCallback?.(n))}releaseLease(){this.lease?.release(),this.lease=null}}const oe=new Set(["agent_message_chunk","agent_progress_chunk","agent_thought_chunk","tool_call","tool_call_update","plan","plan_update","plan_removed","aca_context_maintenance"]);function se(o){return oe.has(o)}async function q(o,e){o.runtimeConfigured||(await o.client.configureRuntime({codexProvider:e?{baseUrl:e.baseUrl,apiKey:e.apiKey,revision:e.revision}:null}),o.markRuntimeConfigured())}function U(o){return o?{OPENAI_API_KEY:o.apiKey,OPENAI_BASE_URL:o.baseUrl}:{}}function ne(o,e){if(e?.aborted)return!0;const t=o instanceof Error?o.message:String(o||"");return/compact.*timed out|request timed out.*compact|adapter closed|aborted|cancelled/i.test(t)}function x(o){if(!o||typeof o!="object"||Array.isArray(o))return null;const e=o.sessionId;return typeof e=="string"&&e.trim()?e.trim():null}function h(o){return o&&typeof o=="object"&&!Array.isArray(o)?o:null}function A(o){const e=String(o||"").trim();return e?e==="read-only"?"read-only":["agent-full-access","full-access","bypassPermissions","danger-full-access"].includes(e)?"danger-full-access":"workspace-write":null}function k(o){const e=String(o||"").trim();return e?["agent-full-access","full-access","bypassPermissions","danger-full-access"].includes(e)?"never":e==="acceptEdits"||e==="accept-edits"?"on-failure":"on-request":null}function L(o){return{...o.model?{model:o.model}:{},...k(o.mode)?{approvalPolicy:k(o.mode)}:{},...A(o.mode)?{sandbox:A(o.mode)}:{},...o.configOptionValues??{}}}function ie(o){const e=o.params&&typeof o.params=="object"&&!Array.isArray(o.params)?o.params:o;for(const t of["command","cwd","reason","grantRoot","method"]){const s=e[t];if(typeof s=="string"&&s.trim())return $(s)}}function re(o){for(const e of["prompt","message","question","title"]){const t=o[e];if(typeof t=="string"&&t.trim())return $(t)}}function $(o){const e=o.replace(/\s+/g," ").trim();return e.length>120?`${e.slice(0,117)}...`:e}function ae(){const o=Number.parseInt(process.env.ACA_CODEX_ACP_PROMPT_TIMEOUT_MS??process.env.ACA_ACP_PROMPT_TIMEOUT_MS??"",10);return Number.isInteger(o)&&o>=3e4?o:360*60*1e3}function ce(o){const e=Number.isInteger(o)?Number(o):Number.parseInt(process.env.ACA_CODEX_ACP_COMPACT_TIMEOUT_MS??"",10);return Number.isFinite(e)&&e>=3e4?Math.min(e,6e5):12e4}function D(o){if(!o||typeof o!="object"||Array.isArray(o))return null;const e=o;return typeof e.sessionId=="string"&&e.sessionId.trim()?e.sessionId:null}function le(o){const e=h(o),t=h(e?._meta);return O(t?.acaContextUsage)}function O(o){const e=h(o);if(!e)return null;const t=y(e.contextWindow),s=y(e.usedTokens),r=y(e.inputTokens),n=String(e.state||"unknown"),i=["active","compacted","new_thread"].includes(n)?n:"unknown";return{usedTokens:s,inputTokens:r,contextWindow:t,ratio:t>0?Math.max(0,y(e.ratio)||s/t):0,observedAtMs:y(e.observedAtMs)||Date.now(),state:i,maintenanceAction:typeof e.maintenanceAction=="string"?e.maintenanceAction:null}}function y(o){return typeof o=="number"&&Number.isFinite(o)&&o>=0?o:0}export{ve as CodexAcpProvider,ee as codexAcpCapabilities};
1
+ import w from"node:fs";import{runAbortableRequest as T}from"../../client/abortable-request.js";import{BoundedItemBuffer as j}from"../../client/bounded-buffer.js";import{promptBlocksToAcpContent as z}from"../../client/acp-content.js";import{acpSessionUpdateFromParams as H,eventFromAcpSessionUpdate as N,statusFromAcpSessionUpdate as V,subtaskContextFromAcpSessionUpdate as B}from"../../client/acp-events.js";import{defaultAcpRuntimePool as M}from"../../client/acp-runtime-pool.js";import{resolveCodexAcpLaunch as q}from"./launch.js";import{cancelCodexRuntime as K}from"./cancellation.js";import{CodexPromptPerformanceTracker as X,CodexUsageLoopDetector as Y,codexRawResponseUsageFromAcpSessionUpdate as G,codexTokenUsageFromAcpSessionUpdate as J,readCodexContextMaintenancePolicy as E}from"./context-maintenance.js";import{ensureCodexProviderRuntimeConfig as U}from"../../../core/codex-provider-runtime-config.js";import{codexControlToolLoopFromAcpSessionUpdate as Q,controlToolLoopThresholds as Z,readCodexControlToolLoopPolicy as ee}from"./control-tool-loop.js";import{attachCodexDiagnostics as te,CodexTurnDiagnosticsTracker as oe}from"./turn-diagnostics.js";const se={sessionResume:!0,imageInput:!0,fileAttachment:!0,filesystem:!0,terminal:!0,permissionRequest:!0,configOptions:!0,usage:!0,contextUsage:!0,contextCompaction:!0,plan:!0,diff:!0};class xe{id="codex-acp";name="Codex ACP";capabilities=se;async createSession(e){return new ne(e.cwd,e.providerSessionId??null,e.timeoutMs)}async forkSession(e){if(!w.existsSync(e.cwd)||!w.statSync(e.cwd).isDirectory())throw new Error(`Project root does not exist or is not a directory: ${e.cwd}`);const t=e.sourceProviderSessionId.trim(),s=await U(),i=await M.acquire({providerId:"codex-acp",cwd:e.cwd,providerSessionId:t,runtimeIdentity:s?.runtimeIdentity||"default",env:D(s),...q(),onSessionUpdate:()=>{}});try{await L(i,s),i.initialized||(await i.client.initialize(6e4),i.markInitialized()),e.onStatus?.({phase:"forking",label:"正在分叉 Codex 会话",detail:t,updateType:"session/fork",atMs:Date.now()});const r=await i.client.forkSession({sessionId:t,cwd:e.cwd,config:$(e)},e.timeoutMs??12e4),n=k(r);if(!n)throw new Error("Codex ACP session/fork did not return sessionId");return{providerSessionId:n}}finally{i.release()}}}class ne{cwd;providerSessionId;defaultTimeoutMs;lease=null;sessionResponse=null;statusCallback=null;updateCallback=null;permissionCallback=null;userInputCallback=null;abortHandler=null;statusTimeline=[];updates=new j(16);content="";performanceTracker=null;usageLoopDetector=null;usagePatternReport=null;controlToolPatternReport=null;turnDiagnosticsTracker=null;usageLoopWarningActive=!1;controlToolLoopWarningActive=!1;cancellation=null;constructor(e,t,s=le()){this.cwd=e,this.providerSessionId=t,this.defaultTimeoutMs=s}async sendPrompt(e){if(!w.existsSync(e.cwd)||!w.statSync(e.cwd).isDirectory())throw new Error(`Project root does not exist or is not a directory: ${e.cwd}`);const t=Date.now();this.statusCallback=e.onStatus??null,this.updateCallback=e.onUpdate??null,this.permissionCallback=e.onPermissionRequest??null,this.userInputCallback=e.onUserInputRequest??null,this.attachAbortSignal(e.signal),this.content="",this.updates.clear(),this.statusTimeline.length=0;const s=E(),i=ee();this.performanceTracker=new X(t),this.usageLoopDetector=new Y(s);const r=new oe;this.turnDiagnosticsTracker=r,this.usageLoopWarningActive=!1,this.controlToolLoopWarningActive=!1;const n={error:null,stopping:null};let a=null,c=null,g=this.providerSessionId,x,I=null;try{this.emitStatus("initializing","Codex ACP 启动中","codex-acp");const u=await this.acquireRuntime(e.cwd);this.lease=u,a=u.client,c=u.started,await this.initialize(u),g=await this.establishSession(u,e),await this.applyConfig(a,g,e);const S=a,A=g;this.emitStatus("thinking","思考中"),I=this.startSlowStatusTimer(),this.usagePatternReport=d=>{if(n.error)return;const v=Math.round(d.maxInputTokens/1e3),f=`同一 Turn 已连续请求至少 ${d.sampleCount} 次,单次输入约 ${v}k tokens`;if(r.recordUsageLoop(d.sampleCount,d.detectedAtMs,f),d.sampleCount<s.usageLoopInterruptSamples){this.usageLoopWarningActive=!0,this.emitStatus("thinking","Codex 出现低产出内部循环",f,"aca.performance.low_output_loop");return}const _=`Codex 低产出内部循环:同一 Turn 已连续请求至少 ${d.sampleCount} 次,单次输入约 ${v}k tokens,且未观察到有效进展`;n.error=new Error(_),this.emitStatus("thinking","正在中断 Codex 低产出内部循环",f,"aca.performance.low_output_loop_interrupting"),n.stopping=this.stopRuntime(u,A).then(l=>{r.recordInterrupt({acknowledged:!l.forced,forcedRuntimeClose:l.forced,reason:l.reason});const h=l.forced?`${f};取消请求失败:${l.reason};已关闭运行实例`:f;l.forced&&(n.error=new Error(`${_};取消请求失败:${l.reason};已关闭运行实例`)),this.emitStatus("failed","Codex 已中断低产出内部循环",h,"aca.performance.low_output_loop_interrupted")}).catch(l=>{const h=l instanceof Error?l.message:String(l);r.recordInterrupt({acknowledged:!1,forcedRuntimeClose:!1,reason:h}),n.error=new Error(`${_};${h}`),this.emitStatus("failed","Codex 循环中断失败",h,"aca.performance.low_output_loop_interrupt_failed")})},this.controlToolPatternReport=d=>{if(n.error)return;const v=Z(d.kind,i);if(d.sampleCount<v.warningCalls)return;const f=d.kind==="list_agents_no_live"?"list_agents 查询不到运行中的子任务":d.kind==="wait_agent_no_live"?"无运行子任务时调用 wait_agent":"短时 sleep",_=Math.max(1,Math.round(d.elapsedMs/6e4)),l=`同一 Turn 已连续 ${d.sampleCount} 次执行 ${f},持续约 ${_} 分钟`;if(r.recordControlLoop(d,l),d.sampleCount<v.interruptCalls){this.controlToolLoopWarningActive=!0,this.emitStatus("thinking","Codex 出现无进展控制工具循环",l,"aca.performance.control_tool_loop");return}const h=`Codex 无进展控制工具循环:${l}`;n.error=new Error(h),this.emitStatus("thinking","正在中断 Codex 无进展控制工具循环",l,"aca.performance.control_tool_loop_interrupting"),n.stopping=this.stopRuntime(u,A).then(p=>{r.recordInterrupt({acknowledged:!p.forced,forcedRuntimeClose:p.forced,reason:p.reason});const y=p.forced?`${l};取消请求失败:${p.reason};已关闭运行实例`:l;p.forced&&(n.error=new Error(`${h};取消请求失败:${p.reason};已关闭运行实例`)),this.emitStatus("failed","Codex 已中断无进展控制工具循环",y,"aca.performance.control_tool_loop_interrupted")}).catch(p=>{const y=p instanceof Error?p.message:String(p);r.recordInterrupt({acknowledged:!1,forcedRuntimeClose:!1,reason:y}),n.error=new Error(`${h};${y}`),this.emitStatus("failed","Codex 控制工具循环中断失败",y,"aca.performance.control_tool_loop_interrupt_failed")})};try{x=await this.promptWithAbort(S,{sessionId:A,prompt:z(e.prompt,e.promptBlocks),timeoutMs:e.timeoutMs??this.defaultTimeoutMs,signal:e.signal})}catch(d){throw await n.stopping,n.error?n.error:d}if(await n.stopping,n.error)throw n.error;const C=W(x);C&&C!==g&&(g=C,this.providerSessionId=C,u.setProviderSession({providerSessionId:C,sessionResponse:this.sessionResponse}))}catch(u){if(u instanceof Error){const S=te(u,r.snapshot());throw S.statusTimeline=[...this.statusTimeline],S.performance=this.performanceTracker?.snapshot(),S}throw u}finally{await n.stopping,await this.cancellation?.catch(()=>{}),I&&clearInterval(I),this.usagePatternReport=null,this.controlToolPatternReport=null,this.turnDiagnosticsTracker=null,this.usageLoopDetector=null,this.usageLoopWarningActive=!1,this.controlToolLoopWarningActive=!1,this.abortHandler&&e.signal&&(e.signal.removeEventListener("abort",this.abortHandler),this.abortHandler=null),this.releaseLease()}return this.emitStatus("completed","已完成"),{providerSessionId:g,cliType:"builtin",agentType:"codex",effectiveConfig:{model:e.model??null,mode:e.mode??null,configOptionValues:e.configOptionValues??null,command:c.command,processPid:c.child.pid??null,provider:"codex-acp",protocol:"acp"},command:c.command,args:c.args,cwd:e.cwd,exitCode:c.child.exitCode,signal:c.child.signalCode,durationMs:Date.now()-t,stdout:a.rpc.stdout,stderr:a.rpc.stderr,content:this.content.trim(),updateCount:this.updates.count(),updates:this.updates.snapshot(),statusTimeline:[...this.statusTimeline],performance:this.performanceTracker?.snapshot(),contextUsage:ue(x),providerDiagnostics:r.snapshot(),promptResponse:x}}async compactContext(e){if(!w.existsSync(e.cwd)||!w.statSync(e.cwd).isDirectory())throw new Error(`Project root does not exist or is not a directory: ${e.cwd}`);const t=Date.now();this.statusCallback=e.onStatus??null,this.updateCallback=e.onUpdate??null,this.attachAbortSignal(e.signal);let s=this.providerSessionId,i;try{this.emitStatus("initializing","Codex ACP 启动中","codex-acp");const n=await this.acquireRuntime(e.cwd);this.lease=n,await this.initialize(n),s=await this.establishSession(n,e),await this.applyConfig(n.client,s,e);const a=de(e.timeoutMs);i=await this.compactWithAbort(n.client,{sessionId:s,allowRollover:e.allowRollover===!0,timeoutMs:a,signal:e.signal});const c=W(i);c&&c!==s&&(s=c,this.providerSessionId=c,n.setProviderSession({providerSessionId:c,sessionResponse:this.sessionResponse}))}catch(n){if(this.lease&&ie(n,e.signal)){const a=this.lease;this.lease=null,await a.dispose().catch(()=>{})}throw this.emitStatus("failed","上下文压缩失败",n instanceof Error?n.message:String(n)),n}finally{this.abortHandler&&e.signal&&(e.signal.removeEventListener("abort",this.abortHandler),this.abortHandler=null),this.releaseLease()}const r=m(i)??{};return{providerSessionId:s||this.providerSessionId||"",contextUsage:F(r.contextUsage),maintenance:m(r.maintenance)??{},durationMs:Date.now()-t}}async cancel(){this.providerSessionId&&this.lease&&await this.stopRuntime(this.lease,this.providerSessionId)}stopRuntime(e,t){if(this.cancellation)return this.cancellation;const s=K(e,t);return this.cancellation=s,s.finally(()=>{this.cancellation===s&&(this.cancellation=null)}).catch(()=>{}),s}async close(){await this.cancellation?.catch(()=>{}),this.statusCallback=null,this.updateCallback=null,this.permissionCallback=null,this.userInputCallback=null,this.abortHandler&&(this.abortHandler=null),this.releaseLease()}async acquireRuntime(e){const t=q(),s=await U(),i=await M.acquire({providerId:"codex-acp",cwd:e,providerSessionId:this.providerSessionId,runtimeIdentity:s?.runtimeIdentity||"default",env:D(s),command:t.command,args:t.args,onSessionUpdate:r=>this.handleSessionUpdate(r),onClientRequest:async(r,n)=>{if(r==="session/request_permission")return this.requestPermission(n);if(r==="elicitation/create"||r==="session/request_user_input")return this.requestUserInput(n);throw new Error(`Unsupported Codex ACP client request: ${r}`)}});try{return await L(i,s),i}catch(r){throw await i.dispose().catch(()=>{}),r}}async initialize(e){e.initialized||(await e.client.initialize(6e4),e.markInitialized())}async establishSession(e,t){const s=e.client,i=$(t);if(e.providerSessionId&&e.sessionEstablished)return this.providerSessionId=e.providerSessionId,this.sessionResponse=e.sessionResponse,e.providerSessionId;if(this.providerSessionId)try{this.emitStatus("resuming","Codex ACP 会话恢复中","session/load");const a=await s.loadSession({sessionId:this.providerSessionId,cwd:t.cwd,config:i},6e4);this.sessionResponse=m(a);const c=k(a)||this.providerSessionId;return this.providerSessionId=c,e.setProviderSession({providerSessionId:c,sessionResponse:this.sessionResponse}),c}catch(a){if(t.requireSessionResume){const c=a instanceof Error?a.message:String(a);throw new Error(`无法恢复导入的 Codex ACP Session ${this.providerSessionId}:${c}`)}this.providerSessionId=null}this.emitStatus("acp","Codex ACP 会话创建中","session/new");const r=await s.newSession({cwd:t.cwd,config:i},12e4);this.sessionResponse=m(r);const n=k(r);if(!n)throw new Error("Codex ACP session/new did not return sessionId");return this.providerSessionId=n,e.setProviderSession({providerSessionId:n,sessionResponse:this.sessionResponse}),n}async applyConfig(e,t,s){s.model&&await e.setSessionConfigOption({sessionId:t,configId:"model",value:s.model},6e4).catch(()=>{});const i=R(s.mode);i&&await e.setSessionConfigOption({sessionId:t,configId:"approvalPolicy",value:i},6e4).catch(()=>{});const r=P(s.mode);r&&await e.setSessionConfigOption({sessionId:t,configId:"sandbox",value:r},6e4).catch(()=>{});for(const[n,a]of Object.entries(s.configOptionValues??{}))["model","mode","sandbox","approvalPolicy"].includes(n)||await e.setSessionConfigOption({sessionId:t,configId:n,value:a},6e4).catch(()=>{})}handleSessionUpdate(e){const t=H(e),s=String(t?.update.sessionUpdate||""),i=B(e);if(!i&&s==="aca_codex_subtask_drain"){const a=String(t?.update.label||"子任务收尾等待超时"),c=String(t?.update.detail||"仍有子任务未结束");this.emitStatus("exploring",a,c,s)}else if(!i&&s==="aca_codex_control_tool_loop"){const a=Q(e);a?.state==="observed"&&this.controlToolPatternReport?.(a.detection),a?.state==="recovered"&&this.controlToolLoopWarningActive&&(this.controlToolLoopWarningActive=!1,this.turnDiagnosticsTracker?.recordRecovery(),this.emitStatus("thinking","Codex 已恢复执行","无进展控制工具循环已结束","aca.performance.control_tool_loop_recovered"))}else if(re(s,t?.update))this.recordMeaningfulProgress();else if(!i&&s==="aca_codex_raw_response_usage"){const a=G(e);a&&this.turnDiagnosticsTracker?.recordUsage(a);const c=a?this.usageLoopDetector?.recordUsage(a):null;c&&this.usagePatternReport?.(c)}else if(!i&&s==="usage_update"){const a=J(e),c=a?this.usageLoopDetector?.recordUsage(a):null;c&&this.usagePatternReport?.(c)}this.updates.push({method:"session/update",params:e});const r=N(e);r&&(r.type==="agent_message_chunk"&&!i&&(this.content+=r.text??""),this.updateCallback?.(r));const n=V(e);n&&this.emitStatus(n.phase,n.label,n.detail,n.updateType)}recordMeaningfulProgress(){this.performanceTracker?.recordEvent();const e=this.usageLoopDetector?.recordMeaningfulEvent();this.usageLoopWarningActive&&(this.usageLoopWarningActive=!1,e&&this.turnDiagnosticsTracker?.recordRecovery(),this.emitStatus("thinking","Codex 已恢复执行","已重新执行工具或产生有效输出","aca.performance.loop_recovered"))}startSlowStatusTimer(){const e=E().slowEventThresholdMs,t=setInterval(()=>{const s=this.performanceTracker,i=Date.now();if(!s?.shouldReportSlow(i,e))return;const r=s.snapshot(i),n=Math.max(1,Math.round(r.silentForMs/6e4)),a=this.usageLoopWarningActive||this.controlToolLoopWarningActive?"Codex 可能卡在内部循环":"Codex 长时间没有有效进展";this.emitStatus("thinking",a,`已 ${n} 分钟没有执行工具或产生有效输出`,"aca.performance.slow")},Math.min(3e4,Math.max(1e3,Math.floor(e/4))));return t.unref(),t}attachAbortSignal(e){if(e){if(this.abortHandler=()=>{this.cancel().catch(()=>{})},e.aborted)throw new Error("Codex ACP prompt cancelled");e.addEventListener("abort",this.abortHandler,{once:!0})}}async promptWithAbort(e,t){return T({signal:t.signal,cancelledMessage:"Codex ACP prompt cancelled",startRequest:()=>e.prompt({sessionId:t.sessionId,prompt:t.prompt},t.timeoutMs),onAbort:async()=>{await this.lease?.dispose()}})}async compactWithAbort(e,t){return T({signal:t.signal,cancelledMessage:"Codex ACP 上下文压缩已取消",startRequest:()=>e.compactSession({sessionId:t.sessionId,allowRollover:t.allowRollover,timeoutMs:t.timeoutMs},t.timeoutMs),onAbort:async()=>{await this.lease?.dispose()}})}async requestPermission(e){if(!this.permissionCallback)return{outcome:{outcome:"cancelled"}};const t=e&&typeof e=="object"&&!Array.isArray(e)?e:{},s=t.request&&typeof t.request=="object"&&!Array.isArray(t.request)?t.request:t,r=(Array.isArray(s.options)?s.options:[]).map((n,a)=>{const c=n&&typeof n=="object"&&!Array.isArray(n)?n:{};return{optionId:String(c.optionId??c.id??a),kind:String(c.kind??"choice"),...typeof c.label=="string"?{label:c.label}:{}}});return this.emitStatus("requestPermission","等待授权",ae(s),"session/request_permission"),this.permissionCallback({requestId:String(s.requestId??s.id??`codex-acp-permission-${Date.now()}`),params:e,options:r})}async requestUserInput(e){if(!this.userInputCallback)return{outcome:{outcome:"cancelled"}};const t=e&&typeof e=="object"&&!Array.isArray(e)?e:{},s=t.request&&typeof t.request=="object"&&!Array.isArray(t.request)?t.request:t;return this.emitStatus("requestPermission","等待输入",ce(s),"elicitation/create"),this.userInputCallback({requestId:String(s.requestId??s.id??`codex-acp-input-${Date.now()}`),prompt:String(s.prompt??s.message??s.question??""),params:e,...typeof s.defaultValue=="string"?{defaultValue:s.defaultValue}:{}})}emitStatus(e,t,s,i){const r={phase:e,label:t,...s?{detail:s}:{},...i?{updateType:i}:{},atMs:Date.now()},n=this.statusTimeline[this.statusTimeline.length-1];n&&n.phase===r.phase&&n.label===r.label&&n.detail===r.detail&&n.updateType===r.updateType||(this.statusTimeline.push(r),this.statusCallback?.(r))}releaseLease(){this.lease?.release(),this.lease=null}}function re(o,e){if(o==="agent_message_chunk"){const t=m(e?.content);return typeof t?.text=="string"&&!!t.text.trim()}if(o==="agent_progress_chunk"||o==="agent_thought_chunk")return!1;if(o==="tool_call"||o==="tool_call_update"){const t=m(e?._meta),s=m(t?.aca),i=String(s?.action||"");return!(s?.processKind==="collaboration_wait"||i==="wait"||i==="listAgents")}return o==="plan"||o==="plan_update"||o==="plan_removed"||o==="aca_context_maintenance"}async function L(o,e){o.runtimeConfigured||(await o.client.configureRuntime({codexProvider:e?{baseUrl:e.baseUrl,apiKey:e.apiKey,revision:e.revision}:null}),o.markRuntimeConfigured())}function D(o){return o?{OPENAI_API_KEY:o.apiKey,OPENAI_BASE_URL:o.baseUrl}:{}}function ie(o,e){if(e?.aborted)return!0;const t=o instanceof Error?o.message:String(o||"");return/compact.*timed out|request timed out.*compact|adapter closed|aborted|cancelled/i.test(t)}function k(o){if(!o||typeof o!="object"||Array.isArray(o))return null;const e=o.sessionId;return typeof e=="string"&&e.trim()?e.trim():null}function m(o){return o&&typeof o=="object"&&!Array.isArray(o)?o:null}function P(o){const e=String(o||"").trim();return e?e==="read-only"?"read-only":["agent-full-access","full-access","bypassPermissions","danger-full-access"].includes(e)?"danger-full-access":"workspace-write":null}function R(o){const e=String(o||"").trim();return e?["agent-full-access","full-access","bypassPermissions","danger-full-access"].includes(e)?"never":e==="acceptEdits"||e==="accept-edits"?"on-failure":"on-request":null}function $(o){return{...o.model?{model:o.model}:{},...R(o.mode)?{approvalPolicy:R(o.mode)}:{},...P(o.mode)?{sandbox:P(o.mode)}:{},...o.configOptionValues??{}}}function ae(o){const e=o.params&&typeof o.params=="object"&&!Array.isArray(o.params)?o.params:o;for(const t of["command","cwd","reason","grantRoot","method"]){const s=e[t];if(typeof s=="string"&&s.trim())return O(s)}}function ce(o){for(const e of["prompt","message","question","title"]){const t=o[e];if(typeof t=="string"&&t.trim())return O(t)}}function O(o){const e=o.replace(/\s+/g," ").trim();return e.length>120?`${e.slice(0,117)}...`:e}function le(){const o=Number.parseInt(process.env.ACA_CODEX_ACP_PROMPT_TIMEOUT_MS??process.env.ACA_ACP_PROMPT_TIMEOUT_MS??"",10);return Number.isInteger(o)&&o>=3e4?o:360*60*1e3}function de(o){const e=Number.isInteger(o)?Number(o):Number.parseInt(process.env.ACA_CODEX_ACP_COMPACT_TIMEOUT_MS??"",10);return Number.isFinite(e)&&e>=3e4?Math.min(e,6e5):12e4}function W(o){if(!o||typeof o!="object"||Array.isArray(o))return null;const e=o;return typeof e.sessionId=="string"&&e.sessionId.trim()?e.sessionId:null}function ue(o){const e=m(o),t=m(e?._meta);return F(t?.acaContextUsage)}function F(o){const e=m(o);if(!e)return null;const t=b(e.contextWindow),s=b(e.usedTokens),i=b(e.inputTokens),r=String(e.state||"unknown"),n=["active","compacted","new_thread"].includes(r)?r:"unknown";return{usedTokens:s,inputTokens:i,contextWindow:t,ratio:t>0?Math.max(0,b(e.ratio)||s/t):0,observedAtMs:b(e.observedAtMs)||Date.now(),state:n,maintenanceAction:typeof e.maintenanceAction=="string"?e.maintenanceAction:null}}function b(o){return typeof o=="number"&&Number.isFinite(o)&&o>=0?o:0}export{xe as CodexAcpProvider,se as codexAcpCapabilities};
@@ -1 +1 @@
1
- class S{routes=new Map;reset(){this.routes.clear()}routeFor(t){return t?this.routes.get(t)??null:null}routesForSubscription(){return[...this.routes.values()]}restoreFromSessionSnapshot(t,e){const n=i(t),o=i(n.thread),s=d(o,"id")||e,a=i(n.initialTurnsPage),c=[...I(o,"turns"),...I(a,"data")];let l=0;for(const p of c){const y=i(p);for(const h of I(y,"items"))l+=this.observe({threadId:s,item:h},e)}return l}observe(t,e){const n=i(t),o=i(n.item);if(d(o,"type")==="subAgentActivity")return this.observeSubAgentActivity(o,d(n,"threadId"),e);if(!b(o))return 0;const s=d(o,"id");if(!s)return 0;const a=d(o,"senderThreadId")||d(n,"threadId"),c=this.routeFor(a||null),l=c?.parentToolCallId||s,p=Math.min(8,(c?.depth??0)+1),y=g(o);let h=0;for(const u of y)!u||u===e||u===a||this.routes.has(u)||(this.routes.set(u,{parentToolCallId:l,sourceThreadId:u,depth:p}),h+=1);return h}observeSubAgentActivity(t,e,n){if(d(t,"kind")!=="started")return 0;const o=d(t,"agentThreadId"),s=this.routeFor(e||null),a=s?.parentToolCallId||d(t,"id"),c=Math.min(8,(s?.depth??0)+1);return!o||!a||o===n||this.routes.has(o)?0:(this.routes.set(o,{parentToolCallId:a,sourceThreadId:o,depth:c}),1)}}function g(r){const t=i(r),e=new Set;f(e,t.newThreadId),f(e,t.receiverThreadId),m(e,t.receiverThreadIds);const n=t.agentsStates;if(Array.isArray(n))for(const o of n)T(e,o);else{const o=i(n);for(const[s,a]of Object.entries(o))T(e,a),A(s)&&e.add(s)}return[...e]}function b(r){const t=d(i(r),"type");return t==="collabAgentToolCall"||t==="collabToolCall"}function T(r,t){const e=i(t);for(const n of["threadId","thread_id","receiverThreadId","newThreadId"])f(r,e[n])}function f(r,t){typeof t=="string"&&t.trim()&&r.add(t.trim())}function m(r,t){if(Array.isArray(t))for(const e of t)f(r,e)}function I(r,t){const e=r[t];return Array.isArray(e)?e:[]}function A(r){return r.length<8?!1:!["pending","running","completed","failed","cancelled","canceled"].includes(r.toLowerCase())}function i(r){return r&&typeof r=="object"&&!Array.isArray(r)?r:{}}function d(r,t){const e=r[t];return typeof e=="string"?e:""}export{S as CodexSubtaskRouter,g as collaborationChildThreadIds,b as isCollaborationItem};
1
+ class C{routes=new Map;statuses=new Map;idleWaiters=new Set;reset(){this.routes.clear(),this.statuses.clear(),this.resolveIdleWaiters(!1)}routeFor(t){return t?this.routes.get(t)??null:null}routesForSubscription(){return[...this.routes.values()]}activeThreadIds(){return[...this.statuses.entries()].filter(([,t])=>t==="pending"||t==="running").map(([t])=>t)}hasActiveSubtasks(){return this.activeThreadIds().length>0}waitForIdle(t){return this.activeThreadIds().length===0?Promise.resolve({timedOut:!1,activeThreadIds:[]}):new Promise(n=>{const r={resolve:n,timer:setTimeout(()=>{this.idleWaiters.delete(r),n({timedOut:!0,activeThreadIds:this.activeThreadIds()})},t)};r.timer.unref(),this.idleWaiters.add(r)})}stopWaiting(){this.resolveIdleWaiters(!1)}restoreFromSessionSnapshot(t,e){const n=a(t),r=a(n.thread),i=o(r,"id")||e,d=a(n.initialTurnsPage),u=[...g(r,"turns"),...g(d,"data")];let l=0;for(const h of u){const p=a(h);for(const I of g(p,"items"))l+=this.observe({threadId:i,item:I},e)}return l}observe(t,e){const n=a(t),r=a(n.item);if(o(r,"type")==="subAgentActivity")return this.observeSubAgentActivity(r,o(n,"threadId"),e);if(!A(r))return 0;const i=o(r,"id");if(!i)return 0;const d=o(r,"senderThreadId")||o(n,"threadId"),u=this.routeFor(d||null),l=u?.parentToolCallId||i,h=Math.min(8,(u?.depth??0)+1),p=v(r),I=y(r.agentsStates);let m=0;for(const c of p){if(!c||c===e||c===d)continue;this.routes.has(c)||(this.routes.set(c,{parentToolCallId:l,sourceThreadId:c,depth:h}),m+=1);const T=I.get(c);T?this.updateStatus(c,T):o(r,"tool")==="spawnAgent"&&this.updateStatus(c,"pending")}return m}observeSubAgentActivity(t,e,n){const r=o(t,"kind"),i=o(t,"agentThreadId");if(!i||i===n)return 0;if(r==="completed"||r==="interrupted")return this.updateStatus(i,r==="completed"?"completed":"interrupted"),0;if(r!=="started"&&r!=="interacted")return 0;const d=this.routeFor(e||null),u=d?.parentToolCallId||o(t,"id"),l=Math.min(8,(d?.depth??0)+1);if(!u)return 0;const h=this.routes.has(i)?0:1;return h&&this.routes.set(i,{parentToolCallId:u,sourceThreadId:i,depth:l}),this.updateStatus(i,"running"),h}updateStatus(t,e){this.statuses.set(t,e),this.hasActiveSubtasks()||this.resolveIdleWaiters(!1)}resolveIdleWaiters(t){const e=this.activeThreadIds();for(const n of this.idleWaiters)clearTimeout(n.timer),n.resolve({timedOut:t,activeThreadIds:e});this.idleWaiters.clear()}}function y(s){const t=new Map,e=a(s);for(const[n,r]of Object.entries(e)){const i=b(o(a(r),"status"));n.trim()&&i!=="unknown"&&t.set(n.trim(),i)}return t}function b(s){switch(s.toLowerCase()){case"pending":case"pendinginit":return"pending";case"running":case"inprogress":case"in_progress":return"running";case"completed":case"shutdown":return"completed";case"failed":case"errored":case"notfound":return"failed";case"cancelled":case"canceled":case"interrupted":return"interrupted";default:return"unknown"}}function v(s){const t=a(s),e=new Set;f(e,t.newThreadId),f(e,t.receiverThreadId),w(e,t.receiverThreadIds);const n=t.agentsStates;if(Array.isArray(n))for(const r of n)S(e,r);else{const r=a(n);for(const[i,d]of Object.entries(r))S(e,d),k(i)&&e.add(i)}return[...e]}function A(s){const t=o(a(s),"type");return t==="collabAgentToolCall"||t==="collabToolCall"}function S(s,t){const e=a(t);for(const n of["threadId","thread_id","receiverThreadId","newThreadId"])f(s,e[n])}function f(s,t){typeof t=="string"&&t.trim()&&s.add(t.trim())}function w(s,t){if(Array.isArray(t))for(const e of t)f(s,e)}function g(s,t){const e=s[t];return Array.isArray(e)?e:[]}function k(s){return s.length<8?!1:!["pending","running","completed","failed","cancelled","canceled"].includes(s.toLowerCase())}function a(s){return s&&typeof s=="object"&&!Array.isArray(s)?s:{}}function o(s,t){const e=s[t];return typeof e=="string"?e:""}export{C as CodexSubtaskRouter,v as collaborationChildThreadIds,A as isCollaborationItem};
@@ -0,0 +1 @@
1
+ class o{seenResponseIds=new Set;samples=[];threadId=null;turnId=null;sumInputTokens=0;sumCachedInputTokens=0;sumCacheWriteInputTokens=0;sumOutputTokens=0;sumReasoningOutputTokens=0;sumTotalTokens=0;maxInputTokens=0;trigger=null;interrupt=null;recordUsage(t){t.source==="raw_response"&&(t.responseId&&this.seenResponseIds.has(t.responseId)||(t.responseId&&this.seenResponseIds.add(t.responseId),this.threadId=t.threadId??this.threadId,this.turnId=t.turnId??this.turnId,this.sumInputTokens+=t.inputTokens,this.sumCachedInputTokens+=t.cachedInputTokens,this.sumCacheWriteInputTokens+=t.cacheWriteInputTokens,this.sumOutputTokens+=t.outputTokens,this.sumReasoningOutputTokens+=t.reasoningOutputTokens,this.sumTotalTokens+=t.totalTokens,this.maxInputTokens=Math.max(this.maxInputTokens,t.inputTokens),this.samples.push({responseId:t.responseId,observedAtMs:t.observedAtMs,inputTokens:t.inputTokens,cachedInputTokens:t.cachedInputTokens,outputTokens:t.outputTokens,reasoningOutputTokens:t.reasoningOutputTokens,totalTokens:t.totalTokens}),this.samples.length>32&&this.samples.shift()))}recordUsageLoop(t,s,n){this.trigger?this.trigger.kind==="low_output_loop"&&this.trigger.recoveredAtMs===null&&(this.trigger.sampleCount=Math.max(this.trigger.sampleCount,t),this.trigger.detail=n):this.trigger={kind:"low_output_loop",sampleCount:t,detectedAtMs:s,detail:n,recoveredAtMs:null}}recordControlLoop(t,s){this.threadId=t.threadId??this.threadId,this.turnId=t.turnId??this.turnId,this.trigger?this.trigger.kind==="control_tool_loop"&&this.trigger.recoveredAtMs===null&&(this.trigger.sampleCount=Math.max(this.trigger.sampleCount,t.sampleCount),this.trigger.detail=s):this.trigger={kind:"control_tool_loop",sampleCount:t.sampleCount,detectedAtMs:t.detectedAtMs,detail:s,recoveredAtMs:null}}recordRecovery(t=Date.now()){this.trigger&&!this.interrupt&&(this.trigger.recoveredAtMs=t)}recordInterrupt(t){this.interrupt={acknowledged:t.acknowledged,forcedRuntimeClose:t.forcedRuntimeClose,reason:t.reason??null}}snapshot(){return{threadId:this.threadId,turnId:this.turnId,responseCount:this.seenResponseIds.size||this.samples.length,sumInputTokens:this.sumInputTokens,sumCachedInputTokens:this.sumCachedInputTokens,sumCacheWriteInputTokens:this.sumCacheWriteInputTokens,sumOutputTokens:this.sumOutputTokens,sumReasoningOutputTokens:this.sumReasoningOutputTokens,sumTotalTokens:this.sumTotalTokens,maxInputTokens:this.maxInputTokens,samples:[...this.samples],trigger:this.trigger?{...this.trigger}:null,interrupt:this.interrupt?{...this.interrupt}:null}}}function r(e,t){return e.providerDiagnostics=t,e}export{o as CodexTurnDiagnosticsTracker,r as attachCodexDiagnostics};
@@ -1,6 +1,6 @@
1
- import H from"node:crypto";import{appendSessionReferenceContext as G}from"./session-reference-prompt.js";import M from"node:fs";import j from"node:path";import{compactAgentContext as K,forkAgentSession as X,sendAgentPrompt as Z}from"../acp/agent.js";import{acaServerDownload as Q,acaServerRequest as S}from"./aca-server-client.js";import{loadAcaConfig as Y}from"./aca-config.js";import{prepareLocalWorktree as ee}from"./worktree.js";import{expandWorkerRuntimePath as te}from"./worker-runtime-paths.js";import{collectAffectedFiles as O,extractAffectedFilePathsFromAcpRaw as ne}from"./affected-files.js";import{resolveJobPermissionPolicy as oe}from"./job-permission-policy.js";import{appendCoalescedAcpEvent as ie}from"./acp-event-coalescer.js";import{shouldBufferAcpEvent as ae,shouldUploadAcpEvent as re}from"./acp-event-upload-filter.js";import{userFacingAcpError as se}from"../acp/error-display.js";import{ensureCodexProviderRuntimeRevision as ce}from"./codex-provider-runtime-config.js";import{acpSessionInvalidationFromError as le}from"../acp/session-errors.js";const ue=1500,F=5e3,z=1e3,de=200,pe=64,U=900,me=1e4,fe=0,he=12e3;function we(e){return te(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,o=null,i=null,r=0,s=0;const u=new Set,a=new Map,p=new Map,v=async()=>{if(n||Date.now()<s)return;const w=Se();if(B(u.size,w))try{const m=t.loadConfig?.()??Y();if(!m.token)return;for(;!n&&B(u.size,w);){const _=t.claimNextJob?await t.claimNextJob(m,[...a.keys()]):await S("POST","/api/client/jobs/claim",{machineId:m.machineId,workspaceId:m.defaultWorkspaceId,activeSessionIds:[...a.keys()]});if(r=0,s=0,!_.item)break;const h=_.item;if(n){await(t.cancelClaimedJob?t.cancelClaimedJob(h,m):k(h.job_id,m.machineId,"cancelled")).catch(()=>{});break}const f=h.session_id,P=a.get(f);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=${f}; waiting for the previous run to release its ACP runtime`),await ye(P,h,m,A)),!A.signal.aborted&&await(t.runJob??ve)(h,m,e,A)})().catch(b=>{e.warn(`job ${h.job_id} failed outside handler: ${b instanceof Error?b.message:String(b)}`)}).finally(()=>{u.delete(h.job_id),a.get(f)===d&&a.delete(f),p.delete(h.job_id)}),a.set(f,d),p.set(h.job_id,{controller:A,promise:d})}}catch(m){if(!n){r+=1;const _=qe(r);s=Date.now()+_,e.warn(`job worker failed, retry in ${_}ms: ${m instanceof Error?m.message:String(m)}`)}}},T=()=>{if(n||o)return;const w=v();o=w,w.finally(()=>{o===w&&(o=null)})};T();const l=setInterval(()=>{T()},t.claimIntervalMs??ue);return{timer:l,stop:()=>i||(i=(async()=>{if(n)return;n=!0,clearInterval(l);for(const m of p.values())m.controller.abort();await o?.catch(()=>{});for(const m of p.values())m.controller.abort();const w=[...p.values()].map(m=>m.promise);w.length!==0&&await Promise.race([Promise.allSettled(w).then(()=>{}),N(he)])})(),i)}}async function ye(e,t,n,o){let i=!1;const s=setInterval(()=>{i||o.signal.aborted||(i=!0,Promise.allSettled([S("POST",`/api/client/jobs/${encodeURIComponent(t.job_id)}/heartbeat`,{machineId:n.machineId}),W(t.job_id,o)]).finally(()=>{i=!1}))},F);try{await Promise.race([e,Ie(o.signal)])}finally{clearInterval(s)}}function Ie(e){return e.aborted?Promise.resolve():new Promise(t=>e.addEventListener("abort",()=>t(),{once:!0}))}function Se(){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?fe:t===0?0:Math.max(1,Math.min(t,8))}function B(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)):de}async function ve(e,t,n,o){const i=e.job_id,r=e.session_id,s=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(i,t.machineId,"job input missing message");return}const v=t.projects.find(d=>d.projectId===u),T=we(v?.rootPath||e.project_root_path||"");if(!T){await k(i,t.machineId,"job project root is unknown");return}be(e)&&M.mkdirSync(T,{recursive:!0}),n.info(`claimed job=${i} session=${r} project=${v?.name??u??"unknown"}`);let l=!1,w=!1;const m=setInterval(()=>{l||(l=!0,S("POST",`/api/client/jobs/${encodeURIComponent(i)}/heartbeat`,{machineId:t.machineId}).catch(()=>{}).finally(()=>{l=!1})),w||(w=!0,W(i,o).catch(()=>{}).finally(()=>{w=!1}))},F);let _=0;const h=Re(i,t.machineId),f=Ee(i,t.machineId),P=new Set;let A=null;try{await f.sendNow({phase:"initializing",label:"准备工作区",detail:e.worktree_id?"worktree":"project root"}).catch(()=>{});const d=ee({projectId:u,sessionId:r,projectRoot:T,worktree:e});A=d,await Be(i,t.machineId,d);const b=Te(a.acpRun);if(Ae(b.agentType)&&await ce(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 c=await K({agentType:x(b.agentType,"codex"),cliType:x(b.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:o.signal,onStatus:g=>{f.report({phase:g.phase,label:g.label,detail:g.detail,updateType:g.updateType,raw:g})}});await f.flush(),await S("POST",`/api/client/jobs/${encodeURIComponent(i)}/complete`,{machineId:t.machineId,acpSessionId:c.acpSessionId,suppressTurn:!0,result:{ok:!0,control:{type:"compact",allowRollover:a.control?.allowRollover===!0},acpSessionId:c.acpSessionId,durationMs:c.durationMs,usage:c.contextUsage,maintenance:c.maintenance,worktree:R(d)}}),n.info(`completed context compact job=${i}`);return}if(p==="fork"){const c=String(a.control?.sourceAcpSessionId||"").trim();if(!c)throw new Error("Source session does not have a Codex ACP context to fork");await f.sendNow({phase:"forking",label:"正在分叉 Codex 会话",detail:a.control?.sourceSessionId?`源会话 ${a.control.sourceSessionId}`:void 0}).catch(()=>{});const g=await X({agentType:x(b.agentType,"codex"),cliType:x(b.cliType,"builtin"),sourceProviderSessionId:c,cwd:d.cwd,model:a.model,mode:a.mode,configOptionValues:a.configOptionValues,timeoutMs:a.timeoutMs,signal:o.signal,onStatus:C=>{f.report({phase:C.phase,label:C.label,detail:C.detail,updateType:C.updateType,raw:C})}});await f.flush(),await S("POST",`/api/client/jobs/${encodeURIComponent(i)}/complete`,{machineId:t.machineId,acpSessionId:g.acpSessionId,suppressTurn:!0,result:{ok:!0,control:{type:"fork",sourceSessionId:a.control?.sourceSessionId||null},acpSessionId:g.acpSessionId,worktree:R(d)}}),n.info(`completed Codex session fork job=${i}`);return}const y=await Ne({cwd:d.cwd,sessionId:r,jobId:i,attachments:a.attachments??[]});y.length>0&&(n.info(`materialized ${y.length} attachment(s) for job=${i}`),await f.sendNow({phase:"initializing",label:"附件已写入本机",detail:y.map(c=>c.relativePath).join(", "),raw:{attachments:L(y)}}).catch(()=>{}));const E=je(a,y),q=Me(E,y),I=await Z({agentType:x(b.agentType,"codex"),cliType:x(b.cliType,"builtin"),acpSessionId:e.acp_session_id??null,requireSessionResume:_e(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:Ce(b.contextWindow),signal:o.signal,onStatus:c=>{f.report({phase:c.phase,label:c.label,detail:c.detail,updateType:c.updateType,raw:c})},onUpdate:c=>{_+=1;for(const C of ne(c.raw))P.add(C);const g={turnId:e.user_turn_id??null,eventType:c.type,label:c.label,text:c.text,status:c.status,toolCallId:c.toolCallId,raw:$e(c.raw,_)};ae(g)&&h.push(g)},onPermissionRequest:c=>{const g=oe(a,c);return g?Promise.resolve(g):Je(i,t.machineId,c,o.signal)},onUserInputRequest:c=>Le(i,t.machineId,c,o.signal)});await h.flush(),await f.flush();const D=O({cwd:d.cwd,evidencePaths:P}),$=Pe(I.promptResponse);if(!(xe($)||!!I.content)){await k(i,t.machineId,`${I.agentType} stopped: ${$??"unknown"}`,{acpSessionId:I.acpSessionId,stopReason:$,exitCode:I.exitCode,stderr:De(I.stderr,4e3),worktree:R(d)});return}await S("POST",`/api/client/jobs/${encodeURIComponent(i)}/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,updateCount:I.updateCount,statusTimeline:I.statusTimeline,worktree:R(d),attachments:L(y),files:D}}),n.info(`completed job=${i}`)}catch(d){if(Ve(o.signal)){h.discard(),f.discard(),n.warn(`stopped local job=${i} because ACA Server already finalized it`);return}await h.flush(),await f.flush();const b=o.signal.aborted?"cancelled":d instanceof Error?d.message:String(d),y=le(d),E=A?O({cwd:A.cwd,evidencePaths:P}):[];await k(i,t.machineId,se(b),{rawError:b,files:E,...y?{acpSessionInvalidated:!0,invalidatedAcpSessionId:y.providerSessionId,failure:{category:y.category,actions:y.actions,details:y.details}}:{}},y?{invalidateAcpSession:!0,invalidatedAcpSessionId:y.providerSessionId}:void 0)}finally{await h.flush(),await f.flush(),clearInterval(m)}}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 x(...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=[],o=null,i=Promise.resolve(),r=!1;const s=async p=>{const v=p.filter(re);v.length!==0&&await S("POST",`/api/client/jobs/${encodeURIComponent(e)}/events/batch`,{machineId:t,events:v})},u=()=>{if(o&&(clearTimeout(o),o=null),r)return n=[],i;const p=n;return n=[],i=i.then(()=>s(p)).catch(()=>{}),i},a=()=>{o||(o=setTimeout(()=>{u()},ge()),o.unref())};return{push(p){if(!r){if(ie(n,p),ke(p)||n.length>=pe){u();return}a()}},flush:u,discard(){r=!0,n=[],o&&clearTimeout(o),o=null}}}function ke(e){return e.eventType==="tool_call"?!0:e.eventType!=="tool_call_update"?!1:["completed","failed","cancelled"].includes(String(e.status||"").toLowerCase())}function Ee(e,t){let n=null,o=0,i="",r=null,s=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 S("POST",`/api/client/jobs/${encodeURIComponent(e)}/presence`,{machineId:t,phase:l.phase,label:l.label,detail:l.detail,updateType:l.updateType,raw:l.raw}),o=Date.now(),i=a(l)},v=()=>{if(r&&(clearTimeout(r),r=null),u)return n=null,s;if(!n)return s;const l=n;return n=null,s=s.then(()=>p(l)).catch(()=>{}),s},T=l=>{r||(r=setTimeout(()=>{v()},l),r.unref())};return{report(l){if(u)return;const w=Date.now(),_=a(l)!==i,h=w-o>=me;if(!_&&!h)return;n=l;const f=w-o;if(f>=U||o===0){v();return}T(U-f)},sendNow(l){return u?s:(n=l,v())},flush:v,discard(){u=!0,n=null,r&&clearTimeout(r),r=null}}}function $e(e,t){return e&&typeof e=="object"&&!Array.isArray(e)?{...e,acaSequence:t}:{value:e,acaSequence:t}}function je(e,t=[]){const n=G(e?.message?.trim()??"",e?.sessionReferences);if(t.length===0)return n;const i=["用户随消息上传了以下附件,aca 已将附件写入本机工作区。需要读取附件时请直接使用下面的 localPath;图片附件也已作为 ACP image block 随本轮请求传入。",...t.map((r,s)=>`${s+1}. ${r.name} (${r.kind}, ${r.mimeType}, ${Ue(r.size)})
1
+ import G from"node:crypto";import{appendSessionReferenceContext as K}from"./session-reference-prompt.js";import M from"node:fs";import $ from"node:path";import{compactAgentContext as X,forkAgentSession as Z,sendAgentPrompt as Q}from"../acp/agent.js";import{acaServerDownload as Y,acaServerRequest as S}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 O,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 ue}from"../acp/session-errors.js";const de=1500,z=5e3,U=1e3,pe=200,me=64,B=900,fe=1e4,he=0,we=12e3;function be(e){return ne(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 dt(e,t={}){let n=!1,o=null,i=null,r=0,s=0;const d=new Set,a=new Map,p=new Map,v=async()=>{if(n||Date.now()<s)return;const w=ge();if(J(d.size,w))try{const m=t.loadConfig?.()??ee();if(!m.token)return;for(;!n&&J(d.size,w);){const T=t.claimNextJob?await t.claimNextJob(m,[...a.keys()]):await S("POST","/api/client/jobs/claim",{machineId:m.machineId,workspaceId:m.defaultWorkspaceId,activeSessionIds:[...a.keys()]});if(r=0,s=0,!T.item)break;const h=T.item;if(n){await(t.cancelClaimedJob?t.cancelClaimedJob(h,m):k(h.job_id,m.machineId,"cancelled")).catch(()=>{});break}const f=h.session_id,P=a.get(f);d.add(h.job_id);const A=new AbortController;let u;u=(async()=>{P&&(e.warn(`claimed job=${h.job_id} for already active session=${f}; waiting for the previous run to release its ACP runtime`),await Ie(P,h,m,A)),!A.signal.aborted&&await(t.runJob??Te)(h,m,e,A)})().catch(y=>{e.warn(`job ${h.job_id} failed outside handler: ${y instanceof Error?y.message:String(y)}`)}).finally(()=>{d.delete(h.job_id),a.get(f)===u&&a.delete(f),p.delete(h.job_id)}),a.set(f,u),p.set(h.job_id,{controller:A,promise:u})}}catch(m){if(!n){r+=1;const T=qe(r);s=Date.now()+T,e.warn(`job worker failed, retry in ${T}ms: ${m instanceof Error?m.message:String(m)}`)}}},_=()=>{if(n||o)return;const w=v();o=w,w.finally(()=>{o===w&&(o=null)})};_();const l=setInterval(()=>{_()},t.claimIntervalMs??de);return{timer:l,stop:()=>i||(i=(async()=>{if(n)return;n=!0,clearInterval(l);for(const m of p.values())m.controller.abort();await o?.catch(()=>{});for(const m of p.values())m.controller.abort();const w=[...p.values()].map(m=>m.promise);w.length!==0&&await Promise.race([Promise.allSettled(w).then(()=>{}),N(we)])})(),i)}}async function Ie(e,t,n,o){let i=!1;const s=setInterval(()=>{i||o.signal.aborted||(i=!0,Promise.allSettled([S("POST",`/api/client/jobs/${encodeURIComponent(t.job_id)}/heartbeat`,{machineId:n.machineId}),V(t.job_id,o)]).finally(()=>{i=!1}))},z);try{await Promise.race([e,Se(o.signal)])}finally{clearInterval(s)}}function Se(e){return e.aborted?Promise.resolve():new Promise(t=>e.addEventListener("abort",()=>t(),{once:!0}))}function ge(){const e=process.env.ACA_MAX_CONCURRENT_JOBS||process.env.ACA_MAX_CONCURRENT_PROJECT_JOBS||"",t=Number.parseInt(e,10);return!e.trim()||!Number.isFinite(t)||t<0?he:t===0?0:Math.max(1,Math.min(t,8))}function J(e,t){return t===0||e<t}function ve(){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 Te(e,t,n,o){const i=e.job_id,r=e.session_id,s=e.workspace_id,d=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(i,t.machineId,"job input missing message");return}const v=t.projects.find(u=>u.projectId===d),_=be(v?.rootPath||e.project_root_path||"");if(!_){await k(i,t.machineId,"job project root is unknown");return}ye(e)&&M.mkdirSync(_,{recursive:!0}),n.info(`claimed job=${i} session=${r} project=${v?.name??d??"unknown"}`);let l=!1,w=!1;const m=setInterval(()=>{l||(l=!0,S("POST",`/api/client/jobs/${encodeURIComponent(i)}/heartbeat`,{machineId:t.machineId}).catch(()=>{}).finally(()=>{l=!1})),w||(w=!0,V(i,o).catch(()=>{}).finally(()=>{w=!1}))},z);let T=0;const h=ke(i,t.machineId),f=je(i,t.machineId),P=new Set;let A=null;try{await f.sendNow({phase:"initializing",label:"准备工作区",detail:e.worktree_id?"worktree":"project root"}).catch(()=>{});const u=te({projectId:d,sessionId:r,projectRoot:_,worktree:e});A=u,await Je(i,t.machineId,u);const y=Ae(a.acpRun);if(Ce(y.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 c=await X({agentType:x(y.agentType,"codex"),cliType:x(y.cliType,"builtin"),acpSessionId:e.acp_session_id,cwd:u.cwd,model:a.model,mode:a.mode,configOptionValues:a.configOptionValues,timeoutMs:a.timeoutMs,allowRollover:a.control?.allowRollover===!0,signal:o.signal,onStatus:g=>{f.report({phase:g.phase,label:g.label,detail:g.detail,updateType:g.updateType,raw:g})}});await f.flush(),await S("POST",`/api/client/jobs/${encodeURIComponent(i)}/complete`,{machineId:t.machineId,acpSessionId:c.acpSessionId,suppressTurn:!0,result:{ok:!0,control:{type:"compact",allowRollover:a.control?.allowRollover===!0},acpSessionId:c.acpSessionId,durationMs:c.durationMs,usage:c.contextUsage,maintenance:c.maintenance,worktree:R(u)}}),n.info(`completed context compact job=${i}`);return}if(p==="fork"){const c=String(a.control?.sourceAcpSessionId||"").trim();if(!c)throw new Error("Source session does not have a Codex ACP context to fork");await f.sendNow({phase:"forking",label:"正在分叉 Codex 会话",detail:a.control?.sourceSessionId?`源会话 ${a.control.sourceSessionId}`:void 0}).catch(()=>{});const g=await Z({agentType:x(y.agentType,"codex"),cliType:x(y.cliType,"builtin"),sourceProviderSessionId:c,cwd:u.cwd,model:a.model,mode:a.mode,configOptionValues:a.configOptionValues,timeoutMs:a.timeoutMs,signal:o.signal,onStatus:C=>{f.report({phase:C.phase,label:C.label,detail:C.detail,updateType:C.updateType,raw:C})}});await f.flush(),await S("POST",`/api/client/jobs/${encodeURIComponent(i)}/complete`,{machineId:t.machineId,acpSessionId:g.acpSessionId,suppressTurn:!0,result:{ok:!0,control:{type:"fork",sourceSessionId:a.control?.sourceSessionId||null},acpSessionId:g.acpSessionId,worktree:R(u)}}),n.info(`completed Codex session fork job=${i}`);return}const I=await Fe({cwd:u.cwd,sessionId:r,jobId:i,attachments:a.attachments??[]});I.length>0&&(n.info(`materialized ${I.length} attachment(s) for job=${i}`),await f.sendNow({phase:"initializing",label:"附件已写入本机",detail:I.map(c=>c.relativePath).join(", "),raw:{attachments:W(I)}}).catch(()=>{}));const E=Me(a,I),q=Ne(E,I),b=await Q({agentType:x(y.agentType,"codex"),cliType:x(y.cliType,"builtin"),acpSessionId:e.acp_session_id??null,requireSessionResume:_e(e.session_raw),prompt:E,promptBlocks:q,cwd:u.cwd,model:a.model,mode:a.mode,configOptionValues:a.configOptionValues,canExecuteTools:a.canExecuteTools,timeoutMs:a.timeoutMs,contextWindow:Pe(y.contextWindow),signal:o.signal,onStatus:c=>{f.report({phase:c.phase,label:c.label,detail:c.detail,updateType:c.updateType,raw:c})},onUpdate:c=>{T+=1;for(const C of oe(c.raw))P.add(C);const g={turnId:e.user_turn_id??null,eventType:c.type,label:c.label,text:c.text,status:c.status,toolCallId:c.toolCallId,raw:$e(c.raw,T)};re(g)&&h.push(g)},onPermissionRequest:c=>{const g=ie(a,c);return g?Promise.resolve(g):Le(i,t.machineId,c,o.signal)},onUserInputRequest:c=>We(i,t.machineId,c,o.signal)});await h.flush(),await f.flush();const H=O({cwd:u.cwd,evidencePaths:P}),j=xe(b.promptResponse);if(!(Re(j)||!!b.content)){await k(i,t.machineId,`${b.agentType} stopped: ${j??"unknown"}`,{acpSessionId:b.acpSessionId,stopReason:j,exitCode:b.exitCode,stderr:He(b.stderr,4e3),worktree:R(u)});return}await S("POST",`/api/client/jobs/${encodeURIComponent(i)}/complete`,{machineId:t.machineId,acpSessionId:b.acpSessionId,assistantText:b.content,result:{ok:!0,acpSessionId:b.acpSessionId,stopReason:j,durationMs:b.durationMs,acpRun:a.acpRun??null,effectiveConfig:b.effectiveConfig??null,performance:b.performance??null,usage:b.contextUsage??null,providerDiagnostics:b.providerDiagnostics??null,updateCount:b.updateCount,statusTimeline:b.statusTimeline,worktree:R(u),attachments:W(I),files:H}}),n.info(`completed job=${i}`)}catch(u){if(De(o.signal)){h.discard(),f.discard(),n.warn(`stopped local job=${i} because ACA Server already finalized it`);return}await h.flush(),await f.flush();const y=o.signal.aborted?"cancelled":u instanceof Error?u.message:String(u),I=ue(u),E=A?O({cwd:A.cwd,evidencePaths:P}):[];await k(i,t.machineId,ce(y),{rawError:y,files:E,providerDiagnostics:F(u,"providerDiagnostics"),statusTimeline:F(u,"statusTimeline"),performance:F(u,"performance"),...I?{acpSessionInvalidated:!0,invalidatedAcpSessionId:I.providerSessionId,failure:{category:I.category,actions:I.actions,details:I.details}}:{}},I?{invalidateAcpSession:!0,invalidatedAcpSessionId:I.providerSessionId}:void 0)}finally{await h.flush(),await f.flush(),clearInterval(m)}}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 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=[],o=null,i=Promise.resolve(),r=!1;const s=async p=>{const v=p.filter(se);v.length!==0&&await S("POST",`/api/client/jobs/${encodeURIComponent(e)}/events/batch`,{machineId:t,events:v})},d=()=>{if(o&&(clearTimeout(o),o=null),r)return n=[],i;const p=n;return n=[],i=i.then(()=>s(p)).catch(()=>{}),i},a=()=>{o||(o=setTimeout(()=>{d()},ve()),o.unref())};return{push(p){if(!r){if(ae(n,p),Ee(p)||n.length>=me){d();return}a()}},flush:d,discard(){r=!0,n=[],o&&clearTimeout(o),o=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 je(e,t){let n=null,o=0,i="",r=null,s=Promise.resolve(),d=!1;const a=l=>JSON.stringify({phase:l.phase??"",label:l.label??"",detail:l.detail??"",updateType:l.updateType??""}),p=async l=>{await S("POST",`/api/client/jobs/${encodeURIComponent(e)}/presence`,{machineId:t,phase:l.phase,label:l.label,detail:l.detail,updateType:l.updateType,raw:l.raw}),o=Date.now(),i=a(l)},v=()=>{if(r&&(clearTimeout(r),r=null),d)return n=null,s;if(!n)return s;const l=n;return n=null,s=s.then(()=>p(l)).catch(()=>{}),s},_=l=>{r||(r=setTimeout(()=>{v()},l),r.unref())};return{report(l){if(d)return;const w=Date.now(),T=a(l)!==i,h=w-o>=fe;if(!T&&!h)return;n=l;const f=w-o;if(f>=B||o===0){v();return}_(B-f)},sendNow(l){return d?s:(n=l,v())},flush:v,discard(){d=!0,n=null,r&&clearTimeout(r),r=null}}}function $e(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 i=["用户随消息上传了以下附件,aca 已将附件写入本机工作区。需要读取附件时请直接使用下面的 localPath;图片附件也已作为 ACP image block 随本轮请求传入。",...t.map((r,s)=>`${s+1}. ${r.name} (${r.kind}, ${r.mimeType}, ${Be(r.size)})
2
2
  localPath: ${r.path}
3
3
  workspacePath: ${r.relativePath}`)].join(`
4
4
  `);return n?`${n}
5
5
 
6
- ${i}`:i}function Me(e,t){const n=[{type:"text",text:e}];for(const o of t)o.kind!=="image"||!o.mimeType.startsWith("image/")||n.push({type:"image",data:o.base64,mimeType:o.mimeType,uri:o.path});return n}async function Ne(e){if(e.attachments.length===0)return[];const t=j.join(e.cwd,".aca","attachments",J(e.sessionId),J(e.jobId));M.mkdirSync(t,{recursive:!0});const n=[];for(const[o,i]of e.attachments.entries()){const r=i.attachmentId?{mimeType:i.mimeType||"application/octet-stream",bytes:await Q(`/api/client/attachments/${encodeURIComponent(i.attachmentId)}`)}:Oe(i.dataUrl??""),s=i.mimeType||r.mimeType||"application/octet-stream",u=i.kind==="image"||s.startsWith("image/")?"image":"file",a=Fe(i.name||`attachment-${o+1}`,s),p=j.join(t,`${String(o+1).padStart(2,"0")}-${a}`);M.writeFileSync(p,r.bytes),n.push({name:i.name||a,mimeType:s,size:r.bytes.byteLength,kind:u,path:p,relativePath:j.relative(e.cwd,p),base64:r.bytes.toString("base64"),sha256:H.createHash("sha256").update(r.bytes).digest("hex")})}return n}function Oe(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),o=e.slice(t+1),i=n.split(";").filter(Boolean),r=i[0]?.includes("/")?i[0]:"application/octet-stream";if(!i.includes("base64"))throw new Error("attachment dataUrl is not base64 encoded");return{mimeType:r,bytes:Buffer.from(o,"base64")}}function Fe(e,t){const n=j.basename(e).replace(/[<>:"/\\|?*\x00-\x1F]/g,"_").trim(),o=`attachment${ze(t)}`;return(!n||n==="."||n===".."?o: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 J(e){return e.replace(/[^a-zA-Z0-9._-]+/g,"-").replace(/^-+|-+$/g,"").slice(0,80)||"item"}function L(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 Ue(e){if(!Number.isFinite(e)||e<=0)return"0 B";const t=["B","KB","MB","GB"];let n=e,o=0;for(;n>=1024&&o<t.length-1;)n/=1024,o+=1;return`${n>=10||o===0?n.toFixed(0):n.toFixed(1)} ${t[o]}`}async function Be(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??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 Je(e,t,n,o){const r=(await S("POST",`/api/client/jobs/${encodeURIComponent(e)}/permissions`,{machineId:t,request:n})).item.permission_id;for(;!o.aborted;){await N(z);const s=await S("GET",`/api/client/permissions/${encodeURIComponent(r)}`);if(s.item.status==="resolved"&&s.item.response){const u=s.item.response.outcome;return u.outcome==="selected"?{outcome:u}:{outcome:{outcome:"cancelled"}}}}return{outcome:{outcome:"cancelled"}}}async function Le(e,t,n,o){const r=(await S("POST",`/api/client/jobs/${encodeURIComponent(e)}/permissions`,{machineId:t,request:{kind:"elicitation",...n}})).item.permission_id;for(;!o.aborted;){await N(z);const s=await S("GET",`/api/client/permissions/${encodeURIComponent(r)}`);if(s.item.status==="resolved"&&s.item.response){const u=s.item.response.outcome;return u.outcome==="submitted"?{outcome:u}:{outcome:{outcome:"cancelled"}}}}return{outcome:{outcome:"cancelled"}}}async function W(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 o=We(n.item);o&&t.abort(new V(e,o))}function We(e){const t=String(e.status||"").toLowerCase();return t&&!["claimed","running"].includes(t)?t:null}class V extends Error{constructor(t,n){super(`ACA Server already finalized job=${t} status=${n}`),this.name="ServerFinalizedJobError"}}function Ve(e){return e.aborted&&e.reason instanceof V}async function k(e,t,n,o,i){await S("POST",`/api/client/jobs/${encodeURIComponent(e)}/fail`,{machineId:t,error:n,result:o,...i?.invalidateAcpSession?{invalidateAcpSession:!0,invalidatedAcpSessionId:i.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 De(e,t){return e.length<=t?e:`${e.slice(0,t)}...`}export{We as serverFinalizedJobStatus,ut as startJobWorkerLoop};
6
+ ${i}`:i}function Ne(e,t){const n=[{type:"text",text:e}];for(const o of t)o.kind!=="image"||!o.mimeType.startsWith("image/")||n.push({type:"image",data:o.base64,mimeType:o.mimeType,uri:o.path});return n}async function Fe(e){if(e.attachments.length===0)return[];const t=$.join(e.cwd,".aca","attachments",L(e.sessionId),L(e.jobId));M.mkdirSync(t,{recursive:!0});const n=[];for(const[o,i]of e.attachments.entries()){const r=i.attachmentId?{mimeType:i.mimeType||"application/octet-stream",bytes:await Y(`/api/client/attachments/${encodeURIComponent(i.attachmentId)}`)}:Oe(i.dataUrl??""),s=i.mimeType||r.mimeType||"application/octet-stream",d=i.kind==="image"||s.startsWith("image/")?"image":"file",a=ze(i.name||`attachment-${o+1}`,s),p=$.join(t,`${String(o+1).padStart(2,"0")}-${a}`);M.writeFileSync(p,r.bytes),n.push({name:i.name||a,mimeType:s,size:r.bytes.byteLength,kind:d,path:p,relativePath:$.relative(e.cwd,p),base64:r.bytes.toString("base64"),sha256:G.createHash("sha256").update(r.bytes).digest("hex")})}return n}function Oe(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),o=e.slice(t+1),i=n.split(";").filter(Boolean),r=i[0]?.includes("/")?i[0]:"application/octet-stream";if(!i.includes("base64"))throw new Error("attachment dataUrl is not base64 encoded");return{mimeType:r,bytes:Buffer.from(o,"base64")}}function ze(e,t){const n=$.basename(e).replace(/[<>:"/\\|?*\x00-\x1F]/g,"_").trim(),o=`attachment${Ue(t)}`;return(!n||n==="."||n===".."?o:n).slice(0,180)}function Ue(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,o=0;for(;n>=1024&&o<t.length-1;)n/=1024,o+=1;return`${n>=10||o===0?n.toFixed(0):n.toFixed(1)} ${t[o]}`}async function Je(e,t,n){!n.usedWorktree||!n.worktreeId||await S("POST",`/api/client/jobs/${encodeURIComponent(e)}/worktree`,{machineId:t,baseCommit:n.baseCommit??null,worktreePath:n.cwd,status:n.status??"active",raw:n.raw??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,o){const r=(await S("POST",`/api/client/jobs/${encodeURIComponent(e)}/permissions`,{machineId:t,request:n})).item.permission_id;for(;!o.aborted;){await N(U);const s=await S("GET",`/api/client/permissions/${encodeURIComponent(r)}`);if(s.item.status==="resolved"&&s.item.response){const d=s.item.response.outcome;return d.outcome==="selected"?{outcome:d}:{outcome:{outcome:"cancelled"}}}}return{outcome:{outcome:"cancelled"}}}async function We(e,t,n,o){const r=(await S("POST",`/api/client/jobs/${encodeURIComponent(e)}/permissions`,{machineId:t,request:{kind:"elicitation",...n}})).item.permission_id;for(;!o.aborted;){await N(U);const s=await S("GET",`/api/client/permissions/${encodeURIComponent(r)}`);if(s.item.status==="resolved"&&s.item.response){const d=s.item.response.outcome;return d.outcome==="submitted"?{outcome:d}:{outcome:{outcome:"cancelled"}}}}return{outcome:{outcome:"cancelled"}}}async function V(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 o=Ve(n.item);o&&t.abort(new D(e,o))}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,o,i){await S("POST",`/api/client/jobs/${encodeURIComponent(e)}/fail`,{machineId:t,error:n,result:o,...i?.invalidateAcpSession?{invalidateAcpSession:!0,invalidatedAcpSessionId:i.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 F(e,t){return!e||typeof e!="object"||Array.isArray(e)?null:e[t]??null}export{Ve as serverFinalizedJobStatus,dt as startJobWorkerLoop};
package/dist/version.js CHANGED
@@ -1 +1 @@
1
- const o="0.1.46";export{o as AICA_VERSION};
1
+ const o="0.1.47";export{o as AICA_VERSION};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yuandc/aica",
3
- "version": "0.1.46",
3
+ "version": "0.1.47",
4
4
  "description": "AICA remote Agent worker",
5
5
  "author": "yuandc",
6
6
  "type": "module",