@yuandc/aica 0.1.95 → 0.1.97
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.
- package/dist/acp/client/acp-client.js +1 -1
- package/dist/acp/client/acp-events.js +1 -1
- package/dist/acp/client/acp-runtime-pool.js +1 -1
- package/dist/acp/client/json-rpc.js +3 -3
- package/dist/acp/providers/codex/provider.js +1 -1
- package/dist/acp/providers/codex-acp/adapter.js +3 -3
- package/dist/acp/providers/codex-acp/provider.js +1 -1
- package/dist/acp/providers/mimo/provider.js +2 -2
- package/dist/acp/providers/opencode/provider.js +3 -3
- package/dist/acp/providers/standard-acp-events.js +1 -1
- package/dist/acp/providers/workbuddy/auth-manager.js +1 -1
- package/dist/acp/providers/workbuddy/auth-transport.js +3 -3
- package/dist/acp/providers/workbuddy/interactions.js +1 -1
- package/dist/acp/providers/workbuddy/provider.js +1 -1
- package/dist/acp/tool-lifecycle-trace.js +2 -2
- package/dist/commands/daemon.js +2 -2
- package/dist/commands/start.js +1 -1
- package/dist/core/job-worker.js +1 -1
- package/dist/core/logger.js +3 -3
- package/dist/core/logging/rotating-log.js +3 -0
- package/dist/interactions/connection-interactions.js +1 -0
- package/dist/interactions/elicitation-tracker.js +1 -0
- package/dist/interactions/form-schema.js +1 -0
- package/dist/interactions/job-interactions.js +1 -1
- package/dist/interactions/provider-input.js +1 -1
- package/dist/interactions/types.js +1 -1
- package/dist/version.js +1 -1
- package/dist/worker-cli.js +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
import{JsonLineRpcClient as
|
|
1
|
+
import{ElicitationTracker as h}from"../../interactions/elicitation-tracker.js";import{JsonLineRpcClient as p}from"./json-rpc.js";class w{onSessionUpdate;onClientRequest;elicitation;sessions=new Set;elicitations=new h;rpc;constructor(e,s,c,a){this.onSessionUpdate=s,this.onClientRequest=c,this.elicitation=a,this.rpc=new p(e,{peerName:"ACP server",includeJsonRpc:!0,onNotification:(i,t,d)=>{i==="elicitation/complete"&&this.elicitations.complete(t),i==="session/update"&&this.onSessionUpdate(t,d)},onRequest:async(i,t,d)=>{if(t=this.elicitation?.normalizeRequest?.(i,t)??t,this.onClientRequest){const r=t,o=this.elicitation?.provider==="codex-acp"&&r?.acaInteractionBridge===1?r.request?.interaction:void 0;if(o&&(!this.sessions.has(r?.sessionId)||o.version===2&&!this.elicitation?.enabled))throw Object.assign(new Error("Invalid ACP interaction bridge scope or capability"),{rpcCode:-32602});if(i==="elicitation/create"&&!o){if(!this.elicitation?.enabled)throw Object.assign(new Error("Unsupported elicitation mode: client did not advertise support"),{rpcCode:-32602});if(!(typeof r?.sessionId=="string"?this.sessions.has(r.sessionId)&&r?.requestId==null:this.rpc.hasPendingRequest(r?.requestId)))throw Object.assign(new Error("Invalid elicitation scope for this connection"),{rpcCode:-32602})}const u=o?.kind==="url"?{mode:"url",elicitationId:o.elicitationId}:t,l=i==="elicitation/create"?this.elicitations.begin(u,o?.requestId??d.id,this.elicitation?.provider??"acp",this.elicitation?.completionHandler?.()):void 0;try{const n=await this.onClientRequest(i,t,d);return this.elicitations.settle(l,o?{action:n?.outcome?.outcome==="submitted"?"accept":"cancel"}:n),n}catch(n){throw this.elicitations.settle(l,null),n}}throw new Error(`Unsupported ACP client request: ${i}`)}})}initialize(e=6e4){return this.rpc.request("initialize",{protocolVersion:1,clientCapabilities:{fs:{readTextFile:!1,writeTextFile:!1},terminal:!1,...this.elicitation?.enabled?{elicitation:{form:{},url:{}}}:{}},clientInfo:{name:"aca",version:"0.1.0"}},e)}configureRuntime(e,s=3e4){return this.rpc.request("aca/runtime_config",e,s)}newSession(e,s=12e4){return this.rememberSession(this.rpc.request("session/new",{cwd:e.cwd,mcpServers:e.mcpServers??[],...e.config?{config:e.config}:{}},s))}resumeSession(e,s=12e4){return this.rememberSession(this.rpc.request("session/resume",{sessionId:e.sessionId,cwd:e.cwd,mcpServers:e.mcpServers??[],...e.config?{config:e.config}:{}},s),e.sessionId)}loadSession(e,s=12e4){return this.rememberSession(this.rpc.request("session/load",{sessionId:e.sessionId,cwd:e.cwd,mcpServers:e.mcpServers??[],...e.config?{config:e.config}:{}},s),e.sessionId)}async rememberSession(e,s){const c=s?this.sessions.has(s):!1;s&&this.sessions.add(s);let a;try{a=await e}catch(t){throw s&&!c&&this.sessions.delete(s),t}const i=a?.sessionId??s;return typeof i=="string"&&this.sessions.add(i),a}async closeSession(e,s=3e4){const c=await this.rpc.request("session/close",{sessionId:e},s);return this.sessions.delete(e),c}listSessions(e={},s=6e4){return this.rpc.request("session/list",e,s)}deleteSession(e,s=6e4){return this.rpc.request("session/delete",{sessionId:e},s)}forkSession(e,s=12e4){return this.rememberSession(this.rpc.request("session/fork",{sessionId:e.sessionId,cwd:e.cwd,...e.mcpServers?{mcpServers:e.mcpServers}:{},...e.config?{config:e.config}:{}},s))}setSessionConfigOption(e,s=6e4){return this.rpc.request("session/set_config_option",e,s)}setSessionMode(e,s=6e4){return this.rpc.request("session/set_mode",e,s)}prompt(e,s){return this.rpc.request("session/prompt",e,s)}compactSession(e,s=600*1e3){return this.rpc.request("session/compact",e,s)}recoverSession(e,s=12e4){return this.rpc.request("aca/session_recover",e,s)}cancel(e){this.rpc.notify("session/cancel",{sessionId:e})}authenticate(e,s=12e4){return this.rpc.request("authenticate",e,s)}logout(e={},s=6e4){return this.rpc.request("logout",e,s)}listProviders(e={},s=6e4){return this.rpc.request("providers/list",e,s)}setProvider(e,s=6e4){return this.rpc.request("providers/set",e,s)}disableProvider(e,s=6e4){return this.rpc.request("providers/disable",e,s)}}export{w as AcpClient};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{createPlanEvent as
|
|
1
|
+
import{createPlanEvent as _,normalizePlanSessionUpdate as y}from"../plan-events.js";import{STANDARD_AGENT_EVENT_TYPES as A,createAgentMessageChunk as b,createAgentProgressChunk as x,createAgentThoughtChunk as k,createToolCall as C,createToolCallUpdate as I,firstString as a,normalizeToolKind as S,normalizeToolStatus as h}from"../standard-events.js";function f(n){if(!n||typeof n!="object"||Array.isArray(n))return null;const t=n,e=t.update;return!e||typeof e!="object"||Array.isArray(e)?null:{sessionId:typeof t.sessionId=="string"?t.sessionId:void 0,update:e}}function E(n){const t=f(n);if(!t)return null;const e=String(t.update.sessionUpdate||""),r=u(t.update._meta)??void 0;if(e==="agent_message_chunk")return b({text:i(t.update.content),method:"session/update",params:n,meta:r});if(e==="agent_progress_chunk")return x({text:i(t.update.content),method:"session/update",params:n,meta:r});if(e==="agent_thought_chunk")return k({text:i(t.update.content),method:"session/update",params:n,meta:r});if(e==="tool_call"||e==="tool_call_update")return w(e,t.update,n);if(e==="plan"||e==="plan_update"||e==="plan_removed"){const o=y(t.update);return o?_({update:o,method:"session/update",params:n,meta:r}):null}return A.has(e)?{type:e,label:T(e),...i(t.update.content)?{text:i(t.update.content)}:{},raw:{method:"session/update",params:n,update:t.update},atMs:Date.now()}:null}function P(n){const t=f(n);if(!t||m(t.update))return null;const e=String(t.update.sessionUpdate||"");if(e==="agent_message_chunk")return{phase:"thinking",label:"回复中",detail:g(i(t.update.content)),updateType:e};if(e==="agent_progress_chunk")return{phase:"thinking",label:"处理中",detail:g(i(t.update.content)),updateType:e};if(e==="agent_thought_chunk")return{phase:"thinking",label:"思考中",detail:g(i(t.update.content)),updateType:e};if(e==="tool_call"||e==="tool_call_update"){const r=a(t.update.toolCallId,t.update.id),o=h(a(t.update.status)||"in_progress"),s=a(t.update.title,t.update.kind),c=U(t.update.rawOutput);if(r==="codex.connection")return{phase:o==="failed"?"failed":o==="completed"?"exploring":"resuming",label:s||(o==="completed"?"Codex 连接已恢复":"Codex 正在重连"),detail:c,updateType:e};if(r==="codex.error")return{phase:"failed",label:s||"Codex 执行异常",detail:c,updateType:e};const d=u(t.update.rawInput),l=a(d?.command);return s==="压缩上下文"?{phase:"thinking",label:"压缩上下文",updateType:e}:s==="等待协作消息"?{phase:"exploring",label:"等待协作消息",updateType:e}:/^(?:(?:启动|发送|恢复|等待|结束|中断|查看|处理)子任务|子任务(?:交互|完成|中断))/.test(s||"")?{phase:"exploring",label:s||"处理子任务",updateType:e}:l?{phase:"exploring",label:"执行命令",detail:s||l,updateType:e}:{phase:"exploring",label:"调用工具",detail:s,updateType:e}}return e==="plan"||e==="plan_update"?{phase:"thinking",label:"规划中",updateType:e}:e==="aca_context_maintenance"?{phase:"thinking",label:a(t.update.label)||"正在整理会话上下文",detail:a(t.update.detail),updateType:e}:null}function D(n){const t=f(n);return t?m(t.update):null}function m(n){const t=u(n._meta),e=u(t?.aca);return u(e?.subtask)}function i(n){if(typeof n=="string")return n;if(!n||typeof n!="object"||Array.isArray(n))return"";const t=n;return typeof t.text=="string"?t.text:""}function w(n,t,e){const r=a(t.toolCallId,t.id)||`acp.${Date.now()}`,o=S(a(t.kind,t.title)),s=h(a(t.status)||"in_progress"),c=t.rawInput??t.input,d=t.rawOutput??t.output,l={method:"session/update",params:e,toolCallId:r,kind:o,title:a(t.title,t.kind),status:s,rawInput:c,rawOutput:d,content:t.content,locations:Array.isArray(t.locations)?t.locations.filter(p=>!!(p&&typeof p=="object"&&!Array.isArray(p))):void 0,meta:t._meta&&typeof t._meta=="object"&&!Array.isArray(t._meta)?t._meta:void 0};return n==="tool_call"?C(l):I(l)}function T(n){return n.replace(/_/g," ")}function g(n){const t=n.replace(/\s+/g," ").trim();if(t)return t.length>120?`${t.slice(0,117)}...`:t}function U(n){if(!n||typeof n!="object"||Array.isArray(n))return;const t=n,e=t.error&&typeof t.error=="object"&&!Array.isArray(t.error)?t.error:null,r=a(e?.message,t.message),o=/reconnecting\.{3}\s*(\d+)\s*\/\s*(\d+)/i.exec(r||"");return o?`第 ${o[1]}/${o[2]} 次尝试`:/selected model is at capacity/i.test(r||"")?"所选模型当前繁忙,请稍后重试":r}function u(n){return n&&typeof n=="object"&&!Array.isArray(n)?n:null}export{f as acpSessionUpdateFromParams,E as eventFromAcpSessionUpdate,P as statusFromAcpSessionUpdate,D as subtaskContextFromAcpSessionUpdate,i as textFromAcpContent};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import
|
|
1
|
+
import c from"node:fs";import{randomUUID as m}from"node:crypto";import{AcpClient as p}from"./acp-client.js";import{closeAcpProcess as h,startAcpProcess as v}from"./acp-process.js";const f=300*1e3,I=60*1e3,w=4;class E{runtimes=new Map;idleTtlMs;sweepIntervalMs;maxIdleRuntimes;sweepTimer=null;disposeAllPromise=null;constructor(e={}){this.idleTtlMs=Math.max(1e3,e.idleTtlMs??R()),this.sweepIntervalMs=Math.max(1e3,e.sweepIntervalMs??A()),this.maxIdleRuntimes=Math.max(0,e.maxIdleRuntimes??S())}async acquire(e){this.disposeAllPromise&&await this.disposeAllPromise;const i=c.realpathSync(e.cwd);this.ensureSweep();const s=e.runtimeIdentity||"default",t=e.providerSessionId?d(e.providerId,i,e.providerSessionId,s):null,n=t?this.runtimes.get(t):null;n&&!this.isRuntimeAlive(n)&&await this.disposeEntry(n).catch(()=>{});let o=t?this.runtimes.get(t):null;if(o&&o.elicitation!==(e.elicitation===!0)){if(o.inUse)throw new Error("ACP runtime is already in use with different elicitation capabilities");await this.disposeEntry(o),o=void 0}if(o)return this.activateEntry(o,e),this.createLease(o);const u=e.providerSessionId||`pending:${m()}`,l=d(e.providerId,i,u,s),a=this.createEntry({...e,cwd:i,key:l,providerSessionId:e.providerSessionId??null,runtimeIdentity:s});return this.runtimes.set(l,a),this.createLease(a)}disposeAll(){if(this.disposeAllPromise)return this.disposeAllPromise;const e=this.performDisposeAll();return this.disposeAllPromise=e,e.finally(()=>{this.disposeAllPromise===e&&(this.disposeAllPromise=null)}),e}async performDisposeAll(){this.sweepTimer&&(clearInterval(this.sweepTimer),this.sweepTimer=null);const e=[...this.runtimes.values()];await Promise.all(e.map(i=>this.disposeEntry(i).catch(()=>{})))}async sweepIdle(e=Date.now()){const i=[...this.runtimes.values()];for(const s of i)s.inUse||s.disposed||e-s.lastUsedAtMs<this.idleTtlMs||await this.disposeEntry(s).catch(()=>{});await this.trimIdleRuntimes()}async retireProviderRuntimes(e){const i=[...this.runtimes.values()].filter(s=>s.providerId===e&&!s.disposed);for(const s of i)s.retired=!0;return await Promise.all(i.filter(s=>!s.inUse).map(s=>this.disposeEntry(s).catch(()=>{}))),i.length}async retireSessionRuntimes(e){const i=c.realpathSync(e.cwd),s=[...this.runtimes.values()].filter(t=>!t.disposed&&t.providerId===e.providerId&&t.cwd===i&&t.providerSessionId===e.providerSessionId&&t.runtimeIdentity!==e.keepRuntimeIdentity);if(s.some(t=>t.inUse))throw new Error("ACP session is already in use with another runtime configuration");for(const t of s)t.retired=!0;await Promise.all(s.map(t=>this.disposeEntry(t)))}stats(){const e=[...this.runtimes.values()].filter(i=>!i.disposed);return{total:e.length,inUse:e.filter(i=>i.inUse).length,idle:e.filter(i=>!i.inUse).length,pids:e.map(i=>i.started.child.pid).filter(i=>Number.isInteger(i))}}createEntry(e){const i=v(e.command,e.args,e.cwd,e.env),s={key:e.key,providerId:e.providerId,cwd:e.cwd,providerSessionId:e.providerSessionId,runtimeIdentity:e.runtimeIdentity,started:i,client:null,initialized:!1,elicitation:e.elicitation===!0,sessionEstablished:!1,runtimeConfigured:!1,inUse:!0,retired:!1,disposed:!1,disposePromise:null,lastUsedAtMs:Date.now(),sessionResponse:null,activeHandlers:{onSessionUpdate:e.onSessionUpdate,onClientRequest:e.onClientRequest,normalizeClientRequest:e.normalizeClientRequest,onElicitationComplete:e.onElicitationComplete}};return s.client=new p(i.child,(t,n)=>s.activeHandlers?.onSessionUpdate(t,n),async(t,n,o)=>{if(!s.activeHandlers?.onClientRequest)throw new Error(`Unsupported ACP client request: ${t}`);return s.activeHandlers.onClientRequest(t,n,o)},{provider:e.elicitationProvider??e.providerId,enabled:e.elicitation,normalizeRequest:(t,n)=>s.activeHandlers?.normalizeClientRequest?.(t,n)??n,completionHandler:()=>s.activeHandlers?.onElicitationComplete}),i.child.once("close",()=>{this.runtimes.get(s.key)===s&&this.runtimes.delete(s.key),s.disposed=!0,s.inUse=!1,s.activeHandlers=null}),s}activateEntry(e,i){if(e.inUse)throw new Error(`ACP runtime is already in use: provider=${e.providerId} session=${e.providerSessionId??"unknown"}`);e.inUse=!0,e.activeHandlers={onSessionUpdate:i.onSessionUpdate,onClientRequest:i.onClientRequest,normalizeClientRequest:i.normalizeClientRequest,onElicitationComplete:i.onElicitationComplete}}createLease(e){let i=!1;return{get client(){return e.client},get started(){return e.started},get cwd(){return e.cwd},get providerSessionId(){return e.providerSessionId},get sessionResponse(){return e.sessionResponse},get initialized(){return e.initialized},get sessionEstablished(){return e.sessionEstablished},get runtimeConfigured(){return e.runtimeConfigured},markInitialized:()=>{e.initialized=!0},markRuntimeConfigured:()=>{e.runtimeConfigured=!0},setProviderSession:s=>{e.providerSessionId=s.providerSessionId,e.sessionEstablished=!0,"sessionResponse"in s&&(e.sessionResponse=s.sessionResponse??null),this.rekeyEntry(e,d(e.providerId,e.cwd,s.providerSessionId,e.runtimeIdentity))},setSessionResponse:s=>{e.sessionResponse=s},release:()=>{i||(i=!0,e.inUse=!1,e.activeHandlers=null,e.lastUsedAtMs=Date.now(),e.retired?this.disposeEntry(e):this.trimIdleRuntimes())},dispose:async()=>{i=!0,await this.disposeEntry(e)}}}rekeyEntry(e,i){if(e.key===i)return;const s=this.runtimes.get(i);if(s&&s!==e){if(s.inUse)throw new Error(`ACP runtime key is already active: provider=${s.providerId} session=${s.providerSessionId??"unknown"}`);this.disposeEntry(s)}this.runtimes.get(e.key)===e&&this.runtimes.delete(e.key),e.key=i,this.runtimes.set(i,e)}async disposeEntry(e){if(e.disposePromise)return e.disposePromise;if(e.disposed)return;const i=this.performDisposeEntry(e);return e.disposePromise=i,i}async performDisposeEntry(e){e.disposed=!0,e.inUse=!1,e.activeHandlers=null,this.runtimes.get(e.key)===e&&this.runtimes.delete(e.key),e.providerSessionId&&await e.client.closeSession(e.providerSessionId,5e3).catch(()=>{}),await h(e.started.child)}async trimIdleRuntimes(){const e=[...this.runtimes.values()].filter(s=>!s.inUse&&!s.disposed).sort((s,t)=>s.lastUsedAtMs-t.lastUsedAtMs),i=Math.max(0,e.length-this.maxIdleRuntimes);for(const s of e.slice(0,i))await this.disposeEntry(s).catch(()=>{})}isRuntimeAlive(e){return!e.disposed&&e.started.child.exitCode===null&&e.started.child.signalCode===null}ensureSweep(){this.sweepTimer||(this.sweepTimer=setInterval(()=>{this.sweepIdle()},this.sweepIntervalMs),this.sweepTimer.unref())}}const M=new E;function d(r,e,i,s){return`${r}\0${e}\0${i}\0${s}`}function R(){const r=Number.parseInt(process.env.ACA_ACP_RUNTIME_IDLE_TTL_MS??"",10);return Number.isInteger(r)&&r>=1e3?r:f}function A(){const r=Number.parseInt(process.env.ACA_ACP_RUNTIME_SWEEP_INTERVAL_MS??"",10);return Number.isInteger(r)&&r>=1e3?r:I}function S(){const r=Number.parseInt(process.env.ACA_ACP_RUNTIME_MAX_IDLE??"",10);return Number.isInteger(r)&&r>=0?Math.min(r,32):w}export{E as AcpRuntimePool,M as defaultAcpRuntimePool};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{appendBoundedText as u}from"./bounded-buffer.js";class s{child;handlers;static MAX_STDOUT_BYTES=1024*1024;static MAX_STDERR_BYTES=1024*1024;nextId=1;lineBuffer="";pending=new Map;peerName;includeJsonRpc;stdoutBuffer="";stderrBuffer="";notificationCount=0;exitCode=null;signal=null;constructor(e,
|
|
2
|
-
`);this.lineBuffer=
|
|
3
|
-
[non-json stdout] ${
|
|
1
|
+
import{appendBoundedText as u}from"./bounded-buffer.js";class s{child;handlers;static MAX_STDOUT_BYTES=1024*1024;static MAX_STDERR_BYTES=1024*1024;nextId=1;lineBuffer="";pending=new Map;peerName;includeJsonRpc;stdoutBuffer="";stderrBuffer="";notificationCount=0;exitCode=null;signal=null;constructor(e,i={}){this.child=e,this.handlers=i,this.peerName=i.peerName||"JSON-RPC peer",this.includeJsonRpc=i.includeJsonRpc===!0,e.stdout.on("data",t=>this.handleStdout(String(t))),e.stderr.on("data",t=>{this.stderr=this.stderr+String(t)}),e.on("close",(t,r)=>{this.exitCode=t,this.signal=r;for(const[n,o]of this.pending)clearTimeout(o.timer),o.reject(new Error(`${this.peerName} exited before ${o.method} completed (code=${t}, signal=${r})`)),this.pending.delete(n)}),e.on("error",t=>{for(const[r,n]of this.pending)clearTimeout(n.timer),n.reject(t),this.pending.delete(r)})}get stdout(){return this.stdoutBuffer}get stderr(){return this.stderrBuffer}set stderr(e){this.stderrBuffer=u("",e,s.MAX_STDERR_BYTES)}request(e,i,t=12e4){const r=this.nextId++;return new Promise((n,o)=>{const d=this.createRequestTimeout(r,e,t,o);d.unref(),this.pending.set(r,{method:e,resolve:n,reject:o,timer:d,timeoutMs:t,pauseCount:0}),this.write({...this.includeJsonRpc?{jsonrpc:"2.0"}:{},id:r,method:e,...i===void 0?{}:{params:i}})})}notify(e,i){this.write({...this.includeJsonRpc?{jsonrpc:"2.0"}:{},method:e,...i===void 0?{}:{params:i}})}hasPendingRequest(e){return typeof e=="number"&&this.pending.has(e)}refreshPendingRequestTimeout(e){for(const[i,t]of this.pending)t.method!==e||t.pauseCount>0||(clearTimeout(t.timer),t.timer=this.createRequestTimeout(i,t.method,t.timeoutMs,t.reject),t.timer.unref())}respond(e,i){this.write({...this.includeJsonRpc?{jsonrpc:"2.0"}:{},id:e,result:i})}respondError(e,i){this.write({...this.includeJsonRpc?{jsonrpc:"2.0"}:{},id:e,error:{code:Number.isInteger(i.rpcCode)?i.rpcCode:-32603,message:i.message}})}createRequestTimeout(e,i,t,r){return setTimeout(()=>{this.pending.delete(e),r(new Error(`${this.peerName} request timed out while waiting for ${i}`))},t)}handleStdout(e){this.stdoutBuffer=u(this.stdoutBuffer,e,s.MAX_STDOUT_BYTES),this.lineBuffer+=e;const i=this.lineBuffer.split(`
|
|
2
|
+
`);this.lineBuffer=i.pop()??"";for(const t of i){const r=t.trim();if(!r)continue;let n;try{n=JSON.parse(r)}catch{this.stderr+=`
|
|
3
|
+
[non-json stdout] ${r}`;continue}this.handleMessage(n)}}async handleMessage(e){if("id"in e&&("result"in e||"error"in e)&&typeof e.method!="string"){const i=Number(e.id),t=this.pending.get(i);if(!t)return;if(this.pending.delete(i),clearTimeout(t.timer),"error"in e){const r=e.error;t.reject(Object.assign(new Error(r?.message||`${this.peerName} error ${r?.code??"unknown"}`),{rpcResponse:!0,rpcCode:r?.code,rpcData:r?.data}))}else t.resolve(e.result);return}if(typeof e.method=="string"){if("id"in e){const t=/^(elicitation\/create|session\/request_permission|session\/request_user_input|item\/tool\/requestUserInput|mcpServer\/elicitation\/request|_codebuddy.ai\/question)$/.test(e.method)?this.pauseForInteraction(e.params?.requestId):()=>{};try{if(!this.handlers.onRequest)throw Object.assign(new Error(`Unsupported client request: ${e.method}`),{rpcCode:-32601});const r=await this.handlers.onRequest(e.method,e.params,e);this.respond(e.id,r??null)}catch(r){this.respondError(e.id,r instanceof Error?r:new Error(String(r)))}finally{t()}return}this.notificationCount+=1,await this.handlers.onNotification?.(e.method,e.params,e)}}pauseForInteraction(e){const i=[...this.pending].filter(([t,r])=>e!==void 0?t===e:["session/prompt","turn/start"].includes(r.method));for(const[,t]of i)t.pauseCount++,clearTimeout(t.timer);return()=>{for(const[t,r]of i)r.pauseCount--,!(this.pending.get(t)!==r||r.pauseCount>0)&&(r.timer=this.createRequestTimeout(t,r.method,r.timeoutMs,r.reject),r.timer.unref())}}write(e){this.child.stdin.write(`${JSON.stringify(e)}
|
|
4
4
|
`)}}export{s as JsonLineRpcClient};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import u from"node:fs";import{handleProviderInput as
|
|
1
|
+
import{ElicitationTracker as c}from"../../../interactions/elicitation-tracker.js";import u from"node:fs";import{handleProviderInput as h,isCodexInputMethod as p}from"../../../interactions/provider-input.js";import{BoundedItemBuffer as m}from"../../client/bounded-buffer.js";import{JsonLineRpcClient as f}from"../../client/json-rpc.js";import{startCodexAppServer as g}from"./codex-process.js";import{extractAssistantTextFromTurn as C,mapCodexNotification as w}from"./events.js";import{CodexPlanDeltaState as v}from"./plan-events.js";import{isCodexApprovalRequest as T,resolveCodexApprovalRequest as x}from"./permissions.js";const I=1500,S={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 O{id="native-codex";name="Native Codex";capabilities=S;async createSession(e){return new y(e.cwd,e.providerSessionId??null,e.timeoutMs)}}class y{cwd;providerSessionId;defaultTimeoutMs;started;rpc;initialized=!1;content="";promptActive=!1;currentTurnId=null;updates=new m(16);statusTimeline=[];statusCallback=null;updateCallback=null;permissionCallback=null;elicitations=new c;elicitationComplete;userInputCallback=null;abortHandler=null;completedTurns=new Map;pendingTurnCompletions=new Map;agentMessagePhases=new Map;planDeltas=new v;constructor(e,s,t=q()){this.cwd=e,this.providerSessionId=s,this.defaultTimeoutMs=t,this.started=g(e),this.rpc=new f(this.started.child,{onNotification:(r,i)=>this.handleNotification(r,i),onRequest:(r,i,a)=>this.handleReverseRequest(r,i,a.id)})}async sendPrompt(e){const s=Date.now(),t=l(e.mode);this.statusCallback=e.onStatus??null,this.updateCallback=e.onUpdate??null,this.permissionCallback=e.onPermissionRequest??null,this.userInputCallback=e.onUserInputRequest??null,this.elicitationComplete=e.onElicitationComplete,this.attachAbortSignal(e.signal),this.emitStatus("initializing","Codex 初始化中","initialize"),await this.initialize();const r=await this.establishThread(e);this.emitStatus("thinking","思考中"),this.content="",this.updates.clear(),this.agentMessagePhases.clear(),this.planDeltas.reset(),this.promptActive=!0;let i,a;try{i=await this.rpc.request("turn/start",{threadId:r,clientUserMessageId:`aca-${Date.now()}-${Math.random().toString(16).slice(2)}`,input:P(e.prompt,e.promptBlocks),cwd:e.cwd,...e.model?{model:e.model}:{},...b(e.configOptionValues)},e.timeoutMs??this.defaultTimeoutMs);const o=String(i?.turn?.id??"");this.currentTurnId=o||null,a=o?await this.waitForTurnCompleted(o,e.timeoutMs??this.defaultTimeoutMs):i?.turn}finally{this.promptActive=!1,this.currentTurnId=null}const d=this.content.trim()||C(a);return this.emitStatus("completed","已完成"),{providerSessionId:r,cliType:"builtin",agentType:"codex",effectiveConfig:t,command:this.started.command,args:this.started.args,cwd:this.cwd,exitCode:this.rpc.exitCode,signal:this.rpc.signal,durationMs:Date.now()-s,stdout:this.rpc.stdout,stderr:this.rpc.stderr,content:d,updateCount:this.updates.count(),updates:this.updates.snapshot(),statusTimeline:this.statusTimeline,promptResponse:i}}async cancel(){try{this.providerSessionId&&this.started.child.exitCode===null&&this.started.child.signalCode===null&&await this.rpc.request("turn/interrupt",{threadId:this.providerSessionId,turnId:this.currentTurnId??""},I).catch(()=>{})}finally{await this.close()}}async close(){this.statusCallback=null,this.updateCallback=null,this.permissionCallback=null,this.userInputCallback=null;for(const e of this.pendingTurnCompletions.values())clearTimeout(e.timer),e.reject(new Error("Codex app-server session closed"));this.pendingTurnCompletions.clear(),this.abortHandler&&(this.abortHandler=null),await this.closeCodexProcess()}async closeCodexProcess(){this.started.child.exitCode!==null||this.started.child.signalCode!==null||(this.started.child.stdin.end(),!await this.waitForProcessClose(2e3)&&(this.started.child.kill("SIGTERM"),!await this.waitForProcessClose(1e3)&&(this.started.child.kill("SIGKILL"),await this.waitForProcessClose(1e3))))}waitForProcessClose(e){return this.started.child.exitCode!==null||this.started.child.signalCode!==null?Promise.resolve(!0):new Promise(s=>{const t=setTimeout(()=>s(!1),e);t.unref(),this.started.child.once("close",()=>{clearTimeout(t),s(!0)})})}async initialize(){this.initialized||(await this.rpc.request("initialize",{clientInfo:{name:"aca",version:"0.1.0"},capabilities:{experimentalApi:!0,requestAttestation:!1}}),this.initialized=!0)}async establishThread(e){const s=l(e.mode);if(this.providerSessionId)try{this.emitStatus("resuming","Codex 会话恢复中","thread/resume");const i=await this.rpc.request("thread/resume",{threadId:this.providerSessionId,cwd:e.cwd,...e.model?{model:e.model}:{},approvalsReviewer:"user",...s},6e4);return this.providerSessionId=String(i?.thread?.id??this.providerSessionId),this.providerSessionId}catch(i){if(e.requireSessionResume){const a=i instanceof Error?i.message:String(i);throw new Error(`无法恢复导入的 Codex Session ${this.providerSessionId}:${a}`)}this.providerSessionId=null}this.emitStatus("acp","Codex 启动中","thread/start");const r=(await this.rpc.request("thread/start",{cwd:e.cwd,...e.model?{model:e.model}:{},approvalsReviewer:"user",...s,ephemeral:!1,threadSource:"aca"},6e4))?.thread?.id;if(!r)throw new Error("Codex app-server thread/start did not return thread id");return this.providerSessionId=r,r}attachAbortSignal(e){if(e){if(this.abortHandler=()=>{this.cancel()},e.aborted)throw new Error("ACP prompt cancelled");e.addEventListener("abort",this.abortHandler,{once:!0})}}handleNotification(e,s){if(e==="elicitation/complete"){this.elicitations.complete(s);return}!this.promptActive&&!R(e)||(this.rpc.refreshPendingRequestTimeout("turn/start"),this.updates.push({method:e,params:s}),this.captureAgentMessagePhase(e,s),this.appendFinalAnswerDelta(e,s),e==="turn/completed"&&this.resolveTurnCompleted(s),w(e,s,{pushUpdate:t=>this.updateCallback?.(t),pushStatus:(t,r,i,a)=>this.emitStatus(t,r,i,a)},{agentMessagePhases:this.agentMessagePhases,planDeltas:this.planDeltas}))}captureAgentMessagePhase(e,s){if(e!=="item/started"&&e!=="item/completed")return;const t=s?.item;t?.type!=="agentMessage"||!t.id||this.agentMessagePhases.set(t.id,t.phase??"")}appendFinalAnswerDelta(e,s){if(e!=="item/agentMessage/delta")return;const t=s;if(!t.itemId||typeof t.delta!="string")return;const r=this.agentMessagePhases.get(t.itemId);(r==="final_answer"||!r)&&(this.content+=t.delta)}waitForTurnCompleted(e,s){const t=this.completedTurns.get(e);return t?Promise.resolve(t):new Promise((r,i)=>{const a=setTimeout(()=>{this.pendingTurnCompletions.delete(e),i(new Error("Codex app-server request timed out while waiting for turn/completed"))},s);a.unref(),this.pendingTurnCompletions.set(e,{resolve:r,reject:i,timer:a})})}resolveTurnCompleted(e){const s=e?.turn,t=s?.id;if(!t)return;this.completedTurns.set(t,s);const r=this.pendingTurnCompletions.get(t);if(r){if(this.pendingTurnCompletions.delete(t),clearTimeout(r.timer),s.status==="failed"){r.reject(new Error(`Codex turn failed: ${JSON.stringify(s.error??{})}`));return}r.resolve(s)}}async handleReverseRequest(e,s,t){if(T(e))return this.emitStatus("requestPermission","等待授权",M(s),e),x(e,s,this.permissionCallback,t);if(p(e)){this.emitStatus("requestPermission","等待回答",void 0,e);const r=this.elicitations.begin(s,t??"","native-codex",this.elicitationComplete);try{const i=await h("native-codex",e,s,this.userInputCallback,t);return this.elicitations.settle(r,i),i}catch(i){throw this.elicitations.settle(r,null),i}}throw new Error(`Unsupported Codex server request: ${e}`)}emitStatus(e,s,t,r){const i={phase:e,label:s,...t?{detail:t}:{},...r?{updateType:r}:{},atMs:Date.now()},a=this.statusTimeline[this.statusTimeline.length-1];a&&a.phase===i.phase&&a.label===i.label&&a.detail===i.detail&&a.updateType===i.updateType||(this.statusTimeline.push(i),this.statusCallback?.(i))}}function P(n,e){return(e?.length?e:[{type:"text",text:n}]).map(t=>t.type==="image"?t.uri&&u.existsSync(t.uri)?{type:"localImage",path:t.uri}:{type:"image",url:`data:${t.mimeType};base64,${t.data}`}:{type:"text",text:t.text,text_elements:[]})}function l(n){const e=String(n||"").trim();return e==="read-only"?{approvalPolicy:"on-request",sandbox:"read-only"}:e==="acceptEdits"||e==="accept-edits"?{approvalPolicy:"on-failure",sandbox:"workspace-write"}:["agent-full-access","full-access","bypassPermissions","danger-full-access"].includes(e)?{approvalPolicy:"never",sandbox:"danger-full-access"}:{approvalPolicy:"on-request",sandbox:"workspace-write"}}function b(n){if(!n)return{};const e={},s=n.reasoning_effort??n.reasoningEffort??n.model_reasoning_effort??n.modelReasoningEffort??n.effort;return typeof s=="string"&&s&&(e.effort=s),e}function M(n){if(!n||typeof n!="object"||Array.isArray(n))return;const e=n;for(const s of["command","cwd","reason","grantRoot"]){const t=e[s];if(typeof t=="string"&&t.trim())return A(t)}}function A(n){const e=n.replace(/\s+/g," ").trim();return e.length>120?`${e.slice(0,117)}...`:e}function R(n){return n.startsWith("thread/")?!0:["account/rateLimits/updated","account/updated","configWarning","warning","model/rerouted","thread/compacted"].includes(n)}function q(){const n=Number.parseInt(process.env.ACA_ACP_PROMPT_TIMEOUT_MS??"",10);return Number.isInteger(n)&&n>=3e4?n:720*60*1e3}export{O as NativeCodexProvider,S as nativeCodexCapabilities};
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import{isCodexInputMethod as
|
|
2
|
-
`);this.lineBuffer=t.pop()??"";for(const s of t){const i=s.trim();if(!i)continue;let o;try{o=JSON.parse(i)}catch{continue}this.handleMessage(o)}}async handleMessage(e){if("id"in e&&("result"in e||"error"in e)&&typeof e.method!="string"){this.handleParentResponse(e);return}const t=typeof e.method=="string"?e.method:"",s=e.id;if(t){if(s===void 0){await this.handleNotification(t,a(e.params));return}try{const i=await this.handleRequest(t,a(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){if(e==="session/cancel")try{await this.cancel(t)}catch(s){process.stderr.write(`Codex cancel failed: ${s instanceof Error?s.message:String(s)}
|
|
1
|
+
import{isCodexInputMethod as j,normalizeProviderInput as L,providerInputResponse as O}from"../../../interactions/provider-input.js";import{UserSteeringLedger as B}from"./user-steering.js";import{createToolLifecycleTrace as y}from"../../tool-lifecycle-trace.js";import{toolEnvironmentInstructions as C}from"../../../core/tool-environment.js";import{JsonLineRpcClient as K}from"../../client/json-rpc.js";import{startCodexAppServer as W}from"../codex/codex-process.js";import{CodexConnectionRecoveryTracker as z}from"../codex/connection-events.js";import{codexAcpSessionUpdatesFromNotification as k}from"../codex/events.js";import{CodexPlanDeltaState as v}from"../codex/plan-events.js";import{isCodexApprovalRequest as G,resolveCodexApprovalRequest as J}from"../codex/permissions.js";import{buildCodexContinuityInstructions as H,codexTokenUsageFromRawResponseCompleted as V,codexTokenUsageFromNotification as X,decideCodexContextMaintenance as R,readCodexContextMaintenancePolicy as A,rolloutFileSize as Q}from"./context-maintenance.js";import{codexNotificationBelongsToCurrentTurn as Y,codexNotificationRouting as P}from"./notification-routing.js";import{completedSubtaskHistoryUpdates as Z}from"./subtask-history.js";import{CodexSubtaskRouter as ee}from"./subtask-routing.js";import{decorateSubtaskUpdate as _}from"./subtask-executions.js";import{decorateSubtaskCompletion as te}from"./subtask-completion.js";import{waitForCancellableTurnStart as se}from"./cancellation.js";import{codexAcpUpdateIsMeaningfulProgress as M,codexRawItemIsMeaningfulProgress as ie,withCodexProgressMetadata as U}from"./progress-evidence.js";import{acpPromptText as oe,acpPromptToCodexInput as ne,turnConfigFromConfig as re}from"./turn-input.js";import{CodexControlToolLoopDetector as ae,codexControlToolLoopAcpUpdate as q,controlToolLoopDetectionThresholds as ce,controlToolLoopReady as ue,readCodexControlToolLoopPolicy as de}from"./control-tool-loop.js";import{CodexControlToolSteeringLimiter as le,codexControlToolSteeringAcpUpdate as E}from"./control-tool-steering.js";class he{toolTrace=y();lineBuffer="";started=null;rpc=null;codexStartupPromise=null;initialized=!1;currentSessionId=null;currentCwd=process.cwd();promptActive=!1;currentTurnId=null;userSteering=new B;turnStartPromise=null;promptCancelled=!1;nextParentRequestId=1;pendingParentRequests=new Map;completedTurns=new Map;pendingTurnCompletions=new Map;agentMessagePhases=new Map;planDeltas=new v;subtaskRouter=new ee;subtaskGeneration=0;subtaskAgentMessagePhases=new Map;subtaskPlanDeltas=new Map;subscribedSubtaskThreadIds=new Set;pendingSubtaskSubscriptions=new Map;hydratedSubtaskThreadIds=new Set;seenSubtaskItemIds=new Set;pendingSubtaskNotifications=[];config={};sessionAliases=new Map;currentAffectedFiles=new Set;connectionRecovery=new z;latestTokenUsage=null;currentRolloutPath=null;threadTurnCount=0;currentPromptText="";currentAssistantText="";previousUserText="";previousAssistantText="";previousAffectedFiles=new Set;maintenanceActive=!1;pendingCompaction=null;shutdownPromise=null;managedProvider=null;controlToolLoop;controlToolPolicy=de();controlToolSteering=new le(this.controlToolPolicy.steeringMaxAttempts);constructor(){this.controlToolLoop=new ae({hasActiveSubtasks:()=>this.subtaskRouter.hasActiveSubtasks(),shortSleepMaxDurationMs:this.controlToolPolicy.shortSleepMaxDurationMs}),process.stdin.setEncoding("utf8"),process.stdin.on("data",t=>this.handleInput(String(t)));const e=()=>{this.shutdown().finally(()=>process.exit(0))};process.stdin.once("end",e),process.stdin.once("close",e),process.stdin.once("error",e),process.once("SIGTERM",()=>{this.shutdown().finally(()=>process.exit(0))}),process.once("SIGINT",()=>{this.shutdown().finally(()=>process.exit(130))})}handleInput(e){this.lineBuffer+=e;const t=this.lineBuffer.split(`
|
|
2
|
+
`);this.lineBuffer=t.pop()??"";for(const s of t){const i=s.trim();if(!i)continue;let o;try{o=JSON.parse(i)}catch{continue}this.handleMessage(o)}}async handleMessage(e){if("id"in e&&("result"in e||"error"in e)&&typeof e.method!="string"){this.handleParentResponse(e);return}const t=typeof e.method=="string"?e.method:"",s=e.id;if(t){if(s===void 0){await this.handleNotification(t,a(e.params));return}try{const i=await this.handleRequest(t,a(e.params));this.write({jsonrpc:"2.0",id:s,result:i})}catch(i){this.write({jsonrpc:"2.0",id:s,error:{code:Number.isInteger(i?.rpcCode)?i.rpcCode:-32603,message:i instanceof Error?i.message:String(i)}})}}}async handleNotification(e,t){if(e==="session/cancel")try{await this.cancel(t)}catch(s){process.stderr.write(`Codex cancel failed: ${s instanceof Error?s.message:String(s)}
|
|
3
3
|
`),await this.shutdown(),process.exit(1)}}async handleRequest(e,t){switch(e){case"initialize":return this.initializeResponse();case"aca/runtime_config":return this.applyRuntimeConfig(t);case"session/new":return this.newSession(t);case"session/resume":case"session/load":return this.resumeSession(t);case"session/fork":return this.forkSession(t);case"session/prompt":return this.prompt(t);case"aca/supplement":return this.userSteering.send({id:String(t.deliveryId||""),threadId:String(t.sessionId||""),turnId:String(t.expectedTurnId||""),prompt:Array.isArray(t.prompt)?t.prompt:[]},{threadId:this.currentSessionId,turnId:this.currentTurnId,ready:this.promptActive&&!this.promptCancelled&&!this.maintenanceActive&&!this.shutdownPromise},s=>this.requireRpc().request("turn/steer",s,6e4),s=>{this.currentPromptText+=`
|
|
4
4
|
|
|
5
5
|
[用户补充]
|
|
6
|
-
${s}`});case"aca/supplement_status":return this.userSteering.receipt(String(t.deliveryId||""));case"session/compact":return this.manualCompact(t);case"aca/session_recover":return this.recoverSession(t);case"session/cancel":return await this.cancel(t),{};case"session/close":return await this.closeSession(t),{};case"session/set_config_option":return this.setConfigOption(t);case"session/set_mode":return this.setMode(t);case"fs/read_text_file":case"fs/write_text_file":case"terminal/create":case"terminal/output":case"terminal/wait_for_exit":case"terminal/kill":case"terminal/release":return{};default:throw new Error(`Unsupported ACP method: ${e}`)}}initializeResponse(){return{protocolVersion:1,agentCapabilities:{loadSession:!0,sessionCapabilities:{resume:!0,close:!0}}}}applyRuntimeConfig(e){if(this.started)throw new Error("Codex runtime config must be applied before app-server starts");const t=a(e.codexProvider);if(Object.keys(t).length===0)return this.managedProvider=null,{configured:!1};const s=d(t,"baseUrl"),i=d(t,"apiKey"),o=Number(t.revision||0);if(!s||!i||!Number.isSafeInteger(o)||o<1)throw new Error("Invalid Codex managed provider runtime config");return this.managedProvider={baseUrl:s,apiKey:i,revision:o},{configured:!0,revision:o}}async newSession(e){const t=d(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=T(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=d(e,"sessionId");if(!t)throw new Error("sessionId is required");const s=d(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=T(i)||t,this.currentCwd=s,this.resetSubtaskState(i),await this.subscribeKnownSubtasks(!1),this.captureThreadMetadata(i),this.sessionResponse(this.currentSessionId)}async forkSession(e){const t=d(e,"sessionId");if(!t)throw new Error("sessionId is required");const s=d(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),o=T(i);if(!o)throw new Error("Codex thread/fork did not return thread id");return this.currentSessionId=o,this.currentCwd=s,this.resetSubtaskState(i),this.captureThreadMetadata(i),this.sessionResponse(o)}async prompt(e){this.promptCancelled=!1;const t=d(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:[],o=this.requireRpc();this.currentSessionId&&this.currentSessionId!==s&&this.resetSubtaskState(),this.currentSessionId=s;const r=await this.maintainContext("before",{userRequest:this.previousUserText,assistantResponse:this.previousAssistantText,affectedFiles:[...this.previousAffectedFiles]},{allowRollover:!0});if(ge(r),this.promptCancelled||this.shutdownPromise)throw new Error("Codex prompt cancelled");s=this.currentSessionId||s,this.toolTrace=y(),this.promptActive=!0,this.currentTurnId=null,this.controlToolLoop.reset(),this.controlToolSteering.reset(),this.currentPromptText=oe(i),this.currentAssistantText="",this.currentAffectedFiles.clear(),this.connectionRecovery.reset(),this.agentMessagePhases.clear(),this.planDeltas.reset(),this.pendingSubtaskNotifications=[],this.flushSubtaskStates();let c;try{this.turnStartPromise=o.request("turn/start",{threadId:s,clientUserMessageId:`aca-acp-${Date.now()}-${Math.random().toString(16).slice(2)}`,input:ne(i),cwd:this.currentCwd,...re(this.config)},$()),c=await this.turnStartPromise;const u=String(c?.turn?.id??"");this.currentTurnId=u||null,u&&this.notifySessionUpdate({sessionUpdate:"aca_supplement_target",ready:!this.completedTurns.has(u),threadId:s,turnId:u});const l=u?await this.waitForTurnCompleted(u,$()):c?.turn;this.notifySessionUpdate({sessionUpdate:"aca_supplement_target",ready:!1});const h=await this.subtaskRouter.waitForIdle(be());if(h.timedOut&&h.activeThreadIds.length>0&&this.notifySessionUpdate({sessionUpdate:"aca_codex_subtask_drain",label:"子任务收尾等待超时",detail:`仍有 ${h.activeThreadIds.length} 个子任务未结束`,_meta:{aca:{subtaskDrain:h}}}),this.promptCancelled)throw new Error("Codex prompt cancelled");this.threadTurnCount+=1,this.previousUserText=this.currentPromptText,this.previousAssistantText=this.currentAssistantText,this.previousAffectedFiles.clear();for(const p of this.currentAffectedFiles)this.previousAffectedFiles.add(p);const f=await this.maintainContext("after",{userRequest:this.previousUserText,assistantResponse:this.previousAssistantText,affectedFiles:[...this.previousAffectedFiles]},{allowRollover:!0});return{stopReason:"end_turn",sessionId:this.currentSessionId,turn:l,_meta:{acaContextUsage:F(f)??me(this.latestTokenUsage),acaContextMaintenance:f,acaSubtaskDrain:h}}}catch(u){const l=this.connectionRecovery.fail(u);throw l&&this.notifySessionUpdate(l),u}finally{this.subtaskRouter.executions.loseObservation(this.promptCancelled?"父任务已中断,子执行状态需重新确认":"本轮观测结束,未确认子执行终态"),this.flushSubtaskStates(),this.controlToolLoop.reset(),this.notifySessionUpdate({sessionUpdate:"aca_supplement_target",ready:!1}),this.promptActive=!1,this.currentTurnId=null,this.turnStartPromise=null,this.pendingSubtaskNotifications=[]}}resetSubtaskState(e){this.subtaskGeneration+=1,this.subtaskRouter.reset(),this.subtaskAgentMessagePhases.clear(),this.subtaskPlanDeltas.clear(),this.subscribedSubtaskThreadIds.clear(),this.pendingSubtaskSubscriptions.clear(),this.hydratedSubtaskThreadIds.clear(),this.seenSubtaskItemIds.clear(),this.pendingSubtaskNotifications=[],e!==void 0&&this.subtaskRouter.restoreFromSessionSnapshot(e,this.currentSessionId)}async subscribeKnownSubtasks(e=!0){const t=this.subtaskRouter.routesForSubscription().map(s=>this.subscribeSubtask(s,e));await Promise.allSettled(t)}subscribeSubtask(e,t){const s=e.sourceThreadId;if(!s||s===this.currentSessionId||this.subscribedSubtaskThreadIds.has(s))return Promise.resolve();const i=this.pendingSubtaskSubscriptions.get(s);if(i)return i;const o=this.subtaskGeneration,r=this.requireRpc().request("thread/resume",{threadId:s,approvalsReviewer:"user",excludeTurns:!1},6e4).then(c=>{if(o!==this.subtaskGeneration)return;this.subscribedSubtaskThreadIds.add(s);const u=this.subtaskRouter.restoreFromSessionSnapshot(c,this.currentSessionId);t&&this.hydrateSubtaskHistory(c,e),u>0&&this.subscribeKnownSubtasks(t)}).catch(()=>{}).finally(()=>{this.pendingSubtaskSubscriptions.get(s)===r&&this.pendingSubtaskSubscriptions.delete(s)});return this.pendingSubtaskSubscriptions.set(s,r),r}async cancel(e){const t=this.resolveSessionAlias(d(e,"sessionId")||this.currentSessionId);if(!t||!this.rpc)return;if(t!==this.currentSessionId)throw new Error("Cannot cancel a different Codex session");this.promptCancelled=!0,this.subtaskRouter.stopWaiting();const s=this.turnStartPromise?await se(this.turnStartPromise):null,i=this.currentTurnId||d(S(s,"turn"),"id");if(await this.rpc.request("turn/interrupt",{threadId:t,turnId:i},5e3),i){const o=this.pendingTurnCompletions.get(i);o&&(this.pendingTurnCompletions.delete(i),clearTimeout(o.timer),o.reject(new Error("Codex prompt cancelled")))}}async manualCompact(e){const t=d(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=A(),o=await this.maintainContext("manual",{userRequest:this.previousUserText,assistantResponse:this.previousAssistantText,affectedFiles:[...this.previousAffectedFiles]},{force:!0,allowRollover:e.allowRollover===!0,compactTimeoutMs:fe(e,i.compactTimeoutMs)}),r=String(o.action||"");if(r==="compact_failed"||r==="rollover_failed"){const c=String(o.compactError||"Codex 上下文压缩未完成");throw new Error(`Codex 上下文压缩失败:${c}`)}return{sessionId:this.currentSessionId,maintenance:o,contextUsage:F(o)}}async recoverSession(e){const t=d(e,"sessionId")||this.currentSessionId,s=this.resolveSessionAlias(t);if(!s)throw new Error("sessionId is required");const i=d(e,"reason");if(i!=="low_output_loop"&&i!=="control_tool_loop")throw new Error("Unsupported Codex recovery reason");if(this.promptActive||this.maintenanceActive)throw new Error("Codex session is busy");if(s!==this.currentSessionId)throw new Error("Cannot recover a different Codex session");const o=new Set([...this.previousAffectedFiles,...this.currentAffectedFiles]);return await this.rotateThread(s,{userRequest:this.currentPromptText||this.previousUserText,assistantResponse:this.currentAssistantText||this.previousAssistantText,affectedFiles:[...o]}),{...a(this.sessionResponse(this.currentSessionId)),previousSessionId:s,recoveryReason:i}}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=W(e,this.managedProvider),i=new K(s.child,{peerName:"Codex app-server",onNotification:(o,r)=>this.handleCodexNotification(o,r),onRequest:async(o,r,c)=>this.handleCodexRequest(o,r,c.id)});this.started=s,this.rpc=i,s.child.once("exit",()=>{this.started===s&&(this.subtaskRouter.executions.loseObservation("执行连接断开,未确认子代理终态"),this.flushSubtaskStates(),this.subtaskRouter.stopWaiting())}),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 x(this.started.child,1e3),this.rpc=null,this.started=null,s}finally{this.codexStartupPromise===t&&(this.codexStartupPromise=null)}}handleCodexNotification(e,t){const s=P(t);if(e==="item/started"||e==="item/completed"){const u=a(a(t).item);this.toolTrace("provider_notification",{method:e,threadId:s.threadId,turnId:s.turnId,toolCallId:u.id,itemType:u.type,status:u.status})}if(this.maintenanceActive&&(!s.threadId||s.threadId===this.currentSessionId)){(e==="item/started"||e==="item/completed")&&this.toolTrace("notification_filtered",{method:e,toolCallId:a(a(t).item).id,reason:"context_maintenance"}),this.captureContextNotification(e,t);return}if(!Y(t,this.currentSessionId,this.currentTurnId)){if(!this.promptActive||!s.threadId||s.threadId===this.currentSessionId){(e==="item/started"||e==="item/completed")&&this.toolTrace("notification_filtered",{method:e,toolCallId:a(a(t).item).id,reason:"outside_current_turn",threadId:s.threadId,turnId:s.turnId});return}const u=this.subtaskRouter.routeFor(s.threadId);if(!u){(e==="item/started"||e==="item/completed")&&this.toolTrace("notification_buffered",{method:e,toolCallId:a(a(t).item).id,threadId:s.threadId,reason:"subtask_route_pending"}),this.bufferPendingSubtaskNotification(e,t,s.threadId);return}this.handleSubtaskNotification(e,t,u),this.flushPendingSubtaskNotifications();return}const i=this.subtaskRouter.observe(t,this.currentSessionId);if(this.flushSubtaskStates(),i>0&&this.subscribeKnownSubtasks(!0),this.captureContextNotification(e,t),this.maintenanceActive)return;if(!this.promptActive&&e!=="turn/completed"){(e==="item/started"||e==="item/completed")&&this.toolTrace("notification_filtered",{method:e,toolCallId:a(a(t).item).id,reason:"prompt_inactive"});return}this.rpc?.refreshPendingRequestTimeout("turn/start"),this.captureAgentMessagePhase(e,t);const o=this.connectionRecovery.beforeNotification(e,t);e==="turn/completed"&&this.resolveTurnCompleted(t);const r=e==="rawResponseItem/completed"?we(this.controlToolLoop,t):[],c=e==="rawResponse/completed"?Se(t):e==="rawResponseItem/completed"?r.map(({update:u})=>u):k(e,t,{agentMessagePhases:this.agentMessagePhases,planDeltas:this.planDeltas});for(const u of r)this.maybeSteerControlToolLoop(u.event);for(const u of[...o,...c]){const l=U(u,e,t,this.agentMessagePhases);M(l)&&this.notifyControlToolProgress(),this.captureUpdateEvidence(l),this.notifySessionUpdate(l)}i>0&&this.flushPendingSubtaskNotifications()}handleSubtaskNotification(e,t,s){if(!this.promptActive)return;const i=this.subtaskRouter.acceptNotification(s,e,t);if(this.flushSubtaskStates(),!i){(e==="item/started"||e==="item/completed")&&this.toolTrace("notification_filtered",{method:e,toolCallId:a(a(t).item).id,threadId:s.sourceThreadId,reason:"subtask_not_current"});return}(e==="rawResponseItem/completed"||e==="item/completed")&&ie(a(a(t).item))&&this.notifyControlToolProgress(),this.rememberSubtaskItem(s.sourceThreadId,t),this.rpc?.refreshPendingRequestTimeout("turn/start");const o=this.subtaskRouter.executions.get(s.sourceThreadId)?.runId||s.sourceThreadId,r=this.subtaskAgentMessagePhases.get(o)??new Map;this.subtaskAgentMessagePhases.set(o,r);const c=this.subtaskPlanDeltas.get(o)??new v;this.subtaskPlanDeltas.set(o,c),this.captureAgentMessagePhase(e,t,r);const u=this.subtaskRouter.observe(t,this.currentSessionId);this.flushSubtaskStates(),u>0&&this.subscribeKnownSubtasks(!0);const l=k(e,t,{agentMessagePhases:r,planDeltas:c}),h=P(t).turnId;for(const f of l){const p=U(f,e,t,r);if(!Ie(p))continue;d(p,"title")&&this.subtaskRouter.executions.action(s.sourceThreadId,d(p,"title")),M(p)&&(this.notifyControlToolProgress(),this.subtaskRouter.executions.progress(s.sourceThreadId,d(p,"title")||"收到执行结果"));const g=_(p,s,h,this.subtaskRouter.executions.get(s.sourceThreadId));I(g,this.currentAffectedFiles),this.notifySessionUpdate(g)}}hydrateSubtaskHistory(e,t){if(!(!this.promptActive||this.hydratedSubtaskThreadIds.has(t.sourceThreadId))){this.hydratedSubtaskThreadIds.add(t.sourceThreadId);for(const s of Z(e)){const i=D(t.sourceThreadId,s.itemId);if(this.seenSubtaskItemIds.has(i))continue;this.seenSubtaskItemIds.add(i);const o=_(s.update,t,s.sourceTurnId,{runId:`history.${t.sourceThreadId}.${s.sourceTurnId}`,state:"unknown",name:this.subtaskRouter.executions.nameFor(t.sourceThreadId),outputIncomplete:!0,reason:"历史过程回放,不代表当前执行状态"}),r=a(o._meta);o._meta={...r,aca:{...a(r.aca),historyReplay:!0}},I(o,this.currentAffectedFiles),this.notifySessionUpdate(o)}}}rememberSubtaskItem(e,t){const s=a(t),i=a(s.item),o=d(i,"id")||d(s,"itemId");o&&this.seenSubtaskItemIds.add(D(e,o))}bufferPendingSubtaskNotification(e,t,s){this.pendingSubtaskNotifications.push({method:e,params:t,threadId:s}),this.pendingSubtaskNotifications.length>200&&this.pendingSubtaskNotifications.shift()}flushPendingSubtaskNotifications(){if(this.pendingSubtaskNotifications.length===0)return;let e=!0;for(;e&&this.pendingSubtaskNotifications.length>0;){e=!1;const t=[];for(const s of this.pendingSubtaskNotifications){const 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){e=te(e,this.subtaskRouter.executions),(e.sessionUpdate==="tool_call"||e.sessionUpdate==="tool_call_update"&&["completed","failed","cancelled"].includes(String(e.status)))&&this.toolTrace("acp_update",{toolCallId:e.toolCallId,status:e.status,threadId:this.currentSessionId,turnId:this.currentTurnId}),this.notify("session/update",{sessionId:this.currentSessionId,update:e})}flushSubtaskStates(){if(this.promptActive)for(const e of this.subtaskRouter.executions.drainUpdates())this.notifySessionUpdate(e)}async handleCodexRequest(e,t,s){if(G(e))return J(e,t,async i=>{const o=await this.requestParent("session/request_permission",{sessionId:this.currentSessionId,request:{...i,params:t,method:e}},Math.max(1,i.expiresAtMs-Date.now())),r=a(o),c=a(r.outcome);return c.outcome==="selected"&&typeof c.optionId=="string"?{outcome:{outcome:"selected",optionId:c.optionId}}:{outcome:{outcome:"cancelled"}}},s);if(L(e)){const i=j("codex-acp",e,t,s),o=await this.requestParent("elicitation/create",{sessionId:this.currentSessionId,acaInteractionBridge:1,request:i},Math.max(1,i.interaction.expiresAtMs-Date.now()));return O(i,o)}throw new Error(`Unsupported Codex server request: ${e}`)}captureAgentMessagePhase(e,t,s=this.agentMessagePhases){if(e!=="item/started"&&e!=="item/completed")return;const i=S(t,"item");if(d(i,"type")!=="agentMessage")return;const o=d(i,"id");o&&s.set(o,d(i,"phase"))}waitForTurnCompleted(e,t){const s=this.completedTurns.get(e);return s?Promise.resolve(s):new Promise((i,o)=>{const r=setTimeout(()=>{this.pendingTurnCompletions.delete(e),o(new Error("Codex app-server request timed out while waiting for turn/completed"))},t);r.unref(),this.pendingTurnCompletions.set(e,{resolve:i,reject:o,timer:r})})}resolveTurnCompleted(e){const t=S(e,"turn"),s=d(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),d(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.subtaskRouter.executions.loseObservation("执行连接关闭,未确认子代理终态"),this.flushSubtaskStates(),this.subtaskRouter.stopWaiting(),this.pendingCompaction&&(clearTimeout(this.pendingCompaction.timer),this.pendingCompaction.reject(new Error("Codex ACP adapter closed")),this.pendingCompaction=null);for(const t of this.pendingParentRequests.values())clearTimeout(t.timer),t.reject(new Error("Codex ACP adapter closed"));this.pendingParentRequests.clear();for(const t of this.pendingTurnCompletions.values())clearTimeout(t.timer),t.reject(new Error("Codex ACP adapter closed"));this.pendingTurnCompletions.clear();const e=this.started?.child;!e||e.exitCode!==null||e.signalCode!==null||(e.stdin.end(),!await x(e,2e3)&&(e.kill("SIGTERM"),!await x(e,1e3)&&(e.kill("SIGKILL"),await x(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((o,r)=>{const c=setTimeout(()=>{this.pendingParentRequests.delete(i),r(new Error(`ACP client request timed out: ${e}`))},s);c.unref(),this.pendingParentRequests.set(i,{resolve:o,reject:r,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=a(e.error);s.reject(new Error(d(i,"message")||"ACP client request failed"));return}s.resolve(e.result)}}setConfigOption(e){const t=d(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=d(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=S(e,"config");for(const[s,i]of Object.entries(t))(typeof i=="string"||typeof i=="boolean")&&(this.config[s]=i)}codexConfig(){return{...C()?{developerInstructions:C()}:{},...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=X(t)??this.latestTokenUsage),e==="thread/compacted"&&this.pendingCompaction&&this.resolveCompaction(),e==="item/completed"){const s=S(t,"item"),i=d(a(t),"threadId");d(s,"type")==="contextCompaction"&&(!i||i===this.currentSessionId)&&this.resolveCompaction(),d(s,"type")==="agentMessage"&&d(s,"phase")==="final_answer"&&(this.currentAssistantText=w(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+=w(e.content)),I(e,this.currentAffectedFiles)}captureThreadMetadata(e){const t=S(e,"thread");this.currentRolloutPath=d(t,"path")||null,this.threadTurnCount=Array.isArray(t.turns)?t.turns.length:0;const s=pe(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=A(),o=this.contextState(),r=R(o,i);if(!r.compact&&!s.force||!this.currentSessionId)return this.maintenanceResult({stage:e,action:"none",reasons:r.reasons,beforeState:o});const c=this.currentSessionId;this.notifyContextStatus("正在整理会话上下文",N(o)),this.maintenanceActive=!0;let u=!1,l="",h=o,f=r,p=!1,g="";try{const m=o.usage?.observedAtMs??0;await this.compactThread(c,s.compactTimeoutMs??i.compactTimeoutMs),await this.waitForTokenUsageAfter(m,2e3)}catch(m){u=!0,l=m instanceof Error?m.message:String(m)}try{if(e!=="manual"&&this.promptCancelled||this.shutdownPromise)throw new Error("Codex prompt cancelled");if(h=this.contextState(),f=R(h,i,{compactAttempted:!0,compactFailed:u}),f.rollover&&s.allowRollover!==!1){this.notifyContextStatus("正在创建轻量会话",f.reasons.join(", "));try{await this.rotateThread(c,t),p=!0}catch(m){g=m instanceof Error?m.message:String(m),this.notifyContextStatus("轻量会话创建失败",g)}}else u||this.notifyContextStatus("会话上下文已压缩",N(h))}finally{this.maintenanceActive=!1}return this.maintenanceResult({action:p?"rollover":g?"rollover_failed":u?"compact_failed":"compact",stage:e,reasons:f.reasons,beforeState:o,afterState:h,compactError:[l,g].filter(Boolean).join("; "),previousThreadId:c,rolloverRecommended:f.rollover&&s.allowRollover===!1})}compactThread(e,t){return this.pendingCompaction?Promise.reject(new Error("Codex context compaction is already running")):new Promise((s,i)=>{const o=setTimeout(()=>{this.pendingCompaction?.timer===o&&(this.pendingCompaction=null),i(new Error("Codex thread compaction timed out"))},t);o.unref(),this.pendingCompaction={resolve:s,reject:i,timer:o},this.requireRpc().request("thread/compact/start",{threadId:e},t).catch(r=>{this.pendingCompaction?.timer===o&&(this.pendingCompaction=null,clearTimeout(o),i(r instanceof Error?r:new Error(String(r))))})})}async rotateThread(e,t){const s=H({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,C()].filter(Boolean).join(`
|
|
6
|
+
${s}`});case"aca/supplement_status":return this.userSteering.receipt(String(t.deliveryId||""));case"session/compact":return this.manualCompact(t);case"aca/session_recover":return this.recoverSession(t);case"session/cancel":return await this.cancel(t),{};case"session/close":return await this.closeSession(t),{};case"session/set_config_option":return this.setConfigOption(t);case"session/set_mode":return this.setMode(t);case"fs/read_text_file":case"fs/write_text_file":case"terminal/create":case"terminal/output":case"terminal/wait_for_exit":case"terminal/kill":case"terminal/release":return{};default:throw new Error(`Unsupported ACP method: ${e}`)}}initializeResponse(){return{protocolVersion:1,agentCapabilities:{loadSession:!0,sessionCapabilities:{resume:!0,close:!0}}}}applyRuntimeConfig(e){if(this.started)throw new Error("Codex runtime config must be applied before app-server starts");const t=a(e.codexProvider);if(Object.keys(t).length===0)return this.managedProvider=null,{configured:!1};const s=d(t,"baseUrl"),i=d(t,"apiKey"),o=Number(t.revision||0);if(!s||!i||!Number.isSafeInteger(o)||o<1)throw new Error("Invalid Codex managed provider runtime config");return this.managedProvider={baseUrl:s,apiKey:i,revision:o},{configured:!0,revision:o}}async newSession(e){const t=d(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=T(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=d(e,"sessionId");if(!t)throw new Error("sessionId is required");const s=d(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=T(i)||t,this.currentCwd=s,this.resetSubtaskState(i),await this.subscribeKnownSubtasks(!1),this.captureThreadMetadata(i),this.sessionResponse(this.currentSessionId)}async forkSession(e){const t=d(e,"sessionId");if(!t)throw new Error("sessionId is required");const s=d(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),o=T(i);if(!o)throw new Error("Codex thread/fork did not return thread id");return this.currentSessionId=o,this.currentCwd=s,this.resetSubtaskState(i),this.captureThreadMetadata(i),this.sessionResponse(o)}async prompt(e){this.promptCancelled=!1;const t=d(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:[],o=this.requireRpc();this.currentSessionId&&this.currentSessionId!==s&&this.resetSubtaskState(),this.currentSessionId=s;const r=await this.maintainContext("before",{userRequest:this.previousUserText,assistantResponse:this.previousAssistantText,affectedFiles:[...this.previousAffectedFiles]},{allowRollover:!0});if(ge(r),this.promptCancelled||this.shutdownPromise)throw new Error("Codex prompt cancelled");s=this.currentSessionId||s,this.toolTrace=y(),this.promptActive=!0,this.currentTurnId=null,this.controlToolLoop.reset(),this.controlToolSteering.reset(),this.currentPromptText=oe(i),this.currentAssistantText="",this.currentAffectedFiles.clear(),this.connectionRecovery.reset(),this.agentMessagePhases.clear(),this.planDeltas.reset(),this.pendingSubtaskNotifications=[],this.flushSubtaskStates();let c;try{this.turnStartPromise=o.request("turn/start",{threadId:s,clientUserMessageId:`aca-acp-${Date.now()}-${Math.random().toString(16).slice(2)}`,input:ne(i),cwd:this.currentCwd,...re(this.config)},$()),c=await this.turnStartPromise;const u=String(c?.turn?.id??"");this.currentTurnId=u||null,u&&this.notifySessionUpdate({sessionUpdate:"aca_supplement_target",ready:!this.completedTurns.has(u),threadId:s,turnId:u});const l=u?await this.waitForTurnCompleted(u,$()):c?.turn;this.notifySessionUpdate({sessionUpdate:"aca_supplement_target",ready:!1});const h=await this.subtaskRouter.waitForIdle(be());if(h.timedOut&&h.activeThreadIds.length>0&&this.notifySessionUpdate({sessionUpdate:"aca_codex_subtask_drain",label:"子任务收尾等待超时",detail:`仍有 ${h.activeThreadIds.length} 个子任务未结束`,_meta:{aca:{subtaskDrain:h}}}),this.promptCancelled)throw new Error("Codex prompt cancelled");this.threadTurnCount+=1,this.previousUserText=this.currentPromptText,this.previousAssistantText=this.currentAssistantText,this.previousAffectedFiles.clear();for(const p of this.currentAffectedFiles)this.previousAffectedFiles.add(p);const f=await this.maintainContext("after",{userRequest:this.previousUserText,assistantResponse:this.previousAssistantText,affectedFiles:[...this.previousAffectedFiles]},{allowRollover:!0});return{stopReason:"end_turn",sessionId:this.currentSessionId,turn:l,_meta:{acaContextUsage:F(f)??me(this.latestTokenUsage),acaContextMaintenance:f,acaSubtaskDrain:h}}}catch(u){const l=this.connectionRecovery.fail(u);throw l&&this.notifySessionUpdate(l),u}finally{this.subtaskRouter.executions.loseObservation(this.promptCancelled?"父任务已中断,子执行状态需重新确认":"本轮观测结束,未确认子执行终态"),this.flushSubtaskStates(),this.controlToolLoop.reset(),this.notifySessionUpdate({sessionUpdate:"aca_supplement_target",ready:!1}),this.promptActive=!1,this.currentTurnId=null,this.turnStartPromise=null,this.pendingSubtaskNotifications=[]}}resetSubtaskState(e){this.subtaskGeneration+=1,this.subtaskRouter.reset(),this.subtaskAgentMessagePhases.clear(),this.subtaskPlanDeltas.clear(),this.subscribedSubtaskThreadIds.clear(),this.pendingSubtaskSubscriptions.clear(),this.hydratedSubtaskThreadIds.clear(),this.seenSubtaskItemIds.clear(),this.pendingSubtaskNotifications=[],e!==void 0&&this.subtaskRouter.restoreFromSessionSnapshot(e,this.currentSessionId)}async subscribeKnownSubtasks(e=!0){const t=this.subtaskRouter.routesForSubscription().map(s=>this.subscribeSubtask(s,e));await Promise.allSettled(t)}subscribeSubtask(e,t){const s=e.sourceThreadId;if(!s||s===this.currentSessionId||this.subscribedSubtaskThreadIds.has(s))return Promise.resolve();const i=this.pendingSubtaskSubscriptions.get(s);if(i)return i;const o=this.subtaskGeneration,r=this.requireRpc().request("thread/resume",{threadId:s,approvalsReviewer:"user",excludeTurns:!1},6e4).then(c=>{if(o!==this.subtaskGeneration)return;this.subscribedSubtaskThreadIds.add(s);const u=this.subtaskRouter.restoreFromSessionSnapshot(c,this.currentSessionId);t&&this.hydrateSubtaskHistory(c,e),u>0&&this.subscribeKnownSubtasks(t)}).catch(()=>{}).finally(()=>{this.pendingSubtaskSubscriptions.get(s)===r&&this.pendingSubtaskSubscriptions.delete(s)});return this.pendingSubtaskSubscriptions.set(s,r),r}async cancel(e){const t=this.resolveSessionAlias(d(e,"sessionId")||this.currentSessionId);if(!t||!this.rpc)return;if(t!==this.currentSessionId)throw new Error("Cannot cancel a different Codex session");this.promptCancelled=!0,this.subtaskRouter.stopWaiting();const s=this.turnStartPromise?await se(this.turnStartPromise):null,i=this.currentTurnId||d(S(s,"turn"),"id");if(await this.rpc.request("turn/interrupt",{threadId:t,turnId:i},5e3),i){const o=this.pendingTurnCompletions.get(i);o&&(this.pendingTurnCompletions.delete(i),clearTimeout(o.timer),o.reject(new Error("Codex prompt cancelled")))}}async manualCompact(e){const t=d(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=A(),o=await this.maintainContext("manual",{userRequest:this.previousUserText,assistantResponse:this.previousAssistantText,affectedFiles:[...this.previousAffectedFiles]},{force:!0,allowRollover:e.allowRollover===!0,compactTimeoutMs:fe(e,i.compactTimeoutMs)}),r=String(o.action||"");if(r==="compact_failed"||r==="rollover_failed"){const c=String(o.compactError||"Codex 上下文压缩未完成");throw new Error(`Codex 上下文压缩失败:${c}`)}return{sessionId:this.currentSessionId,maintenance:o,contextUsage:F(o)}}async recoverSession(e){const t=d(e,"sessionId")||this.currentSessionId,s=this.resolveSessionAlias(t);if(!s)throw new Error("sessionId is required");const i=d(e,"reason");if(i!=="low_output_loop"&&i!=="control_tool_loop")throw new Error("Unsupported Codex recovery reason");if(this.promptActive||this.maintenanceActive)throw new Error("Codex session is busy");if(s!==this.currentSessionId)throw new Error("Cannot recover a different Codex session");const o=new Set([...this.previousAffectedFiles,...this.currentAffectedFiles]);return await this.rotateThread(s,{userRequest:this.currentPromptText||this.previousUserText,assistantResponse:this.currentAssistantText||this.previousAssistantText,affectedFiles:[...o]}),{...a(this.sessionResponse(this.currentSessionId)),previousSessionId:s,recoveryReason:i}}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=W(e,this.managedProvider),i=new K(s.child,{peerName:"Codex app-server",onNotification:(o,r)=>this.handleCodexNotification(o,r),onRequest:async(o,r,c)=>this.handleCodexRequest(o,r,c.id)});this.started=s,this.rpc=i,s.child.once("exit",()=>{this.started===s&&(this.subtaskRouter.executions.loseObservation("执行连接断开,未确认子代理终态"),this.flushSubtaskStates(),this.subtaskRouter.stopWaiting())}),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 x(this.started.child,1e3),this.rpc=null,this.started=null,s}finally{this.codexStartupPromise===t&&(this.codexStartupPromise=null)}}handleCodexNotification(e,t){if(e==="elicitation/complete"){this.write({jsonrpc:"2.0",method:e,params:t});return}const s=P(t);if(e==="item/started"||e==="item/completed"){const u=a(a(t).item);this.toolTrace("provider_notification",{method:e,threadId:s.threadId,turnId:s.turnId,toolCallId:u.id,itemType:u.type,status:u.status})}if(this.maintenanceActive&&(!s.threadId||s.threadId===this.currentSessionId)){(e==="item/started"||e==="item/completed")&&this.toolTrace("notification_filtered",{method:e,toolCallId:a(a(t).item).id,reason:"context_maintenance"}),this.captureContextNotification(e,t);return}if(!Y(t,this.currentSessionId,this.currentTurnId)){if(!this.promptActive||!s.threadId||s.threadId===this.currentSessionId){(e==="item/started"||e==="item/completed")&&this.toolTrace("notification_filtered",{method:e,toolCallId:a(a(t).item).id,reason:"outside_current_turn",threadId:s.threadId,turnId:s.turnId});return}const u=this.subtaskRouter.routeFor(s.threadId);if(!u){(e==="item/started"||e==="item/completed")&&this.toolTrace("notification_buffered",{method:e,toolCallId:a(a(t).item).id,threadId:s.threadId,reason:"subtask_route_pending"}),this.bufferPendingSubtaskNotification(e,t,s.threadId);return}this.handleSubtaskNotification(e,t,u),this.flushPendingSubtaskNotifications();return}const i=this.subtaskRouter.observe(t,this.currentSessionId);if(this.flushSubtaskStates(),i>0&&this.subscribeKnownSubtasks(!0),this.captureContextNotification(e,t),this.maintenanceActive)return;if(!this.promptActive&&e!=="turn/completed"){(e==="item/started"||e==="item/completed")&&this.toolTrace("notification_filtered",{method:e,toolCallId:a(a(t).item).id,reason:"prompt_inactive"});return}this.rpc?.refreshPendingRequestTimeout("turn/start"),this.captureAgentMessagePhase(e,t);const o=this.connectionRecovery.beforeNotification(e,t);e==="turn/completed"&&this.resolveTurnCompleted(t);const r=e==="rawResponseItem/completed"?we(this.controlToolLoop,t):[],c=e==="rawResponse/completed"?Se(t):e==="rawResponseItem/completed"?r.map(({update:u})=>u):k(e,t,{agentMessagePhases:this.agentMessagePhases,planDeltas:this.planDeltas});for(const u of r)this.maybeSteerControlToolLoop(u.event);for(const u of[...o,...c]){const l=U(u,e,t,this.agentMessagePhases);M(l)&&this.notifyControlToolProgress(),this.captureUpdateEvidence(l),this.notifySessionUpdate(l)}i>0&&this.flushPendingSubtaskNotifications()}handleSubtaskNotification(e,t,s){if(!this.promptActive)return;const i=this.subtaskRouter.acceptNotification(s,e,t);if(this.flushSubtaskStates(),!i){(e==="item/started"||e==="item/completed")&&this.toolTrace("notification_filtered",{method:e,toolCallId:a(a(t).item).id,threadId:s.sourceThreadId,reason:"subtask_not_current"});return}(e==="rawResponseItem/completed"||e==="item/completed")&&ie(a(a(t).item))&&this.notifyControlToolProgress(),this.rememberSubtaskItem(s.sourceThreadId,t),this.rpc?.refreshPendingRequestTimeout("turn/start");const o=this.subtaskRouter.executions.get(s.sourceThreadId)?.runId||s.sourceThreadId,r=this.subtaskAgentMessagePhases.get(o)??new Map;this.subtaskAgentMessagePhases.set(o,r);const c=this.subtaskPlanDeltas.get(o)??new v;this.subtaskPlanDeltas.set(o,c),this.captureAgentMessagePhase(e,t,r);const u=this.subtaskRouter.observe(t,this.currentSessionId);this.flushSubtaskStates(),u>0&&this.subscribeKnownSubtasks(!0);const l=k(e,t,{agentMessagePhases:r,planDeltas:c}),h=P(t).turnId;for(const f of l){const p=U(f,e,t,r);if(!Ie(p))continue;d(p,"title")&&this.subtaskRouter.executions.action(s.sourceThreadId,d(p,"title")),M(p)&&(this.notifyControlToolProgress(),this.subtaskRouter.executions.progress(s.sourceThreadId,d(p,"title")||"收到执行结果"));const g=_(p,s,h,this.subtaskRouter.executions.get(s.sourceThreadId));I(g,this.currentAffectedFiles),this.notifySessionUpdate(g)}}hydrateSubtaskHistory(e,t){if(!(!this.promptActive||this.hydratedSubtaskThreadIds.has(t.sourceThreadId))){this.hydratedSubtaskThreadIds.add(t.sourceThreadId);for(const s of Z(e)){const i=D(t.sourceThreadId,s.itemId);if(this.seenSubtaskItemIds.has(i))continue;this.seenSubtaskItemIds.add(i);const o=_(s.update,t,s.sourceTurnId,{runId:`history.${t.sourceThreadId}.${s.sourceTurnId}`,state:"unknown",name:this.subtaskRouter.executions.nameFor(t.sourceThreadId),outputIncomplete:!0,reason:"历史过程回放,不代表当前执行状态"}),r=a(o._meta);o._meta={...r,aca:{...a(r.aca),historyReplay:!0}},I(o,this.currentAffectedFiles),this.notifySessionUpdate(o)}}}rememberSubtaskItem(e,t){const s=a(t),i=a(s.item),o=d(i,"id")||d(s,"itemId");o&&this.seenSubtaskItemIds.add(D(e,o))}bufferPendingSubtaskNotification(e,t,s){this.pendingSubtaskNotifications.push({method:e,params:t,threadId:s}),this.pendingSubtaskNotifications.length>200&&this.pendingSubtaskNotifications.shift()}flushPendingSubtaskNotifications(){if(this.pendingSubtaskNotifications.length===0)return;let e=!0;for(;e&&this.pendingSubtaskNotifications.length>0;){e=!1;const t=[];for(const s of this.pendingSubtaskNotifications){const 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){e=te(e,this.subtaskRouter.executions),(e.sessionUpdate==="tool_call"||e.sessionUpdate==="tool_call_update"&&["completed","failed","cancelled"].includes(String(e.status)))&&this.toolTrace("acp_update",{toolCallId:e.toolCallId,status:e.status,threadId:this.currentSessionId,turnId:this.currentTurnId}),this.notify("session/update",{sessionId:this.currentSessionId,update:e})}flushSubtaskStates(){if(this.promptActive)for(const e of this.subtaskRouter.executions.drainUpdates())this.notifySessionUpdate(e)}async handleCodexRequest(e,t,s){if(G(e))return J(e,t,async i=>{const o=await this.requestParent("session/request_permission",{sessionId:this.currentSessionId,request:{...i,params:t,method:e}},Math.max(1,i.expiresAtMs-Date.now())),r=a(o),c=a(r.outcome);return c.outcome==="selected"&&typeof c.optionId=="string"?{outcome:{outcome:"selected",optionId:c.optionId}}:{outcome:{outcome:"cancelled"}}},s);if(j(e)){const i=L("codex-acp",e,t,s),o=await this.requestParent("elicitation/create",{sessionId:this.currentSessionId,acaInteractionBridge:1,request:i},Math.max(1,i.interaction.expiresAtMs-Date.now()));return O(i,o)}throw new Error(`Unsupported Codex server request: ${e}`)}captureAgentMessagePhase(e,t,s=this.agentMessagePhases){if(e!=="item/started"&&e!=="item/completed")return;const i=S(t,"item");if(d(i,"type")!=="agentMessage")return;const o=d(i,"id");o&&s.set(o,d(i,"phase"))}waitForTurnCompleted(e,t){const s=this.completedTurns.get(e);return s?Promise.resolve(s):new Promise((i,o)=>{const r=setTimeout(()=>{this.pendingTurnCompletions.delete(e),o(new Error("Codex app-server request timed out while waiting for turn/completed"))},t);r.unref(),this.pendingTurnCompletions.set(e,{resolve:i,reject:o,timer:r})})}resolveTurnCompleted(e){const t=S(e,"turn"),s=d(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),d(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.subtaskRouter.executions.loseObservation("执行连接关闭,未确认子代理终态"),this.flushSubtaskStates(),this.subtaskRouter.stopWaiting(),this.pendingCompaction&&(clearTimeout(this.pendingCompaction.timer),this.pendingCompaction.reject(new Error("Codex ACP adapter closed")),this.pendingCompaction=null);for(const t of this.pendingParentRequests.values())clearTimeout(t.timer),t.reject(new Error("Codex ACP adapter closed"));this.pendingParentRequests.clear();for(const t of this.pendingTurnCompletions.values())clearTimeout(t.timer),t.reject(new Error("Codex ACP adapter closed"));this.pendingTurnCompletions.clear();const e=this.started?.child;!e||e.exitCode!==null||e.signalCode!==null||(e.stdin.end(),!await x(e,2e3)&&(e.kill("SIGTERM"),!await x(e,1e3)&&(e.kill("SIGKILL"),await x(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((o,r)=>{const c=setTimeout(()=>{this.pendingParentRequests.delete(i),r(new Error(`ACP client request timed out: ${e}`))},s);c.unref(),this.pendingParentRequests.set(i,{resolve:o,reject:r,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=a(e.error);s.reject(new Error(d(i,"message")||"ACP client request failed"));return}s.resolve(e.result)}}setConfigOption(e){const t=d(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=d(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=S(e,"config");for(const[s,i]of Object.entries(t))(typeof i=="string"||typeof i=="boolean")&&(this.config[s]=i)}codexConfig(){return{...C()?{developerInstructions:C()}:{},...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=X(t)??this.latestTokenUsage),e==="thread/compacted"&&this.pendingCompaction&&this.resolveCompaction(),e==="item/completed"){const s=S(t,"item"),i=d(a(t),"threadId");d(s,"type")==="contextCompaction"&&(!i||i===this.currentSessionId)&&this.resolveCompaction(),d(s,"type")==="agentMessage"&&d(s,"phase")==="final_answer"&&(this.currentAssistantText=w(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+=w(e.content)),I(e,this.currentAffectedFiles)}captureThreadMetadata(e){const t=S(e,"thread");this.currentRolloutPath=d(t,"path")||null,this.threadTurnCount=Array.isArray(t.turns)?t.turns.length:0;const s=pe(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=A(),o=this.contextState(),r=R(o,i);if(!r.compact&&!s.force||!this.currentSessionId)return this.maintenanceResult({stage:e,action:"none",reasons:r.reasons,beforeState:o});const c=this.currentSessionId;this.notifyContextStatus("正在整理会话上下文",N(o)),this.maintenanceActive=!0;let u=!1,l="",h=o,f=r,p=!1,g="";try{const m=o.usage?.observedAtMs??0;await this.compactThread(c,s.compactTimeoutMs??i.compactTimeoutMs),await this.waitForTokenUsageAfter(m,2e3)}catch(m){u=!0,l=m instanceof Error?m.message:String(m)}try{if(e!=="manual"&&this.promptCancelled||this.shutdownPromise)throw new Error("Codex prompt cancelled");if(h=this.contextState(),f=R(h,i,{compactAttempted:!0,compactFailed:u}),f.rollover&&s.allowRollover!==!1){this.notifyContextStatus("正在创建轻量会话",f.reasons.join(", "));try{await this.rotateThread(c,t),p=!0}catch(m){g=m instanceof Error?m.message:String(m),this.notifyContextStatus("轻量会话创建失败",g)}}else u||this.notifyContextStatus("会话上下文已压缩",N(h))}finally{this.maintenanceActive=!1}return this.maintenanceResult({action:p?"rollover":g?"rollover_failed":u?"compact_failed":"compact",stage:e,reasons:f.reasons,beforeState:o,afterState:h,compactError:[l,g].filter(Boolean).join("; "),previousThreadId:c,rolloverRecommended:f.rollover&&s.allowRollover===!1})}compactThread(e,t){return this.pendingCompaction?Promise.reject(new Error("Codex context compaction is already running")):new Promise((s,i)=>{const o=setTimeout(()=>{this.pendingCompaction?.timer===o&&(this.pendingCompaction=null),i(new Error("Codex thread compaction timed out"))},t);o.unref(),this.pendingCompaction={resolve:s,reject:i,timer:o},this.requireRpc().request("thread/compact/start",{threadId:e},t).catch(r=>{this.pendingCompaction?.timer===o&&(this.pendingCompaction=null,clearTimeout(o),i(r instanceof Error?r:new Error(String(r))))})})}async rotateThread(e,t){const s=H({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,C()].filter(Boolean).join(`
|
|
7
7
|
|
|
8
8
|
`),ephemeral:!1,experimentalRawEvents:!0,threadSource:"aca-codex-acp-rollover"},6e4),o=T(i);if(!o)throw new Error("Codex rollover thread/start did not return thread id");this.sessionAliases.set(e,o),this.currentSessionId=o,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:Q(this.currentRolloutPath),turnCount:this.threadTurnCount}}maintenanceResult(e){return{stage:e.stage,action:e.action,reasons:e.reasons,previousThreadId:e.previousThreadId??null,sessionId:this.currentSessionId,before:e.beforeState,after:e.afterState??null,compactError:e.compactError||null,rolloverRecommended:e.rolloverRecommended===!0}}notifyContextStatus(e,t){this.notify("session/update",{sessionId:this.currentSessionId,update:{sessionUpdate:"aca_context_maintenance",label:e,detail:t}})}notifyControlToolProgress(){const e=this.controlToolLoop.recordProgress();e&&this.notifySessionUpdate(q(e))}maybeSteerControlToolLoop(e){if(!ue(e.detection,this.controlToolPolicy))return;const t=ce(e.detection,this.controlToolPolicy),s=this.controlToolSteering.next(e,t.warningCalls);if(!s)return;const{threadId:i,turnId:o}=e.detection;!i||!o||this.requireRpc().request("turn/steer",{threadId:i,expectedTurnId:o,clientUserMessageId:`aca-loop-steer-${Date.now()}-${Math.random().toString(16).slice(2)}`,input:[{type:"text",text:s.text,text_elements:[]}]},6e4).then(()=>{this.notifySessionUpdate(E(e,s,{succeeded:!0}))}).catch(r=>{this.notifySessionUpdate(E(e,s,{succeeded:!1,error:r}))})}write(e){process.stdout.write(`${JSON.stringify(e)}
|
|
9
9
|
`)}}function T(n){const e=S(n,"thread");return d(e,"id")||null}function S(n,e){const t=a(n);return a(t[e])}function a(n){return n&&typeof n=="object"&&!Array.isArray(n)?n:{}}function w(n){if(typeof n=="string")return n;if(Array.isArray(n))return n.map(w).filter(Boolean).join("");if(!n||typeof n!="object")return"";const e=a(n);for(const t of["text","message","content","output_text"]){const s=w(e[t]);if(s)return s}return""}function I(n,e){if(Array.isArray(n)){for(const o of n)I(o,e);return}const t=a(n),s=d(t,"type").toLowerCase(),i=d(t,"path");if(i&&["diff","resource_link","content"].includes(s)&&e.add(i),Array.isArray(t.locations))for(const o of t.locations){const r=d(a(o),"path");r&&e.add(r)}for(const o of[t.content,t.update,t.rawInput,t.rawOutput])o&&o!==n&&I(o,e)}function pe(n){if(!Array.isArray(n)||n.length===0)return{userRequest:"",assistantResponse:"",affectedFiles:[]};const e=n.at(-1),t=new Set;I(e,t);let s="",i="";const o=r=>{if(Array.isArray(r)){for(const l of r)o(l);return}if(!r||typeof r!="object")return;const c=a(r),u=d(c,"type").toLowerCase();u==="usermessage"&&(s=w(c)||s),u==="agentmessage"&&(i=w(c)||i);for(const l of["items","content","input"])c[l]&&o(c[l])};return o(e),{userRequest:s,assistantResponse:i,affectedFiles:[...t]}}function fe(n,e){const t=b(n,"timeoutMs");return!Number.isFinite(t)||t<=0?e:Math.max(3e4,Math.min(59e4,t-1e4))}function F(n){const e=String(n.action||"none"),t=a(n.after),s=a(n.before),i=a(t.usage),o=a(s.usage),r=Object.keys(i).length>0?i:o,c=b(r,"contextWindow");if(e==="rollover")return{usedTokens:0,inputTokens:0,contextWindow:c,ratio:0,observedAtMs:Date.now(),state:"new_thread",maintenanceAction:e};if(Object.keys(r).length===0)return null;const u=b(r,"totalTokens"),l=b(r,"inputTokens");return{usedTokens:u||l,inputTokens:l,contextWindow:c,ratio:c>0?(u||l)/c:0,observedAtMs:b(r,"observedAtMs")||Date.now(),state:e==="compact"?"compacted":"active",maintenanceAction:e}}function me(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 N(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 ge(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 b(n,e){const t=a(n)[e];return typeof t=="number"&&Number.isFinite(t)?t:0}function Se(n){return V(n)?[{sessionUpdate:"aca_codex_raw_response_usage",_meta:{codex:{rawResponse:n}}}]:[]}function we(n,e){const t=n.observeRawResponseItem(e);return t?[{event:t,update:q(t)}]:[]}function Ie(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 D(n,e){return`${n}:${e}`}function d(n,e){const s=a(n)[e];return typeof s=="string"?s:""}function $(){const n=Number.parseInt(process.env.ACA_ACP_PROMPT_TIMEOUT_MS??"",10);return Number.isInteger(n)&&n>=3e4?n:720*60*1e3}function be(){const n=Number.parseInt(process.env.ACA_CODEX_SUBTASK_DRAIN_TIMEOUT_MS??"",10);return Number.isInteger(n)&&n>=1e3?Math.min(n,600*1e3):600*1e3}function x(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 xe(){new he}const Ce=process.argv[1]||"";/(?:^|[\\/])adapter\.(?:js|ts)$/.test(Ce)&&xe();export{xe as runCodexAcpAdapter};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{handleProviderInput as V}from"../../../interactions/provider-input.js";import y from"node:fs";import{runAbortableRequest as L}from"../../client/abortable-request.js";import{BoundedItemBuffer as X}from"../../client/bounded-buffer.js";import{promptBlocksToAcpContent as E}from"../../client/acp-content.js";import{acpSessionUpdateFromParams as Y,eventFromAcpSessionUpdate as G,statusFromAcpSessionUpdate as J,subtaskContextFromAcpSessionUpdate as Q}from"../../client/acp-events.js";import{defaultAcpRuntimePool as D}from"../../client/acp-runtime-pool.js";import{resolveCodexAcpLaunch as U}from"./launch.js";import{cancelCodexRuntime as Z}from"./cancellation.js";import{CodexPromptPerformanceTracker as ee,CodexUsageLoopDetector as q,codexRawResponseUsageFromAcpSessionUpdate as te,codexTokenUsageFromAcpSessionUpdate as oe,readCodexContextMaintenancePolicy as $}from"./context-maintenance.js";import{ensureCodexProviderRuntimeConfig as O}from"../../../core/codex-provider-runtime-config.js";import{codexControlToolLoopFromAcpSessionUpdate as se,controlToolLoopDetectionThresholds as re,controlToolLoopReady as ne,readCodexControlToolLoopPolicy as ie}from"./control-tool-loop.js";import{attachCodexDiagnostics as ae,CodexTurnDiagnosticsTracker as ce}from"./turn-diagnostics.js";import{AcpSessionInvalidatedError as W}from"../../session-errors.js";import{codexAcpUpdateIsMeaningfulProgress as le}from"./progress-evidence.js";const de={sessionResume:!0,liveSupplement:!0,imageInput:!0,fileAttachment:!0,filesystem:!0,terminal:!0,permissionRequest:!0,configOptions:!0,usage:!0,contextUsage:!0,contextCompaction:!0,plan:!0,diff:!0};class Ue{id="codex-acp";name="Codex ACP";capabilities=de;async createSession(e){return new ue(e.cwd,e.providerSessionId??null,e.timeoutMs)}async forkSession(e){if(!y.existsSync(e.cwd)||!y.statSync(e.cwd).isDirectory())throw new Error(`Project root does not exist or is not a directory: ${e.cwd}`);const s=e.sourceProviderSessionId.trim(),r=await O(),i=await D.acquire({providerId:"codex-acp",cwd:e.cwd,providerSessionId:s,runtimeIdentity:r?.runtimeIdentity||"default",env:F(r),...U(),onSessionUpdate:()=>{}});try{await N(i,r),i.initialized||(await i.client.initialize(6e4),i.markInitialized()),e.onStatus?.({phase:"forking",label:"正在分叉 Codex 会话",detail:s,updateType:"session/fork",atMs:Date.now()});const a=await i.client.forkSession({sessionId:s,cwd:e.cwd,config:z(e)},e.timeoutMs??12e4),t=A(a);if(!t)throw new Error("Codex ACP session/fork did not return sessionId");return{providerSessionId:t}}finally{i.release()}}}class ue{cwd;providerSessionId;defaultTimeoutMs;lease=null;supplementCallback=null;supplementRequests=new Set;sessionResponse=null;statusCallback=null;updateCallback=null;permissionCallback=null;userInputCallback=null;abortHandler=null;statusTimeline=[];updates=new X(16);content="";performanceTracker=null;usageLoopDetector=null;usagePatternReport=null;controlToolPatternReport=null;turnDiagnosticsTracker=null;usageLoopWarningActive=!1;controlToolLoopWarningActive=!1;controlToolLoopInterrupting=!1;cancellation=null;constructor(e,s,r=we()){this.cwd=e,this.providerSessionId=s,this.defaultTimeoutMs=r}async sendPrompt(e){if(!y.existsSync(e.cwd)||!y.statSync(e.cwd).isDirectory())throw new Error(`Project root does not exist or is not a directory: ${e.cwd}`);const s=Date.now();this.supplementCallback=e.onSupplementReady??null,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 r=$(),i=ie();this.performanceTracker=new ee(s),this.usageLoopDetector=new q(r);const a=new ce;this.turnDiagnosticsTracker=a,this.usageLoopWarningActive=!1,this.controlToolLoopWarningActive=!1,this.controlToolLoopInterrupting=!1;let t={error:null,stopping:null,detection:null,controlDetection:null,interruption:null,kind:null},n=null,c=null,m=this.providerSessionId,v,C=null;try{this.emitStatus("initializing","Codex ACP 启动中","codex-acp");const h=await this.acquireRuntime(e.cwd);this.lease=h,n=h.client,c=h.started,await this.initialize(h),m=await this.establishSession(h,e),await this.applyConfig(n,m,e);const g=n,K=E(e.prompt,e.promptBlocks);let T=!1;for(this.emitStatus("thinking","思考中"),C=this.startSlowStatusTimer();;){const w=m;t={error:null,stopping:null,detection:null,controlDetection:null,interruption:null,kind:null},this.usageLoopDetector=new q(r),this.usageLoopWarningActive=!1,this.controlToolLoopWarningActive=!1,this.controlToolLoopInterrupting=!1,this.usagePatternReport=u=>{if(t.error)return;const _=Math.round(u.maxInputTokens/1e3),S=`同一 Turn 已连续请求至少 ${u.sampleCount} 次,单次输入约 ${_}k tokens`;if(a.recordUsageLoop(u.sampleCount,u.detectedAtMs,S),u.sampleCount<r.usageLoopInterruptSamples){this.usageLoopWarningActive=!0,this.emitStatus("thinking","Codex 出现低产出内部循环",S,"aca.performance.low_output_loop");return}const I=`Codex 低产出内部循环:同一 Turn 已连续请求至少 ${u.sampleCount} 次,单次输入约 ${_}k tokens,且未观察到有效进展`;t.error=new Error(I),t.detection=u,t.kind="low_output_loop",this.emitStatus("thinking","正在中断 Codex 低产出内部循环",S,"aca.performance.low_output_loop_interrupting"),t.stopping=this.stopRuntime(h,w).then(l=>{if(t.interruption=l,a.recordInterrupt({acknowledged:!l.forced,forcedRuntimeClose:l.forced,reason:l.reason}),l.forced){const d=l.reason||"取消请求失败";t.error=new Error(`${I};取消请求失败:${d};已关闭运行实例`)}}).catch(l=>{const d=l instanceof Error?l.message:String(l);a.recordInterrupt({acknowledged:!1,forcedRuntimeClose:!1,reason:d}),t.error=new Error(`${I};${d}`),this.emitStatus("failed","Codex 循环中断失败",d,"aca.performance.low_output_loop_interrupt_failed")})},this.controlToolPatternReport=u=>{if(t.error||!ne(u,i))return;const _=re(u,i);if(u.sampleCount<_.warningCalls)return;const S=u.kind==="list_agents_no_live"?"list_agents 查询不到运行中的子任务":u.kind==="wait_agent_no_live"?"无运行子任务时调用 wait_agent":u.kind==="send_message_no_progress"?"send_message 投递未产生进展":u.kind==="delegation_no_progress"?"重复创建或重启子任务且未产生进展":u.kind==="mixed_control_no_progress"?"混合控制工具且未产生进展":"短时 sleep",I=Math.max(1,Math.round(u.elapsedMs/6e4)),l=`同一 Turn 已连续 ${u.sampleCount} 次执行 ${S},持续约 ${I} 分钟`;if(a.recordControlLoop(u,l),u.sampleCount<_.interruptCalls){this.controlToolLoopWarningActive=!0,this.emitStatus("thinking","Codex 出现无进展控制工具循环",l,"aca.performance.control_tool_loop");return}const d=`Codex 无进展控制工具循环:${l}`;t.error=new Error(d),t.controlDetection=u,t.kind="control_tool_loop",this.controlToolLoopInterrupting=!0,this.emitStatus("thinking","正在中断 Codex 无进展控制工具循环",l,"aca.performance.control_tool_loop_interrupting"),t.stopping=this.stopRuntime(h,w).then(p=>{t.interruption=p,a.recordInterrupt({acknowledged:!p.forced,forcedRuntimeClose:p.forced,reason:p.reason});const x=p.forced?`${l};取消请求失败:${p.reason};已关闭运行实例`:l;p.forced&&(t.error=new Error(`${d};取消请求失败:${p.reason};已关闭运行实例`)),this.emitStatus("failed","Codex 已中断无进展控制工具循环",x,"aca.performance.control_tool_loop_interrupted")}).catch(p=>{const x=p instanceof Error?p.message:String(p);a.recordInterrupt({acknowledged:!1,forcedRuntimeClose:!1,reason:x}),t.error=new Error(`${d};${x}`),this.emitStatus("failed","Codex 控制工具循环中断失败",x,"aca.performance.control_tool_loop_interrupt_failed")})};let R;try{v=await this.promptWithAbort(g,{sessionId:w,prompt:K,timeoutMs:e.timeoutMs??this.defaultTimeoutMs,signal:e.signal})}catch(u){R=u}if(await t.stopping,t.error){const u=t.detection;if(t.kind==="low_output_loop"&&u?.source==="thread_usage"&&!T&&t.interruption?.forced===!1&&!e.signal?.aborted){T=!0,this.emitStatus("resuming","正在迁移异常 Codex 会话","保留本轮进度并创建干净线程","aca.performance.low_output_loop_recovering");try{const l=await g.recoverSession({sessionId:w,reason:"low_output_loop"}),d=A(l);if(!d||d===w)throw new Error("Codex recovery did not create a new session");m=d,this.providerSessionId=d,this.sessionResponse=f(l),h.setProviderSession({providerSessionId:d,sessionResponse:this.sessionResponse});const p={previousProviderSessionId:w,providerSessionId:d,reason:"low_output_loop",migratedAtMs:Date.now()};a.recordMigration(p),await e.onProviderSessionMigration?.(p),a.recordRecovery(p.migratedAtMs),this.content="",this.emitStatus("thinking","Codex 会话已迁移,继续执行",d,"aca.performance.low_output_loop_recovered_session");continue}catch(l){const d=l instanceof Error?l.message:String(l);t.error=new Error(`${t.error.message};自动迁移失败:${d}`)}}if(t.kind==="low_output_loop")throw this.emitStatus("failed","Codex 已中断低产出内部循环",t.error.message,"aca.performance.low_output_loop_interrupted"),new W(t.error.message,w,{source:u?.source??null,sampleCount:u?.sampleCount??null,recoveryAttempted:T});const S=t.controlDetection;if(t.interruption?.forced===!1&&!e.signal?.aborted){this.emitStatus("resuming","正在迁移异常 Codex 会话","保留本轮证据并为后续任务创建干净线程","aca.performance.control_tool_loop_migrating");try{const l=await g.recoverSession({sessionId:w,reason:"control_tool_loop"}),d=A(l);if(!d||d===w)throw new Error("Codex recovery did not create a new session");m=d,this.providerSessionId=d,this.sessionResponse=f(l),h.setProviderSession({providerSessionId:d,sessionResponse:this.sessionResponse});const p={previousProviderSessionId:w,providerSessionId:d,reason:"control_tool_loop",migratedAtMs:Date.now()};throw a.recordMigration(p),await e.onProviderSessionMigration?.(p),this.emitStatus("failed","Codex 循环已中断,会话已自动迁移",d,"aca.performance.control_tool_loop_migrated_session"),pe(t.error,d)}catch(l){if(me(l))throw l;const d=l instanceof Error?l.message:String(l);t.error=new Error(`${t.error.message};自动迁移失败:${d}`)}}throw new W(t.error.message,w,{source:"control_tool_loop",kind:S?.kind??null,sampleCount:S?.sampleCount??null,segmentId:S?.segmentId??null})}if(R)throw R;const b=H(v);b&&b!==m&&(m=b,this.providerSessionId=b,h.setProviderSession({providerSessionId:b,sessionResponse:this.sessionResponse}));break}}catch(h){if(h instanceof Error){const g=ae(h,a.snapshot());throw g.statusTimeline=[...this.statusTimeline],g.performance=this.performanceTracker?.snapshot(),g}throw h}finally{this.supplementCallback?.(null),this.supplementCallback=null,await Promise.allSettled([...this.supplementRequests]),await t.stopping,await this.cancellation?.catch(()=>{}),C&&clearInterval(C),this.usagePatternReport=null,this.controlToolPatternReport=null,this.turnDiagnosticsTracker=null,this.usageLoopDetector=null,this.usageLoopWarningActive=!1,this.controlToolLoopWarningActive=!1,this.controlToolLoopInterrupting=!1,this.abortHandler&&e.signal&&(e.signal.removeEventListener("abort",this.abortHandler),this.abortHandler=null),this.releaseLease()}return this.emitStatus("completed","已完成"),{providerSessionId:m,cliType:"builtin",agentType:"codex",effectiveConfig:{model:e.model??null,mode:e.mode??null,configOptionValues:e.configOptionValues??null,command:c.command,processPid:c.child.pid??null,provider:"codex-acp",protocol:"acp"},command:c.command,args:c.args,cwd:e.cwd,exitCode:c.child.exitCode,signal:c.child.signalCode,durationMs:Date.now()-s,stdout:n.rpc.stdout,stderr:n.rpc.stderr,content:this.content.trim(),updateCount:this.updates.count(),updates:this.updates.snapshot(),statusTimeline:[...this.statusTimeline],performance:this.performanceTracker?.snapshot(),contextUsage:ve(v),providerDiagnostics:a.snapshot(),promptResponse:v}}async compactContext(e){if(!y.existsSync(e.cwd)||!y.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 r=this.providerSessionId,i;try{this.emitStatus("initializing","Codex ACP 启动中","codex-acp");const t=await this.acquireRuntime(e.cwd);this.lease=t,await this.initialize(t),r=await this.establishSession(t,e),await this.applyConfig(t.client,r,e);const n=Se(e.timeoutMs);i=await this.compactWithAbort(t.client,{sessionId:r,allowRollover:e.allowRollover===!0,timeoutMs:n,signal:e.signal});const c=H(i);c&&c!==r&&(r=c,this.providerSessionId=c,t.setProviderSession({providerSessionId:c,sessionResponse:this.sessionResponse}))}catch(t){if(this.lease&&he(t,e.signal)){const n=this.lease;this.lease=null,await n.dispose().catch(()=>{})}throw this.emitStatus("failed","上下文压缩失败",t instanceof Error?t.message:String(t)),t}finally{this.abortHandler&&e.signal&&(e.signal.removeEventListener("abort",this.abortHandler),this.abortHandler=null),this.releaseLease()}const a=f(i)??{};return{providerSessionId:r||this.providerSessionId||"",contextUsage:B(a.contextUsage),maintenance:f(a.maintenance)??{},durationMs:Date.now()-s}}async cancel(){this.providerSessionId&&this.lease&&await this.stopRuntime(this.lease,this.providerSessionId)}stopRuntime(e,s){if(this.cancellation)return this.cancellation;const r=Z(e,s);return this.cancellation=r,r.finally(()=>{this.cancellation===r&&(this.cancellation=null)}).catch(()=>{}),r}async close(){await this.cancellation?.catch(()=>{}),this.statusCallback=null,this.updateCallback=null,this.permissionCallback=null,this.userInputCallback=null,this.abortHandler&&(this.abortHandler=null),this.releaseLease()}async acquireRuntime(e){const s=U(),r=await O(),i=await D.acquire({providerId:"codex-acp",cwd:e,providerSessionId:this.providerSessionId,runtimeIdentity:r?.runtimeIdentity||"default",env:F(r),command:s.command,args:s.args,onSessionUpdate:a=>this.handleSessionUpdate(a),onClientRequest:async(a,t,n)=>{if(a==="session/request_permission")return this.requestPermission(t,n.id);if(a==="elicitation/create"||a==="session/request_user_input")return this.requestUserInput(a,t,n.id);throw new Error(`Unsupported Codex ACP client request: ${a}`)}});try{return await N(i,r),i}catch(a){throw await i.dispose().catch(()=>{}),a}}async initialize(e){e.initialized||(await e.client.initialize(6e4),e.markInitialized())}async establishSession(e,s){const r=e.client,i=z(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 n=await r.loadSession({sessionId:this.providerSessionId,cwd:s.cwd,config:i},6e4);this.sessionResponse=f(n);const c=A(n)||this.providerSessionId;return this.providerSessionId=c,e.setProviderSession({providerSessionId:c,sessionResponse:this.sessionResponse}),c}catch(n){if(s.requireSessionResume){const c=n instanceof Error?n.message:String(n);throw new Error(`无法恢复导入的 Codex ACP Session ${this.providerSessionId}:${c}`)}this.providerSessionId=null}this.emitStatus("acp","Codex ACP 会话创建中","session/new");const a=await r.newSession({cwd:s.cwd,config:i},12e4);this.sessionResponse=f(a);const t=A(a);if(!t)throw new Error("Codex ACP session/new did not return sessionId");return this.providerSessionId=t,e.setProviderSession({providerSessionId:t,sessionResponse:this.sessionResponse}),t}async applyConfig(e,s,r){r.model&&await e.setSessionConfigOption({sessionId:s,configId:"model",value:r.model},6e4).catch(()=>{});const i=M(r.mode);i&&await e.setSessionConfigOption({sessionId:s,configId:"approvalPolicy",value:i},6e4).catch(()=>{});const a=P(r.mode);a&&await e.setSessionConfigOption({sessionId:s,configId:"sandbox",value:a},6e4).catch(()=>{});for(const[t,n]of Object.entries(r.configOptionValues??{}))["model","mode","sandbox","approvalPolicy"].includes(t)||await e.setSessionConfigOption({sessionId:s,configId:t,value:n},6e4).catch(()=>{})}handleSessionUpdate(e){const s=Y(e),r=String(s?.update.sessionUpdate||"");if(r==="aca_supplement_target"){const n=s.update,c=this.lease?.client,m=String(n.threadId||""),v=String(n.turnId||"");!c||n.ready!==!0||!m||!v?this.supplementCallback?.(null):this.supplementCallback?.({threadId:m,turnId:v,send:(C,h)=>{const g=c.rpc.request("aca/supplement",{sessionId:m,expectedTurnId:v,deliveryId:C,prompt:E("",h)},65e3);return this.supplementRequests.add(g),g.finally(()=>this.supplementRequests.delete(g)).catch(()=>{}),g},receipt:C=>c.rpc.request("aca/supplement_status",{deliveryId:C},5e3)});return}const i=Q(e);if(!i&&r==="aca_codex_subtask_drain"){const n=String(s?.update.label||"子任务收尾等待超时"),c=String(s?.update.detail||"仍有子任务未结束");this.emitStatus("exploring",n,c,r)}else if(!i&&r==="aca_codex_control_tool_loop"){const n=se(e);n?.state==="observed"&&this.controlToolPatternReport?.(n.detection),n?.state==="recovered"&&this.controlToolLoopWarningActive&&!this.controlToolLoopInterrupting&&(this.controlToolLoopWarningActive=!1,this.turnDiagnosticsTracker?.recordRecovery(),this.emitStatus("thinking","Codex 已恢复执行","无进展控制工具循环已结束","aca.performance.control_tool_loop_recovered"))}else if(!i&&r==="aca_codex_control_tool_steer"){const n=fe(s?.update);n&&this.turnDiagnosticsTracker?.recordControlToolSteering(n)}else if(le(s?.update))this.recordMeaningfulProgress();else if(!i&&r==="aca_codex_raw_response_usage"){const n=te(e);n&&this.turnDiagnosticsTracker?.recordUsage(n);const c=n?this.usageLoopDetector?.recordUsage(n):null;c&&this.usagePatternReport?.(c)}else if(!i&&r==="usage_update"){const n=oe(e);n&&this.turnDiagnosticsTracker?.recordUsage(n);const c=n?this.usageLoopDetector?.recordUsage(n):null;c&&this.usagePatternReport?.(c)}this.updates.push({method:"session/update",params:e});const a=G(e);a&&(a.type==="agent_message_chunk"&&!i&&(this.content+=a.text??""),this.updateCallback?.(a));const t=J(e);t&&this.emitStatus(t.phase,t.label,t.detail,t.updateType)}recordMeaningfulProgress(){this.performanceTracker?.recordEvent();const e=this.usageLoopDetector?.recordMeaningfulEvent();this.usageLoopWarningActive&&(this.usageLoopWarningActive=!1,e&&this.turnDiagnosticsTracker?.recordRecovery(),this.emitStatus("thinking","Codex 已恢复执行","已重新执行工具或产生有效输出","aca.performance.loop_recovered"))}startSlowStatusTimer(){const e=$().slowEventThresholdMs,s=setInterval(()=>{const r=this.performanceTracker,i=Date.now();if(!r?.shouldReportSlow(i,e))return;const a=r.snapshot(i),t=Math.max(1,Math.round(a.silentForMs/6e4)),n=this.usageLoopWarningActive||this.controlToolLoopWarningActive?"Codex 可能卡在内部循环":"Codex 长时间没有有效进展";this.emitStatus("thinking",n,`已 ${t} 分钟没有执行工具或产生有效输出`,"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().catch(()=>{})},e.aborted)throw new Error("Codex ACP prompt cancelled");e.addEventListener("abort",this.abortHandler,{once:!0})}}async promptWithAbort(e,s){return L({signal:s.signal,cancelledMessage:"Codex ACP prompt cancelled",startRequest:()=>e.prompt({sessionId:s.sessionId,prompt:s.prompt},s.timeoutMs),onAbort:async()=>{await this.lease?.dispose()}})}async compactWithAbort(e,s){return L({signal:s.signal,cancelledMessage:"Codex ACP 上下文压缩已取消",startRequest:()=>e.compactSession({sessionId:s.sessionId,allowRollover:s.allowRollover,timeoutMs:s.timeoutMs},s.timeoutMs),onAbort:async()=>{await this.lease?.dispose()}})}async requestPermission(e,s){if(!this.permissionCallback)return{outcome:{outcome:"cancelled"}};const r=e&&typeof e=="object"&&!Array.isArray(e)?e:{},i=r.request&&typeof r.request=="object"&&!Array.isArray(r.request)?r.request:r,t=(Array.isArray(i.options)?i.options:[]).map((n,c)=>{const m=n&&typeof n=="object"&&!Array.isArray(n)?n:{};return{optionId:String(m.optionId??m.id??c),kind:String(m.kind??"choice"),...typeof m.label=="string"?{label:m.label}:{}}});return this.emitStatus("requestPermission","等待授权",ge(i),"session/request_permission"),this.permissionCallback({requestId:String(i.requestId??s??i.id??`codex-acp-permission-${Date.now()}`),params:e,method:typeof i.method=="string"?i.method:"session/request_permission",expiresAtMs:typeof i.expiresAtMs=="number"?i.expiresAtMs:void 0,options:t})}async requestUserInput(e,s,r){return this.emitStatus("requestPermission","等待回答",void 0,e),V("codex-acp",e,s,this.userInputCallback,r)}emitStatus(e,s,r,i){const a={phase:e,label:s,...r?{detail:r}:{},...i?{updateType:i}:{},atMs:Date.now()},t=this.statusTimeline[this.statusTimeline.length-1];t&&t.phase===a.phase&&t.label===a.label&&t.detail===a.detail&&t.updateType===a.updateType||(this.statusTimeline.push(a),this.statusCallback?.(a))}releaseLease(){this.lease?.release(),this.lease=null}}function pe(o,e){const s=o;return s.controlLoopMigratedProviderSessionId=e,s}function me(o){if(!o||typeof o!="object"||Array.isArray(o))return null;const e=o.controlLoopMigratedProviderSessionId;return typeof e=="string"&&e.trim()?e.trim():null}async function N(o,e){o.runtimeConfigured||(await o.client.configureRuntime({codexProvider:e?{baseUrl:e.baseUrl,apiKey:e.apiKey,revision:e.revision}:null}),o.markRuntimeConfigured())}function F(o){return o?{OPENAI_API_KEY:o.apiKey,OPENAI_BASE_URL:o.baseUrl}:{}}function he(o,e){if(e?.aborted)return!0;const s=o instanceof Error?o.message:String(o||"");return/compact.*timed out|request timed out.*compact|adapter closed|aborted|cancelled/i.test(s)}function A(o){if(!o||typeof o!="object"||Array.isArray(o))return null;const e=o.sessionId;return typeof e=="string"&&e.trim()?e.trim():null}function f(o){return o&&typeof o=="object"&&!Array.isArray(o)?o:null}function fe(o){const e=f(o),s=f(e?._meta),r=f(s?.aca),i=f(r?.controlToolSteering);if(!i)return null;const a=Number(i.attempt),t=Number(i.maxAttempts);if(!Number.isInteger(a)||a<=0||!Number.isInteger(t)||t<=0)return null;const n=Number(i.steeredAtMs),c=Number(i.failedAtMs);return{segmentId:Math.max(0,Math.floor(Number(i.segmentId)||0)),attempt:a,maxAttempts:t,succeeded:Number.isFinite(n)&&n>0,observedAtMs:Number.isFinite(n)&&n>0?n:Number.isFinite(c)&&c>0?c:Date.now()}}function P(o){const e=String(o||"").trim();return e?e==="read-only"?"read-only":["agent-full-access","full-access","bypassPermissions","danger-full-access"].includes(e)?"danger-full-access":"workspace-write":null}function M(o){const e=String(o||"").trim();return e?["agent-full-access","full-access","bypassPermissions","danger-full-access"].includes(e)?"never":e==="acceptEdits"||e==="accept-edits"?"on-failure":"on-request":null}function z(o){return{...o.model?{model:o.model}:{},...M(o.mode)?{approvalPolicy:M(o.mode)}:{},...P(o.mode)?{sandbox:P(o.mode)}:{},...o.configOptionValues??{}}}function ge(o){const e=o.params&&typeof o.params=="object"&&!Array.isArray(o.params)?o.params:o;for(const s of["command","cwd","reason","grantRoot","method"]){const r=e[s];if(typeof r=="string"&&r.trim())return j(r)}}function qe(o){for(const e of["prompt","message","question","title"]){const s=o[e];if(typeof s=="string"&&s.trim())return j(s)}}function j(o){const e=o.replace(/\s+/g," ").trim();return e.length>120?`${e.slice(0,117)}...`:e}function we(){const o=Number.parseInt(process.env.ACA_CODEX_ACP_PROMPT_TIMEOUT_MS??process.env.ACA_ACP_PROMPT_TIMEOUT_MS??"",10);return Number.isInteger(o)&&o>=3e4?o:720*60*1e3}function Se(o){const e=Number.isInteger(o)?Number(o):Number.parseInt(process.env.ACA_CODEX_ACP_COMPACT_TIMEOUT_MS??"",10);return Number.isFinite(e)&&e>=3e4?Math.min(e,6e5):12e4}function H(o){if(!o||typeof o!="object"||Array.isArray(o))return null;const e=o;return typeof e.sessionId=="string"&&e.sessionId.trim()?e.sessionId:null}function ve(o){const e=f(o),s=f(e?._meta);return B(s?.acaContextUsage)}function B(o){const e=f(o);if(!e)return null;const s=k(e.contextWindow),r=k(e.usedTokens),i=k(e.inputTokens),a=String(e.state||"unknown"),t=["active","compacted","new_thread"].includes(a)?a:"unknown";return{usedTokens:r,inputTokens:i,contextWindow:s,ratio:s>0?Math.max(0,k(e.ratio)||r/s):0,observedAtMs:k(e.observedAtMs)||Date.now(),state:t,maintenanceAction:typeof e.maintenanceAction=="string"?e.maintenanceAction:null}}function k(o){return typeof o=="number"&&Number.isFinite(o)&&o>=0?o:0}export{Ue as CodexAcpProvider,de as codexAcpCapabilities};
|
|
1
|
+
import{handleProviderInput as V}from"../../../interactions/provider-input.js";import y from"node:fs";import{runAbortableRequest as L}from"../../client/abortable-request.js";import{BoundedItemBuffer as X}from"../../client/bounded-buffer.js";import{promptBlocksToAcpContent as E}from"../../client/acp-content.js";import{acpSessionUpdateFromParams as Y,eventFromAcpSessionUpdate as G,statusFromAcpSessionUpdate as J,subtaskContextFromAcpSessionUpdate as Q}from"../../client/acp-events.js";import{defaultAcpRuntimePool as D}from"../../client/acp-runtime-pool.js";import{resolveCodexAcpLaunch as U}from"./launch.js";import{cancelCodexRuntime as Z}from"./cancellation.js";import{CodexPromptPerformanceTracker as ee,CodexUsageLoopDetector as q,codexRawResponseUsageFromAcpSessionUpdate as te,codexTokenUsageFromAcpSessionUpdate as oe,readCodexContextMaintenancePolicy as $}from"./context-maintenance.js";import{ensureCodexProviderRuntimeConfig as O}from"../../../core/codex-provider-runtime-config.js";import{codexControlToolLoopFromAcpSessionUpdate as se,controlToolLoopDetectionThresholds as ne,controlToolLoopReady as re,readCodexControlToolLoopPolicy as ie}from"./control-tool-loop.js";import{attachCodexDiagnostics as ae,CodexTurnDiagnosticsTracker as ce}from"./turn-diagnostics.js";import{AcpSessionInvalidatedError as W}from"../../session-errors.js";import{codexAcpUpdateIsMeaningfulProgress as le}from"./progress-evidence.js";const de={sessionResume:!0,liveSupplement:!0,imageInput:!0,fileAttachment:!0,filesystem:!0,terminal:!0,permissionRequest:!0,configOptions:!0,usage:!0,contextUsage:!0,contextCompaction:!0,plan:!0,diff:!0};class Ue{id="codex-acp";name="Codex ACP";capabilities=de;async createSession(e){return new ue(e.cwd,e.providerSessionId??null,e.timeoutMs)}async forkSession(e){if(!y.existsSync(e.cwd)||!y.statSync(e.cwd).isDirectory())throw new Error(`Project root does not exist or is not a directory: ${e.cwd}`);const s=e.sourceProviderSessionId.trim(),n=await O(),i=await D.acquire({providerId:"codex-acp",cwd:e.cwd,providerSessionId:s,runtimeIdentity:n?.runtimeIdentity||"default",env:F(n),...U(),onSessionUpdate:()=>{}});try{await N(i,n),i.initialized||(await i.client.initialize(6e4),i.markInitialized()),e.onStatus?.({phase:"forking",label:"正在分叉 Codex 会话",detail:s,updateType:"session/fork",atMs:Date.now()});const a=await i.client.forkSession({sessionId:s,cwd:e.cwd,config:z(e)},e.timeoutMs??12e4),t=A(a);if(!t)throw new Error("Codex ACP session/fork did not return sessionId");return{providerSessionId:t}}finally{i.release()}}}class ue{cwd;providerSessionId;defaultTimeoutMs;lease=null;supplementCallback=null;supplementRequests=new Set;sessionResponse=null;statusCallback=null;updateCallback=null;permissionCallback=null;userInputCallback=null;abortHandler=null;statusTimeline=[];updates=new X(16);content="";performanceTracker=null;usageLoopDetector=null;usagePatternReport=null;controlToolPatternReport=null;turnDiagnosticsTracker=null;usageLoopWarningActive=!1;controlToolLoopWarningActive=!1;controlToolLoopInterrupting=!1;cancellation=null;constructor(e,s,n=we()){this.cwd=e,this.providerSessionId=s,this.defaultTimeoutMs=n}async sendPrompt(e){if(!y.existsSync(e.cwd)||!y.statSync(e.cwd).isDirectory())throw new Error(`Project root does not exist or is not a directory: ${e.cwd}`);const s=Date.now();this.supplementCallback=e.onSupplementReady??null,this.statusCallback=e.onStatus??null,this.updateCallback=e.onUpdate??null,this.permissionCallback=e.onPermissionRequest??null,this.userInputCallback=e.onUserInputRequest??null,this.elicitationEnabled=e.elicitation??!!e.onUserInputRequest,this.elicitationComplete=e.onElicitationComplete,this.attachAbortSignal(e.signal),this.content="",this.updates.clear(),this.statusTimeline.length=0;const n=$(),i=ie();this.performanceTracker=new ee(s),this.usageLoopDetector=new q(n);const a=new ce;this.turnDiagnosticsTracker=a,this.usageLoopWarningActive=!1,this.controlToolLoopWarningActive=!1,this.controlToolLoopInterrupting=!1;let t={error:null,stopping:null,detection:null,controlDetection:null,interruption:null,kind:null},r=null,c=null,p=this.providerSessionId,C,v=null;try{this.emitStatus("initializing","Codex ACP 启动中","codex-acp");const h=await this.acquireRuntime(e.cwd);this.lease=h,r=h.client,c=h.started,await this.initialize(h),p=await this.establishSession(h,e),await this.applyConfig(r,p,e);const g=r,K=E(e.prompt,e.promptBlocks);let T=!1;for(this.emitStatus("thinking","思考中"),v=this.startSlowStatusTimer();;){const w=p;t={error:null,stopping:null,detection:null,controlDetection:null,interruption:null,kind:null},this.usageLoopDetector=new q(n),this.usageLoopWarningActive=!1,this.controlToolLoopWarningActive=!1,this.controlToolLoopInterrupting=!1,this.usagePatternReport=u=>{if(t.error)return;const _=Math.round(u.maxInputTokens/1e3),S=`同一 Turn 已连续请求至少 ${u.sampleCount} 次,单次输入约 ${_}k tokens`;if(a.recordUsageLoop(u.sampleCount,u.detectedAtMs,S),u.sampleCount<n.usageLoopInterruptSamples){this.usageLoopWarningActive=!0,this.emitStatus("thinking","Codex 出现低产出内部循环",S,"aca.performance.low_output_loop");return}const I=`Codex 低产出内部循环:同一 Turn 已连续请求至少 ${u.sampleCount} 次,单次输入约 ${_}k tokens,且未观察到有效进展`;t.error=new Error(I),t.detection=u,t.kind="low_output_loop",this.emitStatus("thinking","正在中断 Codex 低产出内部循环",S,"aca.performance.low_output_loop_interrupting"),t.stopping=this.stopRuntime(h,w).then(l=>{if(t.interruption=l,a.recordInterrupt({acknowledged:!l.forced,forcedRuntimeClose:l.forced,reason:l.reason}),l.forced){const d=l.reason||"取消请求失败";t.error=new Error(`${I};取消请求失败:${d};已关闭运行实例`)}}).catch(l=>{const d=l instanceof Error?l.message:String(l);a.recordInterrupt({acknowledged:!1,forcedRuntimeClose:!1,reason:d}),t.error=new Error(`${I};${d}`),this.emitStatus("failed","Codex 循环中断失败",d,"aca.performance.low_output_loop_interrupt_failed")})},this.controlToolPatternReport=u=>{if(t.error||!re(u,i))return;const _=ne(u,i);if(u.sampleCount<_.warningCalls)return;const S=u.kind==="list_agents_no_live"?"list_agents 查询不到运行中的子任务":u.kind==="wait_agent_no_live"?"无运行子任务时调用 wait_agent":u.kind==="send_message_no_progress"?"send_message 投递未产生进展":u.kind==="delegation_no_progress"?"重复创建或重启子任务且未产生进展":u.kind==="mixed_control_no_progress"?"混合控制工具且未产生进展":"短时 sleep",I=Math.max(1,Math.round(u.elapsedMs/6e4)),l=`同一 Turn 已连续 ${u.sampleCount} 次执行 ${S},持续约 ${I} 分钟`;if(a.recordControlLoop(u,l),u.sampleCount<_.interruptCalls){this.controlToolLoopWarningActive=!0,this.emitStatus("thinking","Codex 出现无进展控制工具循环",l,"aca.performance.control_tool_loop");return}const d=`Codex 无进展控制工具循环:${l}`;t.error=new Error(d),t.controlDetection=u,t.kind="control_tool_loop",this.controlToolLoopInterrupting=!0,this.emitStatus("thinking","正在中断 Codex 无进展控制工具循环",l,"aca.performance.control_tool_loop_interrupting"),t.stopping=this.stopRuntime(h,w).then(m=>{t.interruption=m,a.recordInterrupt({acknowledged:!m.forced,forcedRuntimeClose:m.forced,reason:m.reason});const x=m.forced?`${l};取消请求失败:${m.reason};已关闭运行实例`:l;m.forced&&(t.error=new Error(`${d};取消请求失败:${m.reason};已关闭运行实例`)),this.emitStatus("failed","Codex 已中断无进展控制工具循环",x,"aca.performance.control_tool_loop_interrupted")}).catch(m=>{const x=m instanceof Error?m.message:String(m);a.recordInterrupt({acknowledged:!1,forcedRuntimeClose:!1,reason:x}),t.error=new Error(`${d};${x}`),this.emitStatus("failed","Codex 控制工具循环中断失败",x,"aca.performance.control_tool_loop_interrupt_failed")})};let R;try{C=await this.promptWithAbort(g,{sessionId:w,prompt:K,timeoutMs:e.timeoutMs??this.defaultTimeoutMs,signal:e.signal})}catch(u){R=u}if(await t.stopping,t.error){const u=t.detection;if(t.kind==="low_output_loop"&&u?.source==="thread_usage"&&!T&&t.interruption?.forced===!1&&!e.signal?.aborted){T=!0,this.emitStatus("resuming","正在迁移异常 Codex 会话","保留本轮进度并创建干净线程","aca.performance.low_output_loop_recovering");try{const l=await g.recoverSession({sessionId:w,reason:"low_output_loop"}),d=A(l);if(!d||d===w)throw new Error("Codex recovery did not create a new session");p=d,this.providerSessionId=d,this.sessionResponse=f(l),h.setProviderSession({providerSessionId:d,sessionResponse:this.sessionResponse});const m={previousProviderSessionId:w,providerSessionId:d,reason:"low_output_loop",migratedAtMs:Date.now()};a.recordMigration(m),await e.onProviderSessionMigration?.(m),a.recordRecovery(m.migratedAtMs),this.content="",this.emitStatus("thinking","Codex 会话已迁移,继续执行",d,"aca.performance.low_output_loop_recovered_session");continue}catch(l){const d=l instanceof Error?l.message:String(l);t.error=new Error(`${t.error.message};自动迁移失败:${d}`)}}if(t.kind==="low_output_loop")throw this.emitStatus("failed","Codex 已中断低产出内部循环",t.error.message,"aca.performance.low_output_loop_interrupted"),new W(t.error.message,w,{source:u?.source??null,sampleCount:u?.sampleCount??null,recoveryAttempted:T});const S=t.controlDetection;if(t.interruption?.forced===!1&&!e.signal?.aborted){this.emitStatus("resuming","正在迁移异常 Codex 会话","保留本轮证据并为后续任务创建干净线程","aca.performance.control_tool_loop_migrating");try{const l=await g.recoverSession({sessionId:w,reason:"control_tool_loop"}),d=A(l);if(!d||d===w)throw new Error("Codex recovery did not create a new session");p=d,this.providerSessionId=d,this.sessionResponse=f(l),h.setProviderSession({providerSessionId:d,sessionResponse:this.sessionResponse});const m={previousProviderSessionId:w,providerSessionId:d,reason:"control_tool_loop",migratedAtMs:Date.now()};throw a.recordMigration(m),await e.onProviderSessionMigration?.(m),this.emitStatus("failed","Codex 循环已中断,会话已自动迁移",d,"aca.performance.control_tool_loop_migrated_session"),pe(t.error,d)}catch(l){if(me(l))throw l;const d=l instanceof Error?l.message:String(l);t.error=new Error(`${t.error.message};自动迁移失败:${d}`)}}throw new W(t.error.message,w,{source:"control_tool_loop",kind:S?.kind??null,sampleCount:S?.sampleCount??null,segmentId:S?.segmentId??null})}if(R)throw R;const b=H(C);b&&b!==p&&(p=b,this.providerSessionId=b,h.setProviderSession({providerSessionId:b,sessionResponse:this.sessionResponse}));break}}catch(h){if(h instanceof Error){const g=ae(h,a.snapshot());throw g.statusTimeline=[...this.statusTimeline],g.performance=this.performanceTracker?.snapshot(),g}throw h}finally{this.supplementCallback?.(null),this.supplementCallback=null,await Promise.allSettled([...this.supplementRequests]),await t.stopping,await this.cancellation?.catch(()=>{}),v&&clearInterval(v),this.usagePatternReport=null,this.controlToolPatternReport=null,this.turnDiagnosticsTracker=null,this.usageLoopDetector=null,this.usageLoopWarningActive=!1,this.controlToolLoopWarningActive=!1,this.controlToolLoopInterrupting=!1,this.abortHandler&&e.signal&&(e.signal.removeEventListener("abort",this.abortHandler),this.abortHandler=null),this.releaseLease()}return this.emitStatus("completed","已完成"),{providerSessionId:p,cliType:"builtin",agentType:"codex",effectiveConfig:{model:e.model??null,mode:e.mode??null,configOptionValues:e.configOptionValues??null,command:c.command,processPid:c.child.pid??null,provider:"codex-acp",protocol:"acp"},command:c.command,args:c.args,cwd:e.cwd,exitCode:c.child.exitCode,signal:c.child.signalCode,durationMs:Date.now()-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:Ce(C),providerDiagnostics:a.snapshot(),promptResponse:C}}async compactContext(e){if(!y.existsSync(e.cwd)||!y.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 n=this.providerSessionId,i;try{this.emitStatus("initializing","Codex ACP 启动中","codex-acp");const t=await this.acquireRuntime(e.cwd);this.lease=t,await this.initialize(t),n=await this.establishSession(t,e),await this.applyConfig(t.client,n,e);const r=Se(e.timeoutMs);i=await this.compactWithAbort(t.client,{sessionId:n,allowRollover:e.allowRollover===!0,timeoutMs:r,signal:e.signal});const c=H(i);c&&c!==n&&(n=c,this.providerSessionId=c,t.setProviderSession({providerSessionId:c,sessionResponse:this.sessionResponse}))}catch(t){if(this.lease&&he(t,e.signal)){const r=this.lease;this.lease=null,await r.dispose().catch(()=>{})}throw this.emitStatus("failed","上下文压缩失败",t instanceof Error?t.message:String(t)),t}finally{this.abortHandler&&e.signal&&(e.signal.removeEventListener("abort",this.abortHandler),this.abortHandler=null),this.releaseLease()}const a=f(i)??{};return{providerSessionId:n||this.providerSessionId||"",contextUsage:B(a.contextUsage),maintenance:f(a.maintenance)??{},durationMs:Date.now()-s}}async cancel(){this.providerSessionId&&this.lease&&await this.stopRuntime(this.lease,this.providerSessionId)}stopRuntime(e,s){if(this.cancellation)return this.cancellation;const n=Z(e,s);return this.cancellation=n,n.finally(()=>{this.cancellation===n&&(this.cancellation=null)}).catch(()=>{}),n}async close(){await this.cancellation?.catch(()=>{}),this.statusCallback=null,this.updateCallback=null,this.permissionCallback=null,this.userInputCallback=null,this.abortHandler&&(this.abortHandler=null),this.releaseLease()}elicitationEnabled=!1;elicitationComplete;async acquireRuntime(e){const s=U(),n=await O(),i=await D.acquire({providerId:"codex-acp",elicitation:this.elicitationEnabled,onElicitationComplete:this.elicitationComplete,cwd:e,providerSessionId:this.providerSessionId,runtimeIdentity:n?.runtimeIdentity||"default",env:F(n),command:s.command,args:s.args,onSessionUpdate:a=>this.handleSessionUpdate(a),onClientRequest:async(a,t,r)=>{if(a==="session/request_permission")return this.requestPermission(t,r.id);if(a==="elicitation/create"||a==="session/request_user_input")return this.requestUserInput(a,t,r.id);throw new Error(`Unsupported Codex ACP client request: ${a}`)}});try{return await N(i,n),i}catch(a){throw await i.dispose().catch(()=>{}),a}}async initialize(e){e.initialized||(await e.client.initialize(6e4),e.markInitialized())}async establishSession(e,s){const n=e.client,i=z(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 r=await n.loadSession({sessionId:this.providerSessionId,cwd:s.cwd,config:i},6e4);this.sessionResponse=f(r);const c=A(r)||this.providerSessionId;return this.providerSessionId=c,e.setProviderSession({providerSessionId:c,sessionResponse:this.sessionResponse}),c}catch(r){if(s.requireSessionResume){const c=r instanceof Error?r.message:String(r);throw new Error(`无法恢复导入的 Codex ACP Session ${this.providerSessionId}:${c}`)}this.providerSessionId=null}this.emitStatus("acp","Codex ACP 会话创建中","session/new");const a=await n.newSession({cwd:s.cwd,config:i},12e4);this.sessionResponse=f(a);const t=A(a);if(!t)throw new Error("Codex ACP session/new did not return sessionId");return this.providerSessionId=t,e.setProviderSession({providerSessionId:t,sessionResponse:this.sessionResponse}),t}async applyConfig(e,s,n){n.model&&await e.setSessionConfigOption({sessionId:s,configId:"model",value:n.model},6e4).catch(()=>{});const i=M(n.mode);i&&await e.setSessionConfigOption({sessionId:s,configId:"approvalPolicy",value:i},6e4).catch(()=>{});const a=P(n.mode);a&&await e.setSessionConfigOption({sessionId:s,configId:"sandbox",value:a},6e4).catch(()=>{});for(const[t,r]of Object.entries(n.configOptionValues??{}))["model","mode","sandbox","approvalPolicy"].includes(t)||await e.setSessionConfigOption({sessionId:s,configId:t,value:r},6e4).catch(()=>{})}handleSessionUpdate(e){const s=Y(e),n=String(s?.update.sessionUpdate||"");if(n==="aca_supplement_target"){const r=s.update,c=this.lease?.client,p=String(r.threadId||""),C=String(r.turnId||"");!c||r.ready!==!0||!p||!C?this.supplementCallback?.(null):this.supplementCallback?.({threadId:p,turnId:C,send:(v,h)=>{const g=c.rpc.request("aca/supplement",{sessionId:p,expectedTurnId:C,deliveryId:v,prompt:E("",h)},65e3);return this.supplementRequests.add(g),g.finally(()=>this.supplementRequests.delete(g)).catch(()=>{}),g},receipt:v=>c.rpc.request("aca/supplement_status",{deliveryId:v},5e3)});return}const i=Q(e);if(!i&&n==="aca_codex_subtask_drain"){const r=String(s?.update.label||"子任务收尾等待超时"),c=String(s?.update.detail||"仍有子任务未结束");this.emitStatus("exploring",r,c,n)}else if(!i&&n==="aca_codex_control_tool_loop"){const r=se(e);r?.state==="observed"&&this.controlToolPatternReport?.(r.detection),r?.state==="recovered"&&this.controlToolLoopWarningActive&&!this.controlToolLoopInterrupting&&(this.controlToolLoopWarningActive=!1,this.turnDiagnosticsTracker?.recordRecovery(),this.emitStatus("thinking","Codex 已恢复执行","无进展控制工具循环已结束","aca.performance.control_tool_loop_recovered"))}else if(!i&&n==="aca_codex_control_tool_steer"){const r=fe(s?.update);r&&this.turnDiagnosticsTracker?.recordControlToolSteering(r)}else if(le(s?.update))this.recordMeaningfulProgress();else if(!i&&n==="aca_codex_raw_response_usage"){const r=te(e);r&&this.turnDiagnosticsTracker?.recordUsage(r);const c=r?this.usageLoopDetector?.recordUsage(r):null;c&&this.usagePatternReport?.(c)}else if(!i&&n==="usage_update"){const r=oe(e);r&&this.turnDiagnosticsTracker?.recordUsage(r);const c=r?this.usageLoopDetector?.recordUsage(r):null;c&&this.usagePatternReport?.(c)}this.updates.push({method:"session/update",params:e});const a=G(e);a&&(a.type==="agent_message_chunk"&&!i&&(this.content+=a.text??""),this.updateCallback?.(a));const t=J(e);t&&this.emitStatus(t.phase,t.label,t.detail,t.updateType)}recordMeaningfulProgress(){this.performanceTracker?.recordEvent();const e=this.usageLoopDetector?.recordMeaningfulEvent();this.usageLoopWarningActive&&(this.usageLoopWarningActive=!1,e&&this.turnDiagnosticsTracker?.recordRecovery(),this.emitStatus("thinking","Codex 已恢复执行","已重新执行工具或产生有效输出","aca.performance.loop_recovered"))}startSlowStatusTimer(){const e=$().slowEventThresholdMs,s=setInterval(()=>{const n=this.performanceTracker,i=Date.now();if(!n?.shouldReportSlow(i,e))return;const a=n.snapshot(i),t=Math.max(1,Math.round(a.silentForMs/6e4)),r=this.usageLoopWarningActive||this.controlToolLoopWarningActive?"Codex 可能卡在内部循环":"Codex 长时间没有有效进展";this.emitStatus("thinking",r,`已 ${t} 分钟没有执行工具或产生有效输出`,"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().catch(()=>{})},e.aborted)throw new Error("Codex ACP prompt cancelled");e.addEventListener("abort",this.abortHandler,{once:!0})}}async promptWithAbort(e,s){return L({signal:s.signal,cancelledMessage:"Codex ACP prompt cancelled",startRequest:()=>e.prompt({sessionId:s.sessionId,prompt:s.prompt},s.timeoutMs),onAbort:async()=>{await this.lease?.dispose()}})}async compactWithAbort(e,s){return L({signal:s.signal,cancelledMessage:"Codex ACP 上下文压缩已取消",startRequest:()=>e.compactSession({sessionId:s.sessionId,allowRollover:s.allowRollover,timeoutMs:s.timeoutMs},s.timeoutMs),onAbort:async()=>{await this.lease?.dispose()}})}async requestPermission(e,s){if(!this.permissionCallback)return{outcome:{outcome:"cancelled"}};const n=e&&typeof e=="object"&&!Array.isArray(e)?e:{},i=n.request&&typeof n.request=="object"&&!Array.isArray(n.request)?n.request:n,t=(Array.isArray(i.options)?i.options:[]).map((r,c)=>{const p=r&&typeof r=="object"&&!Array.isArray(r)?r:{};return{optionId:typeof p.optionId=="string"?p.optionId:"",kind:String(p.kind??"choice"),...typeof p.name=="string"?{label:p.name}:typeof p.label=="string"?{label:p.label}:{}}}).filter(r=>r.optionId);return this.emitStatus("requestPermission","等待授权",ge(i),"session/request_permission"),this.permissionCallback({requestId:String(i.requestId??s??i.id??`codex-acp-permission-${Date.now()}`),params:e,method:typeof i.method=="string"?i.method:"session/request_permission",expiresAtMs:typeof i.expiresAtMs=="number"?i.expiresAtMs:void 0,options:t})}async requestUserInput(e,s,n){return this.emitStatus("requestPermission","等待回答",void 0,e),V("codex-acp",e,s,this.userInputCallback,n)}emitStatus(e,s,n,i){const a={phase:e,label:s,...n?{detail:n}:{},...i?{updateType:i}:{},atMs:Date.now()},t=this.statusTimeline[this.statusTimeline.length-1];t&&t.phase===a.phase&&t.label===a.label&&t.detail===a.detail&&t.updateType===a.updateType||(this.statusTimeline.push(a),this.statusCallback?.(a))}releaseLease(){this.lease?.release(),this.lease=null}}function pe(o,e){const s=o;return s.controlLoopMigratedProviderSessionId=e,s}function me(o){if(!o||typeof o!="object"||Array.isArray(o))return null;const e=o.controlLoopMigratedProviderSessionId;return typeof e=="string"&&e.trim()?e.trim():null}async function N(o,e){o.runtimeConfigured||(await o.client.configureRuntime({codexProvider:e?{baseUrl:e.baseUrl,apiKey:e.apiKey,revision:e.revision}:null}),o.markRuntimeConfigured())}function F(o){return o?{OPENAI_API_KEY:o.apiKey,OPENAI_BASE_URL:o.baseUrl}:{}}function he(o,e){if(e?.aborted)return!0;const s=o instanceof Error?o.message:String(o||"");return/compact.*timed out|request timed out.*compact|adapter closed|aborted|cancelled/i.test(s)}function A(o){if(!o||typeof o!="object"||Array.isArray(o))return null;const e=o.sessionId;return typeof e=="string"&&e.trim()?e.trim():null}function f(o){return o&&typeof o=="object"&&!Array.isArray(o)?o:null}function fe(o){const e=f(o),s=f(e?._meta),n=f(s?.aca),i=f(n?.controlToolSteering);if(!i)return null;const a=Number(i.attempt),t=Number(i.maxAttempts);if(!Number.isInteger(a)||a<=0||!Number.isInteger(t)||t<=0)return null;const r=Number(i.steeredAtMs),c=Number(i.failedAtMs);return{segmentId:Math.max(0,Math.floor(Number(i.segmentId)||0)),attempt:a,maxAttempts:t,succeeded:Number.isFinite(r)&&r>0,observedAtMs:Number.isFinite(r)&&r>0?r:Number.isFinite(c)&&c>0?c:Date.now()}}function P(o){const e=String(o||"").trim();return e?e==="read-only"?"read-only":["agent-full-access","full-access","bypassPermissions","danger-full-access"].includes(e)?"danger-full-access":"workspace-write":null}function M(o){const e=String(o||"").trim();return e?["agent-full-access","full-access","bypassPermissions","danger-full-access"].includes(e)?"never":e==="acceptEdits"||e==="accept-edits"?"on-failure":"on-request":null}function z(o){return{...o.model?{model:o.model}:{},...M(o.mode)?{approvalPolicy:M(o.mode)}:{},...P(o.mode)?{sandbox:P(o.mode)}:{},...o.configOptionValues??{}}}function ge(o){const e=o.params&&typeof o.params=="object"&&!Array.isArray(o.params)?o.params:o;for(const s of["command","cwd","reason","grantRoot","method"]){const n=e[s];if(typeof n=="string"&&n.trim())return j(n)}}function qe(o){for(const e of["prompt","message","question","title"]){const s=o[e];if(typeof s=="string"&&s.trim())return j(s)}}function j(o){const e=o.replace(/\s+/g," ").trim();return e.length>120?`${e.slice(0,117)}...`:e}function we(){const o=Number.parseInt(process.env.ACA_CODEX_ACP_PROMPT_TIMEOUT_MS??process.env.ACA_ACP_PROMPT_TIMEOUT_MS??"",10);return Number.isInteger(o)&&o>=3e4?o:720*60*1e3}function Se(o){const e=Number.isInteger(o)?Number(o):Number.parseInt(process.env.ACA_CODEX_ACP_COMPACT_TIMEOUT_MS??"",10);return Number.isFinite(e)&&e>=3e4?Math.min(e,6e5):12e4}function H(o){if(!o||typeof o!="object"||Array.isArray(o))return null;const e=o;return typeof e.sessionId=="string"&&e.sessionId.trim()?e.sessionId:null}function Ce(o){const e=f(o),s=f(e?._meta);return B(s?.acaContextUsage)}function B(o){const e=f(o);if(!e)return null;const s=k(e.contextWindow),n=k(e.usedTokens),i=k(e.inputTokens),a=String(e.state||"unknown"),t=["active","compacted","new_thread"].includes(a)?a:"unknown";return{usedTokens:n,inputTokens:i,contextWindow:s,ratio:s>0?Math.max(0,k(e.ratio)||n/s):0,observedAtMs:k(e.observedAtMs)||Date.now(),state:t,maintenanceAction:typeof e.maintenanceAction=="string"?e.maintenanceAction:null}}function k(o){return typeof o=="number"&&Number.isFinite(o)&&o>=0?o:0}export{Ue as CodexAcpProvider,de as codexAcpCapabilities};
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{mimoCommands as y}from"./commands.js";import{assertMimoPromptCompleted as
|
|
2
|
-
`),this.providerSessionId=null}}if(
|
|
1
|
+
import{mimoCommands as y}from"./commands.js";import{assertMimoPromptCompleted as v,diagnoseMimoEmptyResponse as M,mimoError as b,MimoRunError as g}from"./errors.js";import{enterMimoTask as A}from"../../../core/mimo-auth/gate.js";import{resolveInstalledLaunch as R}from"../../../core/provider-install-paths.js";import{handleProviderInput as x}from"../../../interactions/provider-input.js";import m from"node:fs";import h from"node:path";import{runAbortableRequest as E}from"../../client/abortable-request.js";import{BoundedItemBuffer as _}from"../../client/bounded-buffer.js";import{mimoContextUsage as P}from"./usage.js";import{defaultAcpRuntimePool as T}from"../../client/acp-runtime-pool.js";import{providerEventFromAcpSessionUpdate as U,providerStatusFromAcpSessionUpdate as q}from"../standard-acp-events.js";const O={sessionResume:!0,imageInput:!0,fileAttachment:!0,filesystem:!0,terminal:!0,permissionRequest:!0,configOptions:!0,usage:!0,contextUsage:!0,contextCompaction:!0,plan:!0,diff:!1};class oe{id="mimocode";name="MimoCode";capabilities=O;async createSession(e){return new k(e.cwd,e.providerSessionId??null,e.timeoutMs)}}class k{cwd;providerSessionId;defaultTimeoutMs;busy=!1;commands=null;effectiveModel=null;lease=null;acpSessionResponse=null;statusCallback=null;updateCallback=null;permissionCallback=null;userInputCallback=null;canExecuteTools=!0;abortHandler=null;statusTimeline=[];updates=new _(16);content="";latestContextUsage=null;contextWindow=0;constructor(e,s,t=W()){this.cwd=e,this.providerSessionId=s,this.defaultTimeoutMs=t}async compactContext(e){if(!this.providerSessionId)throw new Error("MiMo 当前没有可压缩的会话。");const s=await this.sendPrompt({...e,prompt:"/compact",requireSessionResume:!0});if(!s.providerSessionId)throw new Error("MiMo 未返回压缩后的会话标识。");return{providerSessionId:s.providerSessionId,contextUsage:s.contextUsage?{...s.contextUsage,state:"compacted",maintenanceAction:"compact"}:null,maintenance:{action:"compact",provider:"mimocode",protocol:"session/prompt"},durationMs:s.durationMs}}async sendPrompt(e){if(this.busy)throw new Error("MiMo 会话正在执行另一项操作。");this.busy=!0;try{return await this.runPrompt(e)}finally{this.busy=!1}}async runPrompt(e){if(!m.existsSync(e.cwd)||!m.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.elicitationEnabled=e.elicitation??!!e.onUserInputRequest,this.elicitationComplete=e.onElicitationComplete,this.canExecuteTools=e.canExecuteTools!==!1,this.content="",this.updates.clear(),this.statusTimeline.length=0,this.latestContextUsage=null,this.contextWindow=j(e.contextWindow);let t=null,o=null,n=this.providerSessionId,r;const l=A();try{this.attachAbortSignal(e.signal),this.emitStatus("initializing","MimoCode ACP 启动中","mimo acp");const a=await this.acquireRuntime(e.cwd);this.lease=a,t=a.client,o=a.started,await this.initialize(a),n=await this.establishSession(a,e.cwd,e.requireSessionResume===!0),await this.applyConfig(t,n,e),this.emitStatus("thinking","思考中");const c=t,u=n;if(!c||!u)throw new Error("MimoCode ACP session is unavailable");if(r=await E({signal:e.signal,cancelledMessage:"MimoCode prompt cancelled",startRequest:()=>c.prompt({sessionId:u,prompt:z(e.prompt,e.promptBlocks)},e.timeoutMs??this.defaultTimeoutMs),onAbort:async()=>{c.cancel(u),await this.lease?.dispose()}}),v(r),!this.content.trim()){const f=await M({cwd:e.cwd,sessionId:u,startedAt:s,signal:e.signal});if(e.signal?.aborted)throw new g("cancelled","小米 MiMo 任务已取消。");if(f)throw f;if(!/^\s*\//.test(e.prompt))throw new g("incomplete","小米 MiMo 未返回回复正文,本轮未正常完成。请检查登录状态、所选模型和 Worker 网络后重试。")}}catch(a){await this.lease?.dispose();const c=b(a);throw this.emitStatus("failed","执行失败",c.message),Object.assign(c,{statusTimeline:[...this.statusTimeline],providerDiagnostics:{mimo:{availableCommands:this.commands}}})}finally{this.abortHandler&&e.signal&&(e.signal.removeEventListener("abort",this.abortHandler),this.abortHandler=null),this.releaseLease(),l()}return this.emitStatus("completed","已完成"),{providerSessionId:n,cliType:"builtin",agentType:"mimo",effectiveConfig:{model:this.effectiveModel||e.model||C(),mode:e.mode||null,configOptionValues:e.configOptionValues??null,command:o.command,processPid:o.child.pid??null,provider:"mimocode",protocol:"acp"},command:o.command,args:o.args,cwd:e.cwd,exitCode:o.child.exitCode,signal:o.child.signalCode,durationMs:Date.now()-s,stdout:t.rpc.stdout,stderr:t.rpc.stderr,content:this.content.trim(),updateCount:this.updates.count(),updates:this.updates.snapshot(),statusTimeline:[...this.statusTimeline],contextUsage:this.latestContextUsage,providerDiagnostics:{mimo:{...this.commands!==null?{availableCommands:this.commands}:{}}},promptResponse:r}}async cancel(){this.providerSessionId&&this.lease?.client.cancel(this.providerSessionId)}async close(){this.statusCallback=null,this.updateCallback=null,this.permissionCallback=null,this.userInputCallback=null,this.canExecuteTools=!0,this.abortHandler&&(this.abortHandler=null),this.releaseLease()}elicitationEnabled=!1;elicitationComplete;async acquireRuntime(e){const{command:s,args:t}=R("mimo"),o=[...t,"acp","--cwd",e];return T.acquire({providerId:"mimocode",elicitation:this.elicitationEnabled,onElicitationComplete:this.elicitationComplete,elicitationProvider:"mimo",cwd:e,providerSessionId:this.providerSessionId,command:s,args:o,onSessionUpdate:n=>this.handleSessionUpdate(n),onClientRequest:(n,r,l)=>this.handleClientRequest(n,r,l.id)})}async initialize(e){e.initialized||(await e.client.initialize(6e4),e.markInitialized())}async establishSession(e,s,t=!1){const o=e.client;if(e.providerSessionId&&e.sessionEstablished)return this.providerSessionId=e.providerSessionId,this.acpSessionResponse=e.sessionResponse,this.commands=y(e.sessionResponse?.availableCommands),this.commands!==null&&this.handleSessionUpdate({sessionId:e.providerSessionId,update:{sessionUpdate:"available_commands_update",availableCommands:this.commands}}),e.providerSessionId;if(this.providerSessionId)try{this.emitStatus("resuming","MimoCode 会话恢复中","session/resume");const l=await o.resumeSession({sessionId:this.providerSessionId,cwd:s},6e4);this.acpSessionResponse=d(l);const a=S(l)||this.providerSessionId;return this.providerSessionId=a,e.setProviderSession({providerSessionId:a,sessionResponse:this.acpSessionResponse}),a}catch(l){try{this.emitStatus("resuming","MimoCode 会话加载中","session/load");const a=await o.loadSession({sessionId:this.providerSessionId,cwd:s},6e4);this.acpSessionResponse=d(a);const c=S(a)||this.providerSessionId;return this.providerSessionId=c,e.setProviderSession({providerSessionId:c,sessionResponse:this.acpSessionResponse}),c}catch{if(t)throw new Error("MiMo 历史会话恢复失败,未执行压缩。请先确认原会话可继续对话。");this.appendStderr(`MimoCode resume failed, creating new session: ${L(l)}
|
|
2
|
+
`),this.providerSessionId=null}}if(t)throw new Error("MiMo 历史会话缺少 Session ID,未执行压缩。");this.emitStatus("acp","MimoCode 会话创建中","session/new");const n=await o.newSession({cwd:s},12e4);this.acpSessionResponse=d(n);const r=S(n);if(!r)throw new Error("MimoCode ACP session/new did not return sessionId");return this.providerSessionId=r,e.setProviderSession({providerSessionId:r,sessionResponse:this.acpSessionResponse}),r}async applyConfig(e,s,t){const o=w(this.acpSessionResponse,"model")||"model",n=w(this.acpSessionResponse,"mode")||"mode",r=t.model||C(),l=r==="default"?null:D(r,t.configOptionValues,this.acpSessionResponse);if(this.effectiveModel=l||F(this.acpSessionResponse),l){const c=await e.setSessionConfigOption({sessionId:s,configId:o,value:l},6e4).catch(u=>{throw new Error("MiMo 拒绝了模型或思考档位设置,请刷新模型列表并重新选择后重试。",{cause:u})});this.acpSessionResponse={...this.acpSessionResponse,...d(c)},this.lease?.setProviderSession({providerSessionId:s,sessionResponse:this.acpSessionResponse})}const a=B(t.mode);a&&await e.setSessionMode({sessionId:s,modeId:a},6e4).catch(async()=>{await e.setSessionConfigOption({sessionId:s,configId:n,value:a},6e4).catch(c=>{throw new Error("MiMo 拒绝了运行模式设置,请重新选择后重试。",{cause:c})})});for(const[c,u]of Object.entries(t.configOptionValues??{}))["variant","reasoning_effort","effort","model","mode"].includes(c)||await e.setSessionConfigOption({sessionId:s,configId:c,value:u},6e4).catch(f=>{throw new Error("MiMo 拒绝了配置选项,请更新模型配置后重试。",{cause:f})})}handleSessionUpdate(e){const s=d(e)?.sessionId;if(this.providerSessionId&&s&&s!==this.providerSessionId)return;this.updates.push({method:"session/update",params:e});const t=d(d(e)?.update);if(t?.sessionUpdate==="available_commands_update"){const r=y(t.availableCommands);r!==null&&(this.commands=r,this.acpSessionResponse={...this.acpSessionResponse,availableCommands:r},this.providerSessionId&&this.lease?.setProviderSession({providerSessionId:this.providerSessionId,sessionResponse:this.acpSessionResponse}))}t?.sessionUpdate==="config_option_update"&&Array.isArray(t.configOptions)&&(this.acpSessionResponse={...this.acpSessionResponse,configOptions:t.configOptions},this.providerSessionId&&this.lease?.setProviderSession({providerSessionId:this.providerSessionId,sessionResponse:this.acpSessionResponse})),this.latestContextUsage=P(e,this.contextWindow)??this.latestContextUsage;const o=U("mimo",e);o&&(o.type==="agent_message_chunk"&&(this.content+=o.text??""),this.updateCallback?.(o));const n=q("mimo",e);n&&this.emitStatus(n.phase,n.label,n.detail,n.updateType)}async handleClientRequest(e,s,t){if(!this.canExecuteTools&&(e==="fs/read_text_file"||e==="fs/write_text_file"))throw new Error(`Chat Room role policy disabled tool execution: ${e}`);switch(e){case"fs/read_text_file":return this.readTextFile(s);case"fs/write_text_file":return this.writeTextFile(s);case"session/request_permission":return this.requestPermission(s,t);case"elicitation/create":case"session/request_user_input":return this.requestUserInput(e,s,t);default:throw new Error(`Unsupported ACP client request: ${e}`)}}readTextFile(e){const s=I(this.cwd,p(e,"path")||p(e,"filePath"));return{content:m.readFileSync(s,"utf8")}}writeTextFile(e){const s=I(this.cwd,p(e,"path")||p(e,"filePath")),t=p(e,"content")??p(e,"text")??"";return m.mkdirSync(h.dirname(s),{recursive:!0}),m.writeFileSync(s,t,"utf8"),{}}async requestPermission(e,s){if(!this.permissionCallback)return{outcome:{outcome:"cancelled"}};const t=e&&typeof e=="object"&&!Array.isArray(e)?e:{},n=(Array.isArray(t.options)?t.options:[]).map((r,l)=>{const a=r&&typeof r=="object"&&!Array.isArray(r)?r:{};return{optionId:typeof a.optionId=="string"?a.optionId:"",kind:String(a.kind??a.outcome??"choice"),...typeof a.name=="string"?{label:a.name}:typeof a.label=="string"?{label:a.label}:{}}}).filter(r=>r.optionId);return this.permissionCallback({requestId:String(t.requestId??s??t.id??`mimo-permission-${Date.now()}`),params:e,expiresAtMs:typeof t.expiresAtMs=="number"?t.expiresAtMs:void 0,options:n})}async requestUserInput(e,s,t){return this.emitStatus("requestPermission","等待回答",void 0,e),x("mimo",e,s,this.userInputCallback,t)}attachAbortSignal(e){if(e){if(this.abortHandler=()=>{this.cancel()},e.aborted)throw new Error("MimoCode prompt cancelled");e.addEventListener("abort",this.abortHandler,{once:!0})}}emitStatus(e,s,t,o){const n={phase:e,label:s,...t?{detail:t}:{},...o?{updateType:o}:{},atMs:Date.now()},r=this.statusTimeline[this.statusTimeline.length-1];r&&r.phase===n.phase&&r.label===n.label&&r.detail===n.detail&&r.updateType===n.updateType||(this.statusTimeline.push(n),this.statusCallback?.(n))}requireClient(){if(!this.lease)throw new Error("MimoCode ACP client is not started");return this.lease.client}appendStderr(e){const s=this.lease?.client;s&&(s.rpc.stderr+=e)}releaseLease(){this.lease?.release(),this.lease=null}}function S(i){if(!i||typeof i!="object"||Array.isArray(i))return null;const e=i.sessionId;return typeof e=="string"&&e.trim()?e.trim():null}function d(i){return i&&typeof i=="object"&&!Array.isArray(i)?i:null}function j(i){return typeof i=="number"&&Number.isFinite(i)&&i>0?i:0}function w(i,e){const s=i?.configOptions;if(!Array.isArray(s))return null;for(const t of s){if(!t||typeof t!="object"||Array.isArray(t))continue;const o=t;if(o.category===e&&typeof o.id=="string"&&o.id.trim())return o.id.trim()}return null}function D(i,e,s){const t=H(e?.variant,e?.reasoning_effort,e?.effort);if(!t||i.endsWith(`/${t}`))return i;const o=`${i}/${t}`;if(V(s,o))return o;throw new Error("当前 MiMo 模型不支持所选思考档位,请重新选择。")}function F(i){const e=i?.configOptions;if(Array.isArray(e))for(const t of e){const o=d(t);if(o?.category==="model"&&typeof o.currentValue=="string")return o.currentValue}const s=d(i?.models)?.currentModelId;return typeof s=="string"?s:null}function V(i,e){const s=i?.configOptions;if(!Array.isArray(s))return!1;for(const t of s){if(!t||typeof t!="object"||Array.isArray(t))continue;const o=t;if(!(o.category!=="model"||!Array.isArray(o.options))&&o.options.some(n=>$(n)===e))return!0}return!1}function $(i){if(typeof i=="string"&&i.trim())return i.trim();if(!i||typeof i!="object"||Array.isArray(i))return null;const e=i;for(const s of["value","id","modelId","model_id"]){const t=e[s];if(typeof t=="string"&&t.trim())return t.trim()}return null}function p(i,e){if(!i||typeof i!="object"||Array.isArray(i))return null;const s=i[e];return typeof s=="string"&&s.trim()?s:null}function I(i,e){if(!e)throw new Error("ACP file request did not include a path");const s=h.resolve(i,e),t=h.resolve(i);if(s!==t&&!s.startsWith(`${t}${h.sep}`))throw new Error(`ACP file request is outside project root: ${e}`);return s}function B(i){const e=String(i||"").trim();return e?["agent","agent-full-access","full-access","bypassPermissions","danger-full-access"].includes(e)?"build":e==="read-only"?"plan":e:null}function H(...i){for(const e of i)if(typeof e=="string"&&e.trim())return e.trim();return null}function L(i){return i instanceof Error?i.message:String(i)}function C(){return process.env.ACA_MIMO_DEFAULT_MODEL||"default"}function z(i,e){return(e?.length?e:[{type:"text",text:i}]).map(t=>t.type==="image"?{type:"image",data:t.data,mimeType:t.mimeType}:{type:"text",text:t.text})}function W(){const i=Number.parseInt(process.env.ACA_MIMO_PROMPT_TIMEOUT_MS??process.env.ACA_ACP_PROMPT_TIMEOUT_MS??"",10);return Number.isInteger(i)&&i>=3e4?i:720*60*1e3}export{oe as MimoCodeProvider,O as mimoCodeCapabilities};
|