@yuandc/aica 0.1.80 → 0.1.81
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/json-rpc.js +3 -3
- package/dist/acp/providers/codex-acp/adapter.js +7 -4
- package/dist/acp/providers/codex-acp/provider.js +1 -1
- package/dist/acp/providers/codex-acp/user-steering.js +1 -0
- package/dist/core/job-attachments.js +6 -0
- package/dist/core/job-supplements.js +1 -0
- package/dist/core/job-worker.js +1 -6
- package/dist/version.js +1 -1
- package/package.json +1 -1
|
@@ -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(t
|
|
2
|
-
`);this.lineBuffer=
|
|
3
|
-
[non-json stdout] ${i}`;continue}this.handleMessage(n)}}async handleMessage(
|
|
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,t={}){this.child=e,this.handlers=t,this.peerName=t.peerName||"JSON-RPC peer",this.includeJsonRpc=t.includeJsonRpc===!0,e.stdout.on("data",r=>this.handleStdout(String(r))),e.stderr.on("data",r=>{this.stderr=this.stderr+String(r)}),e.on("close",(r,i)=>{this.exitCode=r,this.signal=i;for(const[n,o]of this.pending)clearTimeout(o.timer),o.reject(new Error(`${this.peerName} exited before ${o.method} completed (code=${r}, signal=${i})`)),this.pending.delete(n)}),e.on("error",r=>{for(const[i,n]of this.pending)clearTimeout(n.timer),n.reject(r),this.pending.delete(i)})}get stdout(){return this.stdoutBuffer}get stderr(){return this.stderrBuffer}set stderr(e){this.stderrBuffer=u("",e,s.MAX_STDERR_BYTES)}request(e,t,r=12e4){const i=this.nextId++;return new Promise((n,o)=>{const d=this.createRequestTimeout(i,e,r,o);d.unref(),this.pending.set(i,{method:e,resolve:n,reject:o,timer:d,timeoutMs:r}),this.write({...this.includeJsonRpc?{jsonrpc:"2.0"}:{},id:i,method:e,...t===void 0?{}:{params:t}})})}notify(e,t){this.write({...this.includeJsonRpc?{jsonrpc:"2.0"}:{},method:e,...t===void 0?{}:{params:t}})}refreshPendingRequestTimeout(e){for(const[t,r]of this.pending)r.method===e&&(clearTimeout(r.timer),r.timer=this.createRequestTimeout(t,r.method,r.timeoutMs,r.reject),r.timer.unref())}respond(e,t){this.write({...this.includeJsonRpc?{jsonrpc:"2.0"}:{},id:e,result:t})}respondError(e,t){this.write({...this.includeJsonRpc?{jsonrpc:"2.0"}:{},id:e,error:{code:-32603,message:t.message}})}createRequestTimeout(e,t,r,i){return setTimeout(()=>{this.pending.delete(e),i(new Error(`${this.peerName} request timed out while waiting for ${t}`))},r)}handleStdout(e){this.stdoutBuffer=u(this.stdoutBuffer,e,s.MAX_STDOUT_BYTES),this.lineBuffer+=e;const t=this.lineBuffer.split(`
|
|
2
|
+
`);this.lineBuffer=t.pop()??"";for(const r of t){const i=r.trim();if(!i)continue;let n;try{n=JSON.parse(i)}catch{this.stderr+=`
|
|
3
|
+
[non-json stdout] ${i}`;continue}this.handleMessage(n)}}async handleMessage(e){if("id"in e&&("result"in e||"error"in e)&&typeof e.method!="string"){const t=Number(e.id),r=this.pending.get(t);if(!r)return;if(this.pending.delete(t),clearTimeout(r.timer),"error"in e){const i=e.error;r.reject(Object.assign(new Error(i?.message||`${this.peerName} error ${i?.code??"unknown"}`),{rpcResponse:!0,rpcCode:i?.code}))}else r.resolve(e.result);return}if(typeof e.method=="string"){if("id"in e){try{const t=await this.handlers.onRequest?.(e.method,e.params,e);this.respond(e.id,t??null)}catch(t){this.respondError(e.id,t instanceof Error?t:new Error(String(t)))}return}this.notificationCount+=1,await this.handlers.onNotification?.(e.method,e.params,e)}}write(e){this.child.stdin.write(`${JSON.stringify(e)}
|
|
4
4
|
`)}}export{s as JsonLineRpcClient};
|
|
@@ -1,4 +1,7 @@
|
|
|
1
|
-
import{createToolLifecycleTrace as x}from"../../tool-lifecycle-trace.js";import{JsonLineRpcClient as
|
|
2
|
-
`);this.lineBuffer=
|
|
3
|
-
`),await this.shutdown(),process.exit(1)}}async handleRequest(t,e){switch(t){case"initialize":return this.initializeResponse();case"aca/runtime_config":return this.applyRuntimeConfig(e);case"session/new":return this.newSession(e);case"session/resume":case"session/load":return this.resumeSession(e);case"session/fork":return this.forkSession(e);case"session/prompt":return this.prompt(e);case"session/compact":return this.manualCompact(e);case"aca/session_recover":return this.recoverSession(e);case"session/cancel":return await this.cancel(e),{};case"session/close":return await this.closeSession(e),{};case"session/set_config_option":return this.setConfigOption(e);case"session/set_mode":return this.setMode(e);case"fs/read_text_file":case"fs/write_text_file":case"terminal/create":case"terminal/output":case"terminal/wait_for_exit":case"terminal/kill":case"terminal/release":return{};default:throw new Error(`Unsupported ACP method: ${t}`)}}initializeResponse(){return{protocolVersion:1,agentCapabilities:{loadSession:!0,sessionCapabilities:{resume:!0,close:!0}}}}applyRuntimeConfig(t){if(this.started)throw new Error("Codex runtime config must be applied before app-server starts");const e=a(t.codexProvider);if(Object.keys(e).length===0)return this.managedProvider=null,{configured:!1};const s=c(e,"baseUrl"),o=c(e,"apiKey"),i=Number(e.revision||0);if(!s||!o||!Number.isSafeInteger(i)||i<1)throw new Error("Invalid Codex managed provider runtime config");return this.managedProvider={baseUrl:s,apiKey:o,revision:i},{configured:!0,revision:i}}async newSession(t){const e=c(t,"cwd")||process.cwd();this.applyInitialConfig(t),await this.ensureCodex(e);const s=await this.requireRpc().request("thread/start",{cwd:e,approvalsReviewer:"user",...this.codexConfig(),ephemeral:!1,experimentalRawEvents:!0,threadSource:"aca-codex-acp"},6e4),o=T(s);if(!o)throw new Error("Codex thread/start did not return thread id");return this.currentSessionId=o,this.currentCwd=e,this.resetSubtaskState(),this.captureThreadMetadata(s),this.sessionResponse(o)}async resumeSession(t){const e=c(t,"sessionId");if(!e)throw new Error("sessionId is required");const s=c(t,"cwd")||this.currentCwd;this.applyInitialConfig(t),await this.ensureCodex(s);const o=await this.requireRpc().request("thread/resume",{threadId:e,cwd:s,approvalsReviewer:"user",...this.codexConfig()},6e4);return this.currentSessionId=T(o)||e,this.currentCwd=s,this.resetSubtaskState(o),await this.subscribeKnownSubtasks(!1),this.captureThreadMetadata(o),this.sessionResponse(this.currentSessionId)}async forkSession(t){const e=c(t,"sessionId");if(!e)throw new Error("sessionId is required");const s=c(t,"cwd")||this.currentCwd;this.applyInitialConfig(t),await this.ensureCodex(s);const o=await this.requireRpc().request("thread/fork",{threadId:e,cwd:s,approvalsReviewer:"user",ephemeral:!1,threadSource:"aca-codex-acp-fork",...this.codexConfig()},12e4),i=T(o);if(!i)throw new Error("Codex thread/fork did not return thread id");return this.currentSessionId=i,this.currentCwd=s,this.resetSubtaskState(o),this.captureThreadMetadata(o),this.sessionResponse(i)}async prompt(t){this.promptCancelled=!1;const e=c(t,"sessionId")||this.currentSessionId;let s=this.resolveSessionAlias(e);if(!s)throw new Error("sessionId is required");const o=Array.isArray(t.prompt)?t.prompt:[],i=this.requireRpc();this.currentSessionId&&this.currentSessionId!==s&&this.resetSubtaskState(),this.currentSessionId=s;const r=await this.maintainContext("before",{userRequest:this.previousUserText,assistantResponse:this.previousAssistantText,affectedFiles:[...this.previousAffectedFiles]},{allowRollover:!0});if(lt(r),this.promptCancelled||this.shutdownPromise)throw new Error("Codex prompt cancelled");s=this.currentSessionId||s,this.toolTrace=x(),this.promptActive=!0,this.currentTurnId=null,this.controlToolLoop.reset(),this.controlToolSteering.reset(),this.currentPromptText=Z(o),this.currentAssistantText="",this.currentAffectedFiles.clear(),this.connectionRecovery.reset(),this.agentMessagePhases.clear(),this.planDeltas.reset(),this.pendingSubtaskNotifications=[],this.flushSubtaskStates();let d;try{this.turnStartPromise=i.request("turn/start",{threadId:s,clientUserMessageId:`aca-acp-${Date.now()}-${Math.random().toString(16).slice(2)}`,input:tt(o),cwd:this.currentCwd,...et(this.config)},D()),d=await this.turnStartPromise;const u=String(d?.turn?.id??"");this.currentTurnId=u||null;const l=u?await this.waitForTurnCompleted(u,D()):d?.turn,h=await this.subtaskRouter.waitForIdle(gt());if(h.timedOut&&h.activeThreadIds.length>0&&this.notifySessionUpdate({sessionUpdate:"aca_codex_subtask_drain",label:"子任务收尾等待超时",detail:`仍有 ${h.activeThreadIds.length} 个子任务未结束`,_meta:{aca:{subtaskDrain:h}}}),this.promptCancelled)throw new Error("Codex prompt cancelled");this.threadTurnCount+=1,this.previousUserText=this.currentPromptText,this.previousAssistantText=this.currentAssistantText,this.previousAffectedFiles.clear();for(const p of this.currentAffectedFiles)this.previousAffectedFiles.add(p);const f=await this.maintainContext("after",{userRequest:this.previousUserText,assistantResponse:this.previousAssistantText,affectedFiles:[...this.previousAffectedFiles]},{allowRollover:!0});return{stopReason:"end_turn",sessionId:this.currentSessionId,turn:l,_meta:{acaContextUsage:E(f)??dt(this.latestTokenUsage),acaContextMaintenance:f,acaSubtaskDrain:h}}}catch(u){const l=this.connectionRecovery.fail(u);throw l&&this.notifySessionUpdate(l),u}finally{this.subtaskRouter.executions.loseObservation(this.promptCancelled?"父任务已中断,子执行状态需重新确认":"本轮观测结束,未确认子执行终态"),this.flushSubtaskStates(),this.controlToolLoop.reset(),this.promptActive=!1,this.currentTurnId=null,this.turnStartPromise=null,this.pendingSubtaskNotifications=[]}}resetSubtaskState(t){this.subtaskGeneration+=1,this.subtaskRouter.reset(),this.subtaskAgentMessagePhases.clear(),this.subtaskPlanDeltas.clear(),this.subscribedSubtaskThreadIds.clear(),this.pendingSubtaskSubscriptions.clear(),this.hydratedSubtaskThreadIds.clear(),this.seenSubtaskItemIds.clear(),this.pendingSubtaskNotifications=[],t!==void 0&&this.subtaskRouter.restoreFromSessionSnapshot(t,this.currentSessionId)}async subscribeKnownSubtasks(t=!0){const e=this.subtaskRouter.routesForSubscription().map(s=>this.subscribeSubtask(s,t));await Promise.allSettled(e)}subscribeSubtask(t,e){const s=t.sourceThreadId;if(!s||s===this.currentSessionId||this.subscribedSubtaskThreadIds.has(s))return Promise.resolve();const o=this.pendingSubtaskSubscriptions.get(s);if(o)return o;const i=this.subtaskGeneration,r=this.requireRpc().request("thread/resume",{threadId:s,approvalsReviewer:"user",excludeTurns:!1},6e4).then(d=>{if(i!==this.subtaskGeneration)return;this.subscribedSubtaskThreadIds.add(s);const u=this.subtaskRouter.restoreFromSessionSnapshot(d,this.currentSessionId);e&&this.hydrateSubtaskHistory(d,t),u>0&&this.subscribeKnownSubtasks(e)}).catch(()=>{}).finally(()=>{this.pendingSubtaskSubscriptions.get(s)===r&&this.pendingSubtaskSubscriptions.delete(s)});return this.pendingSubtaskSubscriptions.set(s,r),r}async cancel(t){const e=this.resolveSessionAlias(c(t,"sessionId")||this.currentSessionId);if(!e||!this.rpc)return;if(e!==this.currentSessionId)throw new Error("Cannot cancel a different Codex session");this.promptCancelled=!0,this.subtaskRouter.stopWaiting();const s=this.turnStartPromise?await Q(this.turnStartPromise):null,o=this.currentTurnId||c(w(s,"turn"),"id");if(await this.rpc.request("turn/interrupt",{threadId:e,turnId:o},5e3),o){const i=this.pendingTurnCompletions.get(o);i&&(this.pendingTurnCompletions.delete(o),clearTimeout(i.timer),i.reject(new Error("Codex prompt cancelled")))}}async manualCompact(t){const e=c(t,"sessionId")||this.currentSessionId,s=this.resolveSessionAlias(e);if(!s)throw new Error("sessionId is required");if(this.promptActive||this.maintenanceActive)throw new Error("Codex session is busy");this.currentSessionId=s;const o=R(),i=await this.maintainContext("manual",{userRequest:this.previousUserText,assistantResponse:this.previousAssistantText,affectedFiles:[...this.previousAffectedFiles]},{force:!0,allowRollover:t.allowRollover===!0,compactTimeoutMs:ut(t,o.compactTimeoutMs)}),r=String(i.action||"");if(r==="compact_failed"||r==="rollover_failed"){const d=String(i.compactError||"Codex 上下文压缩未完成");throw new Error(`Codex 上下文压缩失败:${d}`)}return{sessionId:this.currentSessionId,maintenance:i,contextUsage:E(i)}}async recoverSession(t){const e=c(t,"sessionId")||this.currentSessionId,s=this.resolveSessionAlias(e);if(!s)throw new Error("sessionId is required");const o=c(t,"reason");if(o!=="low_output_loop"&&o!=="control_tool_loop")throw new Error("Unsupported Codex recovery reason");if(this.promptActive||this.maintenanceActive)throw new Error("Codex session is busy");if(s!==this.currentSessionId)throw new Error("Cannot recover a different Codex session");const i=new Set([...this.previousAffectedFiles,...this.currentAffectedFiles]);return await this.rotateThread(s,{userRequest:this.currentPromptText||this.previousUserText,assistantResponse:this.currentAssistantText||this.previousAssistantText,affectedFiles:[...i]}),{...a(this.sessionResponse(this.currentSessionId)),previousSessionId:s,recoveryReason:o}}async closeSession(t){await this.shutdown()}async ensureCodex(t){if(this.rpc&&this.started&&this.started.child.exitCode===null&&this.started.child.signalCode===null)return;if(this.codexStartupPromise){await this.codexStartupPromise;return}const e=(async()=>{if(this.rpc&&this.started&&this.started.child.exitCode===null&&this.started.child.signalCode===null)return;const s=$(t,this.managedProvider),o=new L(s.child,{peerName:"Codex app-server",onNotification:(i,r)=>this.handleCodexNotification(i,r),onRequest:async(i,r)=>this.handleCodexRequest(i,r)});this.started=s,this.rpc=o,s.child.once("exit",()=>{this.started===s&&(this.subtaskRouter.executions.loseObservation("执行连接断开,未确认子代理终态"),this.flushSubtaskStates(),this.subtaskRouter.stopWaiting())}),this.initialized=!1,await o.request("initialize",{clientInfo:{name:"aca-codex-acp",version:"0.1.0"},capabilities:{experimentalApi:!0,requestAttestation:!1}},6e4),this.initialized=!0})();this.codexStartupPromise=e;try{await e}catch(s){throw this.rpc&&this.started&&!this.initialized&&await C(this.started.child,1e3),this.rpc=null,this.started=null,s}finally{this.codexStartupPromise===e&&(this.codexStartupPromise=null)}}handleCodexNotification(t,e){const s=A(e);if(t==="item/started"||t==="item/completed"){const u=a(a(e).item);this.toolTrace("provider_notification",{method:t,threadId:s.threadId,turnId:s.turnId,toolCallId:u.id,itemType:u.type,status:u.status})}if(this.maintenanceActive&&(!s.threadId||s.threadId===this.currentSessionId)){(t==="item/started"||t==="item/completed")&&this.toolTrace("notification_filtered",{method:t,toolCallId:a(a(e).item).id,reason:"context_maintenance"}),this.captureContextNotification(t,e);return}if(!J(e,this.currentSessionId,this.currentTurnId)){if(!this.promptActive||!s.threadId||s.threadId===this.currentSessionId){(t==="item/started"||t==="item/completed")&&this.toolTrace("notification_filtered",{method:t,toolCallId:a(a(e).item).id,reason:"outside_current_turn",threadId:s.threadId,turnId:s.turnId});return}const u=this.subtaskRouter.routeFor(s.threadId);if(!u){(t==="item/started"||t==="item/completed")&&this.toolTrace("notification_buffered",{method:t,toolCallId:a(a(e).item).id,threadId:s.threadId,reason:"subtask_route_pending"}),this.bufferPendingSubtaskNotification(t,e,s.threadId);return}this.handleSubtaskNotification(t,e,u),this.flushPendingSubtaskNotifications();return}const o=this.subtaskRouter.observe(e,this.currentSessionId);if(this.flushSubtaskStates(),o>0&&this.subscribeKnownSubtasks(!0),this.captureContextNotification(t,e),this.maintenanceActive)return;if(!this.promptActive&&t!=="turn/completed"){(t==="item/started"||t==="item/completed")&&this.toolTrace("notification_filtered",{method:t,toolCallId:a(a(e).item).id,reason:"prompt_inactive"});return}this.rpc?.refreshPendingRequestTimeout("turn/start"),this.captureAgentMessagePhase(t,e);const i=this.connectionRecovery.beforeNotification(t,e);t==="turn/completed"&&this.resolveTurnCompleted(e);const r=t==="rawResponseItem/completed"?pt(this.controlToolLoop,e):[],d=t==="rawResponse/completed"?ht(e):t==="rawResponseItem/completed"?r.map(({update:u})=>u):y(t,e,{agentMessagePhases:this.agentMessagePhases,planDeltas:this.planDeltas});for(const u of r)this.maybeSteerControlToolLoop(u.event);for(const u of[...i,...d]){const l=M(u,t,e,this.agentMessagePhases);_(l)&&this.notifyControlToolProgress(),this.captureUpdateEvidence(l),this.notifySessionUpdate(l)}o>0&&this.flushPendingSubtaskNotifications()}handleSubtaskNotification(t,e,s){if(!this.promptActive)return;const o=this.subtaskRouter.acceptNotification(s,t,e);if(this.flushSubtaskStates(),!o){(t==="item/started"||t==="item/completed")&&this.toolTrace("notification_filtered",{method:t,toolCallId:a(a(e).item).id,threadId:s.sourceThreadId,reason:"subtask_not_current"});return}(t==="rawResponseItem/completed"||t==="item/completed")&&Y(a(a(e).item))&&this.notifyControlToolProgress(),this.rememberSubtaskItem(s.sourceThreadId,e),this.rpc?.refreshPendingRequestTimeout("turn/start");const i=this.subtaskRouter.executions.get(s.sourceThreadId)?.runId||s.sourceThreadId,r=this.subtaskAgentMessagePhases.get(i)??new Map;this.subtaskAgentMessagePhases.set(i,r);const d=this.subtaskPlanDeltas.get(i)??new k;this.subtaskPlanDeltas.set(i,d),this.captureAgentMessagePhase(t,e,r);const u=this.subtaskRouter.observe(e,this.currentSessionId);this.flushSubtaskStates(),u>0&&this.subscribeKnownSubtasks(!0);const l=y(t,e,{agentMessagePhases:r,planDeltas:d}),h=A(e).turnId;for(const f of l){const p=M(f,t,e,r);if(!ft(p))continue;c(p,"title")&&this.subtaskRouter.executions.action(s.sourceThreadId,c(p,"title")),_(p)&&(this.notifyControlToolProgress(),this.subtaskRouter.executions.progress(s.sourceThreadId,c(p,"title")||"收到执行结果"));const g=P(p,s,h,this.subtaskRouter.executions.get(s.sourceThreadId));b(g,this.currentAffectedFiles),this.notifySessionUpdate(g)}}hydrateSubtaskHistory(t,e){if(!(!this.promptActive||this.hydratedSubtaskThreadIds.has(e.sourceThreadId))){this.hydratedSubtaskThreadIds.add(e.sourceThreadId);for(const s of H(t)){const o=N(e.sourceThreadId,s.itemId);if(this.seenSubtaskItemIds.has(o))continue;this.seenSubtaskItemIds.add(o);const i=P(s.update,e,s.sourceTurnId,{runId:`history.${e.sourceThreadId}.${s.sourceTurnId}`,state:"unknown",name:this.subtaskRouter.executions.nameFor(e.sourceThreadId),outputIncomplete:!0,reason:"历史过程回放,不代表当前执行状态"}),r=a(i._meta);i._meta={...r,aca:{...a(r.aca),historyReplay:!0}},b(i,this.currentAffectedFiles),this.notifySessionUpdate(i)}}}rememberSubtaskItem(t,e){const s=a(e),o=a(s.item),i=c(o,"id")||c(s,"itemId");i&&this.seenSubtaskItemIds.add(N(t,i))}bufferPendingSubtaskNotification(t,e,s){this.pendingSubtaskNotifications.push({method:t,params:e,threadId:s}),this.pendingSubtaskNotifications.length>200&&this.pendingSubtaskNotifications.shift()}flushPendingSubtaskNotifications(){if(this.pendingSubtaskNotifications.length===0)return;let t=!0;for(;t&&this.pendingSubtaskNotifications.length>0;){t=!1;const e=[];for(const s of this.pendingSubtaskNotifications){const o=this.subtaskRouter.routeFor(s.threadId);if(!o){e.push(s);continue}t=!0,this.handleSubtaskNotification(s.method,s.params,o)}this.pendingSubtaskNotifications=e}}notifySessionUpdate(t){t=X(t,this.subtaskRouter.executions),(t.sessionUpdate==="tool_call"||t.sessionUpdate==="tool_call_update"&&["completed","failed","cancelled"].includes(String(t.status)))&&this.toolTrace("acp_update",{toolCallId:t.toolCallId,status:t.status,threadId:this.currentSessionId,turnId:this.currentTurnId}),this.notify("session/update",{sessionId:this.currentSessionId,update:t})}flushSubtaskStates(){if(this.promptActive)for(const t of this.subtaskRouter.executions.drainUpdates())this.notifySessionUpdate(t)}async handleCodexRequest(t,e){if(j(t))return B(t,e,async s=>{const o=await this.requestParent("session/request_permission",{sessionId:this.currentSessionId,request:{...s,params:e,method:t}}),i=a(o),r=a(i.outcome);return r.outcome==="selected"&&typeof r.optionId=="string"?{outcome:{outcome:"selected",optionId:r.optionId}}:{outcome:{outcome:"cancelled"}}});if(mt(t)){const s=await this.requestParent("elicitation/create",{sessionId:this.currentSessionId,request:{...a(e),method:t}}),o=a(s),i=a(o.outcome);return i.outcome==="submitted"?{outcome:{outcome:"submitted",value:typeof i.value=="string"?i.value:""}}:{outcome:{outcome:"cancelled"}}}return{}}captureAgentMessagePhase(t,e,s=this.agentMessagePhases){if(t!=="item/started"&&t!=="item/completed")return;const o=w(e,"item");if(c(o,"type")!=="agentMessage")return;const i=c(o,"id");i&&s.set(i,c(o,"phase"))}waitForTurnCompleted(t,e){const s=this.completedTurns.get(t);return s?Promise.resolve(s):new Promise((o,i)=>{const r=setTimeout(()=>{this.pendingTurnCompletions.delete(t),i(new Error("Codex app-server request timed out while waiting for turn/completed"))},e);r.unref(),this.pendingTurnCompletions.set(t,{resolve:o,reject:i,timer:r})})}resolveTurnCompleted(t){const e=w(t,"turn"),s=c(e,"id");if(!s)return;this.completedTurns.set(s,e);const o=this.pendingTurnCompletions.get(s);if(o){if(this.pendingTurnCompletions.delete(s),clearTimeout(o.timer),c(e,"status")==="failed"){o.reject(new Error(`Codex turn failed: ${JSON.stringify(e.error??{})}`));return}o.resolve(e)}}async shutdown(){return this.shutdownPromise?this.shutdownPromise:(this.shutdownPromise=this.performShutdown(),this.shutdownPromise)}async performShutdown(){this.subtaskRouter.executions.loseObservation("执行连接关闭,未确认子代理终态"),this.flushSubtaskStates(),this.subtaskRouter.stopWaiting(),this.pendingCompaction&&(clearTimeout(this.pendingCompaction.timer),this.pendingCompaction.reject(new Error("Codex ACP adapter closed")),this.pendingCompaction=null);for(const e of this.pendingParentRequests.values())clearTimeout(e.timer),e.reject(new Error("Codex ACP adapter closed"));this.pendingParentRequests.clear();for(const e of this.pendingTurnCompletions.values())clearTimeout(e.timer),e.reject(new Error("Codex ACP adapter closed"));this.pendingTurnCompletions.clear();const t=this.started?.child;!t||t.exitCode!==null||t.signalCode!==null||(t.stdin.end(),!await C(t,2e3)&&(t.kill("SIGTERM"),!await C(t,1e3)&&(t.kill("SIGKILL"),await C(t,1e3))))}sessionResponse(t){return{sessionId:t,modes:{currentModeId:"workspace-write",availableModes:[{id:"read-only",name:"Read Only"},{id:"workspace-write",name:"Workspace Write"},{id:"danger-full-access",name:"Full Access"}]},configOptions:[{id:"model",category:"model",type:"string"},{id:"approvalPolicy",category:"approval",type:"string"},{id:"sandbox",category:"mode",type:"string"}]}}requireRpc(){if(!this.rpc)throw new Error("Codex app-server is not started");return this.rpc}notify(t,e){this.write({jsonrpc:"2.0",method:t,params:e})}requestParent(t,e,s=600*1e3){const o=this.nextParentRequestId++;return new Promise((i,r)=>{const d=setTimeout(()=>{this.pendingParentRequests.delete(o),r(new Error(`ACP client request timed out: ${t}`))},s);d.unref(),this.pendingParentRequests.set(o,{resolve:i,reject:r,timer:d}),this.write({jsonrpc:"2.0",id:o,method:t,params:e})})}handleParentResponse(t){const e=Number(t.id),s=this.pendingParentRequests.get(e);if(s){if(this.pendingParentRequests.delete(e),clearTimeout(s.timer),"error"in t){const o=a(t.error);s.reject(new Error(c(o,"message")||"ACP client request failed"));return}s.resolve(t.result)}}setConfigOption(t){const e=c(t,"configId"),s=t.value;if(!e)throw new Error("configId is required");if(typeof s!="string"&&typeof s!="boolean")throw new Error("config value must be string or boolean");return this.config[e]=s,this.notify("session/update",{sessionId:this.currentSessionId,update:{sessionUpdate:"config_option_update",configId:e,value:s}}),{}}setMode(t){const e=c(t,"modeId");if(!e)throw new Error("modeId is required");return this.config.sandbox=e,this.notify("session/update",{sessionId:this.currentSessionId,update:{sessionUpdate:"current_mode_update",modeId:e}}),{}}applyInitialConfig(t){const e=w(t,"config");for(const[s,o]of Object.entries(e))(typeof o=="string"||typeof o=="boolean")&&(this.config[s]=o)}codexConfig(){return{...typeof this.config.model=="string"&&this.config.model?{model:this.config.model}:{},approvalPolicy:typeof this.config.approvalPolicy=="string"?this.config.approvalPolicy:"on-request",sandbox:typeof this.config.sandbox=="string"?this.config.sandbox:"workspace-write"}}captureContextNotification(t,e){if(t==="thread/tokenUsage/updated"&&(this.latestTokenUsage=z(e)??this.latestTokenUsage),t==="thread/compacted"&&this.pendingCompaction&&this.resolveCompaction(),t==="item/completed"){const s=w(e,"item"),o=c(a(e),"threadId");c(s,"type")==="contextCompaction"&&(!o||o===this.currentSessionId)&&this.resolveCompaction(),c(s,"type")==="agentMessage"&&c(s,"phase")==="final_answer"&&(this.currentAssistantText=S(s)||this.currentAssistantText)}}resolveCompaction(){if(!this.pendingCompaction)return;const t=this.pendingCompaction;this.pendingCompaction=null,clearTimeout(t.timer),t.resolve()}captureUpdateEvidence(t){String(t.sessionUpdate||"")==="agent_message_chunk"&&(this.currentAssistantText+=S(t.content)),b(t,this.currentAffectedFiles)}captureThreadMetadata(t){const e=w(t,"thread");this.currentRolloutPath=c(e,"path")||null,this.threadTurnCount=Array.isArray(e.turns)?e.turns.length:0;const s=ct(e.turns);s.userRequest&&(this.previousUserText=s.userRequest),s.assistantResponse&&(this.previousAssistantText=s.assistantResponse),this.previousAffectedFiles.clear();for(const o of s.affectedFiles)this.previousAffectedFiles.add(o);this.latestTokenUsage=null}async maintainContext(t,e,s={}){const o=R(),i=this.contextState(),r=v(i,o);if(!r.compact&&!s.force||!this.currentSessionId)return this.maintenanceResult({stage:t,action:"none",reasons:r.reasons,beforeState:i});const d=this.currentSessionId;this.notifyContextStatus("正在整理会话上下文",F(i)),this.maintenanceActive=!0;let u=!1,l="",h=i,f=r,p=!1,g="";try{const m=i.usage?.observedAtMs??0;await this.compactThread(d,s.compactTimeoutMs??o.compactTimeoutMs),await this.waitForTokenUsageAfter(m,2e3)}catch(m){u=!0,l=m instanceof Error?m.message:String(m)}try{if(t!=="manual"&&this.promptCancelled||this.shutdownPromise)throw new Error("Codex prompt cancelled");if(h=this.contextState(),f=v(h,o,{compactAttempted:!0,compactFailed:u}),f.rollover&&s.allowRollover!==!1){this.notifyContextStatus("正在创建轻量会话",f.reasons.join(", "));try{await this.rotateThread(d,e),p=!0}catch(m){g=m instanceof Error?m.message:String(m),this.notifyContextStatus("轻量会话创建失败",g)}}else u||this.notifyContextStatus("会话上下文已压缩",F(h))}finally{this.maintenanceActive=!1}return this.maintenanceResult({action:p?"rollover":g?"rollover_failed":u?"compact_failed":"compact",stage:t,reasons:f.reasons,beforeState:i,afterState:h,compactError:[l,g].filter(Boolean).join("; "),previousThreadId:d,rolloverRecommended:f.rollover&&s.allowRollover===!1})}compactThread(t,e){return this.pendingCompaction?Promise.reject(new Error("Codex context compaction is already running")):new Promise((s,o)=>{const i=setTimeout(()=>{this.pendingCompaction?.timer===i&&(this.pendingCompaction=null),o(new Error("Codex thread compaction timed out"))},e);i.unref(),this.pendingCompaction={resolve:s,reject:o,timer:i},this.requireRpc().request("thread/compact/start",{threadId:t},e).catch(r=>{this.pendingCompaction?.timer===i&&(this.pendingCompaction=null,clearTimeout(i),o(r instanceof Error?r:new Error(String(r))))})})}async rotateThread(t,e){const s=K({previousThreadId:t,cwd:this.currentCwd,userRequest:e.userRequest,assistantResponse:e.assistantResponse,affectedFiles:e.affectedFiles}),o=await this.requireRpc().request("thread/start",{cwd:this.currentCwd,approvalsReviewer:"user",...this.codexConfig(),developerInstructions:s,ephemeral:!1,experimentalRawEvents:!0,threadSource:"aca-codex-acp-rollover"},6e4),i=T(o);if(!i)throw new Error("Codex rollover thread/start did not return thread id");this.sessionAliases.set(t,i),this.currentSessionId=i,this.resetSubtaskState(o),this.captureThreadMetadata(o)}async waitForTokenUsageAfter(t,e){const s=Date.now()+e;for(;Date.now()<s;){if((this.latestTokenUsage?.observedAtMs??0)>t)return;await wt(50)}}resolveSessionAlias(t){if(!t)return null;let e=t;const s=new Set;for(;this.sessionAliases.has(e)&&!s.has(e);)s.add(e),e=this.sessionAliases.get(e)??e;return e}contextState(){return{usage:this.latestTokenUsage,rolloutBytes:G(this.currentRolloutPath),turnCount:this.threadTurnCount}}maintenanceResult(t){return{stage:t.stage,action:t.action,reasons:t.reasons,previousThreadId:t.previousThreadId??null,sessionId:this.currentSessionId,before:t.beforeState,after:t.afterState??null,compactError:t.compactError||null,rolloverRecommended:t.rolloverRecommended===!0}}notifyContextStatus(t,e){this.notify("session/update",{sessionId:this.currentSessionId,update:{sessionUpdate:"aca_context_maintenance",label:t,detail:e}})}notifyControlToolProgress(){const t=this.controlToolLoop.recordProgress();t&&this.notifySessionUpdate(q(t))}maybeSteerControlToolLoop(t){if(!it(t.detection,this.controlToolPolicy))return;const e=ot(t.detection,this.controlToolPolicy),s=this.controlToolSteering.next(t,e.warningCalls);if(!s)return;const{threadId:o,turnId:i}=t.detection;!o||!i||this.requireRpc().request("turn/steer",{threadId:o,expectedTurnId:i,clientUserMessageId:`aca-loop-steer-${Date.now()}-${Math.random().toString(16).slice(2)}`,input:[{type:"text",text:s.text,text_elements:[]}]},6e4).then(()=>{this.notifySessionUpdate(U(t,s,{succeeded:!0}))}).catch(r=>{this.notifySessionUpdate(U(t,s,{succeeded:!1,error:r}))})}write(t){process.stdout.write(`${JSON.stringify(t)}
|
|
4
|
-
|
|
1
|
+
import{UserSteeringLedger as L}from"./user-steering.js";import{createToolLifecycleTrace as x}from"../../tool-lifecycle-trace.js";import{JsonLineRpcClient as $}from"../../client/json-rpc.js";import{startCodexAppServer as O}from"../codex/codex-process.js";import{CodexConnectionRecoveryTracker as j}from"../codex/connection-events.js";import{codexAcpSessionUpdatesFromNotification as y}from"../codex/events.js";import{CodexPlanDeltaState as k}from"../codex/plan-events.js";import{isCodexApprovalRequest as B,resolveCodexApprovalRequest as K}from"../codex/permissions.js";import{buildCodexContinuityInstructions as W,codexTokenUsageFromRawResponseCompleted as z,codexTokenUsageFromNotification as G,decideCodexContextMaintenance as v,readCodexContextMaintenancePolicy as R,rolloutFileSize as J}from"./context-maintenance.js";import{codexNotificationBelongsToCurrentTurn as H,codexNotificationRouting as A}from"./notification-routing.js";import{completedSubtaskHistoryUpdates as V}from"./subtask-history.js";import{CodexSubtaskRouter as X}from"./subtask-routing.js";import{decorateSubtaskUpdate as P}from"./subtask-executions.js";import{decorateSubtaskCompletion as Q}from"./subtask-completion.js";import{waitForCancellableTurnStart as Y}from"./cancellation.js";import{codexAcpUpdateIsMeaningfulProgress as _,codexRawItemIsMeaningfulProgress as Z,withCodexProgressMetadata as M}from"./progress-evidence.js";import{acpPromptText as ee,acpPromptToCodexInput as te,turnConfigFromConfig as se}from"./turn-input.js";import{CodexControlToolLoopDetector as ie,codexControlToolLoopAcpUpdate as U,controlToolLoopDetectionThresholds as oe,controlToolLoopReady as ne,readCodexControlToolLoopPolicy as re}from"./control-tool-loop.js";import{CodexControlToolSteeringLimiter as ae,codexControlToolSteeringAcpUpdate as q}from"./control-tool-steering.js";class ce{toolTrace=x();lineBuffer="";started=null;rpc=null;codexStartupPromise=null;initialized=!1;currentSessionId=null;currentCwd=process.cwd();promptActive=!1;currentTurnId=null;userSteering=new L;turnStartPromise=null;promptCancelled=!1;nextParentRequestId=1;pendingParentRequests=new Map;completedTurns=new Map;pendingTurnCompletions=new Map;agentMessagePhases=new Map;planDeltas=new k;subtaskRouter=new X;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 j;latestTokenUsage=null;currentRolloutPath=null;threadTurnCount=0;currentPromptText="";currentAssistantText="";previousUserText="";previousAssistantText="";previousAffectedFiles=new Set;maintenanceActive=!1;pendingCompaction=null;shutdownPromise=null;managedProvider=null;controlToolLoop;controlToolPolicy=re();controlToolSteering=new ae(this.controlToolPolicy.steeringMaxAttempts);constructor(){this.controlToolLoop=new ie({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:-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
|
+
`),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
|
+
|
|
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=u(t,"baseUrl"),i=u(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=u(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=u(e,"sessionId");if(!t)throw new Error("sessionId is required");const s=u(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=u(e,"sessionId");if(!t)throw new Error("sessionId is required");const s=u(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=u(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(he(r),this.promptCancelled||this.shutdownPromise)throw new Error("Codex prompt cancelled");s=this.currentSessionId||s,this.toolTrace=x(),this.promptActive=!0,this.currentTurnId=null,this.controlToolLoop.reset(),this.controlToolSteering.reset(),this.currentPromptText=ee(i),this.currentAssistantText="",this.currentAffectedFiles.clear(),this.connectionRecovery.reset(),this.agentMessagePhases.clear(),this.planDeltas.reset(),this.pendingSubtaskNotifications=[],this.flushSubtaskStates();let d;try{this.turnStartPromise=o.request("turn/start",{threadId:s,clientUserMessageId:`aca-acp-${Date.now()}-${Math.random().toString(16).slice(2)}`,input:te(i),cwd:this.currentCwd,...se(this.config)},D()),d=await this.turnStartPromise;const c=String(d?.turn?.id??"");this.currentTurnId=c||null,c&&this.notifySessionUpdate({sessionUpdate:"aca_supplement_target",ready:!this.completedTurns.has(c),threadId:s,turnId:c});const l=c?await this.waitForTurnCompleted(c,D()):d?.turn;this.notifySessionUpdate({sessionUpdate:"aca_supplement_target",ready:!1});const h=await this.subtaskRouter.waitForIdle(Se());if(h.timedOut&&h.activeThreadIds.length>0&&this.notifySessionUpdate({sessionUpdate:"aca_codex_subtask_drain",label:"子任务收尾等待超时",detail:`仍有 ${h.activeThreadIds.length} 个子任务未结束`,_meta:{aca:{subtaskDrain:h}}}),this.promptCancelled)throw new Error("Codex prompt cancelled");this.threadTurnCount+=1,this.previousUserText=this.currentPromptText,this.previousAssistantText=this.currentAssistantText,this.previousAffectedFiles.clear();for(const p of this.currentAffectedFiles)this.previousAffectedFiles.add(p);const f=await this.maintainContext("after",{userRequest:this.previousUserText,assistantResponse:this.previousAssistantText,affectedFiles:[...this.previousAffectedFiles]},{allowRollover:!0});return{stopReason:"end_turn",sessionId:this.currentSessionId,turn:l,_meta:{acaContextUsage:E(f)??le(this.latestTokenUsage),acaContextMaintenance:f,acaSubtaskDrain:h}}}catch(c){const l=this.connectionRecovery.fail(c);throw l&&this.notifySessionUpdate(l),c}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(d=>{if(o!==this.subtaskGeneration)return;this.subscribedSubtaskThreadIds.add(s);const c=this.subtaskRouter.restoreFromSessionSnapshot(d,this.currentSessionId);t&&this.hydrateSubtaskHistory(d,e),c>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(u(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 Y(this.turnStartPromise):null,i=this.currentTurnId||u(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=u(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=R(),o=await this.maintainContext("manual",{userRequest:this.previousUserText,assistantResponse:this.previousAssistantText,affectedFiles:[...this.previousAffectedFiles]},{force:!0,allowRollover:e.allowRollover===!0,compactTimeoutMs:de(e,i.compactTimeoutMs)}),r=String(o.action||"");if(r==="compact_failed"||r==="rollover_failed"){const d=String(o.compactError||"Codex 上下文压缩未完成");throw new Error(`Codex 上下文压缩失败:${d}`)}return{sessionId:this.currentSessionId,maintenance:o,contextUsage:E(o)}}async recoverSession(e){const t=u(e,"sessionId")||this.currentSessionId,s=this.resolveSessionAlias(t);if(!s)throw new Error("sessionId is required");const i=u(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=O(e,this.managedProvider),i=new $(s.child,{peerName:"Codex app-server",onNotification:(o,r)=>this.handleCodexNotification(o,r),onRequest:async(o,r)=>this.handleCodexRequest(o,r)});this.started=s,this.rpc=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 C(this.started.child,1e3),this.rpc=null,this.started=null,s}finally{this.codexStartupPromise===t&&(this.codexStartupPromise=null)}}handleCodexNotification(e,t){const s=A(t);if(e==="item/started"||e==="item/completed"){const c=a(a(t).item);this.toolTrace("provider_notification",{method:e,threadId:s.threadId,turnId:s.turnId,toolCallId:c.id,itemType:c.type,status:c.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(!H(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 c=this.subtaskRouter.routeFor(s.threadId);if(!c){(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,c),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"?fe(this.controlToolLoop,t):[],d=e==="rawResponse/completed"?pe(t):e==="rawResponseItem/completed"?r.map(({update:c})=>c):y(e,t,{agentMessagePhases:this.agentMessagePhases,planDeltas:this.planDeltas});for(const c of r)this.maybeSteerControlToolLoop(c.event);for(const c of[...o,...d]){const l=M(c,e,t,this.agentMessagePhases);_(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")&&Z(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 d=this.subtaskPlanDeltas.get(o)??new k;this.subtaskPlanDeltas.set(o,d),this.captureAgentMessagePhase(e,t,r);const c=this.subtaskRouter.observe(t,this.currentSessionId);this.flushSubtaskStates(),c>0&&this.subscribeKnownSubtasks(!0);const l=y(e,t,{agentMessagePhases:r,planDeltas:d}),h=A(t).turnId;for(const f of l){const p=M(f,e,t,r);if(!me(p))continue;u(p,"title")&&this.subtaskRouter.executions.action(s.sourceThreadId,u(p,"title")),_(p)&&(this.notifyControlToolProgress(),this.subtaskRouter.executions.progress(s.sourceThreadId,u(p,"title")||"收到执行结果"));const g=P(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 V(e)){const i=N(t.sourceThreadId,s.itemId);if(this.seenSubtaskItemIds.has(i))continue;this.seenSubtaskItemIds.add(i);const o=P(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=u(i,"id")||u(s,"itemId");o&&this.seenSubtaskItemIds.add(N(e,o))}bufferPendingSubtaskNotification(e,t,s){this.pendingSubtaskNotifications.push({method:e,params:t,threadId:s}),this.pendingSubtaskNotifications.length>200&&this.pendingSubtaskNotifications.shift()}flushPendingSubtaskNotifications(){if(this.pendingSubtaskNotifications.length===0)return;let e=!0;for(;e&&this.pendingSubtaskNotifications.length>0;){e=!1;const t=[];for(const s of this.pendingSubtaskNotifications){const 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=Q(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){if(B(e))return K(e,t,async s=>{const i=await this.requestParent("session/request_permission",{sessionId:this.currentSessionId,request:{...s,params:t,method:e}}),o=a(i),r=a(o.outcome);return r.outcome==="selected"&&typeof r.optionId=="string"?{outcome:{outcome:"selected",optionId:r.optionId}}:{outcome:{outcome:"cancelled"}}});if(ge(e)){const s=await this.requestParent("elicitation/create",{sessionId:this.currentSessionId,request:{...a(t),method:e}}),i=a(s),o=a(i.outcome);return o.outcome==="submitted"?{outcome:{outcome:"submitted",value:typeof o.value=="string"?o.value:""}}:{outcome:{outcome:"cancelled"}}}return{}}captureAgentMessagePhase(e,t,s=this.agentMessagePhases){if(e!=="item/started"&&e!=="item/completed")return;const i=S(t,"item");if(u(i,"type")!=="agentMessage")return;const o=u(i,"id");o&&s.set(o,u(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=u(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),u(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 C(e,2e3)&&(e.kill("SIGTERM"),!await C(e,1e3)&&(e.kill("SIGKILL"),await C(e,1e3))))}sessionResponse(e){return{sessionId:e,modes:{currentModeId:"workspace-write",availableModes:[{id:"read-only",name:"Read Only"},{id:"workspace-write",name:"Workspace Write"},{id:"danger-full-access",name:"Full Access"}]},configOptions:[{id:"model",category:"model",type:"string"},{id:"approvalPolicy",category:"approval",type:"string"},{id:"sandbox",category:"mode",type:"string"}]}}requireRpc(){if(!this.rpc)throw new Error("Codex app-server is not started");return this.rpc}notify(e,t){this.write({jsonrpc:"2.0",method:e,params:t})}requestParent(e,t,s=600*1e3){const i=this.nextParentRequestId++;return new Promise((o,r)=>{const d=setTimeout(()=>{this.pendingParentRequests.delete(i),r(new Error(`ACP client request timed out: ${e}`))},s);d.unref(),this.pendingParentRequests.set(i,{resolve:o,reject:r,timer:d}),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(u(i,"message")||"ACP client request failed"));return}s.resolve(e.result)}}setConfigOption(e){const t=u(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=u(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{...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=G(t)??this.latestTokenUsage),e==="thread/compacted"&&this.pendingCompaction&&this.resolveCompaction(),e==="item/completed"){const s=S(t,"item"),i=u(a(t),"threadId");u(s,"type")==="contextCompaction"&&(!i||i===this.currentSessionId)&&this.resolveCompaction(),u(s,"type")==="agentMessage"&&u(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=u(t,"path")||null,this.threadTurnCount=Array.isArray(t.turns)?t.turns.length:0;const s=ue(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=R(),o=this.contextState(),r=v(o,i);if(!r.compact&&!s.force||!this.currentSessionId)return this.maintenanceResult({stage:e,action:"none",reasons:r.reasons,beforeState:o});const d=this.currentSessionId;this.notifyContextStatus("正在整理会话上下文",F(o)),this.maintenanceActive=!0;let c=!1,l="",h=o,f=r,p=!1,g="";try{const m=o.usage?.observedAtMs??0;await this.compactThread(d,s.compactTimeoutMs??i.compactTimeoutMs),await this.waitForTokenUsageAfter(m,2e3)}catch(m){c=!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=v(h,i,{compactAttempted:!0,compactFailed:c}),f.rollover&&s.allowRollover!==!1){this.notifyContextStatus("正在创建轻量会话",f.reasons.join(", "));try{await this.rotateThread(d,t),p=!0}catch(m){g=m instanceof Error?m.message:String(m),this.notifyContextStatus("轻量会话创建失败",g)}}else c||this.notifyContextStatus("会话上下文已压缩",F(h))}finally{this.maintenanceActive=!1}return this.maintenanceResult({action:p?"rollover":g?"rollover_failed":c?"compact_failed":"compact",stage:e,reasons:f.reasons,beforeState:o,afterState:h,compactError:[l,g].filter(Boolean).join("; "),previousThreadId:d,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=W({previousThreadId:e,cwd:this.currentCwd,userRequest:t.userRequest,assistantResponse:t.assistantResponse,affectedFiles:t.affectedFiles}),i=await this.requireRpc().request("thread/start",{cwd:this.currentCwd,approvalsReviewer:"user",...this.codexConfig(),developerInstructions:s,ephemeral:!1,experimentalRawEvents:!0,threadSource:"aca-codex-acp-rollover"},6e4),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 we(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:J(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(U(e))}maybeSteerControlToolLoop(e){if(!ne(e.detection,this.controlToolPolicy))return;const t=oe(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(q(e,s,{succeeded:!0}))}).catch(r=>{this.notifySessionUpdate(q(e,s,{succeeded:!1,error:r}))})}write(e){process.stdout.write(`${JSON.stringify(e)}
|
|
7
|
+
`)}}function T(n){const e=S(n,"thread");return u(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=u(t,"type").toLowerCase(),i=u(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=u(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 ue(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 d=a(r),c=u(d,"type").toLowerCase();c==="usermessage"&&(s=w(d)||s),c==="agentmessage"&&(i=w(d)||i);for(const l of["items","content","input"])d[l]&&o(d[l])};return o(e),{userRequest:s,assistantResponse:i,affectedFiles:[...t]}}function de(n,e){const t=b(n,"timeoutMs");return!Number.isFinite(t)||t<=0?e:Math.max(3e4,Math.min(59e4,t-1e4))}function E(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,d=b(r,"contextWindow");if(e==="rollover")return{usedTokens:0,inputTokens:0,contextWindow:d,ratio:0,observedAtMs:Date.now(),state:"new_thread",maintenanceAction:e};if(Object.keys(r).length===0)return null;const c=b(r,"totalTokens"),l=b(r,"inputTokens");return{usedTokens:c||l,inputTokens:l,contextWindow:d,ratio:d>0?(c||l)/d:0,observedAtMs:b(r,"observedAtMs")||Date.now(),state:e==="compact"?"compacted":"active",maintenanceAction:e}}function le(n){return n?{usedTokens:n.totalTokens||n.inputTokens,inputTokens:n.inputTokens,contextWindow:n.contextWindow,ratio:n.contextWindow>0?(n.totalTokens||n.inputTokens)/n.contextWindow:0,observedAtMs:n.observedAtMs,state:"active",maintenanceAction:null}:null}function F(n){const 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 he(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 pe(n){return z(n)?[{sessionUpdate:"aca_codex_raw_response_usage",_meta:{codex:{rawResponse:n}}}]:[]}function fe(n,e){const t=n.observeRawResponseItem(e);return t?[{event:t,update:U(t)}]:[]}function me(n){return["agent_message_chunk","agent_progress_chunk","agent_thought_chunk","tool_call","tool_call_update","plan","plan_update","plan_removed"].includes(String(n.sessionUpdate||""))}function N(n,e){return`${n}:${e}`}function u(n,e){const s=a(n)[e];return typeof s=="string"?s:""}function ge(n){const e=n.toLowerCase();return e.includes("elicitation")||e.includes("requestuserinput")||e.includes("request_user_input")||e.includes("input/request")}function D(){const n=Number.parseInt(process.env.ACA_ACP_PROMPT_TIMEOUT_MS??"",10);return Number.isInteger(n)&&n>=3e4?n:720*60*1e3}function Se(){const n=Number.parseInt(process.env.ACA_CODEX_SUBTASK_DRAIN_TIMEOUT_MS??"",10);return Number.isInteger(n)&&n>=1e3?Math.min(n,600*1e3):600*1e3}function C(n,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 we(n){return new Promise(e=>setTimeout(e,n))}function Ie(){new ce}const be=process.argv[1]||"";/(?:^|[\\/])adapter\.(?:js|ts)$/.test(be)&&Ie();export{Ie as runCodexAcpAdapter};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import _ from"node:fs";import{runAbortableRequest as L}from"../../client/abortable-request.js";import{BoundedItemBuffer as B}from"../../client/bounded-buffer.js";import{promptBlocksToAcpContent as K}from"../../client/acp-content.js";import{acpSessionUpdateFromParams as X,eventFromAcpSessionUpdate as Y,statusFromAcpSessionUpdate as G,subtaskContextFromAcpSessionUpdate as J}from"../../client/acp-events.js";import{defaultAcpRuntimePool as E}from"../../client/acp-runtime-pool.js";import{resolveCodexAcpLaunch as D}from"./launch.js";import{cancelCodexRuntime as Q}from"./cancellation.js";import{CodexPromptPerformanceTracker as Z,CodexUsageLoopDetector as U,codexRawResponseUsageFromAcpSessionUpdate as ee,codexTokenUsageFromAcpSessionUpdate as oe,readCodexContextMaintenancePolicy as q}from"./context-maintenance.js";import{ensureCodexProviderRuntimeConfig as $}from"../../../core/codex-provider-runtime-config.js";import{codexControlToolLoopFromAcpSessionUpdate as te,controlToolLoopDetectionThresholds as se,controlToolLoopReady as re,readCodexControlToolLoopPolicy as ne}from"./control-tool-loop.js";import{attachCodexDiagnostics as ie,CodexTurnDiagnosticsTracker as ae}from"./turn-diagnostics.js";import{AcpSessionInvalidatedError as O}from"../../session-errors.js";import{codexAcpUpdateIsMeaningfulProgress as ce}from"./progress-evidence.js";const le={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 De{id="codex-acp";name="Codex ACP";capabilities=le;async createSession(e){return new de(e.cwd,e.providerSessionId??null,e.timeoutMs)}async forkSession(e){if(!_.existsSync(e.cwd)||!_.statSync(e.cwd).isDirectory())throw new Error(`Project root does not exist or is not a directory: ${e.cwd}`);const t=e.sourceProviderSessionId.trim(),r=await $(),a=await E.acquire({providerId:"codex-acp",cwd:e.cwd,providerSessionId:t,runtimeIdentity:r?.runtimeIdentity||"default",env:N(r),...D(),onSessionUpdate:()=>{}});try{await W(a,r),a.initialized||(await a.client.initialize(6e4),a.markInitialized()),e.onStatus?.({phase:"forking",label:"正在分叉 Codex 会话",detail:t,updateType:"session/fork",atMs:Date.now()});const n=await a.client.forkSession({sessionId:t,cwd:e.cwd,config:F(e)},e.timeoutMs??12e4),o=b(n);if(!o)throw new Error("Codex ACP session/fork did not return sessionId");return{providerSessionId:o}}finally{a.release()}}}class de{cwd;providerSessionId;defaultTimeoutMs;lease=null;sessionResponse=null;statusCallback=null;updateCallback=null;permissionCallback=null;userInputCallback=null;abortHandler=null;statusTimeline=[];updates=new B(16);content="";performanceTracker=null;usageLoopDetector=null;usagePatternReport=null;controlToolPatternReport=null;turnDiagnosticsTracker=null;usageLoopWarningActive=!1;controlToolLoopWarningActive=!1;controlToolLoopInterrupting=!1;cancellation=null;constructor(e,t,r=we()){this.cwd=e,this.providerSessionId=t,this.defaultTimeoutMs=r}async sendPrompt(e){if(!_.existsSync(e.cwd)||!_.statSync(e.cwd).isDirectory())throw new Error(`Project root does not exist or is not a directory: ${e.cwd}`);const t=Date.now();this.statusCallback=e.onStatus??null,this.updateCallback=e.onUpdate??null,this.permissionCallback=e.onPermissionRequest??null,this.userInputCallback=e.onUserInputRequest??null,this.attachAbortSignal(e.signal),this.content="",this.updates.clear(),this.statusTimeline.length=0;const r=q(),a=ne();this.performanceTracker=new Z(t),this.usageLoopDetector=new U(r);const n=new ae;this.turnDiagnosticsTracker=n,this.usageLoopWarningActive=!1,this.controlToolLoopWarningActive=!1,this.controlToolLoopInterrupting=!1;let o={error:null,stopping:null,detection:null,controlDetection:null,interruption:null,kind:null},i=null,c=null,w=this.providerSessionId,x,k=null;try{this.emitStatus("initializing","Codex ACP 启动中","codex-acp");const m=await this.acquireRuntime(e.cwd);this.lease=m,i=m.client,c=m.started,await this.initialize(m),w=await this.establishSession(m,e),await this.applyConfig(i,w,e);const S=i,V=K(e.prompt,e.promptBlocks);let T=!1;for(this.emitStatus("thinking","思考中"),k=this.startSlowStatusTimer();;){const f=w;o={error:null,stopping:null,detection:null,controlDetection:null,interruption:null,kind:null},this.usageLoopDetector=new U(r),this.usageLoopWarningActive=!1,this.controlToolLoopWarningActive=!1,this.controlToolLoopInterrupting=!1,this.usagePatternReport=u=>{if(o.error)return;const v=Math.round(u.maxInputTokens/1e3),g=`同一 Turn 已连续请求至少 ${u.sampleCount} 次,单次输入约 ${v}k tokens`;if(n.recordUsageLoop(u.sampleCount,u.detectedAtMs,g),u.sampleCount<r.usageLoopInterruptSamples){this.usageLoopWarningActive=!0,this.emitStatus("thinking","Codex 出现低产出内部循环",g,"aca.performance.low_output_loop");return}const C=`Codex 低产出内部循环:同一 Turn 已连续请求至少 ${u.sampleCount} 次,单次输入约 ${v}k tokens,且未观察到有效进展`;o.error=new Error(C),o.detection=u,o.kind="low_output_loop",this.emitStatus("thinking","正在中断 Codex 低产出内部循环",g,"aca.performance.low_output_loop_interrupting"),o.stopping=this.stopRuntime(m,f).then(l=>{if(o.interruption=l,n.recordInterrupt({acknowledged:!l.forced,forcedRuntimeClose:l.forced,reason:l.reason}),l.forced){const d=l.reason||"取消请求失败";o.error=new Error(`${C};取消请求失败:${d};已关闭运行实例`)}}).catch(l=>{const d=l instanceof Error?l.message:String(l);n.recordInterrupt({acknowledged:!1,forcedRuntimeClose:!1,reason:d}),o.error=new Error(`${C};${d}`),this.emitStatus("failed","Codex 循环中断失败",d,"aca.performance.low_output_loop_interrupt_failed")})},this.controlToolPatternReport=u=>{if(o.error||!re(u,a))return;const v=se(u,a);if(u.sampleCount<v.warningCalls)return;const g=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",C=Math.max(1,Math.round(u.elapsedMs/6e4)),l=`同一 Turn 已连续 ${u.sampleCount} 次执行 ${g},持续约 ${C} 分钟`;if(n.recordControlLoop(u,l),u.sampleCount<v.interruptCalls){this.controlToolLoopWarningActive=!0,this.emitStatus("thinking","Codex 出现无进展控制工具循环",l,"aca.performance.control_tool_loop");return}const d=`Codex 无进展控制工具循环:${l}`;o.error=new Error(d),o.controlDetection=u,o.kind="control_tool_loop",this.controlToolLoopInterrupting=!0,this.emitStatus("thinking","正在中断 Codex 无进展控制工具循环",l,"aca.performance.control_tool_loop_interrupting"),o.stopping=this.stopRuntime(m,f).then(p=>{o.interruption=p,n.recordInterrupt({acknowledged:!p.forced,forcedRuntimeClose:p.forced,reason:p.reason});const I=p.forced?`${l};取消请求失败:${p.reason};已关闭运行实例`:l;p.forced&&(o.error=new Error(`${d};取消请求失败:${p.reason};已关闭运行实例`)),this.emitStatus("failed","Codex 已中断无进展控制工具循环",I,"aca.performance.control_tool_loop_interrupted")}).catch(p=>{const I=p instanceof Error?p.message:String(p);n.recordInterrupt({acknowledged:!1,forcedRuntimeClose:!1,reason:I}),o.error=new Error(`${d};${I}`),this.emitStatus("failed","Codex 控制工具循环中断失败",I,"aca.performance.control_tool_loop_interrupt_failed")})};let P;try{x=await this.promptWithAbort(S,{sessionId:f,prompt:V,timeoutMs:e.timeoutMs??this.defaultTimeoutMs,signal:e.signal})}catch(u){P=u}if(await o.stopping,o.error){const u=o.detection;if(o.kind==="low_output_loop"&&u?.source==="thread_usage"&&!T&&o.interruption?.forced===!1&&!e.signal?.aborted){T=!0,this.emitStatus("resuming","正在迁移异常 Codex 会话","保留本轮进度并创建干净线程","aca.performance.low_output_loop_recovering");try{const l=await S.recoverSession({sessionId:f,reason:"low_output_loop"}),d=b(l);if(!d||d===f)throw new Error("Codex recovery did not create a new session");w=d,this.providerSessionId=d,this.sessionResponse=h(l),m.setProviderSession({providerSessionId:d,sessionResponse:this.sessionResponse});const p={previousProviderSessionId:f,providerSessionId:d,reason:"low_output_loop",migratedAtMs:Date.now()};n.recordMigration(p),await e.onProviderSessionMigration?.(p),n.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);o.error=new Error(`${o.error.message};自动迁移失败:${d}`)}}if(o.kind==="low_output_loop")throw this.emitStatus("failed","Codex 已中断低产出内部循环",o.error.message,"aca.performance.low_output_loop_interrupted"),new O(o.error.message,f,{source:u?.source??null,sampleCount:u?.sampleCount??null,recoveryAttempted:T});const g=o.controlDetection;if(o.interruption?.forced===!1&&!e.signal?.aborted){this.emitStatus("resuming","正在迁移异常 Codex 会话","保留本轮证据并为后续任务创建干净线程","aca.performance.control_tool_loop_migrating");try{const l=await S.recoverSession({sessionId:f,reason:"control_tool_loop"}),d=b(l);if(!d||d===f)throw new Error("Codex recovery did not create a new session");w=d,this.providerSessionId=d,this.sessionResponse=h(l),m.setProviderSession({providerSessionId:d,sessionResponse:this.sessionResponse});const p={previousProviderSessionId:f,providerSessionId:d,reason:"control_tool_loop",migratedAtMs:Date.now()};throw n.recordMigration(p),await e.onProviderSessionMigration?.(p),this.emitStatus("failed","Codex 循环已中断,会话已自动迁移",d,"aca.performance.control_tool_loop_migrated_session"),ue(o.error,d)}catch(l){if(pe(l))throw l;const d=l instanceof Error?l.message:String(l);o.error=new Error(`${o.error.message};自动迁移失败:${d}`)}}throw new O(o.error.message,f,{source:"control_tool_loop",kind:g?.kind??null,sampleCount:g?.sampleCount??null,segmentId:g?.segmentId??null})}if(P)throw P;const y=z(x);y&&y!==w&&(w=y,this.providerSessionId=y,m.setProviderSession({providerSessionId:y,sessionResponse:this.sessionResponse}));break}}catch(m){if(m instanceof Error){const S=ie(m,n.snapshot());throw S.statusTimeline=[...this.statusTimeline],S.performance=this.performanceTracker?.snapshot(),S}throw m}finally{await o.stopping,await this.cancellation?.catch(()=>{}),k&&clearInterval(k),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:w,cliType:"builtin",agentType:"codex",effectiveConfig:{model:e.model??null,mode:e.mode??null,configOptionValues:e.configOptionValues??null,command:c.command,processPid:c.child.pid??null,provider:"codex-acp",protocol:"acp"},command:c.command,args:c.args,cwd:e.cwd,exitCode:c.child.exitCode,signal:c.child.signalCode,durationMs:Date.now()-t,stdout:i.rpc.stdout,stderr:i.rpc.stderr,content:this.content.trim(),updateCount:this.updates.count(),updates:this.updates.snapshot(),statusTimeline:[...this.statusTimeline],performance:this.performanceTracker?.snapshot(),contextUsage:ve(x),providerDiagnostics:n.snapshot(),promptResponse:x}}async compactContext(e){if(!_.existsSync(e.cwd)||!_.statSync(e.cwd).isDirectory())throw new Error(`Project root does not exist or is not a directory: ${e.cwd}`);const t=Date.now();this.statusCallback=e.onStatus??null,this.updateCallback=e.onUpdate??null,this.attachAbortSignal(e.signal);let r=this.providerSessionId,a;try{this.emitStatus("initializing","Codex ACP 启动中","codex-acp");const o=await this.acquireRuntime(e.cwd);this.lease=o,await this.initialize(o),r=await this.establishSession(o,e),await this.applyConfig(o.client,r,e);const i=Se(e.timeoutMs);a=await this.compactWithAbort(o.client,{sessionId:r,allowRollover:e.allowRollover===!0,timeoutMs:i,signal:e.signal});const c=z(a);c&&c!==r&&(r=c,this.providerSessionId=c,o.setProviderSession({providerSessionId:c,sessionResponse:this.sessionResponse}))}catch(o){if(this.lease&&me(o,e.signal)){const i=this.lease;this.lease=null,await i.dispose().catch(()=>{})}throw this.emitStatus("failed","上下文压缩失败",o instanceof Error?o.message:String(o)),o}finally{this.abortHandler&&e.signal&&(e.signal.removeEventListener("abort",this.abortHandler),this.abortHandler=null),this.releaseLease()}const n=h(a)??{};return{providerSessionId:r||this.providerSessionId||"",contextUsage:H(n.contextUsage),maintenance:h(n.maintenance)??{},durationMs:Date.now()-t}}async cancel(){this.providerSessionId&&this.lease&&await this.stopRuntime(this.lease,this.providerSessionId)}stopRuntime(e,t){if(this.cancellation)return this.cancellation;const r=Q(e,t);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 t=D(),r=await $(),a=await E.acquire({providerId:"codex-acp",cwd:e,providerSessionId:this.providerSessionId,runtimeIdentity:r?.runtimeIdentity||"default",env:N(r),command:t.command,args:t.args,onSessionUpdate:n=>this.handleSessionUpdate(n),onClientRequest:async(n,o)=>{if(n==="session/request_permission")return this.requestPermission(o);if(n==="elicitation/create"||n==="session/request_user_input")return this.requestUserInput(o);throw new Error(`Unsupported Codex ACP client request: ${n}`)}});try{return await W(a,r),a}catch(n){throw await a.dispose().catch(()=>{}),n}}async initialize(e){e.initialized||(await e.client.initialize(6e4),e.markInitialized())}async establishSession(e,t){const r=e.client,a=F(t);if(e.providerSessionId&&e.sessionEstablished)return this.providerSessionId=e.providerSessionId,this.sessionResponse=e.sessionResponse,e.providerSessionId;if(this.providerSessionId)try{this.emitStatus("resuming","Codex ACP 会话恢复中","session/load");const i=await r.loadSession({sessionId:this.providerSessionId,cwd:t.cwd,config:a},6e4);this.sessionResponse=h(i);const c=b(i)||this.providerSessionId;return this.providerSessionId=c,e.setProviderSession({providerSessionId:c,sessionResponse:this.sessionResponse}),c}catch(i){if(t.requireSessionResume){const c=i instanceof Error?i.message:String(i);throw new Error(`无法恢复导入的 Codex ACP Session ${this.providerSessionId}:${c}`)}this.providerSessionId=null}this.emitStatus("acp","Codex ACP 会话创建中","session/new");const n=await r.newSession({cwd:t.cwd,config:a},12e4);this.sessionResponse=h(n);const o=b(n);if(!o)throw new Error("Codex ACP session/new did not return sessionId");return this.providerSessionId=o,e.setProviderSession({providerSessionId:o,sessionResponse:this.sessionResponse}),o}async applyConfig(e,t,r){r.model&&await e.setSessionConfigOption({sessionId:t,configId:"model",value:r.model},6e4).catch(()=>{});const a=M(r.mode);a&&await e.setSessionConfigOption({sessionId:t,configId:"approvalPolicy",value:a},6e4).catch(()=>{});const n=R(r.mode);n&&await e.setSessionConfigOption({sessionId:t,configId:"sandbox",value:n},6e4).catch(()=>{});for(const[o,i]of Object.entries(r.configOptionValues??{}))["model","mode","sandbox","approvalPolicy"].includes(o)||await e.setSessionConfigOption({sessionId:t,configId:o,value:i},6e4).catch(()=>{})}handleSessionUpdate(e){const t=X(e),r=String(t?.update.sessionUpdate||""),a=J(e);if(!a&&r==="aca_codex_subtask_drain"){const i=String(t?.update.label||"子任务收尾等待超时"),c=String(t?.update.detail||"仍有子任务未结束");this.emitStatus("exploring",i,c,r)}else if(!a&&r==="aca_codex_control_tool_loop"){const i=te(e);i?.state==="observed"&&this.controlToolPatternReport?.(i.detection),i?.state==="recovered"&&this.controlToolLoopWarningActive&&!this.controlToolLoopInterrupting&&(this.controlToolLoopWarningActive=!1,this.turnDiagnosticsTracker?.recordRecovery(),this.emitStatus("thinking","Codex 已恢复执行","无进展控制工具循环已结束","aca.performance.control_tool_loop_recovered"))}else if(!a&&r==="aca_codex_control_tool_steer"){const i=he(t?.update);i&&this.turnDiagnosticsTracker?.recordControlToolSteering(i)}else if(ce(t?.update))this.recordMeaningfulProgress();else if(!a&&r==="aca_codex_raw_response_usage"){const i=ee(e);i&&this.turnDiagnosticsTracker?.recordUsage(i);const c=i?this.usageLoopDetector?.recordUsage(i):null;c&&this.usagePatternReport?.(c)}else if(!a&&r==="usage_update"){const i=oe(e);i&&this.turnDiagnosticsTracker?.recordUsage(i);const c=i?this.usageLoopDetector?.recordUsage(i):null;c&&this.usagePatternReport?.(c)}this.updates.push({method:"session/update",params:e});const n=Y(e);n&&(n.type==="agent_message_chunk"&&!a&&(this.content+=n.text??""),this.updateCallback?.(n));const o=G(e);o&&this.emitStatus(o.phase,o.label,o.detail,o.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=q().slowEventThresholdMs,t=setInterval(()=>{const r=this.performanceTracker,a=Date.now();if(!r?.shouldReportSlow(a,e))return;const n=r.snapshot(a),o=Math.max(1,Math.round(n.silentForMs/6e4)),i=this.usageLoopWarningActive||this.controlToolLoopWarningActive?"Codex 可能卡在内部循环":"Codex 长时间没有有效进展";this.emitStatus("thinking",i,`已 ${o} 分钟没有执行工具或产生有效输出`,"aca.performance.slow")},Math.min(3e4,Math.max(1e3,Math.floor(e/4))));return t.unref(),t}attachAbortSignal(e){if(e){if(this.abortHandler=()=>{this.cancel().catch(()=>{})},e.aborted)throw new Error("Codex ACP prompt cancelled");e.addEventListener("abort",this.abortHandler,{once:!0})}}async promptWithAbort(e,t){return L({signal:t.signal,cancelledMessage:"Codex ACP prompt cancelled",startRequest:()=>e.prompt({sessionId:t.sessionId,prompt:t.prompt},t.timeoutMs),onAbort:async()=>{await this.lease?.dispose()}})}async compactWithAbort(e,t){return L({signal:t.signal,cancelledMessage:"Codex ACP 上下文压缩已取消",startRequest:()=>e.compactSession({sessionId:t.sessionId,allowRollover:t.allowRollover,timeoutMs:t.timeoutMs},t.timeoutMs),onAbort:async()=>{await this.lease?.dispose()}})}async requestPermission(e){if(!this.permissionCallback)return{outcome:{outcome:"cancelled"}};const t=e&&typeof e=="object"&&!Array.isArray(e)?e:{},r=t.request&&typeof t.request=="object"&&!Array.isArray(t.request)?t.request:t,n=(Array.isArray(r.options)?r.options:[]).map((o,i)=>{const c=o&&typeof o=="object"&&!Array.isArray(o)?o:{};return{optionId:String(c.optionId??c.id??i),kind:String(c.kind??"choice"),...typeof c.label=="string"?{label:c.label}:{}}});return this.emitStatus("requestPermission","等待授权",fe(r),"session/request_permission"),this.permissionCallback({requestId:String(r.requestId??r.id??`codex-acp-permission-${Date.now()}`),params:e,options:n})}async requestUserInput(e){if(!this.userInputCallback)return{outcome:{outcome:"cancelled"}};const t=e&&typeof e=="object"&&!Array.isArray(e)?e:{},r=t.request&&typeof t.request=="object"&&!Array.isArray(t.request)?t.request:t;return this.emitStatus("requestPermission","等待输入",ge(r),"elicitation/create"),this.userInputCallback({requestId:String(r.requestId??r.id??`codex-acp-input-${Date.now()}`),prompt:String(r.prompt??r.message??r.question??""),params:e,...typeof r.defaultValue=="string"?{defaultValue:r.defaultValue}:{}})}emitStatus(e,t,r,a){const n={phase:e,label:t,...r?{detail:r}:{},...a?{updateType:a}:{},atMs:Date.now()},o=this.statusTimeline[this.statusTimeline.length-1];o&&o.phase===n.phase&&o.label===n.label&&o.detail===n.detail&&o.updateType===n.updateType||(this.statusTimeline.push(n),this.statusCallback?.(n))}releaseLease(){this.lease?.release(),this.lease=null}}function ue(s,e){const t=s;return t.controlLoopMigratedProviderSessionId=e,t}function pe(s){if(!s||typeof s!="object"||Array.isArray(s))return null;const e=s.controlLoopMigratedProviderSessionId;return typeof e=="string"&&e.trim()?e.trim():null}async function W(s,e){s.runtimeConfigured||(await s.client.configureRuntime({codexProvider:e?{baseUrl:e.baseUrl,apiKey:e.apiKey,revision:e.revision}:null}),s.markRuntimeConfigured())}function N(s){return s?{OPENAI_API_KEY:s.apiKey,OPENAI_BASE_URL:s.baseUrl}:{}}function me(s,e){if(e?.aborted)return!0;const t=s instanceof Error?s.message:String(s||"");return/compact.*timed out|request timed out.*compact|adapter closed|aborted|cancelled/i.test(t)}function b(s){if(!s||typeof s!="object"||Array.isArray(s))return null;const e=s.sessionId;return typeof e=="string"&&e.trim()?e.trim():null}function h(s){return s&&typeof s=="object"&&!Array.isArray(s)?s:null}function he(s){const e=h(s),t=h(e?._meta),r=h(t?.aca),a=h(r?.controlToolSteering);if(!a)return null;const n=Number(a.attempt),o=Number(a.maxAttempts);if(!Number.isInteger(n)||n<=0||!Number.isInteger(o)||o<=0)return null;const i=Number(a.steeredAtMs),c=Number(a.failedAtMs);return{segmentId:Math.max(0,Math.floor(Number(a.segmentId)||0)),attempt:n,maxAttempts:o,succeeded:Number.isFinite(i)&&i>0,observedAtMs:Number.isFinite(i)&&i>0?i:Number.isFinite(c)&&c>0?c:Date.now()}}function R(s){const e=String(s||"").trim();return e?e==="read-only"?"read-only":["agent-full-access","full-access","bypassPermissions","danger-full-access"].includes(e)?"danger-full-access":"workspace-write":null}function M(s){const e=String(s||"").trim();return e?["agent-full-access","full-access","bypassPermissions","danger-full-access"].includes(e)?"never":e==="acceptEdits"||e==="accept-edits"?"on-failure":"on-request":null}function F(s){return{...s.model?{model:s.model}:{},...M(s.mode)?{approvalPolicy:M(s.mode)}:{},...R(s.mode)?{sandbox:R(s.mode)}:{},...s.configOptionValues??{}}}function fe(s){const e=s.params&&typeof s.params=="object"&&!Array.isArray(s.params)?s.params:s;for(const t of["command","cwd","reason","grantRoot","method"]){const r=e[t];if(typeof r=="string"&&r.trim())return j(r)}}function ge(s){for(const e of["prompt","message","question","title"]){const t=s[e];if(typeof t=="string"&&t.trim())return j(t)}}function j(s){const e=s.replace(/\s+/g," ").trim();return e.length>120?`${e.slice(0,117)}...`:e}function we(){const s=Number.parseInt(process.env.ACA_CODEX_ACP_PROMPT_TIMEOUT_MS??process.env.ACA_ACP_PROMPT_TIMEOUT_MS??"",10);return Number.isInteger(s)&&s>=3e4?s:720*60*1e3}function Se(s){const e=Number.isInteger(s)?Number(s):Number.parseInt(process.env.ACA_CODEX_ACP_COMPACT_TIMEOUT_MS??"",10);return Number.isFinite(e)&&e>=3e4?Math.min(e,6e5):12e4}function z(s){if(!s||typeof s!="object"||Array.isArray(s))return null;const e=s;return typeof e.sessionId=="string"&&e.sessionId.trim()?e.sessionId:null}function ve(s){const e=h(s),t=h(e?._meta);return H(t?.acaContextUsage)}function H(s){const e=h(s);if(!e)return null;const t=A(e.contextWindow),r=A(e.usedTokens),a=A(e.inputTokens),n=String(e.state||"unknown"),o=["active","compacted","new_thread"].includes(n)?n:"unknown";return{usedTokens:r,inputTokens:a,contextWindow:t,ratio:t>0?Math.max(0,A(e.ratio)||r/t):0,observedAtMs:A(e.observedAtMs)||Date.now(),state:o,maintenanceAction:typeof e.maintenanceAction=="string"?e.maintenanceAction:null}}function A(s){return typeof s=="number"&&Number.isFinite(s)&&s>=0?s:0}export{De as CodexAcpProvider,le as codexAcpCapabilities};
|
|
1
|
+
import y from"node:fs";import{runAbortableRequest as L}from"../../client/abortable-request.js";import{BoundedItemBuffer as K}from"../../client/bounded-buffer.js";import{promptBlocksToAcpContent as E}from"../../client/acp-content.js";import{acpSessionUpdateFromParams as X,eventFromAcpSessionUpdate as Y,statusFromAcpSessionUpdate as G,subtaskContextFromAcpSessionUpdate as J}from"../../client/acp-events.js";import{defaultAcpRuntimePool as D}from"../../client/acp-runtime-pool.js";import{resolveCodexAcpLaunch as q}from"./launch.js";import{cancelCodexRuntime as Q}from"./cancellation.js";import{CodexPromptPerformanceTracker as Z,CodexUsageLoopDetector as U,codexRawResponseUsageFromAcpSessionUpdate as ee,codexTokenUsageFromAcpSessionUpdate as te,readCodexContextMaintenancePolicy as $}from"./context-maintenance.js";import{ensureCodexProviderRuntimeConfig as O}from"../../../core/codex-provider-runtime-config.js";import{codexControlToolLoopFromAcpSessionUpdate as oe,controlToolLoopDetectionThresholds as se,controlToolLoopReady as re,readCodexControlToolLoopPolicy as ne}from"./control-tool-loop.js";import{attachCodexDiagnostics as ie,CodexTurnDiagnosticsTracker as ae}from"./turn-diagnostics.js";import{AcpSessionInvalidatedError as W}from"../../session-errors.js";import{codexAcpUpdateIsMeaningfulProgress as ce}from"./progress-evidence.js";const le={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 De{id="codex-acp";name="Codex ACP";capabilities=le;async createSession(e){return new de(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 o=e.sourceProviderSessionId.trim(),r=await O(),a=await D.acquire({providerId:"codex-acp",cwd:e.cwd,providerSessionId:o,runtimeIdentity:r?.runtimeIdentity||"default",env:F(r),...q(),onSessionUpdate:()=>{}});try{await N(a,r),a.initialized||(await a.client.initialize(6e4),a.markInitialized()),e.onStatus?.({phase:"forking",label:"正在分叉 Codex 会话",detail:o,updateType:"session/fork",atMs:Date.now()});const n=await a.client.forkSession({sessionId:o,cwd:e.cwd,config:j(e)},e.timeoutMs??12e4),t=x(n);if(!t)throw new Error("Codex ACP session/fork did not return sessionId");return{providerSessionId:t}}finally{a.release()}}}class de{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 K(16);content="";performanceTracker=null;usageLoopDetector=null;usagePatternReport=null;controlToolPatternReport=null;turnDiagnosticsTracker=null;usageLoopWarningActive=!1;controlToolLoopWarningActive=!1;controlToolLoopInterrupting=!1;cancellation=null;constructor(e,o,r=we()){this.cwd=e,this.providerSessionId=o,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 o=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=$(),a=ne();this.performanceTracker=new Z(o),this.usageLoopDetector=new U(r);const n=new ae;this.turnDiagnosticsTracker=n,this.usageLoopWarningActive=!1,this.controlToolLoopWarningActive=!1,this.controlToolLoopInterrupting=!1;let t={error:null,stopping:null,detection:null,controlDetection:null,interruption:null,kind:null},i=null,c=null,f=this.providerSessionId,v,C=null;try{this.emitStatus("initializing","Codex ACP 启动中","codex-acp");const m=await this.acquireRuntime(e.cwd);this.lease=m,i=m.client,c=m.started,await this.initialize(m),f=await this.establishSession(m,e),await this.applyConfig(i,f,e);const g=i,B=E(e.prompt,e.promptBlocks);let T=!1;for(this.emitStatus("thinking","思考中"),C=this.startSlowStatusTimer();;){const w=f;t={error:null,stopping:null,detection:null,controlDetection:null,interruption:null,kind:null},this.usageLoopDetector=new U(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(n.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(m,w).then(l=>{if(t.interruption=l,n.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);n.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,a))return;const _=se(u,a);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(n.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(m,w).then(p=>{t.interruption=p,n.recordInterrupt({acknowledged:!p.forced,forcedRuntimeClose:p.forced,reason:p.reason});const A=p.forced?`${l};取消请求失败:${p.reason};已关闭运行实例`:l;p.forced&&(t.error=new Error(`${d};取消请求失败:${p.reason};已关闭运行实例`)),this.emitStatus("failed","Codex 已中断无进展控制工具循环",A,"aca.performance.control_tool_loop_interrupted")}).catch(p=>{const A=p instanceof Error?p.message:String(p);n.recordInterrupt({acknowledged:!1,forcedRuntimeClose:!1,reason:A}),t.error=new Error(`${d};${A}`),this.emitStatus("failed","Codex 控制工具循环中断失败",A,"aca.performance.control_tool_loop_interrupt_failed")})};let R;try{v=await this.promptWithAbort(g,{sessionId:w,prompt:B,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=x(l);if(!d||d===w)throw new Error("Codex recovery did not create a new session");f=d,this.providerSessionId=d,this.sessionResponse=h(l),m.setProviderSession({providerSessionId:d,sessionResponse:this.sessionResponse});const p={previousProviderSessionId:w,providerSessionId:d,reason:"low_output_loop",migratedAtMs:Date.now()};n.recordMigration(p),await e.onProviderSessionMigration?.(p),n.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=x(l);if(!d||d===w)throw new Error("Codex recovery did not create a new session");f=d,this.providerSessionId=d,this.sessionResponse=h(l),m.setProviderSession({providerSessionId:d,sessionResponse:this.sessionResponse});const p={previousProviderSessionId:w,providerSessionId:d,reason:"control_tool_loop",migratedAtMs:Date.now()};throw n.recordMigration(p),await e.onProviderSessionMigration?.(p),this.emitStatus("failed","Codex 循环已中断,会话已自动迁移",d,"aca.performance.control_tool_loop_migrated_session"),ue(t.error,d)}catch(l){if(pe(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!==f&&(f=b,this.providerSessionId=b,m.setProviderSession({providerSessionId:b,sessionResponse:this.sessionResponse}));break}}catch(m){if(m instanceof Error){const g=ie(m,n.snapshot());throw g.statusTimeline=[...this.statusTimeline],g.performance=this.performanceTracker?.snapshot(),g}throw m}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:f,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()-o,stdout:i.rpc.stdout,stderr:i.rpc.stderr,content:this.content.trim(),updateCount:this.updates.count(),updates:this.updates.snapshot(),statusTimeline:[...this.statusTimeline],performance:this.performanceTracker?.snapshot(),contextUsage:ve(v),providerDiagnostics:n.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 o=Date.now();this.statusCallback=e.onStatus??null,this.updateCallback=e.onUpdate??null,this.attachAbortSignal(e.signal);let r=this.providerSessionId,a;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 i=Se(e.timeoutMs);a=await this.compactWithAbort(t.client,{sessionId:r,allowRollover:e.allowRollover===!0,timeoutMs:i,signal:e.signal});const c=H(a);c&&c!==r&&(r=c,this.providerSessionId=c,t.setProviderSession({providerSessionId:c,sessionResponse:this.sessionResponse}))}catch(t){if(this.lease&&me(t,e.signal)){const i=this.lease;this.lease=null,await i.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 n=h(a)??{};return{providerSessionId:r||this.providerSessionId||"",contextUsage:V(n.contextUsage),maintenance:h(n.maintenance)??{},durationMs:Date.now()-o}}async cancel(){this.providerSessionId&&this.lease&&await this.stopRuntime(this.lease,this.providerSessionId)}stopRuntime(e,o){if(this.cancellation)return this.cancellation;const r=Q(e,o);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 o=q(),r=await O(),a=await D.acquire({providerId:"codex-acp",cwd:e,providerSessionId:this.providerSessionId,runtimeIdentity:r?.runtimeIdentity||"default",env:F(r),command:o.command,args:o.args,onSessionUpdate:n=>this.handleSessionUpdate(n),onClientRequest:async(n,t)=>{if(n==="session/request_permission")return this.requestPermission(t);if(n==="elicitation/create"||n==="session/request_user_input")return this.requestUserInput(t);throw new Error(`Unsupported Codex ACP client request: ${n}`)}});try{return await N(a,r),a}catch(n){throw await a.dispose().catch(()=>{}),n}}async initialize(e){e.initialized||(await e.client.initialize(6e4),e.markInitialized())}async establishSession(e,o){const r=e.client,a=j(o);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 i=await r.loadSession({sessionId:this.providerSessionId,cwd:o.cwd,config:a},6e4);this.sessionResponse=h(i);const c=x(i)||this.providerSessionId;return this.providerSessionId=c,e.setProviderSession({providerSessionId:c,sessionResponse:this.sessionResponse}),c}catch(i){if(o.requireSessionResume){const c=i instanceof Error?i.message:String(i);throw new Error(`无法恢复导入的 Codex ACP Session ${this.providerSessionId}:${c}`)}this.providerSessionId=null}this.emitStatus("acp","Codex ACP 会话创建中","session/new");const n=await r.newSession({cwd:o.cwd,config:a},12e4);this.sessionResponse=h(n);const t=x(n);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,o,r){r.model&&await e.setSessionConfigOption({sessionId:o,configId:"model",value:r.model},6e4).catch(()=>{});const a=M(r.mode);a&&await e.setSessionConfigOption({sessionId:o,configId:"approvalPolicy",value:a},6e4).catch(()=>{});const n=P(r.mode);n&&await e.setSessionConfigOption({sessionId:o,configId:"sandbox",value:n},6e4).catch(()=>{});for(const[t,i]of Object.entries(r.configOptionValues??{}))["model","mode","sandbox","approvalPolicy"].includes(t)||await e.setSessionConfigOption({sessionId:o,configId:t,value:i},6e4).catch(()=>{})}handleSessionUpdate(e){const o=X(e),r=String(o?.update.sessionUpdate||"");if(r==="aca_supplement_target"){const i=o.update,c=this.lease?.client,f=String(i.threadId||""),v=String(i.turnId||"");!c||i.ready!==!0||!f||!v?this.supplementCallback?.(null):this.supplementCallback?.({threadId:f,turnId:v,send:(C,m)=>{const g=c.rpc.request("aca/supplement",{sessionId:f,expectedTurnId:v,deliveryId:C,prompt:E("",m)},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 a=J(e);if(!a&&r==="aca_codex_subtask_drain"){const i=String(o?.update.label||"子任务收尾等待超时"),c=String(o?.update.detail||"仍有子任务未结束");this.emitStatus("exploring",i,c,r)}else if(!a&&r==="aca_codex_control_tool_loop"){const i=oe(e);i?.state==="observed"&&this.controlToolPatternReport?.(i.detection),i?.state==="recovered"&&this.controlToolLoopWarningActive&&!this.controlToolLoopInterrupting&&(this.controlToolLoopWarningActive=!1,this.turnDiagnosticsTracker?.recordRecovery(),this.emitStatus("thinking","Codex 已恢复执行","无进展控制工具循环已结束","aca.performance.control_tool_loop_recovered"))}else if(!a&&r==="aca_codex_control_tool_steer"){const i=he(o?.update);i&&this.turnDiagnosticsTracker?.recordControlToolSteering(i)}else if(ce(o?.update))this.recordMeaningfulProgress();else if(!a&&r==="aca_codex_raw_response_usage"){const i=ee(e);i&&this.turnDiagnosticsTracker?.recordUsage(i);const c=i?this.usageLoopDetector?.recordUsage(i):null;c&&this.usagePatternReport?.(c)}else if(!a&&r==="usage_update"){const i=te(e);i&&this.turnDiagnosticsTracker?.recordUsage(i);const c=i?this.usageLoopDetector?.recordUsage(i):null;c&&this.usagePatternReport?.(c)}this.updates.push({method:"session/update",params:e});const n=Y(e);n&&(n.type==="agent_message_chunk"&&!a&&(this.content+=n.text??""),this.updateCallback?.(n));const t=G(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,o=setInterval(()=>{const r=this.performanceTracker,a=Date.now();if(!r?.shouldReportSlow(a,e))return;const n=r.snapshot(a),t=Math.max(1,Math.round(n.silentForMs/6e4)),i=this.usageLoopWarningActive||this.controlToolLoopWarningActive?"Codex 可能卡在内部循环":"Codex 长时间没有有效进展";this.emitStatus("thinking",i,`已 ${t} 分钟没有执行工具或产生有效输出`,"aca.performance.slow")},Math.min(3e4,Math.max(1e3,Math.floor(e/4))));return o.unref(),o}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,o){return L({signal:o.signal,cancelledMessage:"Codex ACP prompt cancelled",startRequest:()=>e.prompt({sessionId:o.sessionId,prompt:o.prompt},o.timeoutMs),onAbort:async()=>{await this.lease?.dispose()}})}async compactWithAbort(e,o){return L({signal:o.signal,cancelledMessage:"Codex ACP 上下文压缩已取消",startRequest:()=>e.compactSession({sessionId:o.sessionId,allowRollover:o.allowRollover,timeoutMs:o.timeoutMs},o.timeoutMs),onAbort:async()=>{await this.lease?.dispose()}})}async requestPermission(e){if(!this.permissionCallback)return{outcome:{outcome:"cancelled"}};const o=e&&typeof e=="object"&&!Array.isArray(e)?e:{},r=o.request&&typeof o.request=="object"&&!Array.isArray(o.request)?o.request:o,n=(Array.isArray(r.options)?r.options:[]).map((t,i)=>{const c=t&&typeof t=="object"&&!Array.isArray(t)?t:{};return{optionId:String(c.optionId??c.id??i),kind:String(c.kind??"choice"),...typeof c.label=="string"?{label:c.label}:{}}});return this.emitStatus("requestPermission","等待授权",fe(r),"session/request_permission"),this.permissionCallback({requestId:String(r.requestId??r.id??`codex-acp-permission-${Date.now()}`),params:e,options:n})}async requestUserInput(e){if(!this.userInputCallback)return{outcome:{outcome:"cancelled"}};const o=e&&typeof e=="object"&&!Array.isArray(e)?e:{},r=o.request&&typeof o.request=="object"&&!Array.isArray(o.request)?o.request:o;return this.emitStatus("requestPermission","等待输入",ge(r),"elicitation/create"),this.userInputCallback({requestId:String(r.requestId??r.id??`codex-acp-input-${Date.now()}`),prompt:String(r.prompt??r.message??r.question??""),params:e,...typeof r.defaultValue=="string"?{defaultValue:r.defaultValue}:{}})}emitStatus(e,o,r,a){const n={phase:e,label:o,...r?{detail:r}:{},...a?{updateType:a}:{},atMs:Date.now()},t=this.statusTimeline[this.statusTimeline.length-1];t&&t.phase===n.phase&&t.label===n.label&&t.detail===n.detail&&t.updateType===n.updateType||(this.statusTimeline.push(n),this.statusCallback?.(n))}releaseLease(){this.lease?.release(),this.lease=null}}function ue(s,e){const o=s;return o.controlLoopMigratedProviderSessionId=e,o}function pe(s){if(!s||typeof s!="object"||Array.isArray(s))return null;const e=s.controlLoopMigratedProviderSessionId;return typeof e=="string"&&e.trim()?e.trim():null}async function N(s,e){s.runtimeConfigured||(await s.client.configureRuntime({codexProvider:e?{baseUrl:e.baseUrl,apiKey:e.apiKey,revision:e.revision}:null}),s.markRuntimeConfigured())}function F(s){return s?{OPENAI_API_KEY:s.apiKey,OPENAI_BASE_URL:s.baseUrl}:{}}function me(s,e){if(e?.aborted)return!0;const o=s instanceof Error?s.message:String(s||"");return/compact.*timed out|request timed out.*compact|adapter closed|aborted|cancelled/i.test(o)}function x(s){if(!s||typeof s!="object"||Array.isArray(s))return null;const e=s.sessionId;return typeof e=="string"&&e.trim()?e.trim():null}function h(s){return s&&typeof s=="object"&&!Array.isArray(s)?s:null}function he(s){const e=h(s),o=h(e?._meta),r=h(o?.aca),a=h(r?.controlToolSteering);if(!a)return null;const n=Number(a.attempt),t=Number(a.maxAttempts);if(!Number.isInteger(n)||n<=0||!Number.isInteger(t)||t<=0)return null;const i=Number(a.steeredAtMs),c=Number(a.failedAtMs);return{segmentId:Math.max(0,Math.floor(Number(a.segmentId)||0)),attempt:n,maxAttempts:t,succeeded:Number.isFinite(i)&&i>0,observedAtMs:Number.isFinite(i)&&i>0?i:Number.isFinite(c)&&c>0?c:Date.now()}}function P(s){const e=String(s||"").trim();return e?e==="read-only"?"read-only":["agent-full-access","full-access","bypassPermissions","danger-full-access"].includes(e)?"danger-full-access":"workspace-write":null}function M(s){const e=String(s||"").trim();return e?["agent-full-access","full-access","bypassPermissions","danger-full-access"].includes(e)?"never":e==="acceptEdits"||e==="accept-edits"?"on-failure":"on-request":null}function j(s){return{...s.model?{model:s.model}:{},...M(s.mode)?{approvalPolicy:M(s.mode)}:{},...P(s.mode)?{sandbox:P(s.mode)}:{},...s.configOptionValues??{}}}function fe(s){const e=s.params&&typeof s.params=="object"&&!Array.isArray(s.params)?s.params:s;for(const o of["command","cwd","reason","grantRoot","method"]){const r=e[o];if(typeof r=="string"&&r.trim())return z(r)}}function ge(s){for(const e of["prompt","message","question","title"]){const o=s[e];if(typeof o=="string"&&o.trim())return z(o)}}function z(s){const e=s.replace(/\s+/g," ").trim();return e.length>120?`${e.slice(0,117)}...`:e}function we(){const s=Number.parseInt(process.env.ACA_CODEX_ACP_PROMPT_TIMEOUT_MS??process.env.ACA_ACP_PROMPT_TIMEOUT_MS??"",10);return Number.isInteger(s)&&s>=3e4?s:720*60*1e3}function Se(s){const e=Number.isInteger(s)?Number(s):Number.parseInt(process.env.ACA_CODEX_ACP_COMPACT_TIMEOUT_MS??"",10);return Number.isFinite(e)&&e>=3e4?Math.min(e,6e5):12e4}function H(s){if(!s||typeof s!="object"||Array.isArray(s))return null;const e=s;return typeof e.sessionId=="string"&&e.sessionId.trim()?e.sessionId:null}function ve(s){const e=h(s),o=h(e?._meta);return V(o?.acaContextUsage)}function V(s){const e=h(s);if(!e)return null;const o=k(e.contextWindow),r=k(e.usedTokens),a=k(e.inputTokens),n=String(e.state||"unknown"),t=["active","compacted","new_thread"].includes(n)?n:"unknown";return{usedTokens:r,inputTokens:a,contextWindow:o,ratio:o>0?Math.max(0,k(e.ratio)||r/o):0,observedAtMs:k(e.observedAtMs)||Date.now(),state:t,maintenanceAction:typeof e.maintenanceAction=="string"?e.maintenanceAction:null}}function k(s){return typeof s=="number"&&Number.isFinite(s)&&s>=0?s:0}export{De as CodexAcpProvider,le as codexAcpCapabilities};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{acpPromptToCodexInput as c,acpPromptText as p}from"./turn-input.js";class m{entries=new Map;receipt(e){return this.entries.get(e)?.receipt||null}send(e,s,o,a){const n=JSON.stringify(e),i=this.entries.get(e.id);if(i)return i.fingerprint===n?i.promise:Promise.resolve({status:"failed",error:"补充投递标识与内容不匹配"});if(!s.ready||s.threadId!==e.threadId||s.turnId!==e.turnId)return Promise.resolve({status:"failed",error:"目标执行轮次已结束或变化"});if(this.entries.size>=1e3)for(const[r,d]of this.entries)d.turnId!==s.turnId&&["accepted","failed"].includes(d.receipt.status)&&this.entries.delete(r);if(!e.id.trim()||e.id.length>128||!e.prompt.length||this.entries.size>=1e3)return Promise.resolve({status:"failed",error:"补充为空或当前运行时补充数量已达上限"});const t={fingerprint:n,turnId:e.turnId,receipt:{status:"sending"},promise:null};return this.entries.set(e.id,t),t.promise=Promise.resolve().then(()=>o({threadId:e.threadId,expectedTurnId:e.turnId,clientUserMessageId:e.id,input:c(e.prompt)})).then(()=>(a(p(e.prompt)),t.receipt={status:"accepted"}),r=>t.receipt={status:r?.rpcResponse===!0?"failed":"uncertain",error:r instanceof Error?r.message:String(r)}),t.promise}}export{m as UserSteeringLedger};
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import u from"node:crypto";import m from"node:fs";import o from"node:path";import{acaServerDownload as f}from"./aca-server-client.js";import{appendSessionReferenceContext as h}from"./session-reference-prompt.js";function T(e,t=[]){const a=h(e?.message?.trim()??"",e?.sessionReferences);if(t.length===0)return a;const i=["用户随消息上传了以下附件,aca 已将附件写入本机工作区。需要读取附件时请直接使用下面的 localPath;图片附件也已作为 ACP image block 随本轮请求传入。",...t.map((r,s)=>`${s+1}. ${r.name} (${r.kind}, ${r.mimeType}, ${b(r.size)})
|
|
2
|
+
localPath: ${r.path}
|
|
3
|
+
workspacePath: ${r.relativePath}`)].join(`
|
|
4
|
+
`);return a?`${a}
|
|
5
|
+
|
|
6
|
+
${i}`:i}function P(e,t){const a=[{type:"text",text:e}];for(const n of t)n.kind!=="image"||!n.mimeType.startsWith("image/")||a.push({type:"image",data:n.base64,mimeType:n.mimeType,uri:n.path});return a}async function B(e){if(e.attachments.length===0)return[];const t=o.join(e.cwd,".aca","attachments",l(e.sessionId),l(e.jobId));m.mkdirSync(t,{recursive:!0});const a=[];for(const[n,i]of e.attachments.entries()){const r=i.attachmentId?{mimeType:i.mimeType||"application/octet-stream",bytes:await f(`/api/client/attachments/${encodeURIComponent(i.attachmentId)}`)}:x(i.dataUrl??""),s=i.mimeType||r.mimeType||"application/octet-stream",d=i.kind==="image"||s.startsWith("image/")?"image":"file",p=g(i.name||`attachment-${n+1}`,s),c=o.join(t,`${String(n+1).padStart(2,"0")}-${p}`);m.writeFileSync(c,r.bytes),a.push({name:i.name||p,mimeType:s,size:r.bytes.byteLength,kind:d,path:c,relativePath:o.relative(e.cwd,c),base64:r.bytes.toString("base64"),sha256:u.createHash("sha256").update(r.bytes).digest("hex")})}return a}function x(e){const t=e.indexOf(",");if(!e.startsWith("data:")||t<0)throw new Error("attachment dataUrl is not a data URL");const a=e.slice(5,t),n=e.slice(t+1),i=a.split(";").filter(Boolean),r=i[0]?.includes("/")?i[0]:"application/octet-stream";if(!i.includes("base64"))throw new Error("attachment dataUrl is not base64 encoded");return{mimeType:r,bytes:Buffer.from(n,"base64")}}function g(e,t){const a=o.basename(e).replace(/[<>:"/\\|?*\x00-\x1F]/g,"_").trim(),n=`attachment${y(t)}`;return(!a||a==="."||a===".."?n:a).slice(0,180)}function y(e){switch(e.toLowerCase()){case"image/png":return".png";case"image/jpeg":return".jpg";case"image/webp":return".webp";case"image/gif":return".gif";case"text/plain":return".txt";case"application/json":return".json";case"text/markdown":case"text/x-markdown":return".md";case"application/pdf":return".pdf";case"application/msword":return".doc";case"application/vnd.openxmlformats-officedocument.wordprocessingml.document":return".docx";case"application/vnd.ms-excel":return".xls";case"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet":return".xlsx";case"application/vnd.ms-powerpoint":return".ppt";case"application/vnd.openxmlformats-officedocument.presentationml.presentation":return".pptx";case"application/zip":return".zip";case"application/x-7z-compressed":return".7z";case"application/x-rar-compressed":case"application/vnd.rar":return".rar";case"application/gzip":return".gz";case"application/x-tar":return".tar";default:return""}}function l(e){return e.replace(/[^a-zA-Z0-9._-]+/g,"-").replace(/^-+|-+$/g,"").slice(0,80)||"item"}function S(e){return e.map(t=>({name:t.name,kind:t.kind,mimeType:t.mimeType,size:t.size,path:t.path,relativePath:t.relativePath,sha256:t.sha256}))}function b(e){if(!Number.isFinite(e)||e<=0)return"0 B";const t=["B","KB","MB","GB"];let a=e,n=0;for(;a>=1024&&n<t.length-1;)a/=1024,n+=1;return`${a>=10||n===0?a.toFixed(0):a.toFixed(1)} ${t[n]}`}export{P as buildPromptBlocks,T as buildPromptWithAttachments,B as materializeJobAttachments,S as summarizeMaterializedAttachments};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import E from"node:crypto";import{acaServerRequest as M}from"./aca-server-client.js";import{buildPromptBlocks as q,buildPromptWithAttachments as A,materializeJobAttachments as J}from"./job-attachments.js";function O(n){let i=null,a=null,c=!1,u=!1,d=null,b=Promise.resolve();const y=[],g=new Map,p=new Map,R=`/api/client/jobs/${encodeURIComponent(n.jobId)}/supplements`,l=(t,e)=>M("POST",`${R}${t}`,e,{timeoutMs:1e4}),f=t=>n.logger.warn(`supplement job=${n.jobId}: ${t instanceof Error?t.message:String(t)}`);async function h(){for(const[t,e]of p)try{await l(`/${encodeURIComponent(t)}/status`,{...e.target,status:e.status,error:e.error}),p.delete(t)}catch(r){f(r)}}async function v(){for(const[t,e]of g){const r=await e.channel.receipt(t).catch(()=>null);r?.status!=="accepted"&&r?.status!=="failed"||(g.delete(t),p.set(t,{target:e.target,status:r.status,error:r.error}))}}function _(t){const e=l("/poll",{...t,ready:!1,take:!1}).catch(f);y.push(e)}async function k(t,e,r){let P=!1,s={status:"failed",error:"目标执行轮次已结束"};try{const o=await J({cwd:n.cwd,sessionId:n.sessionId,jobId:`${n.jobId}-supplement-${t.supplement_id}`,attachments:t.input?.attachments||[]});if(c||n.signal.aborted||i!==e||a!==r)throw new Error("目标执行轮次已结束或取消,未投递补充");const C=A(t.input,o);if(!(await l(`/${encodeURIComponent(t.supplement_id)}/status`,{...r,status:"sending"})).changed)return;P=!0;try{s=await e.send(t.supplement_id,q(C,o))}catch(I){s={status:"uncertain",error:I instanceof Error?I.message:String(I)};const w=await e.receipt(t.supplement_id).catch(()=>null);w&&w.status!=="sending"&&(s=w)}s.status==="sending"&&(s={status:"uncertain",error:"运行时仍在确认补充接收结果"})}catch(o){s={status:P?"uncertain":"failed",error:o instanceof Error?o.message:String(o)}}s.status==="uncertain"&&g.set(t.supplement_id,{target:r,channel:e}),p.set(t.supplement_id,{target:r,status:s.status,error:s.error}),await h()}async function U(){if(c||u||!a||!i||n.signal.aborted)return;u=!0;const t=a,e=i;try{await v(),await h();const r=await l("/poll",{...t,ready:!0});r.item&&await k(r.item,e,t)}catch(r){!c&&a===t&&f(r)}finally{u=!1}}function $(){u||c||(b=U())}const j=setInterval($,1e3),S=setInterval(()=>{if(c||d||!a||n.signal.aborted)return;const t=a;d=l("/poll",{...t,ready:!0,take:!1}).catch(e=>{!c&&a===t&&f(e)}).finally(()=>{d=null})},5e3);j.unref(),S.unref();let m;return{setChannel(t){if(c||i&&t?.threadId===i.threadId&&t.turnId===i.turnId)return;const e=a;i=t,a=t?{jobId:n.jobId,runtimeId:E.randomUUID(),threadId:t.threadId,turnId:t.turnId}:null,e&&_(e),t&&$()},stop(){if(m)return m;c=!0,clearInterval(j),clearInterval(S);const t=a;return a=null,i=null,m=(async()=>{await b,await d,await v(),await h(),t&&_(t),await Promise.allSettled(y)})(),m}}}export{O as createJobSupplementReceiver};
|
package/dist/core/job-worker.js
CHANGED
|
@@ -1,6 +1 @@
|
|
|
1
|
-
import{managementActivity as T}from"./management/activity.js";import{managedClaim as te}from"./management/claims.js";import ne from"node:crypto";import{startJobArtifactMonitor as oe,mergeArtifactFiles as D}from"./artifacts/job-artifact-monitor.js";import{appendSessionReferenceContext as ae}from"./session-reference-prompt.js";import J from"node:fs";import z from"node:path";import{compactAgentContext as ie,forkAgentSession as re,sendAgentPrompt as se}from"../acp/agent.js";import{acaServerDownload as ce,acaServerRequest as I}from"./aca-server-client.js";import{loadAcaConfig as le}from"./aca-config.js";import{prepareLocalWorktree as de}from"./worktree.js";import{expandWorkerRuntimePath as ue}from"./worker-runtime-paths.js";import{collectAffectedFiles as V,extractAffectedFilePathsFromAcpRaw as pe}from"./affected-files.js";import{resolveJobPermissionPolicy as me}from"./job-permission-policy.js";import{createAcpEventBatcher as fe}from"./acp-event-batcher.js";import{createToolLifecycleTrace as he}from"../acp/tool-lifecycle-trace.js";import{shouldBufferAcpEvent as we}from"./acp-event-upload-filter.js";import{userFacingAcpError as be}from"../acp/error-display.js";import{ensureCodexProviderRuntimeRevision as ye}from"./codex-provider-runtime-config.js";import{acpSessionInvalidationFromError as Ie}from"../acp/session-errors.js";const Se=1500,q=5e3,G=1e3,K=900,ve=1e4,ge=0,_e=12e3;function Te(e){return ue(e)}function Ae(e){const t=e.project_metadata;return t&&typeof t=="object"&&!Array.isArray(t)&&t.kind==="chat"?!0:typeof e.project_root_path=="string"&&(e.project_root_path.startsWith("~/.aica/chats/")||e.project_root_path.startsWith("~/.aca/chats/")||/(^|\/)\.aica\/chats\//.test(e.project_root_path)||/(^|\/)\.aca\/chats\//.test(e.project_root_path))}function It(e,t={}){let n=!1,a=null,o=null,s=0,l=0;const p=new Set,r=new Map,y=new Map,A=async()=>{if(n||T.paused||T.stopping||Date.now()<l)return;const w=ke();if(!H(p.size,w))return;const S=T.enter("poll");if(S)try{const m=t.loadConfig?.()??le();if(!m.token)return;for(;!n&&!T.paused&&!T.stopping&&H(p.size,w);){const v=t.claimNextJob?await t.claimNextJob(m,[...r.keys()]):await te("job",()=>I("POST","/api/client/jobs/claim",{machineId:m.machineId,workspaceId:m.defaultWorkspaceId,activeSessionIds:[...r.keys()]}),{workspaceId:m.defaultWorkspaceId,activeSessionIds:[...r.keys()]});if(s=0,l=0,!v.item)break;const u=v.item;if(n){await(t.cancelClaimedJob?t.cancelClaimedJob(u,m):N(u.job_id,m.machineId,"cancelled")).catch(()=>{});break}const f=u.session_id,j=r.get(f),E=T.track("job");p.add(u.job_id);const k=new AbortController;let x;x=(async()=>{j&&(e.warn(`claimed job=${u.job_id} for already active session=${f}; waiting for the previous run to release its ACP runtime`),await Ce(j,u,m,k)),!k.signal.aborted&&await(t.runJob??xe)(u,m,e,k)})().catch(c=>{T.uncertain.add("job-finalize"),e.warn(`job ${u.job_id} failed outside handler: ${c instanceof Error?c.message:String(c)}`)}).finally(()=>{E(),p.delete(u.job_id),r.get(f)===x&&r.delete(f),y.delete(u.job_id)}),r.set(f,x),y.set(u.job_id,{controller:k,promise:x})}}catch(m){if(!n){s+=1;const v=Xe(s);l=Date.now()+v,e.warn(`job worker failed, retry in ${v}ms: ${m instanceof Error?m.message:String(m)}`)}}finally{S()}},P=()=>{if(n||a)return;const w=A();a=w,w.finally(()=>{a===w&&(a=null)})};P();const d=setInterval(()=>{P()},t.claimIntervalMs??Se);return{timer:d,stop:()=>o||(o=(async()=>{if(n)return;n=!0,clearInterval(d);for(const S of y.values())S.controller.abort();await a?.catch(()=>{});for(const S of y.values())S.controller.abort();const w=[...y.values()].map(S=>S.promise);w.length!==0&&await Promise.race([Promise.allSettled(w).then(()=>{}),B(_e)])})(),o)}}async function Ce(e,t,n,a){let o=!1;const l=setInterval(()=>{o||a.signal.aborted||(o=!0,Promise.allSettled([I("POST",`/api/client/jobs/${encodeURIComponent(t.job_id)}/heartbeat`,{machineId:n.machineId}),Q(t.job_id,a)]).finally(()=>{o=!1}))},q);try{await Promise.race([e,Pe(a.signal)])}finally{clearInterval(l)}}function Pe(e){return e.aborted?Promise.resolve():new Promise(t=>e.addEventListener("abort",()=>t(),{once:!0}))}function ke(){const e=process.env.ACA_MAX_CONCURRENT_JOBS||process.env.ACA_MAX_CONCURRENT_PROJECT_JOBS||"",t=Number.parseInt(e,10);return!e.trim()||!Number.isFinite(t)||t<0?ge:t===0?0:Math.max(1,Math.min(t,8))}function H(e,t){return t===0||e<t}async function xe(e,t,n,a){const o=e.job_id,s=e.session_id,l=e.workspace_id,p=e.project_id??null,r=e.input??{},y=String(r.control?.type||"").toLowerCase();if(!y&&!r.message?.trim()&&(r.attachments??[]).length===0&&!r.sessionReferences?.length){await N(o,t.machineId,"job input missing message");return}const A=t.projects.find(c=>c.projectId===p),P=Te(A?.rootPath||e.project_root_path||"");if(!P){await N(o,t.machineId,"job project root is unknown");return}Ae(e)&&J.mkdirSync(P,{recursive:!0}),n.info(`claimed job=${o} session=${s} project=${A?.name??p??"unknown"}`);let d=!1,w=!1;const S=setInterval(()=>{d||(d=!0,I("POST",`/api/client/jobs/${encodeURIComponent(o)}/heartbeat`,{machineId:t.machineId}).catch(()=>{}).finally(()=>{d=!1})),w||(w=!0,Q(o,a).catch(()=>{}).finally(()=>{w=!1}))},q);let m=0;const v=he(c=>n.info(c)),u=fe(o,t.machineId,{warn:c=>n.warn(c),trace:v}),f=Ne(o,t.machineId),j=new Set;let E=null,k;const x=async()=>k?.finish().catch(c=>(T.uncertain.add("job-artifacts"),n.warn(`artifact collection failed job=${o}: ${String(c)}`),[]))??[];try{await f.sendNow({phase:"initializing",label:"准备工作区",detail:e.worktree_id?"worktree":"project root"}).catch(()=>{});const c=de({projectId:p,sessionId:s,projectRoot:P,worktree:e});E=c,await Ve(o,t.machineId,c);const g=je(r.acpRun);if($e(g.agentType)&&await ye(e.codex_provider_revision),y==="compact"){if(!e.acp_session_id)throw new Error("Current session does not have an ACP context to compact");const i=await ie({agentType:$(g.agentType,"codex"),cliType:$(g.cliType,"builtin"),acpSessionId:e.acp_session_id,cwd:c.cwd,model:r.model,mode:r.mode,configOptionValues:r.configOptionValues,timeoutMs:r.timeoutMs,allowRollover:r.control?.allowRollover===!0,signal:a.signal,onStatus:b=>{f.report({phase:b.phase,label:b.label,detail:b.detail,updateType:b.updateType,raw:b})}});await f.flush(),await I("POST",`/api/client/jobs/${encodeURIComponent(o)}/complete`,{machineId:t.machineId,acpSessionId:i.acpSessionId,suppressTurn:!0,result:{ok:!0,control:{type:"compact",allowRollover:r.control?.allowRollover===!0},acpSessionId:i.acpSessionId,durationMs:i.durationMs,usage:i.contextUsage,maintenance:i.maintenance,worktree:O(c)}}),n.info(`completed context compact job=${o}`);return}if(y==="fork"){const i=String(r.control?.sourceAcpSessionId||"").trim();if(!i)throw new Error("Source session does not have a Codex ACP context to fork");await f.sendNow({phase:"forking",label:"正在分叉 Codex 会话",detail:r.control?.sourceSessionId?`源会话 ${r.control.sourceSessionId}`:void 0}).catch(()=>{});const b=await re({agentType:$(g.agentType,"codex"),cliType:$(g.cliType,"builtin"),sourceProviderSessionId:i,cwd:c.cwd,model:r.model,mode:r.mode,configOptionValues:r.configOptionValues,timeoutMs:r.timeoutMs,signal:a.signal,onStatus:R=>{f.report({phase:R.phase,label:R.label,detail:R.detail,updateType:R.updateType,raw:R})}});await f.flush(),await I("POST",`/api/client/jobs/${encodeURIComponent(o)}/complete`,{machineId:t.machineId,acpSessionId:b.acpSessionId,suppressTurn:!0,result:{ok:!0,control:{type:"fork",sourceSessionId:r.control?.sourceSessionId||null},acpSessionId:b.acpSessionId,worktree:O(c)}}),n.info(`completed Codex session fork job=${o}`);return}const _=await Je({cwd:c.cwd,sessionId:s,jobId:o,attachments:r.attachments??[]});_.length>0&&(n.info(`materialized ${_.length} attachment(s) for job=${o}`),await f.sendNow({phase:"initializing",label:"附件已写入本机",detail:_.map(i=>i.relativePath).join(", "),raw:{attachments:Z(_)}}).catch(()=>{}));const M=ze(r,_),C=Ue(M,_);k=await oe(c.cwd,i=>n.warn(i)).catch(i=>{n.warn(`artifact monitor unavailable job=${o}: ${String(i)}`)});const h=await se({agentType:$(g.agentType,"codex"),cliType:$(g.cliType,"builtin"),acpSessionId:e.acp_session_id??null,requireSessionResume:Re(e.session_raw),prompt:M,promptBlocks:C,cwd:c.cwd,model:r.model,mode:r.mode,configOptionValues:r.configOptionValues,canExecuteTools:r.canExecuteTools,timeoutMs:r.timeoutMs,contextWindow:Ee(g.contextWindow),signal:a.signal,onStatus:i=>{f.report({phase:i.phase,label:i.label,detail:i.detail,updateType:i.updateType,raw:i})},onUpdate:i=>{m+=1,(i.type==="tool_call"||i.type==="tool_call_update"&&["completed","failed","cancelled"].includes(i.status||""))&&v("worker_event",{jobId:o,uploadRunId:u.uploadRunId,toolCallId:i.toolCallId,status:i.status,sequence:m});for(const R of pe(i.raw))j.add(R);const b={turnId:e.user_turn_id??null,eventType:i.type,label:i.label,text:i.text,status:i.status,toolCallId:i.toolCallId,raw:Fe(i.raw,m)};we(b)&&u.push(b)},onPermissionRequest:i=>{const b=me(r,i);return b?Promise.resolve(b):qe(o,t.machineId,i,a.signal)},onUserInputRequest:i=>Ge(o,t.machineId,i,a.signal),onProviderSessionMigration:async i=>{const b=await I("POST",`/api/client/jobs/${encodeURIComponent(o)}/acp-session`,{machineId:t.machineId,previousAcpSessionId:i.previousProviderSessionId,acpSessionId:i.providerSessionId,reason:i.reason,migratedAtMs:i.migratedAtMs});e.acp_session_id=i.providerSessionId,n.info(`migrated ACP session for job=${o} ${i.previousProviderSessionId} -> ${i.providerSessionId}`),await u.flush().catch(()=>{}),await f.sendNow({phase:"resuming",label:"Codex 会话已迁移",detail:i.providerSessionId,updateType:"aca.performance.provider_session_migrated",raw:{migration:i,server:{migrated:b.migrated===!0}}}).catch(()=>{})}}),ee=await x(),U=await u.finish();U.complete||T.uncertain.add("job-event-upload"),await f.flush();const W=D(V({cwd:c.cwd,evidencePaths:j}),ee),F=Me(h.promptResponse);if(!(Oe(F)||!!h.content)){await N(o,t.machineId,`${h.agentType} stopped: ${F??"unknown"}`,{acpSessionId:h.acpSessionId,stopReason:F,eventUpload:U,files:W,exitCode:h.exitCode,stderr:Ze(h.stderr,4e3),worktree:O(c)});return}await I("POST",`/api/client/jobs/${encodeURIComponent(o)}/complete`,{machineId:t.machineId,acpSessionId:h.acpSessionId,assistantText:h.content,result:{ok:!0,eventUpload:U,acpSessionId:h.acpSessionId,stopReason:F,durationMs:h.durationMs,acpRun:r.acpRun??null,effectiveConfig:h.effectiveConfig??null,performance:h.performance??null,usage:h.contextUsage??null,providerDiagnostics:h.providerDiagnostics??null,updateCount:h.updateCount,statusTimeline:h.statusTimeline,worktree:O(c),attachments:Z(_),files:W}}),n.info(`completed job=${o}`)}catch(c){if(He(a.signal)){u.discard(),f.discard(),n.warn(`stopped local job=${o} because ACA Server already finalized it`);return}const g=await x(),_=await u.finish();_.complete||T.uncertain.add("job-event-upload"),await f.flush();const M=a.signal.aborted?"cancelled":c instanceof Error?c.message:String(c),C=Ie(c),h=D(E?V({cwd:E.cwd,evidencePaths:j}):[],g);await N(o,t.machineId,be(M),{rawError:M,eventUpload:_,files:h,providerDiagnostics:L(c,"providerDiagnostics"),statusTimeline:L(c,"statusTimeline"),performance:L(c,"performance"),...C?{acpSessionInvalidated:!0,invalidatedAcpSessionId:C.providerSessionId,failure:{category:C.category,actions:C.actions,details:C.details}}:{}},C?{invalidateAcpSession:!0,invalidatedAcpSessionId:C.providerSessionId}:void 0)}finally{k?.dispose(),u.discard(),clearInterval(S),await f.flush()}}function Re(e){if(!e||typeof e!="object"||Array.isArray(e))return!1;const t=e.importedProviderSession;return!!(t&&typeof t=="object"&&!Array.isArray(t)&&t.strictResume===!0)}function je(e){return!e||typeof e!="object"||Array.isArray(e)?{}:e}function $e(e){const t=String(e||"codex").toLowerCase().replace(/[^a-z0-9]+/g,"");return t==="codex"||t==="codexacp"||t==="codexstandard"}function Ee(e){return typeof e=="number"&&Number.isFinite(e)&&e>0?e:void 0}function $(...e){for(const t of e)if(typeof t=="string"&&t.trim())return t.trim();return""}function Me(e){if(!e||typeof e!="object"||Array.isArray(e))return;const t=e;if(typeof t.stopReason=="string")return t.stopReason;if(typeof t.reason=="string")return t.reason;const n=t.part;if(n&&typeof n=="object"&&!Array.isArray(n)&&typeof n.reason=="string")return n.reason}function Oe(e){return e?["end_turn","stop","completed","complete"].includes(e):!1}function Ne(e,t){let n=null,a=0,o="",s=null,l=Promise.resolve(),p=!1;const r=d=>JSON.stringify({phase:d.phase??"",label:d.label??"",detail:d.detail??"",updateType:d.updateType??""}),y=async d=>{await I("POST",`/api/client/jobs/${encodeURIComponent(e)}/presence`,{machineId:t,phase:d.phase,label:d.label,detail:d.detail,updateType:d.updateType,raw:d.raw}),a=Date.now(),o=r(d)},A=()=>{if(s&&(clearTimeout(s),s=null),p)return n=null,l;if(!n)return l;const d=n;return n=null,l=l.then(()=>y(d)).catch(()=>{}),l},P=d=>{s||(s=setTimeout(()=>{A()},d),s.unref())};return{report(d){if(p)return;const w=Date.now(),m=r(d)!==o,v=w-a>=ve;if(!m&&!v)return;n=d;const u=w-a;if(u>=K||a===0){A();return}P(K-u)},sendNow(d){return p?l:(n=d,A())},flush:A,discard(){p=!0,n=null,s&&clearTimeout(s),s=null}}}function Fe(e,t){return e&&typeof e=="object"&&!Array.isArray(e)?{...e,acaSequence:t}:{value:e,acaSequence:t}}function ze(e,t=[]){const n=ae(e?.message?.trim()??"",e?.sessionReferences);if(t.length===0)return n;const o=["用户随消息上传了以下附件,aca 已将附件写入本机工作区。需要读取附件时请直接使用下面的 localPath;图片附件也已作为 ACP image block 随本轮请求传入。",...t.map((s,l)=>`${l+1}. ${s.name} (${s.kind}, ${s.mimeType}, ${De(s.size)})
|
|
2
|
-
localPath: ${s.path}
|
|
3
|
-
workspacePath: ${s.relativePath}`)].join(`
|
|
4
|
-
`);return n?`${n}
|
|
5
|
-
|
|
6
|
-
${o}`:o}function Ue(e,t){const n=[{type:"text",text:e}];for(const a of t)a.kind!=="image"||!a.mimeType.startsWith("image/")||n.push({type:"image",data:a.base64,mimeType:a.mimeType,uri:a.path});return n}async function Je(e){if(e.attachments.length===0)return[];const t=z.join(e.cwd,".aca","attachments",X(e.sessionId),X(e.jobId));J.mkdirSync(t,{recursive:!0});const n=[];for(const[a,o]of e.attachments.entries()){const s=o.attachmentId?{mimeType:o.mimeType||"application/octet-stream",bytes:await ce(`/api/client/attachments/${encodeURIComponent(o.attachmentId)}`)}:Be(o.dataUrl??""),l=o.mimeType||s.mimeType||"application/octet-stream",p=o.kind==="image"||l.startsWith("image/")?"image":"file",r=Le(o.name||`attachment-${a+1}`,l),y=z.join(t,`${String(a+1).padStart(2,"0")}-${r}`);J.writeFileSync(y,s.bytes),n.push({name:o.name||r,mimeType:l,size:s.bytes.byteLength,kind:p,path:y,relativePath:z.relative(e.cwd,y),base64:s.bytes.toString("base64"),sha256:ne.createHash("sha256").update(s.bytes).digest("hex")})}return n}function Be(e){const t=e.indexOf(",");if(!e.startsWith("data:")||t<0)throw new Error("attachment dataUrl is not a data URL");const n=e.slice(5,t),a=e.slice(t+1),o=n.split(";").filter(Boolean),s=o[0]?.includes("/")?o[0]:"application/octet-stream";if(!o.includes("base64"))throw new Error("attachment dataUrl is not base64 encoded");return{mimeType:s,bytes:Buffer.from(a,"base64")}}function Le(e,t){const n=z.basename(e).replace(/[<>:"/\\|?*\x00-\x1F]/g,"_").trim(),a=`attachment${We(t)}`;return(!n||n==="."||n===".."?a:n).slice(0,180)}function We(e){switch(e.toLowerCase()){case"image/png":return".png";case"image/jpeg":return".jpg";case"image/webp":return".webp";case"image/gif":return".gif";case"text/plain":return".txt";case"application/json":return".json";case"text/markdown":case"text/x-markdown":return".md";case"application/pdf":return".pdf";case"application/msword":return".doc";case"application/vnd.openxmlformats-officedocument.wordprocessingml.document":return".docx";case"application/vnd.ms-excel":return".xls";case"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet":return".xlsx";case"application/vnd.ms-powerpoint":return".ppt";case"application/vnd.openxmlformats-officedocument.presentationml.presentation":return".pptx";case"application/zip":return".zip";case"application/x-7z-compressed":return".7z";case"application/x-rar-compressed":case"application/vnd.rar":return".rar";case"application/gzip":return".gz";case"application/x-tar":return".tar";default:return""}}function X(e){return e.replace(/[^a-zA-Z0-9._-]+/g,"-").replace(/^-+|-+$/g,"").slice(0,80)||"item"}function Z(e){return e.map(t=>({name:t.name,kind:t.kind,mimeType:t.mimeType,size:t.size,path:t.path,relativePath:t.relativePath,sha256:t.sha256}))}function De(e){if(!Number.isFinite(e)||e<=0)return"0 B";const t=["B","KB","MB","GB"];let n=e,a=0;for(;n>=1024&&a<t.length-1;)n/=1024,a+=1;return`${n>=10||a===0?n.toFixed(0):n.toFixed(1)} ${t[a]}`}async function Ve(e,t,n){!n.usedWorktree||!n.worktreeId||await I("POST",`/api/client/jobs/${encodeURIComponent(e)}/worktree`,{machineId:t,baseCommit:n.baseCommit??null,worktreePath:n.cwd,status:n.status??"active",raw:n.raw??O(n)})}function O(e){return{usedWorktree:e.usedWorktree,worktreeId:e.worktreeId??null,cwd:e.cwd,status:e.status??null,baseCommit:e.baseCommit??null,branchName:e.branchName??null,baseBranch:e.baseBranch??null}}async function qe(e,t,n,a){const s=(await I("POST",`/api/client/jobs/${encodeURIComponent(e)}/permissions`,{machineId:t,request:n})).item.permission_id;for(;!a.aborted;){await B(G);const l=await I("GET",`/api/client/permissions/${encodeURIComponent(s)}`);if(l.item.status==="resolved"&&l.item.response){const p=l.item.response.outcome;return p.outcome==="selected"?{outcome:p}:{outcome:{outcome:"cancelled"}}}}return{outcome:{outcome:"cancelled"}}}async function Ge(e,t,n,a){const s=(await I("POST",`/api/client/jobs/${encodeURIComponent(e)}/permissions`,{machineId:t,request:{kind:"elicitation",...n}})).item.permission_id;for(;!a.aborted;){await B(G);const l=await I("GET",`/api/client/permissions/${encodeURIComponent(s)}`);if(l.item.status==="resolved"&&l.item.response){const p=l.item.response.outcome;return p.outcome==="submitted"?{outcome:p}:{outcome:{outcome:"cancelled"}}}}return{outcome:{outcome:"cancelled"}}}async function Q(e,t){if(t.signal.aborted)return;const n=await I("GET",`/api/client/jobs/${encodeURIComponent(e)}`);if(Number(n.item.cancel_requested??0)===1){t.abort();return}const a=Ke(n.item);a&&t.abort(new Y(e,a))}function Ke(e){const t=String(e.status||"").toLowerCase();return t&&!["claimed","running"].includes(t)?t:null}class Y extends Error{constructor(t,n){super(`ACA Server already finalized job=${t} status=${n}`),this.name="ServerFinalizedJobError"}}function He(e){return e.aborted&&e.reason instanceof Y}async function N(e,t,n,a,o){await I("POST",`/api/client/jobs/${encodeURIComponent(e)}/fail`,{machineId:t,error:n,result:a,...o?.invalidateAcpSession?{invalidateAcpSession:!0,invalidatedAcpSessionId:o.invalidatedAcpSessionId}:{}})}function B(e){return new Promise(t=>setTimeout(t,e))}function Xe(e){return Math.min(1e4,500*2**Math.min(5,Math.max(0,e-1)))+Math.floor(Math.random()*250)}function Ze(e,t){return e.length<=t?e:`${e.slice(0,t)}...`}function L(e,t){return!e||typeof e!="object"||Array.isArray(e)?null:e[t]??null}export{Ke as serverFinalizedJobStatus,It as startJobWorkerLoop};
|
|
1
|
+
import{createJobSupplementReceiver as Z}from"./job-supplements.js";import{buildPromptWithAttachments as ee,buildPromptBlocks as te,materializeJobAttachments as ne,summarizeMaterializedAttachments as B}from"./job-attachments.js";import{managementActivity as T}from"./management/activity.js";import{managedClaim as oe}from"./management/claims.js";import{startJobArtifactMonitor as ie,mergeArtifactFiles as V}from"./artifacts/job-artifact-monitor.js";import ae from"node:fs";import{compactAgentContext as re,forkAgentSession as se,sendAgentPrompt as ce}from"../acp/agent.js";import{acaServerRequest as S}from"./aca-server-client.js";import{loadAcaConfig as le}from"./aca-config.js";import{prepareLocalWorktree as de}from"./worktree.js";import{expandWorkerRuntimePath as ue}from"./worker-runtime-paths.js";import{collectAffectedFiles as q,extractAffectedFilePathsFromAcpRaw as pe}from"./affected-files.js";import{resolveJobPermissionPolicy as me}from"./job-permission-policy.js";import{createAcpEventBatcher as fe}from"./acp-event-batcher.js";import{createToolLifecycleTrace as we}from"../acp/tool-lifecycle-trace.js";import{shouldBufferAcpEvent as he}from"./acp-event-upload-filter.js";import{userFacingAcpError as Ie}from"../acp/error-display.js";import{ensureCodexProviderRuntimeRevision as Se}from"./codex-provider-runtime-config.js";import{acpSessionInvalidationFromError as be}from"../acp/session-errors.js";const ye=1500,D=5e3,G=1e3,K=900,ve=1e4,_e=0,Ae=12e3;function Ce(e){return ue(e)}function Te(e){const t=e.project_metadata;return t&&typeof t=="object"&&!Array.isArray(t)&&t.kind==="chat"?!0:typeof e.project_root_path=="string"&&(e.project_root_path.startsWith("~/.aica/chats/")||e.project_root_path.startsWith("~/.aca/chats/")||/(^|\/)\.aica\/chats\//.test(e.project_root_path)||/(^|\/)\.aca\/chats\//.test(e.project_root_path))}function ut(e,t={}){let n=!1,r=null,i=null,l=0,u=0;const p=new Set,a=new Map,b=new Map,R=async()=>{if(n||T.paused||T.stopping||Date.now()<u)return;const h=ge();if(!X(p.size,h))return;const y=T.enter("poll");if(y)try{const m=t.loadConfig?.()??le();if(!m.token)return;for(;!n&&!T.paused&&!T.stopping&&X(p.size,h);){const v=t.claimNextJob?await t.claimNextJob(m,[...a.keys()]):await oe("job",()=>S("POST","/api/client/jobs/claim",{machineId:m.machineId,workspaceId:m.defaultWorkspaceId,activeSessionIds:[...a.keys()]}),{workspaceId:m.defaultWorkspaceId,activeSessionIds:[...a.keys()]});if(l=0,u=0,!v.item)break;const d=v.item;if(n){await(t.cancelClaimedJob?t.cancelClaimedJob(d,m):U(d.job_id,m.machineId,"cancelled")).catch(()=>{});break}const f=d.session_id,E=a.get(f),x=T.track("job");p.add(d.job_id);const k=new AbortController;let _;_=(async()=>{E&&(e.warn(`claimed job=${d.job_id} for already active session=${f}; waiting for the previous run to release its ACP runtime`),await Re(E,d,m,k)),!k.signal.aborted&&await(t.runJob??ke)(d,m,e,k)})().catch(M=>{T.uncertain.add("job-finalize"),e.warn(`job ${d.job_id} failed outside handler: ${M instanceof Error?M.message:String(M)}`)}).finally(()=>{x(),p.delete(d.job_id),a.get(f)===_&&a.delete(f),b.delete(d.job_id)}),a.set(f,_),b.set(d.job_id,{controller:k,promise:_})}}catch(m){if(!n){l+=1;const v=Be(l);u=Date.now()+v,e.warn(`job worker failed, retry in ${v}ms: ${m instanceof Error?m.message:String(m)}`)}}finally{y()}},g=()=>{if(n||r)return;const h=R();r=h,h.finally(()=>{r===h&&(r=null)})};g();const c=setInterval(()=>{g()},t.claimIntervalMs??ye);return{timer:c,stop:()=>i||(i=(async()=>{if(n)return;n=!0,clearInterval(c);for(const y of b.values())y.controller.abort();await r?.catch(()=>{});for(const y of b.values())y.controller.abort();const h=[...b.values()].map(y=>y.promise);h.length!==0&&await Promise.race([Promise.allSettled(h).then(()=>{}),z(Ae)])})(),i)}}async function Re(e,t,n,r){let i=!1;const u=setInterval(()=>{i||r.signal.aborted||(i=!0,Promise.allSettled([S("POST",`/api/client/jobs/${encodeURIComponent(t.job_id)}/heartbeat`,{machineId:n.machineId}),H(t.job_id,r)]).finally(()=>{i=!1}))},D);try{await Promise.race([e,Pe(r.signal)])}finally{clearInterval(u)}}function Pe(e){return e.aborted?Promise.resolve():new Promise(t=>e.addEventListener("abort",()=>t(),{once:!0}))}function ge(){const e=process.env.ACA_MAX_CONCURRENT_JOBS||process.env.ACA_MAX_CONCURRENT_PROJECT_JOBS||"",t=Number.parseInt(e,10);return!e.trim()||!Number.isFinite(t)||t<0?_e:t===0?0:Math.max(1,Math.min(t,8))}function X(e,t){return t===0||e<t}async function ke(e,t,n,r){const i=e.job_id,l=e.session_id,u=e.workspace_id,p=e.project_id??null,a=e.input??{},b=String(a.control?.type||"").toLowerCase();if(!b&&!a.message?.trim()&&(a.attachments??[]).length===0&&!a.sessionReferences?.length){await U(i,t.machineId,"job input missing message");return}const R=t.projects.find(s=>s.projectId===p),g=Ce(R?.rootPath||e.project_root_path||"");if(!g){await U(i,t.machineId,"job project root is unknown");return}Te(e)&&ae.mkdirSync(g,{recursive:!0}),n.info(`claimed job=${i} session=${l} project=${R?.name??p??"unknown"}`);let c=!1,h=!1;const y=setInterval(()=>{c||(c=!0,S("POST",`/api/client/jobs/${encodeURIComponent(i)}/heartbeat`,{machineId:t.machineId}).catch(()=>{}).finally(()=>{c=!1})),h||(h=!0,H(i,r).catch(()=>{}).finally(()=>{h=!1}))},D);let m=0;const v=we(s=>n.info(s)),d=fe(i,t.machineId,{warn:s=>n.warn(s),trace:v}),f=Ne(i,t.machineId),E=new Set;let x=null,k,_;const M=async()=>k?.finish().catch(s=>(T.uncertain.add("job-artifacts"),n.warn(`artifact collection failed job=${i}: ${String(s)}`),[]))??[];try{await f.sendNow({phase:"initializing",label:"准备工作区",detail:e.worktree_id?"worktree":"project root"}).catch(()=>{});const s=de({projectId:p,sessionId:l,projectRoot:g,worktree:e});x=s,await Je(i,t.machineId,s);const A=Ee(a.acpRun);if(Me(A.agentType)&&await Se(e.codex_provider_revision),b==="compact"){if(!e.acp_session_id)throw new Error("Current session does not have an ACP context to compact");const o=await re({agentType:$(A.agentType,"codex"),cliType:$(A.cliType,"builtin"),acpSessionId:e.acp_session_id,cwd:s.cwd,model:a.model,mode:a.mode,configOptionValues:a.configOptionValues,timeoutMs:a.timeoutMs,allowRollover:a.control?.allowRollover===!0,signal:r.signal,onStatus:I=>{f.report({phase:I.phase,label:I.label,detail:I.detail,updateType:I.updateType,raw:I})}});await f.flush(),await S("POST",`/api/client/jobs/${encodeURIComponent(i)}/complete`,{machineId:t.machineId,acpSessionId:o.acpSessionId,suppressTurn:!0,result:{ok:!0,control:{type:"compact",allowRollover:a.control?.allowRollover===!0},acpSessionId:o.acpSessionId,durationMs:o.durationMs,usage:o.contextUsage,maintenance:o.maintenance,worktree:N(s)}}),n.info(`completed context compact job=${i}`);return}if(b==="fork"){const o=String(a.control?.sourceAcpSessionId||"").trim();if(!o)throw new Error("Source session does not have a Codex ACP context to fork");await f.sendNow({phase:"forking",label:"正在分叉 Codex 会话",detail:a.control?.sourceSessionId?`源会话 ${a.control.sourceSessionId}`:void 0}).catch(()=>{});const I=await se({agentType:$(A.agentType,"codex"),cliType:$(A.cliType,"builtin"),sourceProviderSessionId:o,cwd:s.cwd,model:a.model,mode:a.mode,configOptionValues:a.configOptionValues,timeoutMs:a.timeoutMs,signal:r.signal,onStatus:j=>{f.report({phase:j.phase,label:j.label,detail:j.detail,updateType:j.updateType,raw:j})}});await f.flush(),await S("POST",`/api/client/jobs/${encodeURIComponent(i)}/complete`,{machineId:t.machineId,acpSessionId:I.acpSessionId,suppressTurn:!0,result:{ok:!0,control:{type:"fork",sourceSessionId:a.control?.sourceSessionId||null},acpSessionId:I.acpSessionId,worktree:N(s)}}),n.info(`completed Codex session fork job=${i}`);return}const C=await ne({cwd:s.cwd,sessionId:l,jobId:i,attachments:a.attachments??[]});C.length>0&&(n.info(`materialized ${C.length} attachment(s) for job=${i}`),await f.sendNow({phase:"initializing",label:"附件已写入本机",detail:C.map(o=>o.relativePath).join(", "),raw:{attachments:B(C)}}).catch(()=>{}));const O=ee(a,C),P=te(O,C);k=await ie(s.cwd,o=>n.warn(o)).catch(o=>{n.warn(`artifact monitor unavailable job=${i}: ${String(o)}`)}),a.automation||(_=Z({jobId:i,sessionId:l,cwd:s.cwd,logger:n,signal:r.signal}));const w=await ce({agentType:$(A.agentType,"codex"),cliType:$(A.cliType,"builtin"),acpSessionId:e.acp_session_id??null,requireSessionResume:je(e.session_raw),prompt:O,promptBlocks:P,cwd:s.cwd,model:a.model,mode:a.mode,configOptionValues:a.configOptionValues,canExecuteTools:a.canExecuteTools,timeoutMs:a.timeoutMs,contextWindow:$e(A.contextWindow),signal:r.signal,onSupplementReady:o=>_?.setChannel(o),onStatus:o=>{f.report({phase:o.phase,label:o.label,detail:o.detail,updateType:o.updateType,raw:o})},onUpdate:o=>{m+=1,(o.type==="tool_call"||o.type==="tool_call_update"&&["completed","failed","cancelled"].includes(o.status||""))&&v("worker_event",{jobId:i,uploadRunId:d.uploadRunId,toolCallId:o.toolCallId,status:o.status,sequence:m});for(const j of pe(o.raw))E.add(j);const I={turnId:e.user_turn_id??null,eventType:o.type,label:o.label,text:o.text,status:o.status,toolCallId:o.toolCallId,raw:Ue(o.raw,m)};he(I)&&d.push(I)},onPermissionRequest:o=>{const I=me(a,o);return I?Promise.resolve(I):Fe(i,t.machineId,o,r.signal)},onUserInputRequest:o=>ze(i,t.machineId,o,r.signal),onProviderSessionMigration:async o=>{const I=await S("POST",`/api/client/jobs/${encodeURIComponent(i)}/acp-session`,{machineId:t.machineId,previousAcpSessionId:o.previousProviderSessionId,acpSessionId:o.providerSessionId,reason:o.reason,migratedAtMs:o.migratedAtMs});e.acp_session_id=o.providerSessionId,n.info(`migrated ACP session for job=${i} ${o.previousProviderSessionId} -> ${o.providerSessionId}`),await d.flush().catch(()=>{}),await f.sendNow({phase:"resuming",label:"Codex 会话已迁移",detail:o.providerSessionId,updateType:"aca.performance.provider_session_migrated",raw:{migration:o,server:{migrated:I.migrated===!0}}}).catch(()=>{})}}),Y=await M(),F=await d.finish();F.complete||T.uncertain.add("job-event-upload"),await f.flush(),await _?.stop();const W=V(q({cwd:s.cwd,evidencePaths:E}),Y),J=xe(w.promptResponse);if(!(Oe(J)||!!w.content)){await U(i,t.machineId,`${w.agentType} stopped: ${J??"unknown"}`,{acpSessionId:w.acpSessionId,stopReason:J,eventUpload:F,files:W,exitCode:w.exitCode,stderr:Ve(w.stderr,4e3),worktree:N(s)});return}await S("POST",`/api/client/jobs/${encodeURIComponent(i)}/complete`,{machineId:t.machineId,acpSessionId:w.acpSessionId,assistantText:w.content,result:{ok:!0,eventUpload:F,acpSessionId:w.acpSessionId,stopReason:J,durationMs:w.durationMs,acpRun:a.acpRun??null,effectiveConfig:w.effectiveConfig??null,performance:w.performance??null,usage:w.contextUsage??null,providerDiagnostics:w.providerDiagnostics??null,updateCount:w.updateCount,statusTimeline:w.statusTimeline,worktree:N(s),attachments:B(C),files:W}}),n.info(`completed job=${i}`)}catch(s){if(await _?.stop(),We(r.signal)){d.discard(),f.discard(),n.warn(`stopped local job=${i} because ACA Server already finalized it`);return}const A=await M(),C=await d.finish();C.complete||T.uncertain.add("job-event-upload"),await f.flush();const O=r.signal.aborted?"cancelled":s instanceof Error?s.message:String(s),P=be(s),w=V(x?q({cwd:x.cwd,evidencePaths:E}):[],A);await U(i,t.machineId,Ie(O),{rawError:O,eventUpload:C,files:w,providerDiagnostics:L(s,"providerDiagnostics"),statusTimeline:L(s,"statusTimeline"),performance:L(s,"performance"),...P?{acpSessionInvalidated:!0,invalidatedAcpSessionId:P.providerSessionId,failure:{category:P.category,actions:P.actions,details:P.details}}:{}},P?{invalidateAcpSession:!0,invalidatedAcpSessionId:P.providerSessionId}:void 0)}finally{await _?.stop(),k?.dispose(),d.discard(),clearInterval(y),await f.flush()}}function je(e){if(!e||typeof e!="object"||Array.isArray(e))return!1;const t=e.importedProviderSession;return!!(t&&typeof t=="object"&&!Array.isArray(t)&&t.strictResume===!0)}function Ee(e){return!e||typeof e!="object"||Array.isArray(e)?{}:e}function Me(e){const t=String(e||"codex").toLowerCase().replace(/[^a-z0-9]+/g,"");return t==="codex"||t==="codexacp"||t==="codexstandard"}function $e(e){return typeof e=="number"&&Number.isFinite(e)&&e>0?e:void 0}function $(...e){for(const t of e)if(typeof t=="string"&&t.trim())return t.trim();return""}function xe(e){if(!e||typeof e!="object"||Array.isArray(e))return;const t=e;if(typeof t.stopReason=="string")return t.stopReason;if(typeof t.reason=="string")return t.reason;const n=t.part;if(n&&typeof n=="object"&&!Array.isArray(n)&&typeof n.reason=="string")return n.reason}function Oe(e){return e?["end_turn","stop","completed","complete"].includes(e):!1}function Ne(e,t){let n=null,r=0,i="",l=null,u=Promise.resolve(),p=!1;const a=c=>JSON.stringify({phase:c.phase??"",label:c.label??"",detail:c.detail??"",updateType:c.updateType??""}),b=async c=>{await S("POST",`/api/client/jobs/${encodeURIComponent(e)}/presence`,{machineId:t,phase:c.phase,label:c.label,detail:c.detail,updateType:c.updateType,raw:c.raw}),r=Date.now(),i=a(c)},R=()=>{if(l&&(clearTimeout(l),l=null),p)return n=null,u;if(!n)return u;const c=n;return n=null,u=u.then(()=>b(c)).catch(()=>{}),u},g=c=>{l||(l=setTimeout(()=>{R()},c),l.unref())};return{report(c){if(p)return;const h=Date.now(),m=a(c)!==i,v=h-r>=ve;if(!m&&!v)return;n=c;const d=h-r;if(d>=K||r===0){R();return}g(K-d)},sendNow(c){return p?u:(n=c,R())},flush:R,discard(){p=!0,n=null,l&&clearTimeout(l),l=null}}}function Ue(e,t){return e&&typeof e=="object"&&!Array.isArray(e)?{...e,acaSequence:t}:{value:e,acaSequence:t}}async function Je(e,t,n){!n.usedWorktree||!n.worktreeId||await S("POST",`/api/client/jobs/${encodeURIComponent(e)}/worktree`,{machineId:t,baseCommit:n.baseCommit??null,worktreePath:n.cwd,status:n.status??"active",raw:n.raw??N(n)})}function N(e){return{usedWorktree:e.usedWorktree,worktreeId:e.worktreeId??null,cwd:e.cwd,status:e.status??null,baseCommit:e.baseCommit??null,branchName:e.branchName??null,baseBranch:e.baseBranch??null}}async function Fe(e,t,n,r){const l=(await S("POST",`/api/client/jobs/${encodeURIComponent(e)}/permissions`,{machineId:t,request:n})).item.permission_id;for(;!r.aborted;){await z(G);const u=await S("GET",`/api/client/permissions/${encodeURIComponent(l)}`);if(u.item.status==="resolved"&&u.item.response){const p=u.item.response.outcome;return p.outcome==="selected"?{outcome:p}:{outcome:{outcome:"cancelled"}}}}return{outcome:{outcome:"cancelled"}}}async function ze(e,t,n,r){const l=(await S("POST",`/api/client/jobs/${encodeURIComponent(e)}/permissions`,{machineId:t,request:{kind:"elicitation",...n}})).item.permission_id;for(;!r.aborted;){await z(G);const u=await S("GET",`/api/client/permissions/${encodeURIComponent(l)}`);if(u.item.status==="resolved"&&u.item.response){const p=u.item.response.outcome;return p.outcome==="submitted"?{outcome:p}:{outcome:{outcome:"cancelled"}}}}return{outcome:{outcome:"cancelled"}}}async function H(e,t){if(t.signal.aborted)return;const n=await S("GET",`/api/client/jobs/${encodeURIComponent(e)}`);if(Number(n.item.cancel_requested??0)===1){t.abort();return}const r=Le(n.item);r&&t.abort(new Q(e,r))}function Le(e){const t=String(e.status||"").toLowerCase();return t&&!["claimed","running"].includes(t)?t:null}class Q extends Error{constructor(t,n){super(`ACA Server already finalized job=${t} status=${n}`),this.name="ServerFinalizedJobError"}}function We(e){return e.aborted&&e.reason instanceof Q}async function U(e,t,n,r,i){await S("POST",`/api/client/jobs/${encodeURIComponent(e)}/fail`,{machineId:t,error:n,result:r,...i?.invalidateAcpSession?{invalidateAcpSession:!0,invalidatedAcpSessionId:i.invalidatedAcpSessionId}:{}})}function z(e){return new Promise(t=>setTimeout(t,e))}function Be(e){return Math.min(1e4,500*2**Math.min(5,Math.max(0,e-1)))+Math.floor(Math.random()*250)}function Ve(e,t){return e.length<=t?e:`${e.slice(0,t)}...`}function L(e,t){return!e||typeof e!="object"||Array.isArray(e)?null:e[t]??null}export{Le as serverFinalizedJobStatus,ut as startJobWorkerLoop};
|
package/dist/version.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
const o="0.1.
|
|
1
|
+
const o="0.1.81";export{o as AICA_VERSION};
|