@yuandc/aica 0.1.34 → 0.1.36

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 d from"node:fs";import T from"node:os";import y from"node:path";function k(n={}){const e=n.env??process.env,o=n.platform??process.platform,t=n.arch??process.arch,c=n.homeDir??T.homedir(),i=n.execPath??process.execPath,r=o==="win32"?y.win32:y.posix,s=l(e.CODEX_PATH);if(s){const u=D(s,e,o,r);return u?P(u,o,t,r)??u:s}const f=w([D("codex",e,o,r),...F(e,o,c,i,r)]);let a=null;for(const u of f){if(!u||!b(u,o))continue;const x=P(u,o,t,r);if(x)return x;if(!N(u))return u;a??=u}for(const u of O(e,o,c,i,r)){const x=m(u,o,t,r);if(x)return x}return a??"codex"}function F(n,e,o,t,c){const i=e==="win32"?"codex.exe":"codex",r=e==="win32"?"codex.cmd":"codex",s=l(n.npm_config_prefix),f=l(n.APPDATA),a=l(n.LOCALAPPDATA);return w([c.join(o,".codex","packages","standalone","current","bin",i),n.NVM_BIN?c.join(n.NVM_BIN,i):null,n.VOLTA_HOME?c.join(n.VOLTA_HOME,"bin",i):null,s?c.join(s,e==="win32"?"":"bin",r):null,c.join(c.dirname(t),r),e==="win32"&&f?c.join(f,"npm",r):null,e==="win32"&&a?c.join(a,"npm",r):null,e!=="win32"?c.join(o,".local","bin",i):null,e==="darwin"?"/opt/homebrew/bin/codex":null,e!=="win32"?"/usr/local/bin/codex":null,e!=="win32"?"/usr/bin/codex":null])}function O(n,e,o,t,c){const i=w([l(n.npm_config_prefix),l(n.NVM_BIN)?c.dirname(l(n.NVM_BIN)):null,c.dirname(c.dirname(t)),e==="win32"?l(n.APPDATA):null,e==="win32"?l(n.LOCALAPPDATA):null,e!=="win32"?c.join(o,".local"):null,e!=="win32"?"/usr/local":null,e!=="win32"?"/usr":null]);return w(i.flatMap(r=>_(r,e,c)))}function P(n,e,o,t){const c=g(n,e,t);for(const i of c){const r=m(i,e,o,t);if(r)return r}return null}function g(n,e,o){const t=[];let c=C(n)??n;c=o.dirname(c);for(let i=0;i<8;i+=1){o.basename(c)==="codex"&&o.basename(o.dirname(c))==="@openai"&&t.push(c),t.push(..._(c,e,o));const r=o.dirname(c);if(r===c)break;c=r}return w(t)}function _(n,e,o){return e==="win32"?[o.join(n,"node_modules","@openai","codex")]:[o.join(n,"lib","node_modules","@openai","codex"),o.join(n,"node_modules","@openai","codex")]}function m(n,e,o,t){if(!L(n))return null;const c=`codex-${e}-${o==="x64"?"x64":o}`,i=t.join(n,"node_modules","@openai",c);return j(i,e,t)??j(n,e,t)}function j(n,e,o,t=0){if(t>8||!L(n))return null;let c;try{c=d.readdirSync(n,{withFileTypes:!0})}catch{return null}const i=e==="win32"?"codex.exe":"codex";for(const r of c){const s=o.join(n,r.name);if(r.isFile()&&r.name.toLowerCase()===i&&b(s,e)&&!N(s))return s}for(const r of c){if(!r.isDirectory())continue;const s=j(o.join(n,r.name),e,o,t+1);if(s)return s}return null}function D(n,e,o,t){if(t.isAbsolute(n)||n.includes("/")||n.includes("\\"))return b(n,o)?n:null;const c=o==="win32"?n.includes(".")?[""]:(e.PATHEXT||".EXE;.CMD;.BAT;.COM").split(";").map(r=>r.toLowerCase()):[""],i=o==="win32"?";":":";for(const r of(e.PATH||"").split(i)){const s=l(r);if(s)for(const f of c){const a=t.join(s,`${n}${f}`);if(b(a,o))return a}}return null}function b(n,e){try{return d.accessSync(n,e==="win32"?d.constants.F_OK:d.constants.X_OK),d.statSync(n).isFile()}catch{return!1}}function L(n){try{return d.statSync(n).isDirectory()}catch{return!1}}function N(n){const e=C(n)??n;if(/\.(?:js|mjs|cjs|cmd|bat)$/i.test(e))return!0;try{const o=d.readFileSync(e,{encoding:"utf8",flag:"r"}).slice(0,256);return/^#!.*\bnode\b/m.test(o)}catch{return!1}}function C(n){try{return d.realpathSync(n)}catch{return null}}function l(n){return n?.trim().replace(/^(?:"(.*)"|'(.*)')$/,"$1$2")||null}function w(n){return[...new Set(n.filter(e=>e!=null))]}export{k as resolveCodexPath};
1
+ import d from"node:fs";import{createRequire as T}from"node:module";import F from"node:os";import L from"node:path";const g=T(import.meta.url);function v(n={}){const e=n.env??process.env,r=n.platform??process.platform,c=n.arch??process.arch,o=n.homeDir??F.homedir(),i=n.execPath??process.execPath,t=r==="win32"?L.win32:L.posix,u=l(e.CODEX_PATH);if(u){const s=C(u,e,r,t);return s?$(s,r,c,t)??s:u}const f=n.bundledCodexLauncher===void 0?O():l(n.bundledCodexLauncher??void 0),a=x([f,C("codex",e,r,t),...k(e,r,o,i,t)]);let P=null;for(const s of a){if(!s||!w(s,r))continue;const b=S(s,r,c,t);if(b)return b;if(!D(s))return s;P??=s}for(const s of M(e,r,o,i,t)){const b=j(s,r,c,t);if(b)return b}return P??"codex"}function O(){try{return g.resolve("@openai/codex/bin/codex.js")}catch{return null}}function k(n,e,r,c,o){const i=e==="win32"?"codex.exe":"codex",t=e==="win32"?"codex.cmd":"codex",u=l(n.npm_config_prefix),f=l(n.APPDATA),a=l(n.LOCALAPPDATA);return x([o.join(r,".codex","packages","standalone","current","bin",i),n.NVM_BIN?o.join(n.NVM_BIN,i):null,n.VOLTA_HOME?o.join(n.VOLTA_HOME,"bin",i):null,u?o.join(u,e==="win32"?"":"bin",t):null,o.join(o.dirname(c),t),e==="win32"&&f?o.join(f,"npm",t):null,e==="win32"&&a?o.join(a,"npm",t):null,e!=="win32"?o.join(r,".local","bin",i):null,e==="darwin"?"/opt/homebrew/bin/codex":null,e!=="win32"?"/usr/local/bin/codex":null,e!=="win32"?"/usr/bin/codex":null])}function M(n,e,r,c,o){const i=x([l(n.npm_config_prefix),l(n.NVM_BIN)?o.dirname(l(n.NVM_BIN)):null,o.dirname(o.dirname(c)),e==="win32"?l(n.APPDATA):null,e==="win32"?l(n.LOCALAPPDATA):null,e!=="win32"?o.join(r,".local"):null,e!=="win32"?"/usr/local":null,e!=="win32"?"/usr":null]);return x(i.flatMap(t=>_(t,e,o)))}function S(n,e,r,c){const o=E(n,e,c);for(const i of o){const t=j(i,e,r,c);if(t)return t}return null}function $(n,e,r,c){for(const o of B(n,c)){const i=j(o,e,r,c);if(i)return i}return null}function E(n,e,r){const c=[];let o=y(n)??n;o=r.dirname(o);for(let i=0;i<8;i+=1){r.basename(o)==="codex"&&r.basename(r.dirname(o))==="@openai"&&c.push(o),c.push(..._(o,e,r));const t=r.dirname(o);if(t===o)break;o=t}return x(c)}function B(n,e){const r=[];let c=e.dirname(y(n)??n);for(let o=0;o<8;o+=1){e.basename(c)==="codex"&&e.basename(e.dirname(c))==="@openai"&&r.push(c);const i=e.dirname(c);if(i===c)break;c=i}return x(r)}function _(n,e,r){return e==="win32"?[r.join(n,"node_modules","@openai","codex")]:[r.join(n,"lib","node_modules","@openai","codex"),r.join(n,"node_modules","@openai","codex")]}function j(n,e,r,c){if(!N(n))return null;const o=`codex-${e}-${r==="x64"?"x64":r}`,i=c.join(n,"node_modules","@openai",o),t=c.dirname(n),u=c.basename(n)==="codex"&&c.basename(t)==="@openai"?c.join(t,o):null;return m(i,e,c)??(u?m(u,e,c):null)??m(n,e,c)}function m(n,e,r,c=0){if(c>8||!N(n))return null;let o;try{o=d.readdirSync(n,{withFileTypes:!0})}catch{return null}const i=e==="win32"?"codex.exe":"codex";for(const t of o){const u=r.join(n,t.name);if(t.isFile()&&t.name.toLowerCase()===i&&w(u,e)&&!D(u))return u}for(const t of o){if(!t.isDirectory())continue;const u=m(r.join(n,t.name),e,r,c+1);if(u)return u}return null}function C(n,e,r,c){if(c.isAbsolute(n)||n.includes("/")||n.includes("\\"))return w(n,r)?n:null;const o=r==="win32"?n.includes(".")?[""]:(e.PATHEXT||".EXE;.CMD;.BAT;.COM").split(";").map(t=>t.toLowerCase()):[""],i=r==="win32"?";":":";for(const t of(e.PATH||"").split(i)){const u=l(t);if(u)for(const f of o){const a=c.join(u,`${n}${f}`);if(w(a,r))return a}}return null}function w(n,e){try{return d.accessSync(n,e==="win32"?d.constants.F_OK:d.constants.X_OK),d.statSync(n).isFile()}catch{return!1}}function N(n){try{return d.statSync(n).isDirectory()}catch{return!1}}function D(n){const e=y(n)??n;if(/\.(?:js|mjs|cjs|cmd|bat)$/i.test(e))return!0;try{const r=d.readFileSync(e,{encoding:"utf8",flag:"r"}).slice(0,256);return/^#!.*\bnode\b/m.test(r)}catch{return!1}}function y(n){try{return d.realpathSync(n)}catch{return null}}function l(n){return n?.trim().replace(/^(?:"(.*)"|'(.*)')$/,"$1$2")||null}function x(n){return[...new Set(n.filter(e=>e!=null))]}export{v as resolveCodexPath};
@@ -1,5 +1,5 @@
1
- import{JsonLineRpcClient as M}from"../../client/json-rpc.js";import{startCodexAppServer as U}from"../codex/codex-process.js";import{CodexConnectionRecoveryTracker as E}from"../codex/connection-events.js";import{codexAcpSessionUpdatesFromNotification as T}from"../codex/events.js";import{CodexPlanDeltaState as I}from"../codex/plan-events.js";import{isCodexApprovalRequest as F,resolveCodexApprovalRequest as N}from"../codex/permissions.js";import{buildCodexContinuityInstructions as j,codexTokenUsageFromNotification as D,decideCodexContextMaintenance as v,readCodexContextMaintenancePolicy as k,rolloutFileSize as $}from"./context-maintenance.js";import{codexNotificationBelongsToCurrentTurn as O,codexNotificationRouting as A}from"./notification-routing.js";import{CodexSubtaskRouter as B}from"./subtask-routing.js";class z{lineBuffer="";started=null;rpc=null;codexStartupPromise=null;initialized=!1;currentSessionId=null;currentCwd=process.cwd();promptActive=!1;currentTurnId=null;nextParentRequestId=1;pendingParentRequests=new Map;completedTurns=new Map;pendingTurnCompletions=new Map;agentMessagePhases=new Map;planDeltas=new I;subtaskRouter=new B;subtaskAgentMessagePhases=new Map;subtaskPlanDeltas=new Map;subscribedSubtaskThreadIds=new Set;pendingSubtaskSubscriptions=new Map;pendingSubtaskNotifications=[];config={};sessionAliases=new Map;currentAffectedFiles=new Set;connectionRecovery=new E;latestTokenUsage=null;currentRolloutPath=null;threadTurnCount=0;currentPromptText="";currentAssistantText="";previousUserText="";previousAssistantText="";previousAffectedFiles=new Set;maintenanceActive=!1;pendingCompaction=null;shutdownPromise=null;managedProvider=null;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 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){e==="session/cancel"&&await this.cancel(t)}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,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(),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){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 o=await this.maintainContext("before",{userRequest:this.previousUserText,assistantResponse:this.previousAssistantText,affectedFiles:[...this.previousAffectedFiles]},{allowRollover:!0});H(o),s=this.currentSessionId||s,this.promptActive=!0,this.currentTurnId=null,this.currentPromptText=K(n),this.currentAssistantText="",this.currentAffectedFiles.clear(),this.connectionRecovery.reset(),this.agentMessagePhases.clear(),this.planDeltas.reset(),this.pendingSubtaskNotifications=[];let c;try{c=await r.request("turn/start",{threadId:s,clientUserMessageId:`aca-acp-${Date.now()}-${Math.random().toString(16).slice(2)}`,input:W(n),cwd:this.currentCwd,...L(this.config)},q());const d=String(c?.turn?.id??"");this.currentTurnId=d||null;const l=d?await this.waitForTurnCompleted(d,q()):c?.turn;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:R(h)??V(this.latestTokenUsage),acaContextMaintenance:h}}}catch(d){const l=this.connectionRecovery.fail(d);throw l&&this.notifySessionUpdate(l),d}finally{this.promptActive=!1,this.currentTurnId=null,this.pendingSubtaskNotifications=[]}}resetSubtaskState(e){this.subtaskRouter.reset(),this.subtaskAgentMessagePhases.clear(),this.subtaskPlanDeltas.clear(),this.subscribedSubtaskThreadIds.clear(),this.pendingSubtaskSubscriptions.clear(),this.pendingSubtaskNotifications=[],e!==void 0&&this.subtaskRouter.restoreFromSessionSnapshot(e,this.currentSessionId)}async subscribeKnownSubtasks(){const e=this.subtaskRouter.routesForSubscription().map(t=>this.subscribeSubtask(t.sourceThreadId));await Promise.allSettled(e)}subscribeSubtask(e){if(!e||e===this.currentSessionId||this.subscribedSubtaskThreadIds.has(e))return Promise.resolve();const t=this.pendingSubtaskSubscriptions.get(e);if(t)return t;const s=this.requireRpc().request("thread/resume",{threadId:e,approvalsReviewer:"user",excludeTurns:!0},6e4).then(()=>{this.subscribedSubtaskThreadIds.add(e)}).catch(()=>{}).finally(()=>{this.pendingSubtaskSubscriptions.delete(e)});return this.pendingSubtaskSubscriptions.set(e,s),s}async cancel(e){const t=a(e,"sessionId")||this.currentSessionId;if(!(!t||!this.rpc)&&(await this.rpc.request("turn/interrupt",{threadId:t}).catch(()=>{}),this.currentTurnId)){const s=this.pendingTurnCompletions.get(this.currentTurnId);s&&(this.pendingTurnCompletions.delete(this.currentTurnId),clearTimeout(s.timer),s.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:J(e,n.compactTimeoutMs)}),o=String(r.action||"");if(o==="compact_failed"||o==="rollover_failed"){const c=String(r.compactError||"Codex 上下文压缩未完成");throw new Error(`Codex 上下文压缩失败:${c}`)}return{sessionId:this.currentSessionId,maintenance:r,contextUsage:R(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=U(e,this.managedProvider),n=new M(s.child,{peerName:"Codex app-server",onNotification:(r,o)=>this.handleCodexNotification(r,o),onRequest:async(r,o)=>this.handleCodexRequest(r,o)});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=A(t);if(this.maintenanceActive&&(!s.threadId||s.threadId===this.currentSessionId)){this.captureContextNotification(e,t);return}if(!O(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.subscribeKnownSubtasks(),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 o=T(e,t,{agentMessagePhases:this.agentMessagePhases,planDeltas:this.planDeltas});for(const c of[...r,...o])this.captureUpdateEvidence(c),this.notifySessionUpdate(c);n>0&&this.flushPendingSubtaskNotifications()}handleSubtaskNotification(e,t,s){if(!this.promptActive)return;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 I;this.subtaskPlanDeltas.set(s.sourceThreadId,r),this.captureAgentMessagePhase(e,t,n),this.subtaskRouter.observe(t,this.currentSessionId)>0&&this.subscribeKnownSubtasks();const c=T(e,t,{agentMessagePhases:n,planDeltas:r}),d=A(t).turnId;for(const l of c){if(!Q(l))continue;const h=X(l,s,d);w(h,this.currentAffectedFiles),this.notifySessionUpdate(h)}}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(F(e))return N(e,t,async s=>{const n=await this.requestParent("session/request_permission",{sessionId:this.currentSessionId,request:{...s,params:t,method:e}}),r=u(n),o=u(r.outcome);return o.outcome==="selected"&&typeof o.optionId=="string"?{outcome:{outcome:"selected",optionId:o.optionId}}:{outcome:{outcome:"cancelled"}}});if(Y(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 o=setTimeout(()=>{this.pendingTurnCompletions.delete(e),r(new Error("Codex app-server request timed out while waiting for turn/completed"))},t);o.unref(),this.pendingTurnCompletions.set(e,{resolve:n,reject:r,timer:o})})}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,o)=>{const c=setTimeout(()=>{this.pendingParentRequests.delete(n),o(new Error(`ACP client request timed out: ${e}`))},s);c.unref(),this.pendingParentRequests.set(n,{resolve:r,reject:o,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=D(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=G(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(),o=v(r,n);if(!o.compact&&!s.force||!this.currentSessionId)return this.maintenanceResult({stage:e,action:"none",reasons:o.reasons,beforeState:r});const c=this.currentSessionId;this.notifyContextStatus("正在整理会话上下文",P(r)),this.maintenanceActive=!0;let d=!1,l="",h=r,f=o,x=!1,b="";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(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),x=!0}catch(p){b=p instanceof Error?p.message:String(p),this.notifyContextStatus("轻量会话创建失败",b)}}else d||this.notifyContextStatus("会话上下文已压缩",P(h))}finally{this.maintenanceActive=!1}return this.maintenanceResult({action:x?"rollover":b?"rollover_failed":d?"compact_failed":"compact",stage:e,reasons:f.reasons,beforeState:r,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 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(o=>{this.pendingCompaction?.timer===r&&(this.pendingCompaction=null,clearTimeout(r),n(o instanceof Error?o:new Error(String(o))))})})}async rotateThread(e,t){const s=j({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,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 Z(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:$(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}})}write(e){process.stdout.write(`${JSON.stringify(e)}
3
- `)}}function W(i){return i.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 K(i){return i.map(e=>a(u(e),"text")).filter(Boolean).join(`
1
+ import{JsonLineRpcClient as M}from"../../client/json-rpc.js";import{startCodexAppServer as U}from"../codex/codex-process.js";import{CodexConnectionRecoveryTracker as E}from"../codex/connection-events.js";import{codexAcpSessionUpdatesFromNotification as T}from"../codex/events.js";import{CodexPlanDeltaState as v}from"../codex/plan-events.js";import{isCodexApprovalRequest as F,resolveCodexApprovalRequest as N}from"../codex/permissions.js";import{buildCodexContinuityInstructions as j,codexTokenUsageFromRawResponseCompleted as D,codexTokenUsageFromNotification as $,decideCodexContextMaintenance as I,readCodexContextMaintenancePolicy as k,rolloutFileSize as O}from"./context-maintenance.js";import{codexNotificationBelongsToCurrentTurn as B,codexNotificationRouting as R}from"./notification-routing.js";import{CodexSubtaskRouter as z}from"./subtask-routing.js";class W{lineBuffer="";started=null;rpc=null;codexStartupPromise=null;initialized=!1;currentSessionId=null;currentCwd=process.cwd();promptActive=!1;currentTurnId=null;nextParentRequestId=1;pendingParentRequests=new Map;completedTurns=new Map;pendingTurnCompletions=new Map;agentMessagePhases=new Map;planDeltas=new v;subtaskRouter=new z;subtaskAgentMessagePhases=new Map;subtaskPlanDeltas=new Map;subscribedSubtaskThreadIds=new Set;pendingSubtaskSubscriptions=new Map;pendingSubtaskNotifications=[];config={};sessionAliases=new Map;currentAffectedFiles=new Set;connectionRecovery=new E;latestTokenUsage=null;currentRolloutPath=null;threadTurnCount=0;currentPromptText="";currentAssistantText="";previousUserText="";previousAssistantText="";previousAffectedFiles=new Set;maintenanceActive=!1;pendingCompaction=null;shutdownPromise=null;managedProvider=null;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 i=s.trim();if(!i)continue;let r;try{r=JSON.parse(i)}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 i=await this.handleRequest(t,u(e.params));this.write({jsonrpc:"2.0",id:s,result:i})}catch(i){this.write({jsonrpc:"2.0",id:s,error:{code:-32603,message:i instanceof Error?i.message:String(i)}})}}}async handleNotification(e,t){e==="session/cancel"&&await this.cancel(t)}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"),i=a(t,"apiKey"),r=Number(t.revision||0);if(!s||!i||!Number.isSafeInteger(r)||r<1)throw new Error("Invalid Codex managed provider runtime config");return this.managedProvider={baseUrl:s,apiKey:i,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),i=b(s);if(!i)throw new Error("Codex thread/start did not return thread id");return this.currentSessionId=i,this.currentCwd=t,this.resetSubtaskState(),this.captureThreadMetadata(s),this.sessionResponse(i)}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 i=await this.requireRpc().request("thread/resume",{threadId:t,cwd:s,approvalsReviewer:"user",...this.codexConfig()},6e4);return this.currentSessionId=b(i)||t,this.currentCwd=s,this.resetSubtaskState(i),await this.subscribeKnownSubtasks(),this.captureThreadMetadata(i),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 i=await this.requireRpc().request("thread/fork",{threadId:t,cwd:s,approvalsReviewer:"user",ephemeral:!1,threadSource:"aca-codex-acp-fork",...this.codexConfig()},12e4),r=b(i);if(!r)throw new Error("Codex thread/fork did not return thread id");return this.currentSessionId=r,this.currentCwd=s,this.resetSubtaskState(i),this.captureThreadMetadata(i),this.sessionResponse(r)}async prompt(e){const t=a(e,"sessionId")||this.currentSessionId;let s=this.resolveSessionAlias(t);if(!s)throw new Error("sessionId is required");const i=Array.isArray(e.prompt)?e.prompt:[],r=this.requireRpc();this.currentSessionId&&this.currentSessionId!==s&&this.resetSubtaskState(),this.currentSessionId=s;const o=await this.maintainContext("before",{userRequest:this.previousUserText,assistantResponse:this.previousAssistantText,affectedFiles:[...this.previousAffectedFiles]},{allowRollover:!0});Q(o),s=this.currentSessionId||s,this.promptActive=!0,this.currentTurnId=null,this.currentPromptText=L(i),this.currentAssistantText="",this.currentAffectedFiles.clear(),this.connectionRecovery.reset(),this.agentMessagePhases.clear(),this.planDeltas.reset(),this.pendingSubtaskNotifications=[];let c;try{c=await r.request("turn/start",{threadId:s,clientUserMessageId:`aca-acp-${Date.now()}-${Math.random().toString(16).slice(2)}`,input:K(i),cwd:this.currentCwd,...G(this.config)},q());const d=String(c?.turn?.id??"");this.currentTurnId=d||null;const l=d?await this.waitForTurnCompleted(d,q()):c?.turn;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 p=await this.maintainContext("after",{userRequest:this.previousUserText,assistantResponse:this.previousAssistantText,affectedFiles:[...this.previousAffectedFiles]},{allowRollover:!0});return{stopReason:"end_turn",sessionId:this.currentSessionId,turn:l,_meta:{acaContextUsage:A(p)??H(this.latestTokenUsage),acaContextMaintenance:p}}}catch(d){const l=this.connectionRecovery.fail(d);throw l&&this.notifySessionUpdate(l),d}finally{this.promptActive=!1,this.currentTurnId=null,this.pendingSubtaskNotifications=[]}}resetSubtaskState(e){this.subtaskRouter.reset(),this.subtaskAgentMessagePhases.clear(),this.subtaskPlanDeltas.clear(),this.subscribedSubtaskThreadIds.clear(),this.pendingSubtaskSubscriptions.clear(),this.pendingSubtaskNotifications=[],e!==void 0&&this.subtaskRouter.restoreFromSessionSnapshot(e,this.currentSessionId)}async subscribeKnownSubtasks(){const e=this.subtaskRouter.routesForSubscription().map(t=>this.subscribeSubtask(t.sourceThreadId));await Promise.allSettled(e)}subscribeSubtask(e){if(!e||e===this.currentSessionId||this.subscribedSubtaskThreadIds.has(e))return Promise.resolve();const t=this.pendingSubtaskSubscriptions.get(e);if(t)return t;const s=this.requireRpc().request("thread/resume",{threadId:e,approvalsReviewer:"user",excludeTurns:!0},6e4).then(()=>{this.subscribedSubtaskThreadIds.add(e)}).catch(()=>{}).finally(()=>{this.pendingSubtaskSubscriptions.delete(e)});return this.pendingSubtaskSubscriptions.set(e,s),s}async cancel(e){const t=a(e,"sessionId")||this.currentSessionId;if(!(!t||!this.rpc)&&(await this.rpc.request("turn/interrupt",{threadId:t}).catch(()=>{}),this.currentTurnId)){const s=this.pendingTurnCompletions.get(this.currentTurnId);s&&(this.pendingTurnCompletions.delete(this.currentTurnId),clearTimeout(s.timer),s.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 i=k(),r=await this.maintainContext("manual",{userRequest:this.previousUserText,assistantResponse:this.previousAssistantText,affectedFiles:[...this.previousAffectedFiles]},{force:!0,allowRollover:e.allowRollover===!0,compactTimeoutMs:V(e,i.compactTimeoutMs)}),o=String(r.action||"");if(o==="compact_failed"||o==="rollover_failed"){const c=String(r.compactError||"Codex 上下文压缩未完成");throw new Error(`Codex 上下文压缩失败:${c}`)}return{sessionId:this.currentSessionId,maintenance:r,contextUsage:A(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=U(e,this.managedProvider),i=new M(s.child,{peerName:"Codex app-server",onNotification:(r,o)=>this.handleCodexNotification(r,o),onRequest:async(r,o)=>this.handleCodexRequest(r,o)});this.started=s,this.rpc=i,this.initialized=!1,await i.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(!B(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 i=this.subtaskRouter.observe(t,this.currentSessionId);if(i>0&&this.subscribeKnownSubtasks(),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 o=e==="rawResponse/completed"?X(t):T(e,t,{agentMessagePhases:this.agentMessagePhases,planDeltas:this.planDeltas});for(const c of[...r,...o])this.captureUpdateEvidence(c),this.notifySessionUpdate(c);i>0&&this.flushPendingSubtaskNotifications()}handleSubtaskNotification(e,t,s){if(!this.promptActive)return;this.rpc?.refreshPendingRequestTimeout("turn/start");const i=this.subtaskAgentMessagePhases.get(s.sourceThreadId)??new Map;this.subtaskAgentMessagePhases.set(s.sourceThreadId,i);const r=this.subtaskPlanDeltas.get(s.sourceThreadId)??new v;this.subtaskPlanDeltas.set(s.sourceThreadId,r),this.captureAgentMessagePhase(e,t,i),this.subtaskRouter.observe(t,this.currentSessionId)>0&&this.subscribeKnownSubtasks();const c=T(e,t,{agentMessagePhases:i,planDeltas:r}),d=R(t).turnId;for(const l of c){if(!Y(l))continue;const p=Z(l,s,d);w(p,this.currentAffectedFiles),this.notifySessionUpdate(p)}}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 i=this.subtaskRouter.routeFor(s.threadId);if(!i){t.push(s);continue}e=!0,this.handleSubtaskNotification(s.method,s.params,i)}this.pendingSubtaskNotifications=t}}notifySessionUpdate(e){this.notify("session/update",{sessionId:this.currentSessionId,update:e})}async handleCodexRequest(e,t){if(F(e))return N(e,t,async s=>{const i=await this.requestParent("session/request_permission",{sessionId:this.currentSessionId,request:{...s,params:t,method:e}}),r=u(i),o=u(r.outcome);return o.outcome==="selected"&&typeof o.optionId=="string"?{outcome:{outcome:"selected",optionId:o.optionId}}:{outcome:{outcome:"cancelled"}}});if(ee(e)){const s=await this.requestParent("elicitation/create",{sessionId:this.currentSessionId,request:{...u(t),method:e}}),i=u(s),r=u(i.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 i=m(t,"item");if(a(i,"type")!=="agentMessage")return;const r=a(i,"id");r&&s.set(r,a(i,"phase"))}waitForTurnCompleted(e,t){const s=this.completedTurns.get(e);return s?Promise.resolve(s):new Promise((i,r)=>{const o=setTimeout(()=>{this.pendingTurnCompletions.delete(e),r(new Error("Codex app-server request timed out while waiting for turn/completed"))},t);o.unref(),this.pendingTurnCompletions.set(e,{resolve:i,reject:r,timer:o})})}resolveTurnCompleted(e){const t=m(e,"turn"),s=a(t,"id");if(!s)return;this.completedTurns.set(s,t);const i=this.pendingTurnCompletions.get(s);if(i){if(this.pendingTurnCompletions.delete(s),clearTimeout(i.timer),a(t,"status")==="failed"){i.reject(new Error(`Codex turn failed: ${JSON.stringify(t.error??{})}`));return}i.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 i=this.nextParentRequestId++;return new Promise((r,o)=>{const c=setTimeout(()=>{this.pendingParentRequests.delete(i),o(new Error(`ACP client request timed out: ${e}`))},s);c.unref(),this.pendingParentRequests.set(i,{resolve:r,reject:o,timer:c}),this.write({jsonrpc:"2.0",id:i,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 i=u(e.error);s.reject(new Error(a(i,"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,i]of Object.entries(t))(typeof i=="string"||typeof i=="boolean")&&(this.config[s]=i)}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=$(t)??this.latestTokenUsage),e==="thread/compacted"&&this.pendingCompaction&&this.resolveCompaction(),e==="item/completed"){const s=m(t,"item"),i=a(u(t),"threadId");a(s,"type")==="contextCompaction"&&(!i||i===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=J(t.turns);s.userRequest&&(this.previousUserText=s.userRequest),s.assistantResponse&&(this.previousAssistantText=s.assistantResponse),this.previousAffectedFiles.clear();for(const i of s.affectedFiles)this.previousAffectedFiles.add(i);this.latestTokenUsage=null}async maintainContext(e,t,s={}){const i=k(),r=this.contextState(),o=I(r,i);if(!o.compact&&!s.force||!this.currentSessionId)return this.maintenanceResult({stage:e,action:"none",reasons:o.reasons,beforeState:r});const c=this.currentSessionId;this.notifyContextStatus("正在整理会话上下文",P(r)),this.maintenanceActive=!0;let d=!1,l="",p=r,f=o,y=!1,x="";try{const h=r.usage?.observedAtMs??0;await this.compactThread(c,s.compactTimeoutMs??i.compactTimeoutMs),await this.waitForTokenUsageAfter(h,2e3)}catch(h){d=!0,l=h instanceof Error?h.message:String(h)}try{if(p=this.contextState(),f=I(p,i,{compactAttempted:!0,compactFailed:d}),f.rollover&&s.allowRollover!==!1){this.notifyContextStatus("正在创建轻量会话",f.reasons.join(", "));try{await this.rotateThread(c,t),y=!0}catch(h){x=h instanceof Error?h.message:String(h),this.notifyContextStatus("轻量会话创建失败",x)}}else d||this.notifyContextStatus("会话上下文已压缩",P(p))}finally{this.maintenanceActive=!1}return this.maintenanceResult({action:y?"rollover":x?"rollover_failed":d?"compact_failed":"compact",stage:e,reasons:f.reasons,beforeState:r,afterState:p,compactError:[l,x].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,i)=>{const r=setTimeout(()=>{this.pendingCompaction?.timer===r&&(this.pendingCompaction=null),i(new Error("Codex thread compaction timed out"))},t);r.unref(),this.pendingCompaction={resolve:s,reject:i,timer:r},this.requireRpc().request("thread/compact/start",{threadId:e},t).catch(o=>{this.pendingCompaction?.timer===r&&(this.pendingCompaction=null,clearTimeout(r),i(o instanceof Error?o:new Error(String(o))))})})}async rotateThread(e,t){const s=j({previousThreadId:e,cwd:this.currentCwd,userRequest:t.userRequest,assistantResponse:t.assistantResponse,affectedFiles:t.affectedFiles}),i=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=b(i);if(!r)throw new Error("Codex rollover thread/start did not return thread id");this.sessionAliases.set(e,r),this.currentSessionId=r,this.resetSubtaskState(i),this.captureThreadMetadata(i)}async waitForTokenUsageAfter(e,t){const s=Date.now()+t;for(;Date.now()<s;){if((this.latestTokenUsage?.observedAtMs??0)>e)return;await te(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:O(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}})}write(e){process.stdout.write(`${JSON.stringify(e)}
3
+ `)}}function K(n){return n.map(e=>{const t=u(e);if(t.type==="image"){const s=a(t,"uri");if(s)return{type:"localImage",path:s};const i=a(t,"data");return{type:"image",url:`data:${a(t,"mimeType")||"image/png"};base64,${i}`}}return{type:"text",text:a(t,"text"),text_elements:[]}})}function L(n){return n.map(e=>a(u(e),"text")).filter(Boolean).join(`
4
4
 
5
- `)}function L(i){const e={};typeof i.model=="string"&&i.model&&(e.model=i.model),typeof i.approvalPolicy=="string"&&i.approvalPolicy&&(e.approvalPolicy=i.approvalPolicy),typeof i.sandbox=="string"&&i.sandbox&&(e.sandbox=i.sandbox);const t=i.reasoning_effort??i.reasoningEffort??i.model_reasoning_effort??i.modelReasoningEffort??i.effort;return typeof t=="string"&&t&&(e.effort=t),e}function C(i){const e=m(i,"thread");return a(e,"id")||null}function m(i,e){const t=u(i);return u(t[e])}function u(i){return i&&typeof i=="object"&&!Array.isArray(i)?i:{}}function g(i){if(typeof i=="string")return i;if(Array.isArray(i))return i.map(g).filter(Boolean).join("");if(!i||typeof i!="object")return"";const e=u(i);for(const t of["text","message","content","output_text"]){const s=g(e[t]);if(s)return s}return""}function w(i,e){if(Array.isArray(i)){for(const r of i)w(r,e);return}const t=u(i),s=a(t,"type").toLowerCase(),n=a(t,"path");if(n&&["diff","resource_link","content"].includes(s)&&e.add(n),Array.isArray(t.locations))for(const r of t.locations){const o=a(u(r),"path");o&&e.add(o)}for(const r of[t.content,t.update,t.rawInput,t.rawOutput])r&&r!==i&&w(r,e)}function G(i){if(!Array.isArray(i)||i.length===0)return{userRequest:"",assistantResponse:"",affectedFiles:[]};const e=i.at(-1),t=new Set;w(e,t);let s="",n="";const r=o=>{if(Array.isArray(o)){for(const l of o)r(l);return}if(!o||typeof o!="object")return;const c=u(o),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 J(i,e){const t=S(i,"timeoutMs");return!Number.isFinite(t)||t<=0?e:Math.max(3e4,Math.min(59e4,t-1e4))}function R(i){const e=String(i.action||"none"),t=u(i.after),s=u(i.before),n=u(t.usage),r=u(s.usage),o=Object.keys(n).length>0?n:r,c=S(o,"contextWindow");if(e==="rollover")return{usedTokens:0,inputTokens:0,contextWindow:c,ratio:0,observedAtMs:Date.now(),state:"new_thread",maintenanceAction:e};if(Object.keys(o).length===0)return null;const d=S(o,"totalTokens"),l=S(o,"inputTokens");return{usedTokens:d||l,inputTokens:l,contextWindow:c,ratio:c>0?(d||l)/c:0,observedAtMs:S(o,"observedAtMs")||Date.now(),state:e==="compact"?"compacted":"active",maintenanceAction:e}}function V(i){return i?{usedTokens:i.totalTokens||i.inputTokens,inputTokens:i.inputTokens,contextWindow:i.contextWindow,ratio:i.contextWindow>0?(i.totalTokens||i.inputTokens)/i.contextWindow:0,observedAtMs:i.observedAtMs,state:"active",maintenanceAction:null}:null}function P(i){const e=i.usage?`${i.usage.inputTokens} tokens`:"token unknown",t=i.rolloutBytes>0?`${Math.round(i.rolloutBytes/1024/1024)}MB rollout`:"rollout unknown";return`${e}, ${t}, ${i.turnCount} turns`}function H(i){const e=String(i.action||"none");if(e!=="compact_failed"&&e!=="rollover_failed")return;const t=String(i.compactError||"Codex 上下文维护未完成");throw new Error(`Codex 自动上下文维护失败:${t}`)}function S(i,e){const t=u(i)[e];return typeof t=="number"&&Number.isFinite(t)?t:0}function Q(i){return["agent_message_chunk","agent_progress_chunk","agent_thought_chunk","tool_call","tool_call_update","plan","plan_update","plan_removed"].includes(String(i.sessionUpdate||""))}function X(i,e,t){const s=String(i.sessionUpdate||""),n={...i};if(s==="tool_call"||s==="tool_call_update"){const c=a(i,"toolCallId");c&&(n.toolCallId=_(e.sourceThreadId,c))}if(s==="plan"||s==="plan_update"||s==="plan_removed"){const c=a(i,"planId")||a(i,"plan_id")||a(i,"id")||"current";n.planId=_(e.sourceThreadId,c),delete n.plan_id}const r=u(i._meta),o=u(r.aca);return n._meta={...r,aca:{...o,subtask:{parentToolCallId:e.parentToolCallId,sourceThreadId:e.sourceThreadId,sourceTurnId:t,depth:e.depth}}},n}function _(i,e){return`subtask.${i}.${e}`}function a(i,e){const s=u(i)[e];return typeof s=="string"?s:""}function Y(i){const e=i.toLowerCase();return e.includes("elicitation")||e.includes("requestuserinput")||e.includes("request_user_input")||e.includes("input/request")}function q(){const i=Number.parseInt(process.env.ACA_ACP_PROMPT_TIMEOUT_MS??"",10);return Number.isInteger(i)&&i>=3e4?i:360*60*1e3}function y(i,e){return i.exitCode!==null||i.signalCode!==null?Promise.resolve(!0):new Promise(t=>{const s=setTimeout(()=>t(!1),e);s.unref(),i.once("close",()=>{clearTimeout(s),t(!0)})})}function Z(i){return new Promise(e=>setTimeout(e,i))}function ee(){new z}const te=process.argv[1]||"";/(?:^|[\\/])adapter\.(?:js|ts)$/.test(te)&&ee();export{ee as runCodexAcpAdapter};
5
+ `)}function G(n){const e={};typeof n.model=="string"&&n.model&&(e.model=n.model),typeof n.approvalPolicy=="string"&&n.approvalPolicy&&(e.approvalPolicy=n.approvalPolicy),typeof n.sandbox=="string"&&n.sandbox&&(e.sandbox=n.sandbox);const t=n.reasoning_effort??n.reasoningEffort??n.model_reasoning_effort??n.modelReasoningEffort??n.effort;return typeof t=="string"&&t&&(e.effort=t),e}function b(n){const e=m(n,"thread");return a(e,"id")||null}function m(n,e){const t=u(n);return u(t[e])}function u(n){return n&&typeof n=="object"&&!Array.isArray(n)?n:{}}function g(n){if(typeof n=="string")return n;if(Array.isArray(n))return n.map(g).filter(Boolean).join("");if(!n||typeof n!="object")return"";const e=u(n);for(const t of["text","message","content","output_text"]){const s=g(e[t]);if(s)return s}return""}function w(n,e){if(Array.isArray(n)){for(const r of n)w(r,e);return}const t=u(n),s=a(t,"type").toLowerCase(),i=a(t,"path");if(i&&["diff","resource_link","content"].includes(s)&&e.add(i),Array.isArray(t.locations))for(const r of t.locations){const o=a(u(r),"path");o&&e.add(o)}for(const r of[t.content,t.update,t.rawInput,t.rawOutput])r&&r!==n&&w(r,e)}function J(n){if(!Array.isArray(n)||n.length===0)return{userRequest:"",assistantResponse:"",affectedFiles:[]};const e=n.at(-1),t=new Set;w(e,t);let s="",i="";const r=o=>{if(Array.isArray(o)){for(const l of o)r(l);return}if(!o||typeof o!="object")return;const c=u(o),d=a(c,"type").toLowerCase();d==="usermessage"&&(s=g(c)||s),d==="agentmessage"&&(i=g(c)||i);for(const l of["items","content","input"])c[l]&&r(c[l])};return r(e),{userRequest:s,assistantResponse:i,affectedFiles:[...t]}}function V(n,e){const t=S(n,"timeoutMs");return!Number.isFinite(t)||t<=0?e:Math.max(3e4,Math.min(59e4,t-1e4))}function A(n){const e=String(n.action||"none"),t=u(n.after),s=u(n.before),i=u(t.usage),r=u(s.usage),o=Object.keys(i).length>0?i:r,c=S(o,"contextWindow");if(e==="rollover")return{usedTokens:0,inputTokens:0,contextWindow:c,ratio:0,observedAtMs:Date.now(),state:"new_thread",maintenanceAction:e};if(Object.keys(o).length===0)return null;const d=S(o,"totalTokens"),l=S(o,"inputTokens");return{usedTokens:d||l,inputTokens:l,contextWindow:c,ratio:c>0?(d||l)/c:0,observedAtMs:S(o,"observedAtMs")||Date.now(),state:e==="compact"?"compacted":"active",maintenanceAction:e}}function H(n){return n?{usedTokens:n.totalTokens||n.inputTokens,inputTokens:n.inputTokens,contextWindow:n.contextWindow,ratio:n.contextWindow>0?(n.totalTokens||n.inputTokens)/n.contextWindow:0,observedAtMs:n.observedAtMs,state:"active",maintenanceAction:null}:null}function P(n){const e=n.usage?`${n.usage.inputTokens} tokens`:"token unknown",t=n.rolloutBytes>0?`${Math.round(n.rolloutBytes/1024/1024)}MB rollout`:"rollout unknown";return`${e}, ${t}, ${n.turnCount} turns`}function Q(n){const e=String(n.action||"none");if(e!=="compact_failed"&&e!=="rollover_failed")return;const t=String(n.compactError||"Codex 上下文维护未完成");throw new Error(`Codex 自动上下文维护失败:${t}`)}function S(n,e){const t=u(n)[e];return typeof t=="number"&&Number.isFinite(t)?t:0}function X(n){return D(n)?[{sessionUpdate:"aca_codex_raw_response_usage",_meta:{codex:{rawResponse:n}}}]:[]}function Y(n){return["agent_message_chunk","agent_progress_chunk","agent_thought_chunk","tool_call","tool_call_update","plan","plan_update","plan_removed"].includes(String(n.sessionUpdate||""))}function Z(n,e,t){const s=String(n.sessionUpdate||""),i={...n};if(s==="tool_call"||s==="tool_call_update"){const c=a(n,"toolCallId");c&&(i.toolCallId=_(e.sourceThreadId,c))}if(s==="plan"||s==="plan_update"||s==="plan_removed"){const c=a(n,"planId")||a(n,"plan_id")||a(n,"id")||"current";i.planId=_(e.sourceThreadId,c),delete i.plan_id}const r=u(n._meta),o=u(r.aca);return i._meta={...r,aca:{...o,subtask:{parentToolCallId:e.parentToolCallId,sourceThreadId:e.sourceThreadId,sourceTurnId:t,depth:e.depth}}},i}function _(n,e){return`subtask.${n}.${e}`}function a(n,e){const s=u(n)[e];return typeof s=="string"?s:""}function ee(n){const e=n.toLowerCase();return e.includes("elicitation")||e.includes("requestuserinput")||e.includes("request_user_input")||e.includes("input/request")}function q(){const n=Number.parseInt(process.env.ACA_ACP_PROMPT_TIMEOUT_MS??"",10);return Number.isInteger(n)&&n>=3e4?n:360*60*1e3}function C(n,e){return n.exitCode!==null||n.signalCode!==null?Promise.resolve(!0):new Promise(t=>{const s=setTimeout(()=>t(!1),e);s.unref(),n.once("close",()=>{clearTimeout(s),t(!0)})})}function te(n){return new Promise(e=>setTimeout(e,n))}function se(){new W}const ne=process.argv[1]||"";/(?:^|[\\/])adapter\.(?:js|ts)$/.test(ne)&&se();export{se as runCodexAcpAdapter};
@@ -1,5 +1,5 @@
1
- import A from"node:fs";const M=1024*1024;function _(t=process.env){return{compactTokenThreshold:a(t.ACA_CODEX_COMPACT_TOKEN_THRESHOLD,12e4),compactContextRatio:d(t.ACA_CODEX_COMPACT_CONTEXT_RATIO,.45),rolloverTokenThreshold:a(t.ACA_CODEX_ROLLOVER_TOKEN_THRESHOLD,18e4),rolloverContextRatio:d(t.ACA_CODEX_ROLLOVER_CONTEXT_RATIO,.6),rolloutMaxBytes:a(t.ACA_CODEX_ROLLOUT_MAX_BYTES,50*M),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:a(t.ACA_CODEX_USAGE_LOOP_MIN_SAMPLES,8),usageLoopMinInputTokens:a(t.ACA_CODEX_USAGE_LOOP_MIN_INPUT_TOKENS,5e4),usageLoopMaxOutputTokens:T(t.ACA_CODEX_USAGE_LOOP_MAX_OUTPUT_TOKENS,512)}}function f(t,e=Date.now()){const s=u(t),o=u(s.tokenUsage),n=u(o.last),i=p(n.inputTokens),l=p(n.outputTokens),c=p(n.totalTokens),r=p(o.modelContextWindow);return i===0&&c===0&&r===0?null:{inputTokens:i,outputTokens:l,hasOutputTokens:typeof n.outputTokens=="number"&&Number.isFinite(n.outputTokens),totalTokens:c,contextWindow:r,contextRatio:r>0?i/r:0,observedAtMs:e}}function C(t,e=Date.now()){const s=u(t),o=u(s.update);if(String(o.sessionUpdate||"")!=="usage_update")return null;const n=u(o._meta),i=u(n.codex);return Object.keys(i).length===0?null:f(i,e)}function E(t,e,s={}){const o=[],n=[];return t.usage&&t.usage.inputTokens>=e.compactTokenThreshold&&o.push("token-threshold"),t.usage&&t.usage.contextRatio>=e.compactContextRatio&&o.push("context-ratio"),t.rolloutBytes>=e.rolloutMaxBytes&&o.push("rollout-size"),t.turnCount>=e.threadMaxTurns&&o.push("turn-count"),s.compactAttempted&&(s.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:o.length>0,rollover:n.length>0,reasons:[...new Set([...o,...n])]}}function O(t){if(!t)return 0;try{const e=A.statSync(t);return e.isFile()?e.size:0}catch{return 0}}function x(t){const e=[...new Set(t.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: ${t.previousThreadId}`,`Working directory: ${t.cwd}`,"","Latest user request:",h(t.userRequest,3e3)||"(not available)","","Latest completed assistant result:",h(t.assistantResponse,7e3)||"(not available)","","Recently affected files:",e.length>0?e.map(s=>`- ${s}`).join(`
1
+ import f from"node:fs";const T=1024*1024;function x(t=process.env){return{compactTokenThreshold:u(t.ACA_CODEX_COMPACT_TOKEN_THRESHOLD,12e4),compactContextRatio:h(t.ACA_CODEX_COMPACT_CONTEXT_RATIO,.45),rolloverTokenThreshold:u(t.ACA_CODEX_ROLLOVER_TOKEN_THRESHOLD,18e4),rolloverContextRatio:h(t.ACA_CODEX_ROLLOVER_CONTEXT_RATIO,.6),rolloutMaxBytes:u(t.ACA_CODEX_ROLLOUT_MAX_BYTES,50*T),threadMaxTurns:u(t.ACA_CODEX_THREAD_MAX_TURNS,40),compactTimeoutMs:u(t.ACA_CODEX_COMPACT_TIMEOUT_MS,600*1e3),slowEventThresholdMs:u(t.ACA_CODEX_SLOW_EVENT_THRESHOLD_MS,300*1e3),usageLoopWindowMs:u(t.ACA_CODEX_USAGE_LOOP_WINDOW_MS,120*1e3),usageLoopMinSamples:u(t.ACA_CODEX_USAGE_LOOP_MIN_SAMPLES,4),usageLoopMinInputTokens:u(t.ACA_CODEX_USAGE_LOOP_MIN_INPUT_TOKENS,5e4),usageLoopMaxOutputTokens:_(t.ACA_CODEX_USAGE_LOOP_MAX_OUTPUT_TOKENS,128)}}function m(t,e=Date.now()){const s=a(t),o=a(s.tokenUsage),n=a(o.last),r=c(n.inputTokens),l=c(n.outputTokens),d=c(n.totalTokens),i=c(o.modelContextWindow);return r===0&&d===0&&i===0?null:{source:"thread_usage",inputTokens:r,outputTokens:l,hasOutputTokens:typeof n.outputTokens=="number"&&Number.isFinite(n.outputTokens),totalTokens:d,contextWindow:i,contextRatio:i>0?r/i:0,observedAtMs:e,threadId:p(s.threadId),turnId:p(s.turnId),responseId:null}}function M(t,e=Date.now()){const s=a(t),o=a(s.usage);if(Object.keys(o).length===0)return null;const n=c(o.inputTokens),r=c(o.outputTokens),l=c(o.totalTokens);return n===0&&r===0&&l===0?null:{source:"raw_response",inputTokens:n,outputTokens:r,hasOutputTokens:typeof o.outputTokens=="number"&&Number.isFinite(o.outputTokens),totalTokens:l,contextWindow:0,contextRatio:0,observedAtMs:e,threadId:p(s.threadId),turnId:p(s.turnId),responseId:p(s.responseId)}}function C(t,e=Date.now()){const s=a(t),o=a(s.update);if(String(o.sessionUpdate||"")!=="usage_update")return null;const n=a(o._meta),r=a(n.codex);return Object.keys(r).length===0?null:m(r,e)}function E(t,e=Date.now()){const s=a(t),o=a(s.update);if(String(o.sessionUpdate||"")!=="aca_codex_raw_response_usage")return null;const n=a(o._meta),r=a(n.codex);return M(r.rawResponse,e)}function k(t,e,s={}){const o=[],n=[];return t.usage&&t.usage.inputTokens>=e.compactTokenThreshold&&o.push("token-threshold"),t.usage&&t.usage.contextRatio>=e.compactContextRatio&&o.push("context-ratio"),t.rolloutBytes>=e.rolloutMaxBytes&&o.push("rollout-size"),t.turnCount>=e.threadMaxTurns&&o.push("turn-count"),s.compactAttempted&&(s.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:o.length>0,rollover:n.length>0,reasons:[...new Set([...o,...n])]}}function g(t){if(!t)return 0;try{const e=f.statSync(t);return e.isFile()?e.size:0}catch{return 0}}function R(t){const e=[...new Set(t.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: ${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(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,s){const o=this.lastEventAtMs??this.startedAtMs;return e-o<s||this.lastSlowReportAtMs>0&&e-this.lastSlowReportAtMs<s?!1:(this.lastSlowReportAtMs=e,!0)}snapshot(e=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,e-s),durationMs:Math.max(0,e-this.startedAtMs)}}}class k{policy;samples=[];detected=!1;constructor(e){this.policy=e}recordMeaningfulEvent(){this.samples=[],this.detected=!1}recordUsage(e){if(this.detected)return null;if(!e.hasOutputTokens||e.inputTokens<this.policy.usageLoopMinInputTokens||e.outputTokens>this.policy.usageLoopMaxOutputTokens)return this.samples=[],null;const s=e.observedAtMs-this.policy.usageLoopWindowMs;if(this.samples=this.samples.filter(r=>r.observedAtMs>=s),this.samples.push(e),this.samples.length<this.policy.usageLoopMinSamples)return null;const o=this.samples.slice(-this.policy.usageLoopMinSamples),n=o.map(r=>r.inputTokens),i=Math.min(...n),l=Math.max(...n),c=Math.max(2e3,Math.floor(l*.05));return l-i>c?null:(this.detected=!0,{sampleCount:o.length,windowMs:o.at(-1).observedAtMs-o[0].observedAtMs,minInputTokens:i,maxInputTokens:l,maxOutputTokens:Math.max(...o.map(r=>r.outputTokens)),detectedAtMs:e.observedAtMs})}}function a(t,e){const s=Number.parseInt(t??"",10);return Number.isInteger(s)&&s>0?s:e}function T(t,e){const s=Number.parseInt(t??"",10);return Number.isInteger(s)&&s>=0?s:e}function d(t,e){const s=Number.parseFloat(t??"");return Number.isFinite(s)&&s>0&&s<=1?s:e}function p(t){return typeof t=="number"&&Number.isFinite(t)&&t>=0?Math.floor(t):0}function h(t,e){const s=t.replace(/\r\n/g,`
3
+ `)}class I{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,s){const o=this.lastEventAtMs??this.startedAtMs;return e-o<s||this.lastSlowReportAtMs>0&&e-this.lastSlowReportAtMs<s?!1:(this.lastSlowReportAtMs=e,!0)}snapshot(e=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,e-s),durationMs:Math.max(0,e-this.startedAtMs)}}}class w{policy;samples=[];detected=!1;rawResponseObserved=!1;constructor(e){this.policy=e}recordMeaningfulEvent(){this.samples=[],this.detected=!1}recordUsage(e){if(this.detected)return null;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=[],null;const s=e.observedAtMs-this.policy.usageLoopWindowMs;if(this.samples=this.samples.filter(i=>i.observedAtMs>=s),this.samples.push(e),this.samples.length<this.policy.usageLoopMinSamples)return null;const o=this.samples.slice(-this.policy.usageLoopMinSamples),n=o.map(i=>i.inputTokens),r=Math.min(...n),l=Math.max(...n),d=Math.max(2e3,Math.floor(l*.05));return l-r>d?null:(this.detected=!0,{source:e.source,sampleCount:o.length,windowMs:o.at(-1).observedAtMs-o[0].observedAtMs,minInputTokens:r,maxInputTokens:l,maxOutputTokens:Math.max(...o.map(i=>i.outputTokens)),detectedAtMs:e.observedAtMs,threadId:e.threadId,turnId:e.turnId,responseIds:o.map(i=>i.responseId).filter(i=>!!i)})}}function u(t,e){const s=Number.parseInt(t??"",10);return Number.isInteger(s)&&s>0?s:e}function _(t,e){const s=Number.parseInt(t??"",10);return Number.isInteger(s)&&s>=0?s:e}function h(t,e){const s=Number.parseFloat(t??"");return Number.isFinite(s)&&s>0&&s<=1?s:e}function c(t){return typeof t=="number"&&Number.isFinite(t)&&t>=0?Math.floor(t):0}function A(t,e){const s=t.replace(/\r\n/g,`
4
4
  `).trim();return s.length<=e?s:`${s.slice(0,e-16).trimEnd()}
5
- ...[truncated]`}function u(t){return t&&typeof t=="object"&&!Array.isArray(t)?t:{}}export{g as CodexPromptPerformanceTracker,k as CodexUsageLoopDetector,x as buildCodexContinuityInstructions,C as codexTokenUsageFromAcpSessionUpdate,f as codexTokenUsageFromNotification,E as decideCodexContextMaintenance,_ as readCodexContextMaintenancePolicy,O as rolloutFileSize};
5
+ ...[truncated]`}function a(t){return t&&typeof t=="object"&&!Array.isArray(t)?t:{}}function p(t){return typeof t=="string"&&t.trim()?t.trim():null}export{I as CodexPromptPerformanceTracker,w as CodexUsageLoopDetector,R as buildCodexContinuityInstructions,E as codexRawResponseUsageFromAcpSessionUpdate,C as codexTokenUsageFromAcpSessionUpdate,m as codexTokenUsageFromNotification,M as codexTokenUsageFromRawResponseCompleted,k as decideCodexContextMaintenance,x as readCodexContextMaintenancePolicy,g as rolloutFileSize};
@@ -1 +1 @@
1
- import p from"node:fs";import{runAbortableRequest as w}from"../../client/abortable-request.js";import{BoundedItemBuffer as k}from"../../client/bounded-buffer.js";import{promptBlocksToAcpContent as R}from"../../client/acp-content.js";import{acpSessionUpdateFromParams as M,eventFromAcpSessionUpdate as T,statusFromAcpSessionUpdate as q,subtaskContextFromAcpSessionUpdate as U}from"../../client/acp-events.js";import{defaultAcpRuntimePool as S}from"../../client/acp-runtime-pool.js";import{resolveCodexAcpLaunch as y}from"./launch.js";import{CodexPromptPerformanceTracker as E,CodexUsageLoopDetector as D,codexTokenUsageFromAcpSessionUpdate as O,readCodexContextMaintenancePolicy as C}from"./context-maintenance.js";import{ensureCodexProviderRuntimeConfig as I}from"../../../core/codex-provider-runtime-config.js";const L={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 te{id="codex-acp";name="Codex ACP";capabilities=L;async createSession(e){return new j(e.cwd,e.providerSessionId??null,e.timeoutMs)}async forkSession(e){if(!p.existsSync(e.cwd)||!p.statSync(e.cwd).isDirectory())throw new Error(`Project root does not exist or is not a directory: ${e.cwd}`);const s=e.sourceProviderSessionId.trim(),o=await I(),r=await S.acquire({providerId:"codex-acp",cwd:e.cwd,providerSessionId:s,runtimeIdentity:o?.runtimeIdentity||"default",env:v(o),...y(),onSessionUpdate:()=>{}});try{await b(r,o),r.initialized||(await r.client.initialize(6e4),r.markInitialized()),e.onStatus?.({phase:"forking",label:"正在分叉 Codex 会话",detail:s,updateType:"session/fork",atMs:Date.now()});const n=await r.client.forkSession({sessionId:s,cwd:e.cwd,config:A(e)},e.timeoutMs??12e4),i=h(n);if(!i)throw new Error("Codex ACP session/fork did not return sessionId");return{providerSessionId:i}}finally{r.release()}}}class j{cwd;providerSessionId;defaultTimeoutMs;lease=null;sessionResponse=null;statusCallback=null;updateCallback=null;permissionCallback=null;userInputCallback=null;abortHandler=null;statusTimeline=[];updates=new k(16);content="";performanceTracker=null;usageLoopDetector=null;usageLoopReject=null;constructor(e,s,o=W()){this.cwd=e,this.providerSessionId=s,this.defaultTimeoutMs=o}async sendPrompt(e){if(!p.existsSync(e.cwd)||!p.statSync(e.cwd).isDirectory())throw new Error(`Project root does not exist or is not a directory: ${e.cwd}`);const s=Date.now();this.statusCallback=e.onStatus??null,this.updateCallback=e.onUpdate??null,this.permissionCallback=e.onPermissionRequest??null,this.userInputCallback=e.onUserInputRequest??null,this.attachAbortSignal(e.signal),this.content="",this.updates.clear(),this.statusTimeline.length=0;const o=C();this.performanceTracker=new E(s),this.usageLoopDetector=new D(o);let r=null,n=null,i=this.providerSessionId,c,a=null;try{this.emitStatus("initializing","Codex ACP 启动中","codex-acp");const l=await this.acquireRuntime(e.cwd);this.lease=l,r=l.client,n=l.started,await this.initialize(l),i=await this.establishSession(l,e),await this.applyConfig(r,i,e),this.emitStatus("thinking","思考中"),a=this.startSlowStatusTimer(),c=await this.promptWithWatchdog(r,{sessionId:i,prompt:R(e.prompt,e.promptBlocks),timeoutMs:e.timeoutMs??this.defaultTimeoutMs,signal:e.signal});const d=P(c);d&&d!==i&&(i=d,this.providerSessionId=d,l.setProviderSession({providerSessionId:d,sessionResponse:this.sessionResponse}))}finally{a&&clearInterval(a),this.usageLoopReject=null,this.usageLoopDetector=null,this.abortHandler&&e.signal&&(e.signal.removeEventListener("abort",this.abortHandler),this.abortHandler=null),this.releaseLease()}return this.emitStatus("completed","已完成"),{providerSessionId:i,cliType:"builtin",agentType:"codex",effectiveConfig:{model:e.model??null,mode:e.mode??null,configOptionValues:e.configOptionValues??null,command:n.command,processPid:n.child.pid??null,provider:"codex-acp",protocol:"acp"},command:n.command,args:n.args,cwd:e.cwd,exitCode:n.child.exitCode,signal:n.child.signalCode,durationMs:Date.now()-s,stdout:r.rpc.stdout,stderr:r.rpc.stderr,content:this.content.trim(),updateCount:this.updates.count(),updates:this.updates.snapshot(),statusTimeline:[...this.statusTimeline],performance:this.performanceTracker?.snapshot(),contextUsage:B(c),promptResponse:c}}async compactContext(e){if(!p.existsSync(e.cwd)||!p.statSync(e.cwd).isDirectory())throw new Error(`Project root does not exist or is not a directory: ${e.cwd}`);const s=Date.now();this.statusCallback=e.onStatus??null,this.updateCallback=e.onUpdate??null,this.attachAbortSignal(e.signal);let o=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),o=await this.establishSession(i,e),await this.applyConfig(i.client,o,e);const c=V(e.timeoutMs);r=await this.compactWithAbort(i.client,{sessionId:o,allowRollover:e.allowRollover===!0,timeoutMs:c,signal:e.signal});const a=P(r);a&&a!==o&&(o=a,this.providerSessionId=a,i.setProviderSession({providerSessionId:a,sessionResponse:this.sessionResponse}))}catch(i){if(this.lease&&N(i,e.signal)){const c=this.lease;this.lease=null,await c.dispose().catch(()=>{})}throw this.emitStatus("failed","上下文压缩失败",i instanceof Error?i.message:String(i)),i}finally{this.abortHandler&&e.signal&&(e.signal.removeEventListener("abort",this.abortHandler),this.abortHandler=null),this.releaseLease()}const n=u(r)??{};return{providerSessionId:o||this.providerSessionId||"",contextUsage:_(n.contextUsage),maintenance:u(n.maintenance)??{},durationMs:Date.now()-s}}async cancel(){this.providerSessionId&&this.lease?.client.cancel(this.providerSessionId)}async close(){this.statusCallback=null,this.updateCallback=null,this.permissionCallback=null,this.userInputCallback=null,this.abortHandler&&(this.abortHandler=null),this.releaseLease()}async acquireRuntime(e){const s=y(),o=await I(),r=await S.acquire({providerId:"codex-acp",cwd:e,providerSessionId:this.providerSessionId,runtimeIdentity:o?.runtimeIdentity||"default",env:v(o),command:s.command,args:s.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 b(r,o),r}catch(n){throw await r.dispose().catch(()=>{}),n}}async initialize(e){e.initialized||(await e.client.initialize(6e4),e.markInitialized())}async establishSession(e,s){const o=e.client,r=A(s);if(e.providerSessionId&&e.sessionEstablished)return this.providerSessionId=e.providerSessionId,this.sessionResponse=e.sessionResponse,e.providerSessionId;if(this.providerSessionId)try{this.emitStatus("resuming","Codex ACP 会话恢复中","session/load");const c=await o.loadSession({sessionId:this.providerSessionId,cwd:s.cwd,config:r},6e4);this.sessionResponse=u(c);const a=h(c)||this.providerSessionId;return this.providerSessionId=a,e.setProviderSession({providerSessionId:a,sessionResponse:this.sessionResponse}),a}catch(c){if(s.requireSessionResume){const a=c instanceof Error?c.message:String(c);throw new Error(`无法恢复导入的 Codex ACP Session ${this.providerSessionId}:${a}`)}this.providerSessionId=null}this.emitStatus("acp","Codex ACP 会话创建中","session/new");const n=await o.newSession({cwd:s.cwd,config:r},12e4);this.sessionResponse=u(n);const i=h(n);if(!i)throw new Error("Codex ACP session/new did not return sessionId");return this.providerSessionId=i,e.setProviderSession({providerSessionId:i,sessionResponse:this.sessionResponse}),i}async applyConfig(e,s,o){o.model&&await e.setSessionConfigOption({sessionId:s,configId:"model",value:o.model},6e4).catch(()=>{});const r=g(o.mode);r&&await e.setSessionConfigOption({sessionId:s,configId:"approvalPolicy",value:r},6e4).catch(()=>{});const n=f(o.mode);n&&await e.setSessionConfigOption({sessionId:s,configId:"sandbox",value:n},6e4).catch(()=>{});for(const[i,c]of Object.entries(o.configOptionValues??{}))["model","mode","sandbox","approvalPolicy"].includes(i)||await e.setSessionConfigOption({sessionId:s,configId:i,value:c},6e4).catch(()=>{})}handleSessionUpdate(e){const s=M(e),o=String(s?.update.sessionUpdate||"");if(F(o))this.performanceTracker?.recordEvent(),this.usageLoopDetector?.recordMeaningfulEvent();else if(o==="usage_update"){const c=O(e),a=c?this.usageLoopDetector?.recordUsage(c):null;a&&this.usageLoopReject?.(a)}this.updates.push({method:"session/update",params:e});const r=U(e),n=T(e);n&&(n.type==="agent_message_chunk"&&!r&&(this.content+=n.text??""),this.updateCallback?.(n));const i=q(e);i&&this.emitStatus(i.phase,i.label,i.detail,i.updateType)}startSlowStatusTimer(){const e=C().slowEventThresholdMs,s=setInterval(()=>{const o=this.performanceTracker,r=Date.now();if(!o?.shouldReportSlow(r,e))return;const n=o.snapshot(r),i=Math.max(1,Math.round(n.silentForMs/6e4));this.emitStatus("thinking","模型长时间处理中",`已 ${i} 分钟没有收到新的 ACP 事件`,"aca.performance.slow")},Math.min(3e4,Math.max(1e3,Math.floor(e/4))));return s.unref(),s}attachAbortSignal(e){if(e){if(this.abortHandler=()=>{this.cancel()},e.aborted)throw new Error("Codex ACP prompt cancelled");e.addEventListener("abort",this.abortHandler,{once:!0})}}async promptWithAbort(e,s){return w({signal:s.signal,cancelledMessage:"Codex ACP prompt cancelled",startRequest:()=>e.prompt({sessionId:s.sessionId,prompt:s.prompt},s.timeoutMs),onAbort:async()=>{e.cancel(s.sessionId),await this.lease?.dispose()}})}async promptWithWatchdog(e,s){const o=this.promptWithAbort(e,s),r=new Promise((n,i)=>{this.usageLoopReject=c=>{this.usageLoopReject=null;const a=`${c.sampleCount} 次请求均输入约 ${Math.round(c.maxInputTokens/1e3)}k tokens,且未产生有效过程事件`,l=new Error(`Codex 内部重复请求:${a}`);this.emitStatus("failed","Codex 内部重复请求",a,"aca.performance.usage_loop");const d=this.lease;this.lease=null,e.cancel(s.sessionId),d?.dispose().catch(()=>{}),i(l)}});try{return await Promise.race([o,r])}finally{this.usageLoopReject=null}}async compactWithAbort(e,s){return w({signal:s.signal,cancelledMessage:"Codex ACP 上下文压缩已取消",startRequest:()=>e.compactSession({sessionId:s.sessionId,allowRollover:s.allowRollover,timeoutMs:s.timeoutMs},s.timeoutMs),onAbort:async()=>{e.cancel(s.sessionId),await this.lease?.dispose()}})}async requestPermission(e){if(!this.permissionCallback)return{outcome:{outcome:"cancelled"}};const s=e&&typeof e=="object"&&!Array.isArray(e)?e:{},o=s.request&&typeof s.request=="object"&&!Array.isArray(s.request)?s.request:s,n=(Array.isArray(o.options)?o.options:[]).map((i,c)=>{const a=i&&typeof i=="object"&&!Array.isArray(i)?i:{};return{optionId:String(a.optionId??a.id??c),kind:String(a.kind??"choice"),...typeof a.label=="string"?{label:a.label}:{}}});return this.emitStatus("requestPermission","等待授权",$(o),"session/request_permission"),this.permissionCallback({requestId:String(o.requestId??o.id??`codex-acp-permission-${Date.now()}`),params:e,options:n})}async requestUserInput(e){if(!this.userInputCallback)return{outcome:{outcome:"cancelled"}};const s=e&&typeof e=="object"&&!Array.isArray(e)?e:{},o=s.request&&typeof s.request=="object"&&!Array.isArray(s.request)?s.request:s;return this.emitStatus("requestPermission","等待输入",H(o),"elicitation/create"),this.userInputCallback({requestId:String(o.requestId??o.id??`codex-acp-input-${Date.now()}`),prompt:String(o.prompt??o.message??o.question??""),params:e,...typeof o.defaultValue=="string"?{defaultValue:o.defaultValue}:{}})}emitStatus(e,s,o,r){const n={phase:e,label:s,...o?{detail:o}:{},...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 z=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 F(t){return z.has(t)}async function b(t,e){t.runtimeConfigured||(await t.client.configureRuntime({codexProvider:e?{baseUrl:e.baseUrl,apiKey:e.apiKey,revision:e.revision}:null}),t.markRuntimeConfigured())}function v(t){return t?{OPENAI_API_KEY:t.apiKey,OPENAI_BASE_URL:t.baseUrl}:{}}function N(t,e){if(e?.aborted)return!0;const s=t instanceof Error?t.message:String(t||"");return/compact.*timed out|request timed out.*compact|adapter closed|aborted|cancelled/i.test(s)}function h(t){if(!t||typeof t!="object"||Array.isArray(t))return null;const e=t.sessionId;return typeof e=="string"&&e.trim()?e.trim():null}function u(t){return t&&typeof t=="object"&&!Array.isArray(t)?t:null}function f(t){const e=String(t||"").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 g(t){const e=String(t||"").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 A(t){return{...t.model?{model:t.model}:{},...g(t.mode)?{approvalPolicy:g(t.mode)}:{},...f(t.mode)?{sandbox:f(t.mode)}:{},...t.configOptionValues??{}}}function $(t){const e=t.params&&typeof t.params=="object"&&!Array.isArray(t.params)?t.params:t;for(const s of["command","cwd","reason","grantRoot","method"]){const o=e[s];if(typeof o=="string"&&o.trim())return x(o)}}function H(t){for(const e of["prompt","message","question","title"]){const s=t[e];if(typeof s=="string"&&s.trim())return x(s)}}function x(t){const e=t.replace(/\s+/g," ").trim();return e.length>120?`${e.slice(0,117)}...`:e}function W(){const t=Number.parseInt(process.env.ACA_CODEX_ACP_PROMPT_TIMEOUT_MS??process.env.ACA_ACP_PROMPT_TIMEOUT_MS??"",10);return Number.isInteger(t)&&t>=3e4?t:360*60*1e3}function V(t){const e=Number.isInteger(t)?Number(t):Number.parseInt(process.env.ACA_CODEX_ACP_COMPACT_TIMEOUT_MS??"",10);return Number.isFinite(e)&&e>=3e4?Math.min(e,6e5):12e4}function P(t){if(!t||typeof t!="object"||Array.isArray(t))return null;const e=t;return typeof e.sessionId=="string"&&e.sessionId.trim()?e.sessionId:null}function B(t){const e=u(t),s=u(e?._meta);return _(s?.acaContextUsage)}function _(t){const e=u(t);if(!e)return null;const s=m(e.contextWindow),o=m(e.usedTokens),r=m(e.inputTokens),n=String(e.state||"unknown"),i=["active","compacted","new_thread"].includes(n)?n:"unknown";return{usedTokens:o,inputTokens:r,contextWindow:s,ratio:s>0?Math.max(0,m(e.ratio)||o/s):0,observedAtMs:m(e.observedAtMs)||Date.now(),state:i,maintenanceAction:typeof e.maintenanceAction=="string"?e.maintenanceAction:null}}function m(t){return typeof t=="number"&&Number.isFinite(t)&&t>=0?t:0}export{te as CodexAcpProvider,L as codexAcpCapabilities};
1
+ import u from"node:fs";import{runAbortableRequest as w}from"../../client/abortable-request.js";import{BoundedItemBuffer as M}from"../../client/bounded-buffer.js";import{promptBlocksToAcpContent as T}from"../../client/acp-content.js";import{acpSessionUpdateFromParams as q,eventFromAcpSessionUpdate as U,statusFromAcpSessionUpdate as E,subtaskContextFromAcpSessionUpdate as D}from"../../client/acp-events.js";import{defaultAcpRuntimePool as y}from"../../client/acp-runtime-pool.js";import{resolveCodexAcpLaunch as C}from"./launch.js";import{CodexPromptPerformanceTracker as O,CodexUsageLoopDetector as L,codexRawResponseUsageFromAcpSessionUpdate as F,codexTokenUsageFromAcpSessionUpdate as j,readCodexContextMaintenancePolicy as I}from"./context-maintenance.js";import{ensureCodexProviderRuntimeConfig as b}from"../../../core/codex-provider-runtime-config.js";const z={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 ie{id="codex-acp";name="Codex ACP";capabilities=z;async createSession(e){return new N(e.cwd,e.providerSessionId??null,e.timeoutMs)}async forkSession(e){if(!u.existsSync(e.cwd)||!u.statSync(e.cwd).isDirectory())throw new Error(`Project root does not exist or is not a directory: ${e.cwd}`);const t=e.sourceProviderSessionId.trim(),o=await b(),r=await y.acquire({providerId:"codex-acp",cwd:e.cwd,providerSessionId:t,runtimeIdentity:o?.runtimeIdentity||"default",env:v(o),...C(),onSessionUpdate:()=>{}});try{await A(r,o),r.initialized||(await r.client.initialize(6e4),r.markInitialized()),e.onStatus?.({phase:"forking",label:"正在分叉 Codex 会话",detail:t,updateType:"session/fork",atMs:Date.now()});const i=await r.client.forkSession({sessionId:t,cwd:e.cwd,config:x(e)},e.timeoutMs??12e4),n=h(i);if(!n)throw new Error("Codex ACP session/fork did not return sessionId");return{providerSessionId:n}}finally{r.release()}}}class N{cwd;providerSessionId;defaultTimeoutMs;lease=null;sessionResponse=null;statusCallback=null;updateCallback=null;permissionCallback=null;userInputCallback=null;abortHandler=null;statusTimeline=[];updates=new M(16);content="";performanceTracker=null;usageLoopDetector=null;usagePatternReport=null;constructor(e,t,o=K()){this.cwd=e,this.providerSessionId=t,this.defaultTimeoutMs=o}async sendPrompt(e){if(!u.existsSync(e.cwd)||!u.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 o=I();this.performanceTracker=new O(t),this.usageLoopDetector=new L(o);let r=null,i=null,n=this.providerSessionId,c,a=null;try{this.emitStatus("initializing","Codex ACP 启动中","codex-acp");const d=await this.acquireRuntime(e.cwd);this.lease=d,r=d.client,i=d.started,await this.initialize(d),n=await this.establishSession(d,e),await this.applyConfig(r,n,e),this.emitStatus("thinking","思考中"),a=this.startSlowStatusTimer(),this.usagePatternReport=S=>{const R=`${S.sampleCount} 次内部采样均输入约 ${Math.round(S.maxInputTokens/1e3)}k tokens`;this.emitStatus("thinking","Codex 正在进行内部续步",R,"aca.performance.multi_response")},c=await this.promptWithAbort(r,{sessionId:n,prompt:T(e.prompt,e.promptBlocks),timeoutMs:e.timeoutMs??this.defaultTimeoutMs,signal:e.signal});const p=_(c);p&&p!==n&&(n=p,this.providerSessionId=p,d.setProviderSession({providerSessionId:p,sessionResponse:this.sessionResponse}))}finally{a&&clearInterval(a),this.usagePatternReport=null,this.usageLoopDetector=null,this.abortHandler&&e.signal&&(e.signal.removeEventListener("abort",this.abortHandler),this.abortHandler=null),this.releaseLease()}return this.emitStatus("completed","已完成"),{providerSessionId:n,cliType:"builtin",agentType:"codex",effectiveConfig:{model:e.model??null,mode:e.mode??null,configOptionValues:e.configOptionValues??null,command:i.command,processPid:i.child.pid??null,provider:"codex-acp",protocol:"acp"},command:i.command,args:i.args,cwd:e.cwd,exitCode:i.child.exitCode,signal:i.child.signalCode,durationMs:Date.now()-t,stdout:r.rpc.stdout,stderr:r.rpc.stderr,content:this.content.trim(),updateCount:this.updates.count(),updates:this.updates.snapshot(),statusTimeline:[...this.statusTimeline],performance:this.performanceTracker?.snapshot(),contextUsage:G(c),promptResponse:c}}async compactContext(e){if(!u.existsSync(e.cwd)||!u.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 o=this.providerSessionId,r;try{this.emitStatus("initializing","Codex ACP 启动中","codex-acp");const n=await this.acquireRuntime(e.cwd);this.lease=n,await this.initialize(n),o=await this.establishSession(n,e),await this.applyConfig(n.client,o,e);const c=X(e.timeoutMs);r=await this.compactWithAbort(n.client,{sessionId:o,allowRollover:e.allowRollover===!0,timeoutMs:c,signal:e.signal});const a=_(r);a&&a!==o&&(o=a,this.providerSessionId=a,n.setProviderSession({providerSessionId:a,sessionResponse:this.sessionResponse}))}catch(n){if(this.lease&&V(n,e.signal)){const c=this.lease;this.lease=null,await c.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 i=l(r)??{};return{providerSessionId:o||this.providerSessionId||"",contextUsage:k(i.contextUsage),maintenance:l(i.maintenance)??{},durationMs:Date.now()-t}}async cancel(){this.providerSessionId&&this.lease?.client.cancel(this.providerSessionId)}async close(){this.statusCallback=null,this.updateCallback=null,this.permissionCallback=null,this.userInputCallback=null,this.abortHandler&&(this.abortHandler=null),this.releaseLease()}async acquireRuntime(e){const t=C(),o=await b(),r=await y.acquire({providerId:"codex-acp",cwd:e,providerSessionId:this.providerSessionId,runtimeIdentity:o?.runtimeIdentity||"default",env:v(o),command:t.command,args:t.args,onSessionUpdate:i=>this.handleSessionUpdate(i),onClientRequest:async(i,n)=>{if(i==="session/request_permission")return this.requestPermission(n);if(i==="elicitation/create"||i==="session/request_user_input")return this.requestUserInput(n);throw new Error(`Unsupported Codex ACP client request: ${i}`)}});try{return await A(r,o),r}catch(i){throw await r.dispose().catch(()=>{}),i}}async initialize(e){e.initialized||(await e.client.initialize(6e4),e.markInitialized())}async establishSession(e,t){const o=e.client,r=x(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 c=await o.loadSession({sessionId:this.providerSessionId,cwd:t.cwd,config:r},6e4);this.sessionResponse=l(c);const a=h(c)||this.providerSessionId;return this.providerSessionId=a,e.setProviderSession({providerSessionId:a,sessionResponse:this.sessionResponse}),a}catch(c){if(t.requireSessionResume){const a=c instanceof Error?c.message:String(c);throw new Error(`无法恢复导入的 Codex ACP Session ${this.providerSessionId}:${a}`)}this.providerSessionId=null}this.emitStatus("acp","Codex ACP 会话创建中","session/new");const i=await o.newSession({cwd:t.cwd,config:r},12e4);this.sessionResponse=l(i);const n=h(i);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,o){o.model&&await e.setSessionConfigOption({sessionId:t,configId:"model",value:o.model},6e4).catch(()=>{});const r=g(o.mode);r&&await e.setSessionConfigOption({sessionId:t,configId:"approvalPolicy",value:r},6e4).catch(()=>{});const i=f(o.mode);i&&await e.setSessionConfigOption({sessionId:t,configId:"sandbox",value:i},6e4).catch(()=>{});for(const[n,c]of Object.entries(o.configOptionValues??{}))["model","mode","sandbox","approvalPolicy"].includes(n)||await e.setSessionConfigOption({sessionId:t,configId:n,value:c},6e4).catch(()=>{})}handleSessionUpdate(e){const t=q(e),o=String(t?.update.sessionUpdate||""),r=D(e);if(!r&&H(o))this.performanceTracker?.recordEvent(),this.usageLoopDetector?.recordMeaningfulEvent();else if(!r&&o==="aca_codex_raw_response_usage"){const c=F(e),a=c?this.usageLoopDetector?.recordUsage(c):null;a&&this.usagePatternReport?.(a)}else if(!r&&o==="usage_update"){const c=j(e),a=c?this.usageLoopDetector?.recordUsage(c):null;a&&this.usagePatternReport?.(a)}this.updates.push({method:"session/update",params:e});const i=U(e);i&&(i.type==="agent_message_chunk"&&!r&&(this.content+=i.text??""),this.updateCallback?.(i));const n=E(e);n&&this.emitStatus(n.phase,n.label,n.detail,n.updateType)}startSlowStatusTimer(){const e=I().slowEventThresholdMs,t=setInterval(()=>{const o=this.performanceTracker,r=Date.now();if(!o?.shouldReportSlow(r,e))return;const i=o.snapshot(r),n=Math.max(1,Math.round(i.silentForMs/6e4));this.emitStatus("thinking","模型长时间处理中",`已 ${n} 分钟没有收到新的 ACP 事件`,"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()},e.aborted)throw new Error("Codex ACP prompt cancelled");e.addEventListener("abort",this.abortHandler,{once:!0})}}async promptWithAbort(e,t){return w({signal:t.signal,cancelledMessage:"Codex ACP prompt cancelled",startRequest:()=>e.prompt({sessionId:t.sessionId,prompt:t.prompt},t.timeoutMs),onAbort:async()=>{e.cancel(t.sessionId),await this.lease?.dispose()}})}async compactWithAbort(e,t){return w({signal:t.signal,cancelledMessage:"Codex ACP 上下文压缩已取消",startRequest:()=>e.compactSession({sessionId:t.sessionId,allowRollover:t.allowRollover,timeoutMs:t.timeoutMs},t.timeoutMs),onAbort:async()=>{e.cancel(t.sessionId),await this.lease?.dispose()}})}async requestPermission(e){if(!this.permissionCallback)return{outcome:{outcome:"cancelled"}};const t=e&&typeof e=="object"&&!Array.isArray(e)?e:{},o=t.request&&typeof t.request=="object"&&!Array.isArray(t.request)?t.request:t,i=(Array.isArray(o.options)?o.options:[]).map((n,c)=>{const a=n&&typeof n=="object"&&!Array.isArray(n)?n:{};return{optionId:String(a.optionId??a.id??c),kind:String(a.kind??"choice"),...typeof a.label=="string"?{label:a.label}:{}}});return this.emitStatus("requestPermission","等待授权",W(o),"session/request_permission"),this.permissionCallback({requestId:String(o.requestId??o.id??`codex-acp-permission-${Date.now()}`),params:e,options:i})}async requestUserInput(e){if(!this.userInputCallback)return{outcome:{outcome:"cancelled"}};const t=e&&typeof e=="object"&&!Array.isArray(e)?e:{},o=t.request&&typeof t.request=="object"&&!Array.isArray(t.request)?t.request:t;return this.emitStatus("requestPermission","等待输入",B(o),"elicitation/create"),this.userInputCallback({requestId:String(o.requestId??o.id??`codex-acp-input-${Date.now()}`),prompt:String(o.prompt??o.message??o.question??""),params:e,...typeof o.defaultValue=="string"?{defaultValue:o.defaultValue}:{}})}emitStatus(e,t,o,r){const i={phase:e,label:t,...o?{detail:o}:{},...r?{updateType:r}:{},atMs:Date.now()},n=this.statusTimeline[this.statusTimeline.length-1];n&&n.phase===i.phase&&n.label===i.label&&n.detail===i.detail&&n.updateType===i.updateType||(this.statusTimeline.push(i),this.statusCallback?.(i))}releaseLease(){this.lease?.release(),this.lease=null}}const $=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 H(s){return $.has(s)}async function A(s,e){s.runtimeConfigured||(await s.client.configureRuntime({codexProvider:e?{baseUrl:e.baseUrl,apiKey:e.apiKey,revision:e.revision}:null}),s.markRuntimeConfigured())}function v(s){return s?{OPENAI_API_KEY:s.apiKey,OPENAI_BASE_URL:s.baseUrl}:{}}function V(s,e){if(e?.aborted)return!0;const t=s instanceof Error?s.message:String(s||"");return/compact.*timed out|request timed out.*compact|adapter closed|aborted|cancelled/i.test(t)}function h(s){if(!s||typeof s!="object"||Array.isArray(s))return null;const e=s.sessionId;return typeof e=="string"&&e.trim()?e.trim():null}function l(s){return s&&typeof s=="object"&&!Array.isArray(s)?s:null}function f(s){const e=String(s||"").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 g(s){const e=String(s||"").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 x(s){return{...s.model?{model:s.model}:{},...g(s.mode)?{approvalPolicy:g(s.mode)}:{},...f(s.mode)?{sandbox:f(s.mode)}:{},...s.configOptionValues??{}}}function W(s){const e=s.params&&typeof s.params=="object"&&!Array.isArray(s.params)?s.params:s;for(const t of["command","cwd","reason","grantRoot","method"]){const o=e[t];if(typeof o=="string"&&o.trim())return P(o)}}function B(s){for(const e of["prompt","message","question","title"]){const t=s[e];if(typeof t=="string"&&t.trim())return P(t)}}function P(s){const e=s.replace(/\s+/g," ").trim();return e.length>120?`${e.slice(0,117)}...`:e}function K(){const s=Number.parseInt(process.env.ACA_CODEX_ACP_PROMPT_TIMEOUT_MS??process.env.ACA_ACP_PROMPT_TIMEOUT_MS??"",10);return Number.isInteger(s)&&s>=3e4?s:360*60*1e3}function X(s){const e=Number.isInteger(s)?Number(s):Number.parseInt(process.env.ACA_CODEX_ACP_COMPACT_TIMEOUT_MS??"",10);return Number.isFinite(e)&&e>=3e4?Math.min(e,6e5):12e4}function _(s){if(!s||typeof s!="object"||Array.isArray(s))return null;const e=s;return typeof e.sessionId=="string"&&e.sessionId.trim()?e.sessionId:null}function G(s){const e=l(s),t=l(e?._meta);return k(t?.acaContextUsage)}function k(s){const e=l(s);if(!e)return null;const t=m(e.contextWindow),o=m(e.usedTokens),r=m(e.inputTokens),i=String(e.state||"unknown"),n=["active","compacted","new_thread"].includes(i)?i:"unknown";return{usedTokens:o,inputTokens:r,contextWindow:t,ratio:t>0?Math.max(0,m(e.ratio)||o/t):0,observedAtMs:m(e.observedAtMs)||Date.now(),state:n,maintenanceAction:typeof e.maintenanceAction=="string"?e.maintenanceAction:null}}function m(s){return typeof s=="number"&&Number.isFinite(s)&&s>=0?s:0}export{ie as CodexAcpProvider,z as codexAcpCapabilities};
@@ -0,0 +1 @@
1
+ class a extends Error{providerSessionId;details;code="acp_session_invalidated";invalidateAcpSession=!0;category="internal_error";actions=["new_session"];constructor(r,i,t=null){super(r),this.providerSessionId=i,this.details=t,this.name="AcpSessionInvalidatedError"}}function d(e){if(!e||typeof e!="object"||Array.isArray(e))return null;const r=e;if(r.invalidateAcpSession!==!0&&r.code!=="acp_session_invalidated")return null;const i=typeof r.providerSessionId=="string"?r.providerSessionId.trim():"";if(!i)return null;const t=typeof r.category=="string"&&r.category.trim()?r.category.trim():"internal_error",n=Array.isArray(r.actions)?r.actions.filter(s=>typeof s=="string"&&!!s.trim()).map(s=>s.trim()):["new_session"],o=r.details&&typeof r.details=="object"&&!Array.isArray(r.details)?r.details:null;return{providerSessionId:i,category:t,actions:n,details:o}}export{a as AcpSessionInvalidatedError,d as acpSessionInvalidationFromError};
@@ -1,6 +1,6 @@
1
- import H from"node:crypto";import j from"node:fs";import $ from"node:path";import{compactAgentContext as G,forkAgentSession as K,sendAgentPrompt as X}from"../acp/agent.js";import{acaServerDownload as Z,acaServerRequest as I}from"./aca-server-client.js";import{loadAcaConfig as Q}from"./aca-config.js";import{prepareLocalWorktree as Y}from"./worktree.js";import{expandWorkerRuntimePath as ee}from"./worker-runtime-paths.js";import{collectAffectedFiles as O,extractAffectedFilePathsFromAcpRaw as te}from"./affected-files.js";import{resolveJobPermissionPolicy as ne}from"./job-permission-policy.js";import{appendCoalescedAcpEvent as oe}from"./acp-event-coalescer.js";import{shouldBufferAcpEvent as ae,shouldUploadAcpEvent as ie}from"./acp-event-upload-filter.js";import{userFacingAcpError as re}from"../acp/error-display.js";import{ensureCodexProviderRuntimeRevision as se}from"./codex-provider-runtime-config.js";const ce=1500,F=5e3,z=1e3,le=200,ue=64,U=900,de=1e4,pe=0,me=12e3;function fe(e){return ee(e)}function he(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 rt(e,t={}){let n=!1,o=null,a=null,r=0,s=0;const u=new Set,i=new Map,d=new Map,g=async()=>{if(n||Date.now()<s)return;const w=ye();if(B(u.size,w))try{const m=t.loadConfig?.()??Q();if(!m.token)return;for(;!n&&B(u.size,w);){const _=t.claimNextJob?await t.claimNextJob(m,[...i.keys()]):await I("POST","/api/client/jobs/claim",{machineId:m.machineId,workspaceId:m.defaultWorkspaceId,activeSessionIds:[...i.keys()]});if(r=0,s=0,!_.item)break;const h=_.item;if(n){await(t.cancelClaimedJob?t.cancelClaimedJob(h,m):R(h.job_id,m.machineId,"cancelled")).catch(()=>{});break}const f=h.session_id,P=i.get(f);u.add(h.job_id);const A=new AbortController;let p;p=(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 we(P,h,m,A)),!A.signal.aborted&&await(t.runJob??Se)(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),i.get(f)===p&&i.delete(f),d.delete(h.job_id)}),i.set(f,p),d.set(h.job_id,{controller:A,promise:p})}}catch(m){if(!n){r+=1;const _=We(r);s=Date.now()+_,e.warn(`job worker failed, retry in ${_}ms: ${m instanceof Error?m.message:String(m)}`)}}},v=()=>{if(n||o)return;const w=g();o=w,w.finally(()=>{o===w&&(o=null)})};v();const l=setInterval(()=>{v()},t.claimIntervalMs??ce);return{timer:l,stop:()=>a||(a=(async()=>{if(n)return;n=!0,clearInterval(l);for(const m of d.values())m.controller.abort();await o?.catch(()=>{});for(const m of d.values())m.controller.abort();const w=[...d.values()].map(m=>m.promise);w.length!==0&&await Promise.race([Promise.allSettled(w).then(()=>{}),M(me)])})(),a)}}async function we(e,t,n,o){let a=!1;const s=setInterval(()=>{a||o.signal.aborted||(a=!0,Promise.allSettled([I("POST",`/api/client/jobs/${encodeURIComponent(t.job_id)}/heartbeat`,{machineId:n.machineId}),W(t.job_id,o)]).finally(()=>{a=!1}))},F);try{await Promise.race([e,be(o.signal)])}finally{clearInterval(s)}}function be(e){return e.aborted?Promise.resolve():new Promise(t=>e.addEventListener("abort",()=>t(),{once:!0}))}function ye(){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?pe:t===0?0:Math.max(1,Math.min(t,8))}function B(e,t){return t===0||e<t}function Ie(){const e=Number.parseInt(process.env.ACA_ACP_EVENT_BATCH_INTERVAL_MS||"",10);return Number.isFinite(e)?Math.max(40,Math.min(e,2e3)):le}async function Se(e,t,n,o){const a=e.job_id,r=e.session_id,s=e.workspace_id,u=e.project_id??null,i=e.input??{},d=String(i.control?.type||"").toLowerCase();if(!d&&!i.message?.trim()&&(i.attachments??[]).length===0){await R(a,t.machineId,"job input missing message");return}const g=t.projects.find(p=>p.projectId===u),v=fe(g?.rootPath||e.project_root_path||"");if(!v){await R(a,t.machineId,"job project root is unknown");return}he(e)&&j.mkdirSync(v,{recursive:!0}),n.info(`claimed job=${a} session=${r} project=${g?.name??u??"unknown"}`);let l=!1,w=!1;const m=setInterval(()=>{l||(l=!0,I("POST",`/api/client/jobs/${encodeURIComponent(a)}/heartbeat`,{machineId:t.machineId}).catch(()=>{}).finally(()=>{l=!1})),w||(w=!0,W(a,o).catch(()=>{}).finally(()=>{w=!1}))},F);let _=0;const h=Pe(a,t.machineId),f=ke(a,t.machineId),P=new Set;let A=null;try{await f.sendNow({phase:"initializing",label:"准备工作区",detail:e.worktree_id?"worktree":"project root"}).catch(()=>{});const p=Y({projectId:u,sessionId:r,projectRoot:v,worktree:e});A=p,await ze(a,t.machineId,p);const b=_e(i.acpRun);if(Te(b.agentType)&&await se(e.codex_provider_revision),d==="compact"){if(!e.acp_session_id)throw new Error("Current session does not have an ACP context to compact");const c=await G({agentType:x(b.agentType,"codex"),cliType:x(b.cliType,"builtin"),acpSessionId:e.acp_session_id,cwd:p.cwd,model:i.model,mode:i.mode,configOptionValues:i.configOptionValues,timeoutMs:i.timeoutMs,allowRollover:i.control?.allowRollover===!0,signal:o.signal,onStatus:S=>{f.report({phase:S.phase,label:S.label,detail:S.detail,updateType:S.updateType,raw:S})}});await f.flush(),await I("POST",`/api/client/jobs/${encodeURIComponent(a)}/complete`,{machineId:t.machineId,acpSessionId:c.acpSessionId,suppressTurn:!0,result:{ok:!0,control:{type:"compact",allowRollover:i.control?.allowRollover===!0},acpSessionId:c.acpSessionId,durationMs:c.durationMs,usage:c.contextUsage,maintenance:c.maintenance,worktree:k(p)}}),n.info(`completed context compact job=${a}`);return}if(d==="fork"){const c=String(i.control?.sourceAcpSessionId||"").trim();if(!c)throw new Error("Source session does not have a Codex ACP context to fork");await f.sendNow({phase:"forking",label:"正在分叉 Codex 会话",detail:i.control?.sourceSessionId?`源会话 ${i.control.sourceSessionId}`:void 0}).catch(()=>{});const S=await K({agentType:x(b.agentType,"codex"),cliType:x(b.cliType,"builtin"),sourceProviderSessionId:c,cwd:p.cwd,model:i.model,mode:i.mode,configOptionValues:i.configOptionValues,timeoutMs:i.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 I("POST",`/api/client/jobs/${encodeURIComponent(a)}/complete`,{machineId:t.machineId,acpSessionId:S.acpSessionId,suppressTurn:!0,result:{ok:!0,control:{type:"fork",sourceSessionId:i.control?.sourceSessionId||null},acpSessionId:S.acpSessionId,worktree:k(p)}}),n.info(`completed Codex session fork job=${a}`);return}const T=await je({cwd:p.cwd,sessionId:r,jobId:a,attachments:i.attachments??[]});T.length>0&&(n.info(`materialized ${T.length} attachment(s) for job=${a}`),await f.sendNow({phase:"initializing",label:"附件已写入本机",detail:T.map(c=>c.relativePath).join(", "),raw:{attachments:L(T)}}).catch(()=>{}));const N=Ee(i,T),q=$e(N,T),y=await X({agentType:x(b.agentType,"codex"),cliType:x(b.cliType,"builtin"),acpSessionId:e.acp_session_id??null,requireSessionResume:ge(e.session_raw),prompt:N,promptBlocks:q,cwd:p.cwd,model:i.model,mode:i.mode,configOptionValues:i.configOptionValues,canExecuteTools:i.canExecuteTools,timeoutMs:i.timeoutMs,contextWindow:ve(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 te(c.raw))P.add(C);const S={turnId:e.user_turn_id??null,eventType:c.type,label:c.label,text:c.text,status:c.status,toolCallId:c.toolCallId,raw:Re(c.raw,_)};ae(S)&&h.push(S)},onPermissionRequest:c=>{const S=ne(i,c);return S?Promise.resolve(S):Ue(a,t.machineId,c,o.signal)},onUserInputRequest:c=>Be(a,t.machineId,c,o.signal)});await h.flush(),await f.flush();const D=O({cwd:p.cwd,evidencePaths:P}),E=Ae(y.promptResponse);if(!(Ce(E)||!!y.content)){await R(a,t.machineId,`${y.agentType} stopped: ${E??"unknown"}`,{acpSessionId:y.acpSessionId,stopReason:E,exitCode:y.exitCode,stderr:Ve(y.stderr,4e3),worktree:k(p)});return}await I("POST",`/api/client/jobs/${encodeURIComponent(a)}/complete`,{machineId:t.machineId,acpSessionId:y.acpSessionId,assistantText:y.content,result:{ok:!0,acpSessionId:y.acpSessionId,stopReason:E,durationMs:y.durationMs,acpRun:i.acpRun??null,effectiveConfig:y.effectiveConfig??null,performance:y.performance??null,usage:y.contextUsage??null,updateCount:y.updateCount,statusTimeline:y.statusTimeline,worktree:k(p),attachments:L(T),files:D}}),n.info(`completed job=${a}`)}catch(p){if(Le(o.signal)){h.discard(),f.discard(),n.warn(`stopped local job=${a} because ACA Server already finalized it`);return}await h.flush(),await f.flush();const b=o.signal.aborted?"cancelled":p instanceof Error?p.message:String(p),T=A?O({cwd:A.cwd,evidencePaths:P}):[];await R(a,t.machineId,re(b),{rawError:b,files:T})}finally{await h.flush(),await f.flush(),clearInterval(m)}}function ge(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 _e(e){return!e||typeof e!="object"||Array.isArray(e)?{}:e}function Te(e){const t=String(e||"codex").toLowerCase().replace(/[^a-z0-9]+/g,"");return t==="codex"||t==="codexacp"||t==="codexstandard"}function ve(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 Ae(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 Ce(e){return e?["end_turn","stop","completed","complete"].includes(e):!1}function Pe(e,t){let n=[],o=null,a=Promise.resolve(),r=!1;const s=async d=>{const g=d.filter(ie);g.length!==0&&await I("POST",`/api/client/jobs/${encodeURIComponent(e)}/events/batch`,{machineId:t,events:g})},u=()=>{if(o&&(clearTimeout(o),o=null),r)return n=[],a;const d=n;return n=[],a=a.then(()=>s(d)).catch(()=>{}),a},i=()=>{o||(o=setTimeout(()=>{u()},Ie()),o.unref())};return{push(d){if(!r){if(oe(n,d),xe(d)||n.length>=ue){u();return}i()}},flush:u,discard(){r=!0,n=[],o&&clearTimeout(o),o=null}}}function xe(e){return e.eventType==="tool_call"?!0:e.eventType!=="tool_call_update"?!1:["completed","failed","cancelled"].includes(String(e.status||"").toLowerCase())}function ke(e,t){let n=null,o=0,a="",r=null,s=Promise.resolve(),u=!1;const i=l=>JSON.stringify({phase:l.phase??"",label:l.label??"",detail:l.detail??"",updateType:l.updateType??""}),d=async l=>{await I("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(),a=i(l)},g=()=>{if(r&&(clearTimeout(r),r=null),u)return n=null,s;if(!n)return s;const l=n;return n=null,s=s.then(()=>d(l)).catch(()=>{}),s},v=l=>{r||(r=setTimeout(()=>{g()},l),r.unref())};return{report(l){if(u)return;const w=Date.now(),_=i(l)!==a,h=w-o>=de;if(!_&&!h)return;n=l;const f=w-o;if(f>=U||o===0){g();return}v(U-f)},sendNow(l){return u?s:(n=l,g())},flush:g,discard(){u=!0,n=null,r&&clearTimeout(r),r=null}}}function Re(e,t){return e&&typeof e=="object"&&!Array.isArray(e)?{...e,acaSequence:t}:{value:e,acaSequence:t}}function Ee(e,t=[]){const n=e?.message?.trim()??"";if(t.length===0)return n;const a=["用户随消息上传了以下附件,aca 已将附件写入本机工作区。需要读取附件时请直接使用下面的 localPath;图片附件也已作为 ACP image block 随本轮请求传入。",...t.map((r,s)=>`${s+1}. ${r.name} (${r.kind}, ${r.mimeType}, ${Fe(r.size)})
1
+ import H from"node:crypto";import M from"node:fs";import j from"node:path";import{compactAgentContext as G,forkAgentSession as K,sendAgentPrompt as X}from"../acp/agent.js";import{acaServerDownload as Z,acaServerRequest as S}from"./aca-server-client.js";import{loadAcaConfig as Q}from"./aca-config.js";import{prepareLocalWorktree as Y}from"./worktree.js";import{expandWorkerRuntimePath as ee}from"./worker-runtime-paths.js";import{collectAffectedFiles as O,extractAffectedFilePathsFromAcpRaw as te}from"./affected-files.js";import{resolveJobPermissionPolicy as ne}from"./job-permission-policy.js";import{appendCoalescedAcpEvent as oe}from"./acp-event-coalescer.js";import{shouldBufferAcpEvent as ie,shouldUploadAcpEvent as ae}from"./acp-event-upload-filter.js";import{userFacingAcpError as re}from"../acp/error-display.js";import{ensureCodexProviderRuntimeRevision as se}from"./codex-provider-runtime-config.js";import{acpSessionInvalidationFromError as ce}from"../acp/session-errors.js";const le=1500,F=5e3,z=1e3,ue=200,de=64,U=900,pe=1e4,me=0,fe=12e3;function he(e){return ee(e)}function we(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 ct(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=Ie();if(B(u.size,w))try{const m=t.loadConfig?.()??Q();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):R(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 be(P,h,m,A)),!A.signal.aborted&&await(t.runJob??ge)(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 _=Ve(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??le);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(fe)])})(),i)}}async function be(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,ye(o.signal)])}finally{clearInterval(s)}}function ye(e){return e.aborted?Promise.resolve():new Promise(t=>e.addEventListener("abort",()=>t(),{once:!0}))}function Ie(){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?me:t===0?0:Math.max(1,Math.min(t,8))}function B(e,t){return t===0||e<t}function Se(){const e=Number.parseInt(process.env.ACA_ACP_EVENT_BATCH_INTERVAL_MS||"",10);return Number.isFinite(e)?Math.max(40,Math.min(e,2e3)):ue}async function ge(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){await R(i,t.machineId,"job input missing message");return}const v=t.projects.find(d=>d.projectId===u),T=he(v?.rootPath||e.project_root_path||"");if(!T){await R(i,t.machineId,"job project root is unknown");return}we(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=xe(i,t.machineId),f=Re(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=Y({projectId:u,sessionId:r,projectRoot:T,worktree:e});A=d,await Ue(i,t.machineId,d);const b=_e(a.acpRun);if(Te(b.agentType)&&await se(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 G({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:k(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 K({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:k(d)}}),n.info(`completed Codex session fork job=${i}`);return}const y=await Me({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=$e(a,y),q=je(E,y),I=await X({agentType:x(b.agentType,"codex"),cliType:x(b.cliType,"builtin"),acpSessionId:e.acp_session_id??null,requireSessionResume:ve(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:Ae(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 te(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:Ee(c.raw,_)};ie(g)&&h.push(g)},onPermissionRequest:c=>{const g=ne(a,c);return g?Promise.resolve(g):Be(i,t.machineId,c,o.signal)},onUserInputRequest:c=>Je(i,t.machineId,c,o.signal)});await h.flush(),await f.flush();const D=O({cwd:d.cwd,evidencePaths:P}),$=Ce(I.promptResponse);if(!(Pe($)||!!I.content)){await R(i,t.machineId,`${I.agentType} stopped: ${$??"unknown"}`,{acpSessionId:I.acpSessionId,stopReason:$,exitCode:I.exitCode,stderr:qe(I.stderr,4e3),worktree:k(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:k(d),attachments:L(y),files:D}}),n.info(`completed job=${i}`)}catch(d){if(We(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=ce(d),E=A?O({cwd:A.cwd,evidencePaths:P}):[];await R(i,t.machineId,re(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 ve(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 _e(e){return!e||typeof e!="object"||Array.isArray(e)?{}:e}function Te(e){const t=String(e||"codex").toLowerCase().replace(/[^a-z0-9]+/g,"");return t==="codex"||t==="codexacp"||t==="codexstandard"}function Ae(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 Ce(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 Pe(e){return e?["end_turn","stop","completed","complete"].includes(e):!1}function xe(e,t){let n=[],o=null,i=Promise.resolve(),r=!1;const s=async p=>{const v=p.filter(ae);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()},Se()),o.unref())};return{push(p){if(!r){if(oe(n,p),ke(p)||n.length>=de){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 Re(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>=pe;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 Ee(e,t){return e&&typeof e=="object"&&!Array.isArray(e)?{...e,acaSequence:t}:{value:e,acaSequence:t}}function $e(e,t=[]){const n=e?.message?.trim()??"";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}, ${ze(r.size)})
2
2
  localPath: ${r.path}
3
3
  workspacePath: ${r.relativePath}`)].join(`
4
4
  `);return n?`${n}
5
5
 
6
- ${a}`:a}function $e(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 je(e){if(e.attachments.length===0)return[];const t=$.join(e.cwd,".aca","attachments",J(e.sessionId),J(e.jobId));j.mkdirSync(t,{recursive:!0});const n=[];for(const[o,a]of e.attachments.entries()){const r=a.attachmentId?{mimeType:a.mimeType||"application/octet-stream",bytes:await Z(`/api/client/attachments/${encodeURIComponent(a.attachmentId)}`)}:Me(a.dataUrl??""),s=a.mimeType||r.mimeType||"application/octet-stream",u=a.kind==="image"||s.startsWith("image/")?"image":"file",i=Ne(a.name||`attachment-${o+1}`,s),d=$.join(t,`${String(o+1).padStart(2,"0")}-${i}`);j.writeFileSync(d,r.bytes),n.push({name:a.name||i,mimeType:s,size:r.bytes.byteLength,kind:u,path:d,relativePath:$.relative(e.cwd,d),base64:r.bytes.toString("base64"),sha256:H.createHash("sha256").update(r.bytes).digest("hex")})}return n}function Me(e){const t=e.indexOf(",");if(!e.startsWith("data:")||t<0)throw new Error("attachment dataUrl is not a data URL");const n=e.slice(5,t),o=e.slice(t+1),a=n.split(";").filter(Boolean),r=a[0]?.includes("/")?a[0]:"application/octet-stream";if(!a.includes("base64"))throw new Error("attachment dataUrl is not base64 encoded");return{mimeType:r,bytes:Buffer.from(o,"base64")}}function Ne(e,t){const n=$.basename(e).replace(/[<>:"/\\|?*\x00-\x1F]/g,"_").trim(),o=`attachment${Oe(t)}`;return(!n||n==="."||n===".."?o:n).slice(0,180)}function Oe(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 Fe(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 ze(e,t,n){!n.usedWorktree||!n.worktreeId||await I("POST",`/api/client/jobs/${encodeURIComponent(e)}/worktree`,{machineId:t,baseCommit:n.baseCommit??null,worktreePath:n.cwd,status:n.status??"active",raw:n.raw??k(n)})}function k(e){return{usedWorktree:e.usedWorktree,worktreeId:e.worktreeId??null,cwd:e.cwd,status:e.status??null,baseCommit:e.baseCommit??null,branchName:e.branchName??null,baseBranch:e.baseBranch??null}}async function Ue(e,t,n,o){const r=(await I("POST",`/api/client/jobs/${encodeURIComponent(e)}/permissions`,{machineId:t,request:n})).item.permission_id;for(;!o.aborted;){await M(z);const s=await I("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 Be(e,t,n,o){const r=(await I("POST",`/api/client/jobs/${encodeURIComponent(e)}/permissions`,{machineId:t,request:{kind:"elicitation",...n}})).item.permission_id;for(;!o.aborted;){await M(z);const s=await I("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 I("GET",`/api/client/jobs/${encodeURIComponent(e)}`);if(Number(n.item.cancel_requested??0)===1){t.abort();return}const o=Je(n.item);o&&t.abort(new V(e,o))}function Je(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 Le(e){return e.aborted&&e.reason instanceof V}async function R(e,t,n,o){await I("POST",`/api/client/jobs/${encodeURIComponent(e)}/fail`,{machineId:t,error:n,result:o})}function M(e){return new Promise(t=>setTimeout(t,e))}function We(e){return Math.min(1e4,500*2**Math.min(5,Math.max(0,e-1)))+Math.floor(Math.random()*250)}function Ve(e,t){return e.length<=t?e:`${e.slice(0,t)}...`}export{Je as serverFinalizedJobStatus,rt as startJobWorkerLoop};
6
+ ${i}`:i}function je(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 Me(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 Z(`/api/client/attachments/${encodeURIComponent(i.attachmentId)}`)}:Ne(i.dataUrl??""),s=i.mimeType||r.mimeType||"application/octet-stream",u=i.kind==="image"||s.startsWith("image/")?"image":"file",a=Oe(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 Ne(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 Oe(e,t){const n=j.basename(e).replace(/[<>:"/\\|?*\x00-\x1F]/g,"_").trim(),o=`attachment${Fe(t)}`;return(!n||n==="."||n===".."?o:n).slice(0,180)}function Fe(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 ze(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 Ue(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??k(n)})}function k(e){return{usedWorktree:e.usedWorktree,worktreeId:e.worktreeId??null,cwd:e.cwd,status:e.status??null,baseCommit:e.baseCommit??null,branchName:e.branchName??null,baseBranch:e.baseBranch??null}}async function Be(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 Je(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=Le(n.item);o&&t.abort(new V(e,o))}function Le(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 We(e){return e.aborted&&e.reason instanceof V}async function R(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 Ve(e){return Math.min(1e4,500*2**Math.min(5,Math.max(0,e-1)))+Math.floor(Math.random()*250)}function qe(e,t){return e.length<=t?e:`${e.slice(0,t)}...`}export{Le as serverFinalizedJobStatus,ct as startJobWorkerLoop};
package/dist/version.js CHANGED
@@ -1 +1 @@
1
- const o="0.1.34";export{o as AICA_VERSION};
1
+ const o="0.1.36";export{o as AICA_VERSION};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yuandc/aica",
3
- "version": "0.1.34",
3
+ "version": "0.1.36",
4
4
  "description": "AICA remote Agent worker",
5
5
  "author": "yuandc",
6
6
  "type": "module",
@@ -26,6 +26,7 @@
26
26
  "worker"
27
27
  ],
28
28
  "dependencies": {
29
+ "@openai/codex": "0.151.0",
29
30
  "chalk": "^5.4.1",
30
31
  "commander": "^12.1.0",
31
32
  "diff": "^9.0.0",