@yuandc/aica 0.1.96 → 0.1.97
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/acp/client/acp-client.js +1 -1
- package/dist/acp/client/acp-events.js +1 -1
- package/dist/acp/client/acp-runtime-pool.js +1 -1
- package/dist/acp/client/json-rpc.js +3 -3
- package/dist/acp/providers/codex/provider.js +1 -1
- package/dist/acp/providers/codex-acp/adapter.js +3 -3
- package/dist/acp/providers/codex-acp/provider.js +1 -1
- package/dist/acp/providers/mimo/provider.js +2 -2
- package/dist/acp/providers/opencode/provider.js +3 -3
- package/dist/acp/providers/standard-acp-events.js +1 -1
- package/dist/acp/providers/workbuddy/auth-manager.js +1 -1
- package/dist/acp/providers/workbuddy/auth-transport.js +3 -3
- package/dist/acp/providers/workbuddy/interactions.js +1 -1
- package/dist/acp/providers/workbuddy/provider.js +1 -1
- package/dist/core/job-worker.js +1 -1
- package/dist/interactions/connection-interactions.js +1 -0
- package/dist/interactions/elicitation-tracker.js +1 -0
- package/dist/interactions/form-schema.js +1 -0
- package/dist/interactions/job-interactions.js +1 -1
- package/dist/interactions/provider-input.js +1 -1
- package/dist/interactions/types.js +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
import{JsonLineRpcClient as
|
|
1
|
+
import{ElicitationTracker as h}from"../../interactions/elicitation-tracker.js";import{JsonLineRpcClient as p}from"./json-rpc.js";class w{onSessionUpdate;onClientRequest;elicitation;sessions=new Set;elicitations=new h;rpc;constructor(e,s,c,a){this.onSessionUpdate=s,this.onClientRequest=c,this.elicitation=a,this.rpc=new p(e,{peerName:"ACP server",includeJsonRpc:!0,onNotification:(i,t,d)=>{i==="elicitation/complete"&&this.elicitations.complete(t),i==="session/update"&&this.onSessionUpdate(t,d)},onRequest:async(i,t,d)=>{if(t=this.elicitation?.normalizeRequest?.(i,t)??t,this.onClientRequest){const r=t,o=this.elicitation?.provider==="codex-acp"&&r?.acaInteractionBridge===1?r.request?.interaction:void 0;if(o&&(!this.sessions.has(r?.sessionId)||o.version===2&&!this.elicitation?.enabled))throw Object.assign(new Error("Invalid ACP interaction bridge scope or capability"),{rpcCode:-32602});if(i==="elicitation/create"&&!o){if(!this.elicitation?.enabled)throw Object.assign(new Error("Unsupported elicitation mode: client did not advertise support"),{rpcCode:-32602});if(!(typeof r?.sessionId=="string"?this.sessions.has(r.sessionId)&&r?.requestId==null:this.rpc.hasPendingRequest(r?.requestId)))throw Object.assign(new Error("Invalid elicitation scope for this connection"),{rpcCode:-32602})}const u=o?.kind==="url"?{mode:"url",elicitationId:o.elicitationId}:t,l=i==="elicitation/create"?this.elicitations.begin(u,o?.requestId??d.id,this.elicitation?.provider??"acp",this.elicitation?.completionHandler?.()):void 0;try{const n=await this.onClientRequest(i,t,d);return this.elicitations.settle(l,o?{action:n?.outcome?.outcome==="submitted"?"accept":"cancel"}:n),n}catch(n){throw this.elicitations.settle(l,null),n}}throw new Error(`Unsupported ACP client request: ${i}`)}})}initialize(e=6e4){return this.rpc.request("initialize",{protocolVersion:1,clientCapabilities:{fs:{readTextFile:!1,writeTextFile:!1},terminal:!1,...this.elicitation?.enabled?{elicitation:{form:{},url:{}}}:{}},clientInfo:{name:"aca",version:"0.1.0"}},e)}configureRuntime(e,s=3e4){return this.rpc.request("aca/runtime_config",e,s)}newSession(e,s=12e4){return this.rememberSession(this.rpc.request("session/new",{cwd:e.cwd,mcpServers:e.mcpServers??[],...e.config?{config:e.config}:{}},s))}resumeSession(e,s=12e4){return this.rememberSession(this.rpc.request("session/resume",{sessionId:e.sessionId,cwd:e.cwd,mcpServers:e.mcpServers??[],...e.config?{config:e.config}:{}},s),e.sessionId)}loadSession(e,s=12e4){return this.rememberSession(this.rpc.request("session/load",{sessionId:e.sessionId,cwd:e.cwd,mcpServers:e.mcpServers??[],...e.config?{config:e.config}:{}},s),e.sessionId)}async rememberSession(e,s){const c=s?this.sessions.has(s):!1;s&&this.sessions.add(s);let a;try{a=await e}catch(t){throw s&&!c&&this.sessions.delete(s),t}const i=a?.sessionId??s;return typeof i=="string"&&this.sessions.add(i),a}async closeSession(e,s=3e4){const c=await this.rpc.request("session/close",{sessionId:e},s);return this.sessions.delete(e),c}listSessions(e={},s=6e4){return this.rpc.request("session/list",e,s)}deleteSession(e,s=6e4){return this.rpc.request("session/delete",{sessionId:e},s)}forkSession(e,s=12e4){return this.rememberSession(this.rpc.request("session/fork",{sessionId:e.sessionId,cwd:e.cwd,...e.mcpServers?{mcpServers:e.mcpServers}:{},...e.config?{config:e.config}:{}},s))}setSessionConfigOption(e,s=6e4){return this.rpc.request("session/set_config_option",e,s)}setSessionMode(e,s=6e4){return this.rpc.request("session/set_mode",e,s)}prompt(e,s){return this.rpc.request("session/prompt",e,s)}compactSession(e,s=600*1e3){return this.rpc.request("session/compact",e,s)}recoverSession(e,s=12e4){return this.rpc.request("aca/session_recover",e,s)}cancel(e){this.rpc.notify("session/cancel",{sessionId:e})}authenticate(e,s=12e4){return this.rpc.request("authenticate",e,s)}logout(e={},s=6e4){return this.rpc.request("logout",e,s)}listProviders(e={},s=6e4){return this.rpc.request("providers/list",e,s)}setProvider(e,s=6e4){return this.rpc.request("providers/set",e,s)}disableProvider(e,s=6e4){return this.rpc.request("providers/disable",e,s)}}export{w as AcpClient};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{createPlanEvent as
|
|
1
|
+
import{createPlanEvent as _,normalizePlanSessionUpdate as y}from"../plan-events.js";import{STANDARD_AGENT_EVENT_TYPES as A,createAgentMessageChunk as b,createAgentProgressChunk as x,createAgentThoughtChunk as k,createToolCall as C,createToolCallUpdate as I,firstString as a,normalizeToolKind as S,normalizeToolStatus as h}from"../standard-events.js";function f(n){if(!n||typeof n!="object"||Array.isArray(n))return null;const t=n,e=t.update;return!e||typeof e!="object"||Array.isArray(e)?null:{sessionId:typeof t.sessionId=="string"?t.sessionId:void 0,update:e}}function E(n){const t=f(n);if(!t)return null;const e=String(t.update.sessionUpdate||""),r=u(t.update._meta)??void 0;if(e==="agent_message_chunk")return b({text:i(t.update.content),method:"session/update",params:n,meta:r});if(e==="agent_progress_chunk")return x({text:i(t.update.content),method:"session/update",params:n,meta:r});if(e==="agent_thought_chunk")return k({text:i(t.update.content),method:"session/update",params:n,meta:r});if(e==="tool_call"||e==="tool_call_update")return w(e,t.update,n);if(e==="plan"||e==="plan_update"||e==="plan_removed"){const o=y(t.update);return o?_({update:o,method:"session/update",params:n,meta:r}):null}return A.has(e)?{type:e,label:T(e),...i(t.update.content)?{text:i(t.update.content)}:{},raw:{method:"session/update",params:n,update:t.update},atMs:Date.now()}:null}function P(n){const t=f(n);if(!t||m(t.update))return null;const e=String(t.update.sessionUpdate||"");if(e==="agent_message_chunk")return{phase:"thinking",label:"回复中",detail:g(i(t.update.content)),updateType:e};if(e==="agent_progress_chunk")return{phase:"thinking",label:"处理中",detail:g(i(t.update.content)),updateType:e};if(e==="agent_thought_chunk")return{phase:"thinking",label:"思考中",detail:g(i(t.update.content)),updateType:e};if(e==="tool_call"||e==="tool_call_update"){const r=a(t.update.toolCallId,t.update.id),o=h(a(t.update.status)||"in_progress"),s=a(t.update.title,t.update.kind),c=U(t.update.rawOutput);if(r==="codex.connection")return{phase:o==="failed"?"failed":o==="completed"?"exploring":"resuming",label:s||(o==="completed"?"Codex 连接已恢复":"Codex 正在重连"),detail:c,updateType:e};if(r==="codex.error")return{phase:"failed",label:s||"Codex 执行异常",detail:c,updateType:e};const d=u(t.update.rawInput),l=a(d?.command);return s==="压缩上下文"?{phase:"thinking",label:"压缩上下文",updateType:e}:s==="等待协作消息"?{phase:"exploring",label:"等待协作消息",updateType:e}:/^(?:(?:启动|发送|恢复|等待|结束|中断|查看|处理)子任务|子任务(?:交互|完成|中断))/.test(s||"")?{phase:"exploring",label:s||"处理子任务",updateType:e}:l?{phase:"exploring",label:"执行命令",detail:s||l,updateType:e}:{phase:"exploring",label:"调用工具",detail:s,updateType:e}}return e==="plan"||e==="plan_update"?{phase:"thinking",label:"规划中",updateType:e}:e==="aca_context_maintenance"?{phase:"thinking",label:a(t.update.label)||"正在整理会话上下文",detail:a(t.update.detail),updateType:e}:null}function D(n){const t=f(n);return t?m(t.update):null}function m(n){const t=u(n._meta),e=u(t?.aca);return u(e?.subtask)}function i(n){if(typeof n=="string")return n;if(!n||typeof n!="object"||Array.isArray(n))return"";const t=n;return typeof t.text=="string"?t.text:""}function w(n,t,e){const r=a(t.toolCallId,t.id)||`acp.${Date.now()}`,o=S(a(t.kind,t.title)),s=h(a(t.status)||"in_progress"),c=t.rawInput??t.input,d=t.rawOutput??t.output,l={method:"session/update",params:e,toolCallId:r,kind:o,title:a(t.title,t.kind),status:s,rawInput:c,rawOutput:d,content:t.content,locations:Array.isArray(t.locations)?t.locations.filter(p=>!!(p&&typeof p=="object"&&!Array.isArray(p))):void 0,meta:t._meta&&typeof t._meta=="object"&&!Array.isArray(t._meta)?t._meta:void 0};return n==="tool_call"?C(l):I(l)}function T(n){return n.replace(/_/g," ")}function g(n){const t=n.replace(/\s+/g," ").trim();if(t)return t.length>120?`${t.slice(0,117)}...`:t}function U(n){if(!n||typeof n!="object"||Array.isArray(n))return;const t=n,e=t.error&&typeof t.error=="object"&&!Array.isArray(t.error)?t.error:null,r=a(e?.message,t.message),o=/reconnecting\.{3}\s*(\d+)\s*\/\s*(\d+)/i.exec(r||"");return o?`第 ${o[1]}/${o[2]} 次尝试`:/selected model is at capacity/i.test(r||"")?"所选模型当前繁忙,请稍后重试":r}function u(n){return n&&typeof n=="object"&&!Array.isArray(n)?n:null}export{f as acpSessionUpdateFromParams,E as eventFromAcpSessionUpdate,P as statusFromAcpSessionUpdate,D as subtaskContextFromAcpSessionUpdate,i as textFromAcpContent};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import
|
|
1
|
+
import c from"node:fs";import{randomUUID as m}from"node:crypto";import{AcpClient as p}from"./acp-client.js";import{closeAcpProcess as h,startAcpProcess as v}from"./acp-process.js";const f=300*1e3,I=60*1e3,w=4;class E{runtimes=new Map;idleTtlMs;sweepIntervalMs;maxIdleRuntimes;sweepTimer=null;disposeAllPromise=null;constructor(e={}){this.idleTtlMs=Math.max(1e3,e.idleTtlMs??R()),this.sweepIntervalMs=Math.max(1e3,e.sweepIntervalMs??A()),this.maxIdleRuntimes=Math.max(0,e.maxIdleRuntimes??S())}async acquire(e){this.disposeAllPromise&&await this.disposeAllPromise;const i=c.realpathSync(e.cwd);this.ensureSweep();const s=e.runtimeIdentity||"default",t=e.providerSessionId?d(e.providerId,i,e.providerSessionId,s):null,n=t?this.runtimes.get(t):null;n&&!this.isRuntimeAlive(n)&&await this.disposeEntry(n).catch(()=>{});let o=t?this.runtimes.get(t):null;if(o&&o.elicitation!==(e.elicitation===!0)){if(o.inUse)throw new Error("ACP runtime is already in use with different elicitation capabilities");await this.disposeEntry(o),o=void 0}if(o)return this.activateEntry(o,e),this.createLease(o);const u=e.providerSessionId||`pending:${m()}`,l=d(e.providerId,i,u,s),a=this.createEntry({...e,cwd:i,key:l,providerSessionId:e.providerSessionId??null,runtimeIdentity:s});return this.runtimes.set(l,a),this.createLease(a)}disposeAll(){if(this.disposeAllPromise)return this.disposeAllPromise;const e=this.performDisposeAll();return this.disposeAllPromise=e,e.finally(()=>{this.disposeAllPromise===e&&(this.disposeAllPromise=null)}),e}async performDisposeAll(){this.sweepTimer&&(clearInterval(this.sweepTimer),this.sweepTimer=null);const e=[...this.runtimes.values()];await Promise.all(e.map(i=>this.disposeEntry(i).catch(()=>{})))}async sweepIdle(e=Date.now()){const i=[...this.runtimes.values()];for(const s of i)s.inUse||s.disposed||e-s.lastUsedAtMs<this.idleTtlMs||await this.disposeEntry(s).catch(()=>{});await this.trimIdleRuntimes()}async retireProviderRuntimes(e){const i=[...this.runtimes.values()].filter(s=>s.providerId===e&&!s.disposed);for(const s of i)s.retired=!0;return await Promise.all(i.filter(s=>!s.inUse).map(s=>this.disposeEntry(s).catch(()=>{}))),i.length}async retireSessionRuntimes(e){const i=c.realpathSync(e.cwd),s=[...this.runtimes.values()].filter(t=>!t.disposed&&t.providerId===e.providerId&&t.cwd===i&&t.providerSessionId===e.providerSessionId&&t.runtimeIdentity!==e.keepRuntimeIdentity);if(s.some(t=>t.inUse))throw new Error("ACP session is already in use with another runtime configuration");for(const t of s)t.retired=!0;await Promise.all(s.map(t=>this.disposeEntry(t)))}stats(){const e=[...this.runtimes.values()].filter(i=>!i.disposed);return{total:e.length,inUse:e.filter(i=>i.inUse).length,idle:e.filter(i=>!i.inUse).length,pids:e.map(i=>i.started.child.pid).filter(i=>Number.isInteger(i))}}createEntry(e){const i=v(e.command,e.args,e.cwd,e.env),s={key:e.key,providerId:e.providerId,cwd:e.cwd,providerSessionId:e.providerSessionId,runtimeIdentity:e.runtimeIdentity,started:i,client:null,initialized:!1,elicitation:e.elicitation===!0,sessionEstablished:!1,runtimeConfigured:!1,inUse:!0,retired:!1,disposed:!1,disposePromise:null,lastUsedAtMs:Date.now(),sessionResponse:null,activeHandlers:{onSessionUpdate:e.onSessionUpdate,onClientRequest:e.onClientRequest,normalizeClientRequest:e.normalizeClientRequest,onElicitationComplete:e.onElicitationComplete}};return s.client=new p(i.child,(t,n)=>s.activeHandlers?.onSessionUpdate(t,n),async(t,n,o)=>{if(!s.activeHandlers?.onClientRequest)throw new Error(`Unsupported ACP client request: ${t}`);return s.activeHandlers.onClientRequest(t,n,o)},{provider:e.elicitationProvider??e.providerId,enabled:e.elicitation,normalizeRequest:(t,n)=>s.activeHandlers?.normalizeClientRequest?.(t,n)??n,completionHandler:()=>s.activeHandlers?.onElicitationComplete}),i.child.once("close",()=>{this.runtimes.get(s.key)===s&&this.runtimes.delete(s.key),s.disposed=!0,s.inUse=!1,s.activeHandlers=null}),s}activateEntry(e,i){if(e.inUse)throw new Error(`ACP runtime is already in use: provider=${e.providerId} session=${e.providerSessionId??"unknown"}`);e.inUse=!0,e.activeHandlers={onSessionUpdate:i.onSessionUpdate,onClientRequest:i.onClientRequest,normalizeClientRequest:i.normalizeClientRequest,onElicitationComplete:i.onElicitationComplete}}createLease(e){let i=!1;return{get client(){return e.client},get started(){return e.started},get cwd(){return e.cwd},get providerSessionId(){return e.providerSessionId},get sessionResponse(){return e.sessionResponse},get initialized(){return e.initialized},get sessionEstablished(){return e.sessionEstablished},get runtimeConfigured(){return e.runtimeConfigured},markInitialized:()=>{e.initialized=!0},markRuntimeConfigured:()=>{e.runtimeConfigured=!0},setProviderSession:s=>{e.providerSessionId=s.providerSessionId,e.sessionEstablished=!0,"sessionResponse"in s&&(e.sessionResponse=s.sessionResponse??null),this.rekeyEntry(e,d(e.providerId,e.cwd,s.providerSessionId,e.runtimeIdentity))},setSessionResponse:s=>{e.sessionResponse=s},release:()=>{i||(i=!0,e.inUse=!1,e.activeHandlers=null,e.lastUsedAtMs=Date.now(),e.retired?this.disposeEntry(e):this.trimIdleRuntimes())},dispose:async()=>{i=!0,await this.disposeEntry(e)}}}rekeyEntry(e,i){if(e.key===i)return;const s=this.runtimes.get(i);if(s&&s!==e){if(s.inUse)throw new Error(`ACP runtime key is already active: provider=${s.providerId} session=${s.providerSessionId??"unknown"}`);this.disposeEntry(s)}this.runtimes.get(e.key)===e&&this.runtimes.delete(e.key),e.key=i,this.runtimes.set(i,e)}async disposeEntry(e){if(e.disposePromise)return e.disposePromise;if(e.disposed)return;const i=this.performDisposeEntry(e);return e.disposePromise=i,i}async performDisposeEntry(e){e.disposed=!0,e.inUse=!1,e.activeHandlers=null,this.runtimes.get(e.key)===e&&this.runtimes.delete(e.key),e.providerSessionId&&await e.client.closeSession(e.providerSessionId,5e3).catch(()=>{}),await h(e.started.child)}async trimIdleRuntimes(){const e=[...this.runtimes.values()].filter(s=>!s.inUse&&!s.disposed).sort((s,t)=>s.lastUsedAtMs-t.lastUsedAtMs),i=Math.max(0,e.length-this.maxIdleRuntimes);for(const s of e.slice(0,i))await this.disposeEntry(s).catch(()=>{})}isRuntimeAlive(e){return!e.disposed&&e.started.child.exitCode===null&&e.started.child.signalCode===null}ensureSweep(){this.sweepTimer||(this.sweepTimer=setInterval(()=>{this.sweepIdle()},this.sweepIntervalMs),this.sweepTimer.unref())}}const M=new E;function d(r,e,i,s){return`${r}\0${e}\0${i}\0${s}`}function R(){const r=Number.parseInt(process.env.ACA_ACP_RUNTIME_IDLE_TTL_MS??"",10);return Number.isInteger(r)&&r>=1e3?r:f}function A(){const r=Number.parseInt(process.env.ACA_ACP_RUNTIME_SWEEP_INTERVAL_MS??"",10);return Number.isInteger(r)&&r>=1e3?r:I}function S(){const r=Number.parseInt(process.env.ACA_ACP_RUNTIME_MAX_IDLE??"",10);return Number.isInteger(r)&&r>=0?Math.min(r,32):w}export{E as AcpRuntimePool,M as defaultAcpRuntimePool};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{appendBoundedText as u}from"./bounded-buffer.js";class s{child;handlers;static MAX_STDOUT_BYTES=1024*1024;static MAX_STDERR_BYTES=1024*1024;nextId=1;lineBuffer="";pending=new Map;peerName;includeJsonRpc;stdoutBuffer="";stderrBuffer="";notificationCount=0;exitCode=null;signal=null;constructor(e,
|
|
2
|
-
`);this.lineBuffer=
|
|
3
|
-
[non-json stdout] ${
|
|
1
|
+
import{appendBoundedText as u}from"./bounded-buffer.js";class s{child;handlers;static MAX_STDOUT_BYTES=1024*1024;static MAX_STDERR_BYTES=1024*1024;nextId=1;lineBuffer="";pending=new Map;peerName;includeJsonRpc;stdoutBuffer="";stderrBuffer="";notificationCount=0;exitCode=null;signal=null;constructor(e,i={}){this.child=e,this.handlers=i,this.peerName=i.peerName||"JSON-RPC peer",this.includeJsonRpc=i.includeJsonRpc===!0,e.stdout.on("data",t=>this.handleStdout(String(t))),e.stderr.on("data",t=>{this.stderr=this.stderr+String(t)}),e.on("close",(t,r)=>{this.exitCode=t,this.signal=r;for(const[n,o]of this.pending)clearTimeout(o.timer),o.reject(new Error(`${this.peerName} exited before ${o.method} completed (code=${t}, signal=${r})`)),this.pending.delete(n)}),e.on("error",t=>{for(const[r,n]of this.pending)clearTimeout(n.timer),n.reject(t),this.pending.delete(r)})}get stdout(){return this.stdoutBuffer}get stderr(){return this.stderrBuffer}set stderr(e){this.stderrBuffer=u("",e,s.MAX_STDERR_BYTES)}request(e,i,t=12e4){const r=this.nextId++;return new Promise((n,o)=>{const d=this.createRequestTimeout(r,e,t,o);d.unref(),this.pending.set(r,{method:e,resolve:n,reject:o,timer:d,timeoutMs:t,pauseCount:0}),this.write({...this.includeJsonRpc?{jsonrpc:"2.0"}:{},id:r,method:e,...i===void 0?{}:{params:i}})})}notify(e,i){this.write({...this.includeJsonRpc?{jsonrpc:"2.0"}:{},method:e,...i===void 0?{}:{params:i}})}hasPendingRequest(e){return typeof e=="number"&&this.pending.has(e)}refreshPendingRequestTimeout(e){for(const[i,t]of this.pending)t.method!==e||t.pauseCount>0||(clearTimeout(t.timer),t.timer=this.createRequestTimeout(i,t.method,t.timeoutMs,t.reject),t.timer.unref())}respond(e,i){this.write({...this.includeJsonRpc?{jsonrpc:"2.0"}:{},id:e,result:i})}respondError(e,i){this.write({...this.includeJsonRpc?{jsonrpc:"2.0"}:{},id:e,error:{code:Number.isInteger(i.rpcCode)?i.rpcCode:-32603,message:i.message}})}createRequestTimeout(e,i,t,r){return setTimeout(()=>{this.pending.delete(e),r(new Error(`${this.peerName} request timed out while waiting for ${i}`))},t)}handleStdout(e){this.stdoutBuffer=u(this.stdoutBuffer,e,s.MAX_STDOUT_BYTES),this.lineBuffer+=e;const i=this.lineBuffer.split(`
|
|
2
|
+
`);this.lineBuffer=i.pop()??"";for(const t of i){const r=t.trim();if(!r)continue;let n;try{n=JSON.parse(r)}catch{this.stderr+=`
|
|
3
|
+
[non-json stdout] ${r}`;continue}this.handleMessage(n)}}async handleMessage(e){if("id"in e&&("result"in e||"error"in e)&&typeof e.method!="string"){const i=Number(e.id),t=this.pending.get(i);if(!t)return;if(this.pending.delete(i),clearTimeout(t.timer),"error"in e){const r=e.error;t.reject(Object.assign(new Error(r?.message||`${this.peerName} error ${r?.code??"unknown"}`),{rpcResponse:!0,rpcCode:r?.code,rpcData:r?.data}))}else t.resolve(e.result);return}if(typeof e.method=="string"){if("id"in e){const t=/^(elicitation\/create|session\/request_permission|session\/request_user_input|item\/tool\/requestUserInput|mcpServer\/elicitation\/request|_codebuddy.ai\/question)$/.test(e.method)?this.pauseForInteraction(e.params?.requestId):()=>{};try{if(!this.handlers.onRequest)throw Object.assign(new Error(`Unsupported client request: ${e.method}`),{rpcCode:-32601});const r=await this.handlers.onRequest(e.method,e.params,e);this.respond(e.id,r??null)}catch(r){this.respondError(e.id,r instanceof Error?r:new Error(String(r)))}finally{t()}return}this.notificationCount+=1,await this.handlers.onNotification?.(e.method,e.params,e)}}pauseForInteraction(e){const i=[...this.pending].filter(([t,r])=>e!==void 0?t===e:["session/prompt","turn/start"].includes(r.method));for(const[,t]of i)t.pauseCount++,clearTimeout(t.timer);return()=>{for(const[t,r]of i)r.pauseCount--,!(this.pending.get(t)!==r||r.pauseCount>0)&&(r.timer=this.createRequestTimeout(t,r.method,r.timeoutMs,r.reject),r.timer.unref())}}write(e){this.child.stdin.write(`${JSON.stringify(e)}
|
|
4
4
|
`)}}export{s as JsonLineRpcClient};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import u from"node:fs";import{handleProviderInput as
|
|
1
|
+
import{ElicitationTracker as c}from"../../../interactions/elicitation-tracker.js";import u from"node:fs";import{handleProviderInput as h,isCodexInputMethod as p}from"../../../interactions/provider-input.js";import{BoundedItemBuffer as m}from"../../client/bounded-buffer.js";import{JsonLineRpcClient as f}from"../../client/json-rpc.js";import{startCodexAppServer as g}from"./codex-process.js";import{extractAssistantTextFromTurn as C,mapCodexNotification as w}from"./events.js";import{CodexPlanDeltaState as v}from"./plan-events.js";import{isCodexApprovalRequest as T,resolveCodexApprovalRequest as x}from"./permissions.js";const I=1500,S={sessionResume:!0,imageInput:!0,fileAttachment:!0,filesystem:!0,terminal:!0,permissionRequest:!0,configOptions:!0,usage:!0,contextUsage:!0,contextCompaction:!0,plan:!0,diff:!0};class O{id="native-codex";name="Native Codex";capabilities=S;async createSession(e){return new y(e.cwd,e.providerSessionId??null,e.timeoutMs)}}class y{cwd;providerSessionId;defaultTimeoutMs;started;rpc;initialized=!1;content="";promptActive=!1;currentTurnId=null;updates=new m(16);statusTimeline=[];statusCallback=null;updateCallback=null;permissionCallback=null;elicitations=new c;elicitationComplete;userInputCallback=null;abortHandler=null;completedTurns=new Map;pendingTurnCompletions=new Map;agentMessagePhases=new Map;planDeltas=new v;constructor(e,s,t=q()){this.cwd=e,this.providerSessionId=s,this.defaultTimeoutMs=t,this.started=g(e),this.rpc=new f(this.started.child,{onNotification:(r,i)=>this.handleNotification(r,i),onRequest:(r,i,a)=>this.handleReverseRequest(r,i,a.id)})}async sendPrompt(e){const s=Date.now(),t=l(e.mode);this.statusCallback=e.onStatus??null,this.updateCallback=e.onUpdate??null,this.permissionCallback=e.onPermissionRequest??null,this.userInputCallback=e.onUserInputRequest??null,this.elicitationComplete=e.onElicitationComplete,this.attachAbortSignal(e.signal),this.emitStatus("initializing","Codex 初始化中","initialize"),await this.initialize();const r=await this.establishThread(e);this.emitStatus("thinking","思考中"),this.content="",this.updates.clear(),this.agentMessagePhases.clear(),this.planDeltas.reset(),this.promptActive=!0;let i,a;try{i=await this.rpc.request("turn/start",{threadId:r,clientUserMessageId:`aca-${Date.now()}-${Math.random().toString(16).slice(2)}`,input:P(e.prompt,e.promptBlocks),cwd:e.cwd,...e.model?{model:e.model}:{},...b(e.configOptionValues)},e.timeoutMs??this.defaultTimeoutMs);const o=String(i?.turn?.id??"");this.currentTurnId=o||null,a=o?await this.waitForTurnCompleted(o,e.timeoutMs??this.defaultTimeoutMs):i?.turn}finally{this.promptActive=!1,this.currentTurnId=null}const d=this.content.trim()||C(a);return this.emitStatus("completed","已完成"),{providerSessionId:r,cliType:"builtin",agentType:"codex",effectiveConfig:t,command:this.started.command,args:this.started.args,cwd:this.cwd,exitCode:this.rpc.exitCode,signal:this.rpc.signal,durationMs:Date.now()-s,stdout:this.rpc.stdout,stderr:this.rpc.stderr,content:d,updateCount:this.updates.count(),updates:this.updates.snapshot(),statusTimeline:this.statusTimeline,promptResponse:i}}async cancel(){try{this.providerSessionId&&this.started.child.exitCode===null&&this.started.child.signalCode===null&&await this.rpc.request("turn/interrupt",{threadId:this.providerSessionId,turnId:this.currentTurnId??""},I).catch(()=>{})}finally{await this.close()}}async close(){this.statusCallback=null,this.updateCallback=null,this.permissionCallback=null,this.userInputCallback=null;for(const e of this.pendingTurnCompletions.values())clearTimeout(e.timer),e.reject(new Error("Codex app-server session closed"));this.pendingTurnCompletions.clear(),this.abortHandler&&(this.abortHandler=null),await this.closeCodexProcess()}async closeCodexProcess(){this.started.child.exitCode!==null||this.started.child.signalCode!==null||(this.started.child.stdin.end(),!await this.waitForProcessClose(2e3)&&(this.started.child.kill("SIGTERM"),!await this.waitForProcessClose(1e3)&&(this.started.child.kill("SIGKILL"),await this.waitForProcessClose(1e3))))}waitForProcessClose(e){return this.started.child.exitCode!==null||this.started.child.signalCode!==null?Promise.resolve(!0):new Promise(s=>{const t=setTimeout(()=>s(!1),e);t.unref(),this.started.child.once("close",()=>{clearTimeout(t),s(!0)})})}async initialize(){this.initialized||(await this.rpc.request("initialize",{clientInfo:{name:"aca",version:"0.1.0"},capabilities:{experimentalApi:!0,requestAttestation:!1}}),this.initialized=!0)}async establishThread(e){const s=l(e.mode);if(this.providerSessionId)try{this.emitStatus("resuming","Codex 会话恢复中","thread/resume");const i=await this.rpc.request("thread/resume",{threadId:this.providerSessionId,cwd:e.cwd,...e.model?{model:e.model}:{},approvalsReviewer:"user",...s},6e4);return this.providerSessionId=String(i?.thread?.id??this.providerSessionId),this.providerSessionId}catch(i){if(e.requireSessionResume){const a=i instanceof Error?i.message:String(i);throw new Error(`无法恢复导入的 Codex Session ${this.providerSessionId}:${a}`)}this.providerSessionId=null}this.emitStatus("acp","Codex 启动中","thread/start");const r=(await this.rpc.request("thread/start",{cwd:e.cwd,...e.model?{model:e.model}:{},approvalsReviewer:"user",...s,ephemeral:!1,threadSource:"aca"},6e4))?.thread?.id;if(!r)throw new Error("Codex app-server thread/start did not return thread id");return this.providerSessionId=r,r}attachAbortSignal(e){if(e){if(this.abortHandler=()=>{this.cancel()},e.aborted)throw new Error("ACP prompt cancelled");e.addEventListener("abort",this.abortHandler,{once:!0})}}handleNotification(e,s){if(e==="elicitation/complete"){this.elicitations.complete(s);return}!this.promptActive&&!R(e)||(this.rpc.refreshPendingRequestTimeout("turn/start"),this.updates.push({method:e,params:s}),this.captureAgentMessagePhase(e,s),this.appendFinalAnswerDelta(e,s),e==="turn/completed"&&this.resolveTurnCompleted(s),w(e,s,{pushUpdate:t=>this.updateCallback?.(t),pushStatus:(t,r,i,a)=>this.emitStatus(t,r,i,a)},{agentMessagePhases:this.agentMessagePhases,planDeltas:this.planDeltas}))}captureAgentMessagePhase(e,s){if(e!=="item/started"&&e!=="item/completed")return;const t=s?.item;t?.type!=="agentMessage"||!t.id||this.agentMessagePhases.set(t.id,t.phase??"")}appendFinalAnswerDelta(e,s){if(e!=="item/agentMessage/delta")return;const t=s;if(!t.itemId||typeof t.delta!="string")return;const r=this.agentMessagePhases.get(t.itemId);(r==="final_answer"||!r)&&(this.content+=t.delta)}waitForTurnCompleted(e,s){const t=this.completedTurns.get(e);return t?Promise.resolve(t):new Promise((r,i)=>{const a=setTimeout(()=>{this.pendingTurnCompletions.delete(e),i(new Error("Codex app-server request timed out while waiting for turn/completed"))},s);a.unref(),this.pendingTurnCompletions.set(e,{resolve:r,reject:i,timer:a})})}resolveTurnCompleted(e){const s=e?.turn,t=s?.id;if(!t)return;this.completedTurns.set(t,s);const r=this.pendingTurnCompletions.get(t);if(r){if(this.pendingTurnCompletions.delete(t),clearTimeout(r.timer),s.status==="failed"){r.reject(new Error(`Codex turn failed: ${JSON.stringify(s.error??{})}`));return}r.resolve(s)}}async handleReverseRequest(e,s,t){if(T(e))return this.emitStatus("requestPermission","等待授权",M(s),e),x(e,s,this.permissionCallback,t);if(p(e)){this.emitStatus("requestPermission","等待回答",void 0,e);const r=this.elicitations.begin(s,t??"","native-codex",this.elicitationComplete);try{const i=await h("native-codex",e,s,this.userInputCallback,t);return this.elicitations.settle(r,i),i}catch(i){throw this.elicitations.settle(r,null),i}}throw new Error(`Unsupported Codex server request: ${e}`)}emitStatus(e,s,t,r){const i={phase:e,label:s,...t?{detail:t}:{},...r?{updateType:r}:{},atMs:Date.now()},a=this.statusTimeline[this.statusTimeline.length-1];a&&a.phase===i.phase&&a.label===i.label&&a.detail===i.detail&&a.updateType===i.updateType||(this.statusTimeline.push(i),this.statusCallback?.(i))}}function P(n,e){return(e?.length?e:[{type:"text",text:n}]).map(t=>t.type==="image"?t.uri&&u.existsSync(t.uri)?{type:"localImage",path:t.uri}:{type:"image",url:`data:${t.mimeType};base64,${t.data}`}:{type:"text",text:t.text,text_elements:[]})}function l(n){const e=String(n||"").trim();return e==="read-only"?{approvalPolicy:"on-request",sandbox:"read-only"}:e==="acceptEdits"||e==="accept-edits"?{approvalPolicy:"on-failure",sandbox:"workspace-write"}:["agent-full-access","full-access","bypassPermissions","danger-full-access"].includes(e)?{approvalPolicy:"never",sandbox:"danger-full-access"}:{approvalPolicy:"on-request",sandbox:"workspace-write"}}function b(n){if(!n)return{};const e={},s=n.reasoning_effort??n.reasoningEffort??n.model_reasoning_effort??n.modelReasoningEffort??n.effort;return typeof s=="string"&&s&&(e.effort=s),e}function M(n){if(!n||typeof n!="object"||Array.isArray(n))return;const e=n;for(const s of["command","cwd","reason","grantRoot"]){const t=e[s];if(typeof t=="string"&&t.trim())return A(t)}}function A(n){const e=n.replace(/\s+/g," ").trim();return e.length>120?`${e.slice(0,117)}...`:e}function R(n){return n.startsWith("thread/")?!0:["account/rateLimits/updated","account/updated","configWarning","warning","model/rerouted","thread/compacted"].includes(n)}function q(){const n=Number.parseInt(process.env.ACA_ACP_PROMPT_TIMEOUT_MS??"",10);return Number.isInteger(n)&&n>=3e4?n:720*60*1e3}export{O as NativeCodexProvider,S as nativeCodexCapabilities};
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import{isCodexInputMethod as
|
|
2
|
-
`);this.lineBuffer=t.pop()??"";for(const s of t){const i=s.trim();if(!i)continue;let o;try{o=JSON.parse(i)}catch{continue}this.handleMessage(o)}}async handleMessage(e){if("id"in e&&("result"in e||"error"in e)&&typeof e.method!="string"){this.handleParentResponse(e);return}const t=typeof e.method=="string"?e.method:"",s=e.id;if(t){if(s===void 0){await this.handleNotification(t,a(e.params));return}try{const i=await this.handleRequest(t,a(e.params));this.write({jsonrpc:"2.0",id:s,result:i})}catch(i){this.write({jsonrpc:"2.0",id:s,error:{code:-32603,message:i instanceof Error?i.message:String(i)}})}}}async handleNotification(e,t){if(e==="session/cancel")try{await this.cancel(t)}catch(s){process.stderr.write(`Codex cancel failed: ${s instanceof Error?s.message:String(s)}
|
|
1
|
+
import{isCodexInputMethod as j,normalizeProviderInput as L,providerInputResponse as O}from"../../../interactions/provider-input.js";import{UserSteeringLedger as B}from"./user-steering.js";import{createToolLifecycleTrace as y}from"../../tool-lifecycle-trace.js";import{toolEnvironmentInstructions as C}from"../../../core/tool-environment.js";import{JsonLineRpcClient as K}from"../../client/json-rpc.js";import{startCodexAppServer as W}from"../codex/codex-process.js";import{CodexConnectionRecoveryTracker as z}from"../codex/connection-events.js";import{codexAcpSessionUpdatesFromNotification as k}from"../codex/events.js";import{CodexPlanDeltaState as v}from"../codex/plan-events.js";import{isCodexApprovalRequest as G,resolveCodexApprovalRequest as J}from"../codex/permissions.js";import{buildCodexContinuityInstructions as H,codexTokenUsageFromRawResponseCompleted as V,codexTokenUsageFromNotification as X,decideCodexContextMaintenance as R,readCodexContextMaintenancePolicy as A,rolloutFileSize as Q}from"./context-maintenance.js";import{codexNotificationBelongsToCurrentTurn as Y,codexNotificationRouting as P}from"./notification-routing.js";import{completedSubtaskHistoryUpdates as Z}from"./subtask-history.js";import{CodexSubtaskRouter as ee}from"./subtask-routing.js";import{decorateSubtaskUpdate as _}from"./subtask-executions.js";import{decorateSubtaskCompletion as te}from"./subtask-completion.js";import{waitForCancellableTurnStart as se}from"./cancellation.js";import{codexAcpUpdateIsMeaningfulProgress as M,codexRawItemIsMeaningfulProgress as ie,withCodexProgressMetadata as U}from"./progress-evidence.js";import{acpPromptText as oe,acpPromptToCodexInput as ne,turnConfigFromConfig as re}from"./turn-input.js";import{CodexControlToolLoopDetector as ae,codexControlToolLoopAcpUpdate as q,controlToolLoopDetectionThresholds as ce,controlToolLoopReady as ue,readCodexControlToolLoopPolicy as de}from"./control-tool-loop.js";import{CodexControlToolSteeringLimiter as le,codexControlToolSteeringAcpUpdate as E}from"./control-tool-steering.js";class he{toolTrace=y();lineBuffer="";started=null;rpc=null;codexStartupPromise=null;initialized=!1;currentSessionId=null;currentCwd=process.cwd();promptActive=!1;currentTurnId=null;userSteering=new B;turnStartPromise=null;promptCancelled=!1;nextParentRequestId=1;pendingParentRequests=new Map;completedTurns=new Map;pendingTurnCompletions=new Map;agentMessagePhases=new Map;planDeltas=new v;subtaskRouter=new ee;subtaskGeneration=0;subtaskAgentMessagePhases=new Map;subtaskPlanDeltas=new Map;subscribedSubtaskThreadIds=new Set;pendingSubtaskSubscriptions=new Map;hydratedSubtaskThreadIds=new Set;seenSubtaskItemIds=new Set;pendingSubtaskNotifications=[];config={};sessionAliases=new Map;currentAffectedFiles=new Set;connectionRecovery=new z;latestTokenUsage=null;currentRolloutPath=null;threadTurnCount=0;currentPromptText="";currentAssistantText="";previousUserText="";previousAssistantText="";previousAffectedFiles=new Set;maintenanceActive=!1;pendingCompaction=null;shutdownPromise=null;managedProvider=null;controlToolLoop;controlToolPolicy=de();controlToolSteering=new le(this.controlToolPolicy.steeringMaxAttempts);constructor(){this.controlToolLoop=new ae({hasActiveSubtasks:()=>this.subtaskRouter.hasActiveSubtasks(),shortSleepMaxDurationMs:this.controlToolPolicy.shortSleepMaxDurationMs}),process.stdin.setEncoding("utf8"),process.stdin.on("data",t=>this.handleInput(String(t)));const e=()=>{this.shutdown().finally(()=>process.exit(0))};process.stdin.once("end",e),process.stdin.once("close",e),process.stdin.once("error",e),process.once("SIGTERM",()=>{this.shutdown().finally(()=>process.exit(0))}),process.once("SIGINT",()=>{this.shutdown().finally(()=>process.exit(130))})}handleInput(e){this.lineBuffer+=e;const t=this.lineBuffer.split(`
|
|
2
|
+
`);this.lineBuffer=t.pop()??"";for(const s of t){const i=s.trim();if(!i)continue;let o;try{o=JSON.parse(i)}catch{continue}this.handleMessage(o)}}async handleMessage(e){if("id"in e&&("result"in e||"error"in e)&&typeof e.method!="string"){this.handleParentResponse(e);return}const t=typeof e.method=="string"?e.method:"",s=e.id;if(t){if(s===void 0){await this.handleNotification(t,a(e.params));return}try{const i=await this.handleRequest(t,a(e.params));this.write({jsonrpc:"2.0",id:s,result:i})}catch(i){this.write({jsonrpc:"2.0",id:s,error:{code:Number.isInteger(i?.rpcCode)?i.rpcCode:-32603,message:i instanceof Error?i.message:String(i)}})}}}async handleNotification(e,t){if(e==="session/cancel")try{await this.cancel(t)}catch(s){process.stderr.write(`Codex cancel failed: ${s instanceof Error?s.message:String(s)}
|
|
3
3
|
`),await this.shutdown(),process.exit(1)}}async handleRequest(e,t){switch(e){case"initialize":return this.initializeResponse();case"aca/runtime_config":return this.applyRuntimeConfig(t);case"session/new":return this.newSession(t);case"session/resume":case"session/load":return this.resumeSession(t);case"session/fork":return this.forkSession(t);case"session/prompt":return this.prompt(t);case"aca/supplement":return this.userSteering.send({id:String(t.deliveryId||""),threadId:String(t.sessionId||""),turnId:String(t.expectedTurnId||""),prompt:Array.isArray(t.prompt)?t.prompt:[]},{threadId:this.currentSessionId,turnId:this.currentTurnId,ready:this.promptActive&&!this.promptCancelled&&!this.maintenanceActive&&!this.shutdownPromise},s=>this.requireRpc().request("turn/steer",s,6e4),s=>{this.currentPromptText+=`
|
|
4
4
|
|
|
5
5
|
[用户补充]
|
|
6
|
-
${s}`});case"aca/supplement_status":return this.userSteering.receipt(String(t.deliveryId||""));case"session/compact":return this.manualCompact(t);case"aca/session_recover":return this.recoverSession(t);case"session/cancel":return await this.cancel(t),{};case"session/close":return await this.closeSession(t),{};case"session/set_config_option":return this.setConfigOption(t);case"session/set_mode":return this.setMode(t);case"fs/read_text_file":case"fs/write_text_file":case"terminal/create":case"terminal/output":case"terminal/wait_for_exit":case"terminal/kill":case"terminal/release":return{};default:throw new Error(`Unsupported ACP method: ${e}`)}}initializeResponse(){return{protocolVersion:1,agentCapabilities:{loadSession:!0,sessionCapabilities:{resume:!0,close:!0}}}}applyRuntimeConfig(e){if(this.started)throw new Error("Codex runtime config must be applied before app-server starts");const t=a(e.codexProvider);if(Object.keys(t).length===0)return this.managedProvider=null,{configured:!1};const s=d(t,"baseUrl"),i=d(t,"apiKey"),o=Number(t.revision||0);if(!s||!i||!Number.isSafeInteger(o)||o<1)throw new Error("Invalid Codex managed provider runtime config");return this.managedProvider={baseUrl:s,apiKey:i,revision:o},{configured:!0,revision:o}}async newSession(e){const t=d(e,"cwd")||process.cwd();this.applyInitialConfig(e),await this.ensureCodex(t);const s=await this.requireRpc().request("thread/start",{cwd:t,approvalsReviewer:"user",...this.codexConfig(),ephemeral:!1,experimentalRawEvents:!0,threadSource:"aca-codex-acp"},6e4),i=T(s);if(!i)throw new Error("Codex thread/start did not return thread id");return this.currentSessionId=i,this.currentCwd=t,this.resetSubtaskState(),this.captureThreadMetadata(s),this.sessionResponse(i)}async resumeSession(e){const t=d(e,"sessionId");if(!t)throw new Error("sessionId is required");const s=d(e,"cwd")||this.currentCwd;this.applyInitialConfig(e),await this.ensureCodex(s);const i=await this.requireRpc().request("thread/resume",{threadId:t,cwd:s,approvalsReviewer:"user",...this.codexConfig()},6e4);return this.currentSessionId=T(i)||t,this.currentCwd=s,this.resetSubtaskState(i),await this.subscribeKnownSubtasks(!1),this.captureThreadMetadata(i),this.sessionResponse(this.currentSessionId)}async forkSession(e){const t=d(e,"sessionId");if(!t)throw new Error("sessionId is required");const s=d(e,"cwd")||this.currentCwd;this.applyInitialConfig(e),await this.ensureCodex(s);const i=await this.requireRpc().request("thread/fork",{threadId:t,cwd:s,approvalsReviewer:"user",ephemeral:!1,threadSource:"aca-codex-acp-fork",...this.codexConfig()},12e4),o=T(i);if(!o)throw new Error("Codex thread/fork did not return thread id");return this.currentSessionId=o,this.currentCwd=s,this.resetSubtaskState(i),this.captureThreadMetadata(i),this.sessionResponse(o)}async prompt(e){this.promptCancelled=!1;const t=d(e,"sessionId")||this.currentSessionId;let s=this.resolveSessionAlias(t);if(!s)throw new Error("sessionId is required");const i=Array.isArray(e.prompt)?e.prompt:[],o=this.requireRpc();this.currentSessionId&&this.currentSessionId!==s&&this.resetSubtaskState(),this.currentSessionId=s;const r=await this.maintainContext("before",{userRequest:this.previousUserText,assistantResponse:this.previousAssistantText,affectedFiles:[...this.previousAffectedFiles]},{allowRollover:!0});if(ge(r),this.promptCancelled||this.shutdownPromise)throw new Error("Codex prompt cancelled");s=this.currentSessionId||s,this.toolTrace=y(),this.promptActive=!0,this.currentTurnId=null,this.controlToolLoop.reset(),this.controlToolSteering.reset(),this.currentPromptText=oe(i),this.currentAssistantText="",this.currentAffectedFiles.clear(),this.connectionRecovery.reset(),this.agentMessagePhases.clear(),this.planDeltas.reset(),this.pendingSubtaskNotifications=[],this.flushSubtaskStates();let c;try{this.turnStartPromise=o.request("turn/start",{threadId:s,clientUserMessageId:`aca-acp-${Date.now()}-${Math.random().toString(16).slice(2)}`,input:ne(i),cwd:this.currentCwd,...re(this.config)},$()),c=await this.turnStartPromise;const u=String(c?.turn?.id??"");this.currentTurnId=u||null,u&&this.notifySessionUpdate({sessionUpdate:"aca_supplement_target",ready:!this.completedTurns.has(u),threadId:s,turnId:u});const l=u?await this.waitForTurnCompleted(u,$()):c?.turn;this.notifySessionUpdate({sessionUpdate:"aca_supplement_target",ready:!1});const h=await this.subtaskRouter.waitForIdle(be());if(h.timedOut&&h.activeThreadIds.length>0&&this.notifySessionUpdate({sessionUpdate:"aca_codex_subtask_drain",label:"子任务收尾等待超时",detail:`仍有 ${h.activeThreadIds.length} 个子任务未结束`,_meta:{aca:{subtaskDrain:h}}}),this.promptCancelled)throw new Error("Codex prompt cancelled");this.threadTurnCount+=1,this.previousUserText=this.currentPromptText,this.previousAssistantText=this.currentAssistantText,this.previousAffectedFiles.clear();for(const p of this.currentAffectedFiles)this.previousAffectedFiles.add(p);const f=await this.maintainContext("after",{userRequest:this.previousUserText,assistantResponse:this.previousAssistantText,affectedFiles:[...this.previousAffectedFiles]},{allowRollover:!0});return{stopReason:"end_turn",sessionId:this.currentSessionId,turn:l,_meta:{acaContextUsage:F(f)??me(this.latestTokenUsage),acaContextMaintenance:f,acaSubtaskDrain:h}}}catch(u){const l=this.connectionRecovery.fail(u);throw l&&this.notifySessionUpdate(l),u}finally{this.subtaskRouter.executions.loseObservation(this.promptCancelled?"父任务已中断,子执行状态需重新确认":"本轮观测结束,未确认子执行终态"),this.flushSubtaskStates(),this.controlToolLoop.reset(),this.notifySessionUpdate({sessionUpdate:"aca_supplement_target",ready:!1}),this.promptActive=!1,this.currentTurnId=null,this.turnStartPromise=null,this.pendingSubtaskNotifications=[]}}resetSubtaskState(e){this.subtaskGeneration+=1,this.subtaskRouter.reset(),this.subtaskAgentMessagePhases.clear(),this.subtaskPlanDeltas.clear(),this.subscribedSubtaskThreadIds.clear(),this.pendingSubtaskSubscriptions.clear(),this.hydratedSubtaskThreadIds.clear(),this.seenSubtaskItemIds.clear(),this.pendingSubtaskNotifications=[],e!==void 0&&this.subtaskRouter.restoreFromSessionSnapshot(e,this.currentSessionId)}async subscribeKnownSubtasks(e=!0){const t=this.subtaskRouter.routesForSubscription().map(s=>this.subscribeSubtask(s,e));await Promise.allSettled(t)}subscribeSubtask(e,t){const s=e.sourceThreadId;if(!s||s===this.currentSessionId||this.subscribedSubtaskThreadIds.has(s))return Promise.resolve();const i=this.pendingSubtaskSubscriptions.get(s);if(i)return i;const o=this.subtaskGeneration,r=this.requireRpc().request("thread/resume",{threadId:s,approvalsReviewer:"user",excludeTurns:!1},6e4).then(c=>{if(o!==this.subtaskGeneration)return;this.subscribedSubtaskThreadIds.add(s);const u=this.subtaskRouter.restoreFromSessionSnapshot(c,this.currentSessionId);t&&this.hydrateSubtaskHistory(c,e),u>0&&this.subscribeKnownSubtasks(t)}).catch(()=>{}).finally(()=>{this.pendingSubtaskSubscriptions.get(s)===r&&this.pendingSubtaskSubscriptions.delete(s)});return this.pendingSubtaskSubscriptions.set(s,r),r}async cancel(e){const t=this.resolveSessionAlias(d(e,"sessionId")||this.currentSessionId);if(!t||!this.rpc)return;if(t!==this.currentSessionId)throw new Error("Cannot cancel a different Codex session");this.promptCancelled=!0,this.subtaskRouter.stopWaiting();const s=this.turnStartPromise?await se(this.turnStartPromise):null,i=this.currentTurnId||d(S(s,"turn"),"id");if(await this.rpc.request("turn/interrupt",{threadId:t,turnId:i},5e3),i){const o=this.pendingTurnCompletions.get(i);o&&(this.pendingTurnCompletions.delete(i),clearTimeout(o.timer),o.reject(new Error("Codex prompt cancelled")))}}async manualCompact(e){const t=d(e,"sessionId")||this.currentSessionId,s=this.resolveSessionAlias(t);if(!s)throw new Error("sessionId is required");if(this.promptActive||this.maintenanceActive)throw new Error("Codex session is busy");this.currentSessionId=s;const i=A(),o=await this.maintainContext("manual",{userRequest:this.previousUserText,assistantResponse:this.previousAssistantText,affectedFiles:[...this.previousAffectedFiles]},{force:!0,allowRollover:e.allowRollover===!0,compactTimeoutMs:fe(e,i.compactTimeoutMs)}),r=String(o.action||"");if(r==="compact_failed"||r==="rollover_failed"){const c=String(o.compactError||"Codex 上下文压缩未完成");throw new Error(`Codex 上下文压缩失败:${c}`)}return{sessionId:this.currentSessionId,maintenance:o,contextUsage:F(o)}}async recoverSession(e){const t=d(e,"sessionId")||this.currentSessionId,s=this.resolveSessionAlias(t);if(!s)throw new Error("sessionId is required");const i=d(e,"reason");if(i!=="low_output_loop"&&i!=="control_tool_loop")throw new Error("Unsupported Codex recovery reason");if(this.promptActive||this.maintenanceActive)throw new Error("Codex session is busy");if(s!==this.currentSessionId)throw new Error("Cannot recover a different Codex session");const o=new Set([...this.previousAffectedFiles,...this.currentAffectedFiles]);return await this.rotateThread(s,{userRequest:this.currentPromptText||this.previousUserText,assistantResponse:this.currentAssistantText||this.previousAssistantText,affectedFiles:[...o]}),{...a(this.sessionResponse(this.currentSessionId)),previousSessionId:s,recoveryReason:i}}async closeSession(e){await this.shutdown()}async ensureCodex(e){if(this.rpc&&this.started&&this.started.child.exitCode===null&&this.started.child.signalCode===null)return;if(this.codexStartupPromise){await this.codexStartupPromise;return}const t=(async()=>{if(this.rpc&&this.started&&this.started.child.exitCode===null&&this.started.child.signalCode===null)return;const s=W(e,this.managedProvider),i=new K(s.child,{peerName:"Codex app-server",onNotification:(o,r)=>this.handleCodexNotification(o,r),onRequest:async(o,r,c)=>this.handleCodexRequest(o,r,c.id)});this.started=s,this.rpc=i,s.child.once("exit",()=>{this.started===s&&(this.subtaskRouter.executions.loseObservation("执行连接断开,未确认子代理终态"),this.flushSubtaskStates(),this.subtaskRouter.stopWaiting())}),this.initialized=!1,await i.request("initialize",{clientInfo:{name:"aca-codex-acp",version:"0.1.0"},capabilities:{experimentalApi:!0,requestAttestation:!1}},6e4),this.initialized=!0})();this.codexStartupPromise=t;try{await t}catch(s){throw this.rpc&&this.started&&!this.initialized&&await x(this.started.child,1e3),this.rpc=null,this.started=null,s}finally{this.codexStartupPromise===t&&(this.codexStartupPromise=null)}}handleCodexNotification(e,t){const s=P(t);if(e==="item/started"||e==="item/completed"){const u=a(a(t).item);this.toolTrace("provider_notification",{method:e,threadId:s.threadId,turnId:s.turnId,toolCallId:u.id,itemType:u.type,status:u.status})}if(this.maintenanceActive&&(!s.threadId||s.threadId===this.currentSessionId)){(e==="item/started"||e==="item/completed")&&this.toolTrace("notification_filtered",{method:e,toolCallId:a(a(t).item).id,reason:"context_maintenance"}),this.captureContextNotification(e,t);return}if(!Y(t,this.currentSessionId,this.currentTurnId)){if(!this.promptActive||!s.threadId||s.threadId===this.currentSessionId){(e==="item/started"||e==="item/completed")&&this.toolTrace("notification_filtered",{method:e,toolCallId:a(a(t).item).id,reason:"outside_current_turn",threadId:s.threadId,turnId:s.turnId});return}const u=this.subtaskRouter.routeFor(s.threadId);if(!u){(e==="item/started"||e==="item/completed")&&this.toolTrace("notification_buffered",{method:e,toolCallId:a(a(t).item).id,threadId:s.threadId,reason:"subtask_route_pending"}),this.bufferPendingSubtaskNotification(e,t,s.threadId);return}this.handleSubtaskNotification(e,t,u),this.flushPendingSubtaskNotifications();return}const i=this.subtaskRouter.observe(t,this.currentSessionId);if(this.flushSubtaskStates(),i>0&&this.subscribeKnownSubtasks(!0),this.captureContextNotification(e,t),this.maintenanceActive)return;if(!this.promptActive&&e!=="turn/completed"){(e==="item/started"||e==="item/completed")&&this.toolTrace("notification_filtered",{method:e,toolCallId:a(a(t).item).id,reason:"prompt_inactive"});return}this.rpc?.refreshPendingRequestTimeout("turn/start"),this.captureAgentMessagePhase(e,t);const o=this.connectionRecovery.beforeNotification(e,t);e==="turn/completed"&&this.resolveTurnCompleted(t);const r=e==="rawResponseItem/completed"?we(this.controlToolLoop,t):[],c=e==="rawResponse/completed"?Se(t):e==="rawResponseItem/completed"?r.map(({update:u})=>u):k(e,t,{agentMessagePhases:this.agentMessagePhases,planDeltas:this.planDeltas});for(const u of r)this.maybeSteerControlToolLoop(u.event);for(const u of[...o,...c]){const l=U(u,e,t,this.agentMessagePhases);M(l)&&this.notifyControlToolProgress(),this.captureUpdateEvidence(l),this.notifySessionUpdate(l)}i>0&&this.flushPendingSubtaskNotifications()}handleSubtaskNotification(e,t,s){if(!this.promptActive)return;const i=this.subtaskRouter.acceptNotification(s,e,t);if(this.flushSubtaskStates(),!i){(e==="item/started"||e==="item/completed")&&this.toolTrace("notification_filtered",{method:e,toolCallId:a(a(t).item).id,threadId:s.sourceThreadId,reason:"subtask_not_current"});return}(e==="rawResponseItem/completed"||e==="item/completed")&&ie(a(a(t).item))&&this.notifyControlToolProgress(),this.rememberSubtaskItem(s.sourceThreadId,t),this.rpc?.refreshPendingRequestTimeout("turn/start");const o=this.subtaskRouter.executions.get(s.sourceThreadId)?.runId||s.sourceThreadId,r=this.subtaskAgentMessagePhases.get(o)??new Map;this.subtaskAgentMessagePhases.set(o,r);const c=this.subtaskPlanDeltas.get(o)??new v;this.subtaskPlanDeltas.set(o,c),this.captureAgentMessagePhase(e,t,r);const u=this.subtaskRouter.observe(t,this.currentSessionId);this.flushSubtaskStates(),u>0&&this.subscribeKnownSubtasks(!0);const l=k(e,t,{agentMessagePhases:r,planDeltas:c}),h=P(t).turnId;for(const f of l){const p=U(f,e,t,r);if(!Ie(p))continue;d(p,"title")&&this.subtaskRouter.executions.action(s.sourceThreadId,d(p,"title")),M(p)&&(this.notifyControlToolProgress(),this.subtaskRouter.executions.progress(s.sourceThreadId,d(p,"title")||"收到执行结果"));const g=_(p,s,h,this.subtaskRouter.executions.get(s.sourceThreadId));I(g,this.currentAffectedFiles),this.notifySessionUpdate(g)}}hydrateSubtaskHistory(e,t){if(!(!this.promptActive||this.hydratedSubtaskThreadIds.has(t.sourceThreadId))){this.hydratedSubtaskThreadIds.add(t.sourceThreadId);for(const s of Z(e)){const i=D(t.sourceThreadId,s.itemId);if(this.seenSubtaskItemIds.has(i))continue;this.seenSubtaskItemIds.add(i);const o=_(s.update,t,s.sourceTurnId,{runId:`history.${t.sourceThreadId}.${s.sourceTurnId}`,state:"unknown",name:this.subtaskRouter.executions.nameFor(t.sourceThreadId),outputIncomplete:!0,reason:"历史过程回放,不代表当前执行状态"}),r=a(o._meta);o._meta={...r,aca:{...a(r.aca),historyReplay:!0}},I(o,this.currentAffectedFiles),this.notifySessionUpdate(o)}}}rememberSubtaskItem(e,t){const s=a(t),i=a(s.item),o=d(i,"id")||d(s,"itemId");o&&this.seenSubtaskItemIds.add(D(e,o))}bufferPendingSubtaskNotification(e,t,s){this.pendingSubtaskNotifications.push({method:e,params:t,threadId:s}),this.pendingSubtaskNotifications.length>200&&this.pendingSubtaskNotifications.shift()}flushPendingSubtaskNotifications(){if(this.pendingSubtaskNotifications.length===0)return;let e=!0;for(;e&&this.pendingSubtaskNotifications.length>0;){e=!1;const t=[];for(const s of this.pendingSubtaskNotifications){const i=this.subtaskRouter.routeFor(s.threadId);if(!i){t.push(s);continue}e=!0,this.handleSubtaskNotification(s.method,s.params,i)}this.pendingSubtaskNotifications=t}}notifySessionUpdate(e){e=te(e,this.subtaskRouter.executions),(e.sessionUpdate==="tool_call"||e.sessionUpdate==="tool_call_update"&&["completed","failed","cancelled"].includes(String(e.status)))&&this.toolTrace("acp_update",{toolCallId:e.toolCallId,status:e.status,threadId:this.currentSessionId,turnId:this.currentTurnId}),this.notify("session/update",{sessionId:this.currentSessionId,update:e})}flushSubtaskStates(){if(this.promptActive)for(const e of this.subtaskRouter.executions.drainUpdates())this.notifySessionUpdate(e)}async handleCodexRequest(e,t,s){if(G(e))return J(e,t,async i=>{const o=await this.requestParent("session/request_permission",{sessionId:this.currentSessionId,request:{...i,params:t,method:e}},Math.max(1,i.expiresAtMs-Date.now())),r=a(o),c=a(r.outcome);return c.outcome==="selected"&&typeof c.optionId=="string"?{outcome:{outcome:"selected",optionId:c.optionId}}:{outcome:{outcome:"cancelled"}}},s);if(L(e)){const i=j("codex-acp",e,t,s),o=await this.requestParent("elicitation/create",{sessionId:this.currentSessionId,acaInteractionBridge:1,request:i},Math.max(1,i.interaction.expiresAtMs-Date.now()));return O(i,o)}throw new Error(`Unsupported Codex server request: ${e}`)}captureAgentMessagePhase(e,t,s=this.agentMessagePhases){if(e!=="item/started"&&e!=="item/completed")return;const i=S(t,"item");if(d(i,"type")!=="agentMessage")return;const o=d(i,"id");o&&s.set(o,d(i,"phase"))}waitForTurnCompleted(e,t){const s=this.completedTurns.get(e);return s?Promise.resolve(s):new Promise((i,o)=>{const r=setTimeout(()=>{this.pendingTurnCompletions.delete(e),o(new Error("Codex app-server request timed out while waiting for turn/completed"))},t);r.unref(),this.pendingTurnCompletions.set(e,{resolve:i,reject:o,timer:r})})}resolveTurnCompleted(e){const t=S(e,"turn"),s=d(t,"id");if(!s)return;this.completedTurns.set(s,t);const i=this.pendingTurnCompletions.get(s);if(i){if(this.pendingTurnCompletions.delete(s),clearTimeout(i.timer),d(t,"status")==="failed"){i.reject(new Error(`Codex turn failed: ${JSON.stringify(t.error??{})}`));return}i.resolve(t)}}async shutdown(){return this.shutdownPromise?this.shutdownPromise:(this.shutdownPromise=this.performShutdown(),this.shutdownPromise)}async performShutdown(){this.subtaskRouter.executions.loseObservation("执行连接关闭,未确认子代理终态"),this.flushSubtaskStates(),this.subtaskRouter.stopWaiting(),this.pendingCompaction&&(clearTimeout(this.pendingCompaction.timer),this.pendingCompaction.reject(new Error("Codex ACP adapter closed")),this.pendingCompaction=null);for(const t of this.pendingParentRequests.values())clearTimeout(t.timer),t.reject(new Error("Codex ACP adapter closed"));this.pendingParentRequests.clear();for(const t of this.pendingTurnCompletions.values())clearTimeout(t.timer),t.reject(new Error("Codex ACP adapter closed"));this.pendingTurnCompletions.clear();const e=this.started?.child;!e||e.exitCode!==null||e.signalCode!==null||(e.stdin.end(),!await x(e,2e3)&&(e.kill("SIGTERM"),!await x(e,1e3)&&(e.kill("SIGKILL"),await x(e,1e3))))}sessionResponse(e){return{sessionId:e,modes:{currentModeId:"workspace-write",availableModes:[{id:"read-only",name:"Read Only"},{id:"workspace-write",name:"Workspace Write"},{id:"danger-full-access",name:"Full Access"}]},configOptions:[{id:"model",category:"model",type:"string"},{id:"approvalPolicy",category:"approval",type:"string"},{id:"sandbox",category:"mode",type:"string"}]}}requireRpc(){if(!this.rpc)throw new Error("Codex app-server is not started");return this.rpc}notify(e,t){this.write({jsonrpc:"2.0",method:e,params:t})}requestParent(e,t,s=600*1e3){const i=this.nextParentRequestId++;return new Promise((o,r)=>{const c=setTimeout(()=>{this.pendingParentRequests.delete(i),r(new Error(`ACP client request timed out: ${e}`))},s);c.unref(),this.pendingParentRequests.set(i,{resolve:o,reject:r,timer:c}),this.write({jsonrpc:"2.0",id:i,method:e,params:t})})}handleParentResponse(e){const t=Number(e.id),s=this.pendingParentRequests.get(t);if(s){if(this.pendingParentRequests.delete(t),clearTimeout(s.timer),"error"in e){const i=a(e.error);s.reject(new Error(d(i,"message")||"ACP client request failed"));return}s.resolve(e.result)}}setConfigOption(e){const t=d(e,"configId"),s=e.value;if(!t)throw new Error("configId is required");if(typeof s!="string"&&typeof s!="boolean")throw new Error("config value must be string or boolean");return this.config[t]=s,this.notify("session/update",{sessionId:this.currentSessionId,update:{sessionUpdate:"config_option_update",configId:t,value:s}}),{}}setMode(e){const t=d(e,"modeId");if(!t)throw new Error("modeId is required");return this.config.sandbox=t,this.notify("session/update",{sessionId:this.currentSessionId,update:{sessionUpdate:"current_mode_update",modeId:t}}),{}}applyInitialConfig(e){const t=S(e,"config");for(const[s,i]of Object.entries(t))(typeof i=="string"||typeof i=="boolean")&&(this.config[s]=i)}codexConfig(){return{...C()?{developerInstructions:C()}:{},...typeof this.config.model=="string"&&this.config.model?{model:this.config.model}:{},approvalPolicy:typeof this.config.approvalPolicy=="string"?this.config.approvalPolicy:"on-request",sandbox:typeof this.config.sandbox=="string"?this.config.sandbox:"workspace-write"}}captureContextNotification(e,t){if(e==="thread/tokenUsage/updated"&&(this.latestTokenUsage=X(t)??this.latestTokenUsage),e==="thread/compacted"&&this.pendingCompaction&&this.resolveCompaction(),e==="item/completed"){const s=S(t,"item"),i=d(a(t),"threadId");d(s,"type")==="contextCompaction"&&(!i||i===this.currentSessionId)&&this.resolveCompaction(),d(s,"type")==="agentMessage"&&d(s,"phase")==="final_answer"&&(this.currentAssistantText=w(s)||this.currentAssistantText)}}resolveCompaction(){if(!this.pendingCompaction)return;const e=this.pendingCompaction;this.pendingCompaction=null,clearTimeout(e.timer),e.resolve()}captureUpdateEvidence(e){String(e.sessionUpdate||"")==="agent_message_chunk"&&(this.currentAssistantText+=w(e.content)),I(e,this.currentAffectedFiles)}captureThreadMetadata(e){const t=S(e,"thread");this.currentRolloutPath=d(t,"path")||null,this.threadTurnCount=Array.isArray(t.turns)?t.turns.length:0;const s=pe(t.turns);s.userRequest&&(this.previousUserText=s.userRequest),s.assistantResponse&&(this.previousAssistantText=s.assistantResponse),this.previousAffectedFiles.clear();for(const i of s.affectedFiles)this.previousAffectedFiles.add(i);this.latestTokenUsage=null}async maintainContext(e,t,s={}){const i=A(),o=this.contextState(),r=R(o,i);if(!r.compact&&!s.force||!this.currentSessionId)return this.maintenanceResult({stage:e,action:"none",reasons:r.reasons,beforeState:o});const c=this.currentSessionId;this.notifyContextStatus("正在整理会话上下文",N(o)),this.maintenanceActive=!0;let u=!1,l="",h=o,f=r,p=!1,g="";try{const m=o.usage?.observedAtMs??0;await this.compactThread(c,s.compactTimeoutMs??i.compactTimeoutMs),await this.waitForTokenUsageAfter(m,2e3)}catch(m){u=!0,l=m instanceof Error?m.message:String(m)}try{if(e!=="manual"&&this.promptCancelled||this.shutdownPromise)throw new Error("Codex prompt cancelled");if(h=this.contextState(),f=R(h,i,{compactAttempted:!0,compactFailed:u}),f.rollover&&s.allowRollover!==!1){this.notifyContextStatus("正在创建轻量会话",f.reasons.join(", "));try{await this.rotateThread(c,t),p=!0}catch(m){g=m instanceof Error?m.message:String(m),this.notifyContextStatus("轻量会话创建失败",g)}}else u||this.notifyContextStatus("会话上下文已压缩",N(h))}finally{this.maintenanceActive=!1}return this.maintenanceResult({action:p?"rollover":g?"rollover_failed":u?"compact_failed":"compact",stage:e,reasons:f.reasons,beforeState:o,afterState:h,compactError:[l,g].filter(Boolean).join("; "),previousThreadId:c,rolloverRecommended:f.rollover&&s.allowRollover===!1})}compactThread(e,t){return this.pendingCompaction?Promise.reject(new Error("Codex context compaction is already running")):new Promise((s,i)=>{const o=setTimeout(()=>{this.pendingCompaction?.timer===o&&(this.pendingCompaction=null),i(new Error("Codex thread compaction timed out"))},t);o.unref(),this.pendingCompaction={resolve:s,reject:i,timer:o},this.requireRpc().request("thread/compact/start",{threadId:e},t).catch(r=>{this.pendingCompaction?.timer===o&&(this.pendingCompaction=null,clearTimeout(o),i(r instanceof Error?r:new Error(String(r))))})})}async rotateThread(e,t){const s=H({previousThreadId:e,cwd:this.currentCwd,userRequest:t.userRequest,assistantResponse:t.assistantResponse,affectedFiles:t.affectedFiles}),i=await this.requireRpc().request("thread/start",{cwd:this.currentCwd,approvalsReviewer:"user",...this.codexConfig(),developerInstructions:[s,C()].filter(Boolean).join(`
|
|
6
|
+
${s}`});case"aca/supplement_status":return this.userSteering.receipt(String(t.deliveryId||""));case"session/compact":return this.manualCompact(t);case"aca/session_recover":return this.recoverSession(t);case"session/cancel":return await this.cancel(t),{};case"session/close":return await this.closeSession(t),{};case"session/set_config_option":return this.setConfigOption(t);case"session/set_mode":return this.setMode(t);case"fs/read_text_file":case"fs/write_text_file":case"terminal/create":case"terminal/output":case"terminal/wait_for_exit":case"terminal/kill":case"terminal/release":return{};default:throw new Error(`Unsupported ACP method: ${e}`)}}initializeResponse(){return{protocolVersion:1,agentCapabilities:{loadSession:!0,sessionCapabilities:{resume:!0,close:!0}}}}applyRuntimeConfig(e){if(this.started)throw new Error("Codex runtime config must be applied before app-server starts");const t=a(e.codexProvider);if(Object.keys(t).length===0)return this.managedProvider=null,{configured:!1};const s=d(t,"baseUrl"),i=d(t,"apiKey"),o=Number(t.revision||0);if(!s||!i||!Number.isSafeInteger(o)||o<1)throw new Error("Invalid Codex managed provider runtime config");return this.managedProvider={baseUrl:s,apiKey:i,revision:o},{configured:!0,revision:o}}async newSession(e){const t=d(e,"cwd")||process.cwd();this.applyInitialConfig(e),await this.ensureCodex(t);const s=await this.requireRpc().request("thread/start",{cwd:t,approvalsReviewer:"user",...this.codexConfig(),ephemeral:!1,experimentalRawEvents:!0,threadSource:"aca-codex-acp"},6e4),i=T(s);if(!i)throw new Error("Codex thread/start did not return thread id");return this.currentSessionId=i,this.currentCwd=t,this.resetSubtaskState(),this.captureThreadMetadata(s),this.sessionResponse(i)}async resumeSession(e){const t=d(e,"sessionId");if(!t)throw new Error("sessionId is required");const s=d(e,"cwd")||this.currentCwd;this.applyInitialConfig(e),await this.ensureCodex(s);const i=await this.requireRpc().request("thread/resume",{threadId:t,cwd:s,approvalsReviewer:"user",...this.codexConfig()},6e4);return this.currentSessionId=T(i)||t,this.currentCwd=s,this.resetSubtaskState(i),await this.subscribeKnownSubtasks(!1),this.captureThreadMetadata(i),this.sessionResponse(this.currentSessionId)}async forkSession(e){const t=d(e,"sessionId");if(!t)throw new Error("sessionId is required");const s=d(e,"cwd")||this.currentCwd;this.applyInitialConfig(e),await this.ensureCodex(s);const i=await this.requireRpc().request("thread/fork",{threadId:t,cwd:s,approvalsReviewer:"user",ephemeral:!1,threadSource:"aca-codex-acp-fork",...this.codexConfig()},12e4),o=T(i);if(!o)throw new Error("Codex thread/fork did not return thread id");return this.currentSessionId=o,this.currentCwd=s,this.resetSubtaskState(i),this.captureThreadMetadata(i),this.sessionResponse(o)}async prompt(e){this.promptCancelled=!1;const t=d(e,"sessionId")||this.currentSessionId;let s=this.resolveSessionAlias(t);if(!s)throw new Error("sessionId is required");const i=Array.isArray(e.prompt)?e.prompt:[],o=this.requireRpc();this.currentSessionId&&this.currentSessionId!==s&&this.resetSubtaskState(),this.currentSessionId=s;const r=await this.maintainContext("before",{userRequest:this.previousUserText,assistantResponse:this.previousAssistantText,affectedFiles:[...this.previousAffectedFiles]},{allowRollover:!0});if(ge(r),this.promptCancelled||this.shutdownPromise)throw new Error("Codex prompt cancelled");s=this.currentSessionId||s,this.toolTrace=y(),this.promptActive=!0,this.currentTurnId=null,this.controlToolLoop.reset(),this.controlToolSteering.reset(),this.currentPromptText=oe(i),this.currentAssistantText="",this.currentAffectedFiles.clear(),this.connectionRecovery.reset(),this.agentMessagePhases.clear(),this.planDeltas.reset(),this.pendingSubtaskNotifications=[],this.flushSubtaskStates();let c;try{this.turnStartPromise=o.request("turn/start",{threadId:s,clientUserMessageId:`aca-acp-${Date.now()}-${Math.random().toString(16).slice(2)}`,input:ne(i),cwd:this.currentCwd,...re(this.config)},$()),c=await this.turnStartPromise;const u=String(c?.turn?.id??"");this.currentTurnId=u||null,u&&this.notifySessionUpdate({sessionUpdate:"aca_supplement_target",ready:!this.completedTurns.has(u),threadId:s,turnId:u});const l=u?await this.waitForTurnCompleted(u,$()):c?.turn;this.notifySessionUpdate({sessionUpdate:"aca_supplement_target",ready:!1});const h=await this.subtaskRouter.waitForIdle(be());if(h.timedOut&&h.activeThreadIds.length>0&&this.notifySessionUpdate({sessionUpdate:"aca_codex_subtask_drain",label:"子任务收尾等待超时",detail:`仍有 ${h.activeThreadIds.length} 个子任务未结束`,_meta:{aca:{subtaskDrain:h}}}),this.promptCancelled)throw new Error("Codex prompt cancelled");this.threadTurnCount+=1,this.previousUserText=this.currentPromptText,this.previousAssistantText=this.currentAssistantText,this.previousAffectedFiles.clear();for(const p of this.currentAffectedFiles)this.previousAffectedFiles.add(p);const f=await this.maintainContext("after",{userRequest:this.previousUserText,assistantResponse:this.previousAssistantText,affectedFiles:[...this.previousAffectedFiles]},{allowRollover:!0});return{stopReason:"end_turn",sessionId:this.currentSessionId,turn:l,_meta:{acaContextUsage:F(f)??me(this.latestTokenUsage),acaContextMaintenance:f,acaSubtaskDrain:h}}}catch(u){const l=this.connectionRecovery.fail(u);throw l&&this.notifySessionUpdate(l),u}finally{this.subtaskRouter.executions.loseObservation(this.promptCancelled?"父任务已中断,子执行状态需重新确认":"本轮观测结束,未确认子执行终态"),this.flushSubtaskStates(),this.controlToolLoop.reset(),this.notifySessionUpdate({sessionUpdate:"aca_supplement_target",ready:!1}),this.promptActive=!1,this.currentTurnId=null,this.turnStartPromise=null,this.pendingSubtaskNotifications=[]}}resetSubtaskState(e){this.subtaskGeneration+=1,this.subtaskRouter.reset(),this.subtaskAgentMessagePhases.clear(),this.subtaskPlanDeltas.clear(),this.subscribedSubtaskThreadIds.clear(),this.pendingSubtaskSubscriptions.clear(),this.hydratedSubtaskThreadIds.clear(),this.seenSubtaskItemIds.clear(),this.pendingSubtaskNotifications=[],e!==void 0&&this.subtaskRouter.restoreFromSessionSnapshot(e,this.currentSessionId)}async subscribeKnownSubtasks(e=!0){const t=this.subtaskRouter.routesForSubscription().map(s=>this.subscribeSubtask(s,e));await Promise.allSettled(t)}subscribeSubtask(e,t){const s=e.sourceThreadId;if(!s||s===this.currentSessionId||this.subscribedSubtaskThreadIds.has(s))return Promise.resolve();const i=this.pendingSubtaskSubscriptions.get(s);if(i)return i;const o=this.subtaskGeneration,r=this.requireRpc().request("thread/resume",{threadId:s,approvalsReviewer:"user",excludeTurns:!1},6e4).then(c=>{if(o!==this.subtaskGeneration)return;this.subscribedSubtaskThreadIds.add(s);const u=this.subtaskRouter.restoreFromSessionSnapshot(c,this.currentSessionId);t&&this.hydrateSubtaskHistory(c,e),u>0&&this.subscribeKnownSubtasks(t)}).catch(()=>{}).finally(()=>{this.pendingSubtaskSubscriptions.get(s)===r&&this.pendingSubtaskSubscriptions.delete(s)});return this.pendingSubtaskSubscriptions.set(s,r),r}async cancel(e){const t=this.resolveSessionAlias(d(e,"sessionId")||this.currentSessionId);if(!t||!this.rpc)return;if(t!==this.currentSessionId)throw new Error("Cannot cancel a different Codex session");this.promptCancelled=!0,this.subtaskRouter.stopWaiting();const s=this.turnStartPromise?await se(this.turnStartPromise):null,i=this.currentTurnId||d(S(s,"turn"),"id");if(await this.rpc.request("turn/interrupt",{threadId:t,turnId:i},5e3),i){const o=this.pendingTurnCompletions.get(i);o&&(this.pendingTurnCompletions.delete(i),clearTimeout(o.timer),o.reject(new Error("Codex prompt cancelled")))}}async manualCompact(e){const t=d(e,"sessionId")||this.currentSessionId,s=this.resolveSessionAlias(t);if(!s)throw new Error("sessionId is required");if(this.promptActive||this.maintenanceActive)throw new Error("Codex session is busy");this.currentSessionId=s;const i=A(),o=await this.maintainContext("manual",{userRequest:this.previousUserText,assistantResponse:this.previousAssistantText,affectedFiles:[...this.previousAffectedFiles]},{force:!0,allowRollover:e.allowRollover===!0,compactTimeoutMs:fe(e,i.compactTimeoutMs)}),r=String(o.action||"");if(r==="compact_failed"||r==="rollover_failed"){const c=String(o.compactError||"Codex 上下文压缩未完成");throw new Error(`Codex 上下文压缩失败:${c}`)}return{sessionId:this.currentSessionId,maintenance:o,contextUsage:F(o)}}async recoverSession(e){const t=d(e,"sessionId")||this.currentSessionId,s=this.resolveSessionAlias(t);if(!s)throw new Error("sessionId is required");const i=d(e,"reason");if(i!=="low_output_loop"&&i!=="control_tool_loop")throw new Error("Unsupported Codex recovery reason");if(this.promptActive||this.maintenanceActive)throw new Error("Codex session is busy");if(s!==this.currentSessionId)throw new Error("Cannot recover a different Codex session");const o=new Set([...this.previousAffectedFiles,...this.currentAffectedFiles]);return await this.rotateThread(s,{userRequest:this.currentPromptText||this.previousUserText,assistantResponse:this.currentAssistantText||this.previousAssistantText,affectedFiles:[...o]}),{...a(this.sessionResponse(this.currentSessionId)),previousSessionId:s,recoveryReason:i}}async closeSession(e){await this.shutdown()}async ensureCodex(e){if(this.rpc&&this.started&&this.started.child.exitCode===null&&this.started.child.signalCode===null)return;if(this.codexStartupPromise){await this.codexStartupPromise;return}const t=(async()=>{if(this.rpc&&this.started&&this.started.child.exitCode===null&&this.started.child.signalCode===null)return;const s=W(e,this.managedProvider),i=new K(s.child,{peerName:"Codex app-server",onNotification:(o,r)=>this.handleCodexNotification(o,r),onRequest:async(o,r,c)=>this.handleCodexRequest(o,r,c.id)});this.started=s,this.rpc=i,s.child.once("exit",()=>{this.started===s&&(this.subtaskRouter.executions.loseObservation("执行连接断开,未确认子代理终态"),this.flushSubtaskStates(),this.subtaskRouter.stopWaiting())}),this.initialized=!1,await i.request("initialize",{clientInfo:{name:"aca-codex-acp",version:"0.1.0"},capabilities:{experimentalApi:!0,requestAttestation:!1}},6e4),this.initialized=!0})();this.codexStartupPromise=t;try{await t}catch(s){throw this.rpc&&this.started&&!this.initialized&&await x(this.started.child,1e3),this.rpc=null,this.started=null,s}finally{this.codexStartupPromise===t&&(this.codexStartupPromise=null)}}handleCodexNotification(e,t){if(e==="elicitation/complete"){this.write({jsonrpc:"2.0",method:e,params:t});return}const s=P(t);if(e==="item/started"||e==="item/completed"){const u=a(a(t).item);this.toolTrace("provider_notification",{method:e,threadId:s.threadId,turnId:s.turnId,toolCallId:u.id,itemType:u.type,status:u.status})}if(this.maintenanceActive&&(!s.threadId||s.threadId===this.currentSessionId)){(e==="item/started"||e==="item/completed")&&this.toolTrace("notification_filtered",{method:e,toolCallId:a(a(t).item).id,reason:"context_maintenance"}),this.captureContextNotification(e,t);return}if(!Y(t,this.currentSessionId,this.currentTurnId)){if(!this.promptActive||!s.threadId||s.threadId===this.currentSessionId){(e==="item/started"||e==="item/completed")&&this.toolTrace("notification_filtered",{method:e,toolCallId:a(a(t).item).id,reason:"outside_current_turn",threadId:s.threadId,turnId:s.turnId});return}const u=this.subtaskRouter.routeFor(s.threadId);if(!u){(e==="item/started"||e==="item/completed")&&this.toolTrace("notification_buffered",{method:e,toolCallId:a(a(t).item).id,threadId:s.threadId,reason:"subtask_route_pending"}),this.bufferPendingSubtaskNotification(e,t,s.threadId);return}this.handleSubtaskNotification(e,t,u),this.flushPendingSubtaskNotifications();return}const i=this.subtaskRouter.observe(t,this.currentSessionId);if(this.flushSubtaskStates(),i>0&&this.subscribeKnownSubtasks(!0),this.captureContextNotification(e,t),this.maintenanceActive)return;if(!this.promptActive&&e!=="turn/completed"){(e==="item/started"||e==="item/completed")&&this.toolTrace("notification_filtered",{method:e,toolCallId:a(a(t).item).id,reason:"prompt_inactive"});return}this.rpc?.refreshPendingRequestTimeout("turn/start"),this.captureAgentMessagePhase(e,t);const o=this.connectionRecovery.beforeNotification(e,t);e==="turn/completed"&&this.resolveTurnCompleted(t);const r=e==="rawResponseItem/completed"?we(this.controlToolLoop,t):[],c=e==="rawResponse/completed"?Se(t):e==="rawResponseItem/completed"?r.map(({update:u})=>u):k(e,t,{agentMessagePhases:this.agentMessagePhases,planDeltas:this.planDeltas});for(const u of r)this.maybeSteerControlToolLoop(u.event);for(const u of[...o,...c]){const l=U(u,e,t,this.agentMessagePhases);M(l)&&this.notifyControlToolProgress(),this.captureUpdateEvidence(l),this.notifySessionUpdate(l)}i>0&&this.flushPendingSubtaskNotifications()}handleSubtaskNotification(e,t,s){if(!this.promptActive)return;const i=this.subtaskRouter.acceptNotification(s,e,t);if(this.flushSubtaskStates(),!i){(e==="item/started"||e==="item/completed")&&this.toolTrace("notification_filtered",{method:e,toolCallId:a(a(t).item).id,threadId:s.sourceThreadId,reason:"subtask_not_current"});return}(e==="rawResponseItem/completed"||e==="item/completed")&&ie(a(a(t).item))&&this.notifyControlToolProgress(),this.rememberSubtaskItem(s.sourceThreadId,t),this.rpc?.refreshPendingRequestTimeout("turn/start");const o=this.subtaskRouter.executions.get(s.sourceThreadId)?.runId||s.sourceThreadId,r=this.subtaskAgentMessagePhases.get(o)??new Map;this.subtaskAgentMessagePhases.set(o,r);const c=this.subtaskPlanDeltas.get(o)??new v;this.subtaskPlanDeltas.set(o,c),this.captureAgentMessagePhase(e,t,r);const u=this.subtaskRouter.observe(t,this.currentSessionId);this.flushSubtaskStates(),u>0&&this.subscribeKnownSubtasks(!0);const l=k(e,t,{agentMessagePhases:r,planDeltas:c}),h=P(t).turnId;for(const f of l){const p=U(f,e,t,r);if(!Ie(p))continue;d(p,"title")&&this.subtaskRouter.executions.action(s.sourceThreadId,d(p,"title")),M(p)&&(this.notifyControlToolProgress(),this.subtaskRouter.executions.progress(s.sourceThreadId,d(p,"title")||"收到执行结果"));const g=_(p,s,h,this.subtaskRouter.executions.get(s.sourceThreadId));I(g,this.currentAffectedFiles),this.notifySessionUpdate(g)}}hydrateSubtaskHistory(e,t){if(!(!this.promptActive||this.hydratedSubtaskThreadIds.has(t.sourceThreadId))){this.hydratedSubtaskThreadIds.add(t.sourceThreadId);for(const s of Z(e)){const i=D(t.sourceThreadId,s.itemId);if(this.seenSubtaskItemIds.has(i))continue;this.seenSubtaskItemIds.add(i);const o=_(s.update,t,s.sourceTurnId,{runId:`history.${t.sourceThreadId}.${s.sourceTurnId}`,state:"unknown",name:this.subtaskRouter.executions.nameFor(t.sourceThreadId),outputIncomplete:!0,reason:"历史过程回放,不代表当前执行状态"}),r=a(o._meta);o._meta={...r,aca:{...a(r.aca),historyReplay:!0}},I(o,this.currentAffectedFiles),this.notifySessionUpdate(o)}}}rememberSubtaskItem(e,t){const s=a(t),i=a(s.item),o=d(i,"id")||d(s,"itemId");o&&this.seenSubtaskItemIds.add(D(e,o))}bufferPendingSubtaskNotification(e,t,s){this.pendingSubtaskNotifications.push({method:e,params:t,threadId:s}),this.pendingSubtaskNotifications.length>200&&this.pendingSubtaskNotifications.shift()}flushPendingSubtaskNotifications(){if(this.pendingSubtaskNotifications.length===0)return;let e=!0;for(;e&&this.pendingSubtaskNotifications.length>0;){e=!1;const t=[];for(const s of this.pendingSubtaskNotifications){const i=this.subtaskRouter.routeFor(s.threadId);if(!i){t.push(s);continue}e=!0,this.handleSubtaskNotification(s.method,s.params,i)}this.pendingSubtaskNotifications=t}}notifySessionUpdate(e){e=te(e,this.subtaskRouter.executions),(e.sessionUpdate==="tool_call"||e.sessionUpdate==="tool_call_update"&&["completed","failed","cancelled"].includes(String(e.status)))&&this.toolTrace("acp_update",{toolCallId:e.toolCallId,status:e.status,threadId:this.currentSessionId,turnId:this.currentTurnId}),this.notify("session/update",{sessionId:this.currentSessionId,update:e})}flushSubtaskStates(){if(this.promptActive)for(const e of this.subtaskRouter.executions.drainUpdates())this.notifySessionUpdate(e)}async handleCodexRequest(e,t,s){if(G(e))return J(e,t,async i=>{const o=await this.requestParent("session/request_permission",{sessionId:this.currentSessionId,request:{...i,params:t,method:e}},Math.max(1,i.expiresAtMs-Date.now())),r=a(o),c=a(r.outcome);return c.outcome==="selected"&&typeof c.optionId=="string"?{outcome:{outcome:"selected",optionId:c.optionId}}:{outcome:{outcome:"cancelled"}}},s);if(j(e)){const i=L("codex-acp",e,t,s),o=await this.requestParent("elicitation/create",{sessionId:this.currentSessionId,acaInteractionBridge:1,request:i},Math.max(1,i.interaction.expiresAtMs-Date.now()));return O(i,o)}throw new Error(`Unsupported Codex server request: ${e}`)}captureAgentMessagePhase(e,t,s=this.agentMessagePhases){if(e!=="item/started"&&e!=="item/completed")return;const i=S(t,"item");if(d(i,"type")!=="agentMessage")return;const o=d(i,"id");o&&s.set(o,d(i,"phase"))}waitForTurnCompleted(e,t){const s=this.completedTurns.get(e);return s?Promise.resolve(s):new Promise((i,o)=>{const r=setTimeout(()=>{this.pendingTurnCompletions.delete(e),o(new Error("Codex app-server request timed out while waiting for turn/completed"))},t);r.unref(),this.pendingTurnCompletions.set(e,{resolve:i,reject:o,timer:r})})}resolveTurnCompleted(e){const t=S(e,"turn"),s=d(t,"id");if(!s)return;this.completedTurns.set(s,t);const i=this.pendingTurnCompletions.get(s);if(i){if(this.pendingTurnCompletions.delete(s),clearTimeout(i.timer),d(t,"status")==="failed"){i.reject(new Error(`Codex turn failed: ${JSON.stringify(t.error??{})}`));return}i.resolve(t)}}async shutdown(){return this.shutdownPromise?this.shutdownPromise:(this.shutdownPromise=this.performShutdown(),this.shutdownPromise)}async performShutdown(){this.subtaskRouter.executions.loseObservation("执行连接关闭,未确认子代理终态"),this.flushSubtaskStates(),this.subtaskRouter.stopWaiting(),this.pendingCompaction&&(clearTimeout(this.pendingCompaction.timer),this.pendingCompaction.reject(new Error("Codex ACP adapter closed")),this.pendingCompaction=null);for(const t of this.pendingParentRequests.values())clearTimeout(t.timer),t.reject(new Error("Codex ACP adapter closed"));this.pendingParentRequests.clear();for(const t of this.pendingTurnCompletions.values())clearTimeout(t.timer),t.reject(new Error("Codex ACP adapter closed"));this.pendingTurnCompletions.clear();const e=this.started?.child;!e||e.exitCode!==null||e.signalCode!==null||(e.stdin.end(),!await x(e,2e3)&&(e.kill("SIGTERM"),!await x(e,1e3)&&(e.kill("SIGKILL"),await x(e,1e3))))}sessionResponse(e){return{sessionId:e,modes:{currentModeId:"workspace-write",availableModes:[{id:"read-only",name:"Read Only"},{id:"workspace-write",name:"Workspace Write"},{id:"danger-full-access",name:"Full Access"}]},configOptions:[{id:"model",category:"model",type:"string"},{id:"approvalPolicy",category:"approval",type:"string"},{id:"sandbox",category:"mode",type:"string"}]}}requireRpc(){if(!this.rpc)throw new Error("Codex app-server is not started");return this.rpc}notify(e,t){this.write({jsonrpc:"2.0",method:e,params:t})}requestParent(e,t,s=600*1e3){const i=this.nextParentRequestId++;return new Promise((o,r)=>{const c=setTimeout(()=>{this.pendingParentRequests.delete(i),r(new Error(`ACP client request timed out: ${e}`))},s);c.unref(),this.pendingParentRequests.set(i,{resolve:o,reject:r,timer:c}),this.write({jsonrpc:"2.0",id:i,method:e,params:t})})}handleParentResponse(e){const t=Number(e.id),s=this.pendingParentRequests.get(t);if(s){if(this.pendingParentRequests.delete(t),clearTimeout(s.timer),"error"in e){const i=a(e.error);s.reject(new Error(d(i,"message")||"ACP client request failed"));return}s.resolve(e.result)}}setConfigOption(e){const t=d(e,"configId"),s=e.value;if(!t)throw new Error("configId is required");if(typeof s!="string"&&typeof s!="boolean")throw new Error("config value must be string or boolean");return this.config[t]=s,this.notify("session/update",{sessionId:this.currentSessionId,update:{sessionUpdate:"config_option_update",configId:t,value:s}}),{}}setMode(e){const t=d(e,"modeId");if(!t)throw new Error("modeId is required");return this.config.sandbox=t,this.notify("session/update",{sessionId:this.currentSessionId,update:{sessionUpdate:"current_mode_update",modeId:t}}),{}}applyInitialConfig(e){const t=S(e,"config");for(const[s,i]of Object.entries(t))(typeof i=="string"||typeof i=="boolean")&&(this.config[s]=i)}codexConfig(){return{...C()?{developerInstructions:C()}:{},...typeof this.config.model=="string"&&this.config.model?{model:this.config.model}:{},approvalPolicy:typeof this.config.approvalPolicy=="string"?this.config.approvalPolicy:"on-request",sandbox:typeof this.config.sandbox=="string"?this.config.sandbox:"workspace-write"}}captureContextNotification(e,t){if(e==="thread/tokenUsage/updated"&&(this.latestTokenUsage=X(t)??this.latestTokenUsage),e==="thread/compacted"&&this.pendingCompaction&&this.resolveCompaction(),e==="item/completed"){const s=S(t,"item"),i=d(a(t),"threadId");d(s,"type")==="contextCompaction"&&(!i||i===this.currentSessionId)&&this.resolveCompaction(),d(s,"type")==="agentMessage"&&d(s,"phase")==="final_answer"&&(this.currentAssistantText=w(s)||this.currentAssistantText)}}resolveCompaction(){if(!this.pendingCompaction)return;const e=this.pendingCompaction;this.pendingCompaction=null,clearTimeout(e.timer),e.resolve()}captureUpdateEvidence(e){String(e.sessionUpdate||"")==="agent_message_chunk"&&(this.currentAssistantText+=w(e.content)),I(e,this.currentAffectedFiles)}captureThreadMetadata(e){const t=S(e,"thread");this.currentRolloutPath=d(t,"path")||null,this.threadTurnCount=Array.isArray(t.turns)?t.turns.length:0;const s=pe(t.turns);s.userRequest&&(this.previousUserText=s.userRequest),s.assistantResponse&&(this.previousAssistantText=s.assistantResponse),this.previousAffectedFiles.clear();for(const i of s.affectedFiles)this.previousAffectedFiles.add(i);this.latestTokenUsage=null}async maintainContext(e,t,s={}){const i=A(),o=this.contextState(),r=R(o,i);if(!r.compact&&!s.force||!this.currentSessionId)return this.maintenanceResult({stage:e,action:"none",reasons:r.reasons,beforeState:o});const c=this.currentSessionId;this.notifyContextStatus("正在整理会话上下文",N(o)),this.maintenanceActive=!0;let u=!1,l="",h=o,f=r,p=!1,g="";try{const m=o.usage?.observedAtMs??0;await this.compactThread(c,s.compactTimeoutMs??i.compactTimeoutMs),await this.waitForTokenUsageAfter(m,2e3)}catch(m){u=!0,l=m instanceof Error?m.message:String(m)}try{if(e!=="manual"&&this.promptCancelled||this.shutdownPromise)throw new Error("Codex prompt cancelled");if(h=this.contextState(),f=R(h,i,{compactAttempted:!0,compactFailed:u}),f.rollover&&s.allowRollover!==!1){this.notifyContextStatus("正在创建轻量会话",f.reasons.join(", "));try{await this.rotateThread(c,t),p=!0}catch(m){g=m instanceof Error?m.message:String(m),this.notifyContextStatus("轻量会话创建失败",g)}}else u||this.notifyContextStatus("会话上下文已压缩",N(h))}finally{this.maintenanceActive=!1}return this.maintenanceResult({action:p?"rollover":g?"rollover_failed":u?"compact_failed":"compact",stage:e,reasons:f.reasons,beforeState:o,afterState:h,compactError:[l,g].filter(Boolean).join("; "),previousThreadId:c,rolloverRecommended:f.rollover&&s.allowRollover===!1})}compactThread(e,t){return this.pendingCompaction?Promise.reject(new Error("Codex context compaction is already running")):new Promise((s,i)=>{const o=setTimeout(()=>{this.pendingCompaction?.timer===o&&(this.pendingCompaction=null),i(new Error("Codex thread compaction timed out"))},t);o.unref(),this.pendingCompaction={resolve:s,reject:i,timer:o},this.requireRpc().request("thread/compact/start",{threadId:e},t).catch(r=>{this.pendingCompaction?.timer===o&&(this.pendingCompaction=null,clearTimeout(o),i(r instanceof Error?r:new Error(String(r))))})})}async rotateThread(e,t){const s=H({previousThreadId:e,cwd:this.currentCwd,userRequest:t.userRequest,assistantResponse:t.assistantResponse,affectedFiles:t.affectedFiles}),i=await this.requireRpc().request("thread/start",{cwd:this.currentCwd,approvalsReviewer:"user",...this.codexConfig(),developerInstructions:[s,C()].filter(Boolean).join(`
|
|
7
7
|
|
|
8
8
|
`),ephemeral:!1,experimentalRawEvents:!0,threadSource:"aca-codex-acp-rollover"},6e4),o=T(i);if(!o)throw new Error("Codex rollover thread/start did not return thread id");this.sessionAliases.set(e,o),this.currentSessionId=o,this.resetSubtaskState(i),this.captureThreadMetadata(i)}async waitForTokenUsageAfter(e,t){const s=Date.now()+t;for(;Date.now()<s;){if((this.latestTokenUsage?.observedAtMs??0)>e)return;await Te(50)}}resolveSessionAlias(e){if(!e)return null;let t=e;const s=new Set;for(;this.sessionAliases.has(t)&&!s.has(t);)s.add(t),t=this.sessionAliases.get(t)??t;return t}contextState(){return{usage:this.latestTokenUsage,rolloutBytes:Q(this.currentRolloutPath),turnCount:this.threadTurnCount}}maintenanceResult(e){return{stage:e.stage,action:e.action,reasons:e.reasons,previousThreadId:e.previousThreadId??null,sessionId:this.currentSessionId,before:e.beforeState,after:e.afterState??null,compactError:e.compactError||null,rolloverRecommended:e.rolloverRecommended===!0}}notifyContextStatus(e,t){this.notify("session/update",{sessionId:this.currentSessionId,update:{sessionUpdate:"aca_context_maintenance",label:e,detail:t}})}notifyControlToolProgress(){const e=this.controlToolLoop.recordProgress();e&&this.notifySessionUpdate(q(e))}maybeSteerControlToolLoop(e){if(!ue(e.detection,this.controlToolPolicy))return;const t=ce(e.detection,this.controlToolPolicy),s=this.controlToolSteering.next(e,t.warningCalls);if(!s)return;const{threadId:i,turnId:o}=e.detection;!i||!o||this.requireRpc().request("turn/steer",{threadId:i,expectedTurnId:o,clientUserMessageId:`aca-loop-steer-${Date.now()}-${Math.random().toString(16).slice(2)}`,input:[{type:"text",text:s.text,text_elements:[]}]},6e4).then(()=>{this.notifySessionUpdate(E(e,s,{succeeded:!0}))}).catch(r=>{this.notifySessionUpdate(E(e,s,{succeeded:!1,error:r}))})}write(e){process.stdout.write(`${JSON.stringify(e)}
|
|
9
9
|
`)}}function T(n){const e=S(n,"thread");return d(e,"id")||null}function S(n,e){const t=a(n);return a(t[e])}function a(n){return n&&typeof n=="object"&&!Array.isArray(n)?n:{}}function w(n){if(typeof n=="string")return n;if(Array.isArray(n))return n.map(w).filter(Boolean).join("");if(!n||typeof n!="object")return"";const e=a(n);for(const t of["text","message","content","output_text"]){const s=w(e[t]);if(s)return s}return""}function I(n,e){if(Array.isArray(n)){for(const o of n)I(o,e);return}const t=a(n),s=d(t,"type").toLowerCase(),i=d(t,"path");if(i&&["diff","resource_link","content"].includes(s)&&e.add(i),Array.isArray(t.locations))for(const o of t.locations){const r=d(a(o),"path");r&&e.add(r)}for(const o of[t.content,t.update,t.rawInput,t.rawOutput])o&&o!==n&&I(o,e)}function pe(n){if(!Array.isArray(n)||n.length===0)return{userRequest:"",assistantResponse:"",affectedFiles:[]};const e=n.at(-1),t=new Set;I(e,t);let s="",i="";const o=r=>{if(Array.isArray(r)){for(const l of r)o(l);return}if(!r||typeof r!="object")return;const c=a(r),u=d(c,"type").toLowerCase();u==="usermessage"&&(s=w(c)||s),u==="agentmessage"&&(i=w(c)||i);for(const l of["items","content","input"])c[l]&&o(c[l])};return o(e),{userRequest:s,assistantResponse:i,affectedFiles:[...t]}}function fe(n,e){const t=b(n,"timeoutMs");return!Number.isFinite(t)||t<=0?e:Math.max(3e4,Math.min(59e4,t-1e4))}function F(n){const e=String(n.action||"none"),t=a(n.after),s=a(n.before),i=a(t.usage),o=a(s.usage),r=Object.keys(i).length>0?i:o,c=b(r,"contextWindow");if(e==="rollover")return{usedTokens:0,inputTokens:0,contextWindow:c,ratio:0,observedAtMs:Date.now(),state:"new_thread",maintenanceAction:e};if(Object.keys(r).length===0)return null;const u=b(r,"totalTokens"),l=b(r,"inputTokens");return{usedTokens:u||l,inputTokens:l,contextWindow:c,ratio:c>0?(u||l)/c:0,observedAtMs:b(r,"observedAtMs")||Date.now(),state:e==="compact"?"compacted":"active",maintenanceAction:e}}function me(n){return n?{usedTokens:n.totalTokens||n.inputTokens,inputTokens:n.inputTokens,contextWindow:n.contextWindow,ratio:n.contextWindow>0?(n.totalTokens||n.inputTokens)/n.contextWindow:0,observedAtMs:n.observedAtMs,state:"active",maintenanceAction:null}:null}function N(n){const e=n.usage?`${n.usage.inputTokens} tokens`:"token unknown",t=n.rolloutBytes>0?`${Math.round(n.rolloutBytes/1024/1024)}MB rollout`:"rollout unknown";return`${e}, ${t}, ${n.turnCount} turns`}function ge(n){const e=String(n.action||"none");if(e!=="compact_failed"&&e!=="rollover_failed")return;const t=String(n.compactError||"Codex 上下文维护未完成");throw new Error(`Codex 自动上下文维护失败:${t}`)}function b(n,e){const t=a(n)[e];return typeof t=="number"&&Number.isFinite(t)?t:0}function Se(n){return V(n)?[{sessionUpdate:"aca_codex_raw_response_usage",_meta:{codex:{rawResponse:n}}}]:[]}function we(n,e){const t=n.observeRawResponseItem(e);return t?[{event:t,update:q(t)}]:[]}function Ie(n){return["agent_message_chunk","agent_progress_chunk","agent_thought_chunk","tool_call","tool_call_update","plan","plan_update","plan_removed"].includes(String(n.sessionUpdate||""))}function D(n,e){return`${n}:${e}`}function d(n,e){const s=a(n)[e];return typeof s=="string"?s:""}function $(){const n=Number.parseInt(process.env.ACA_ACP_PROMPT_TIMEOUT_MS??"",10);return Number.isInteger(n)&&n>=3e4?n:720*60*1e3}function be(){const n=Number.parseInt(process.env.ACA_CODEX_SUBTASK_DRAIN_TIMEOUT_MS??"",10);return Number.isInteger(n)&&n>=1e3?Math.min(n,600*1e3):600*1e3}function x(n,e){return n.exitCode!==null||n.signalCode!==null?Promise.resolve(!0):new Promise(t=>{const s=setTimeout(()=>t(!1),e);s.unref(),n.once("close",()=>{clearTimeout(s),t(!0)})})}function Te(n){return new Promise(e=>setTimeout(e,n))}function xe(){new he}const Ce=process.argv[1]||"";/(?:^|[\\/])adapter\.(?:js|ts)$/.test(Ce)&&xe();export{xe as runCodexAcpAdapter};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{handleProviderInput as V}from"../../../interactions/provider-input.js";import y from"node:fs";import{runAbortableRequest as L}from"../../client/abortable-request.js";import{BoundedItemBuffer as X}from"../../client/bounded-buffer.js";import{promptBlocksToAcpContent as E}from"../../client/acp-content.js";import{acpSessionUpdateFromParams as Y,eventFromAcpSessionUpdate as G,statusFromAcpSessionUpdate as J,subtaskContextFromAcpSessionUpdate as Q}from"../../client/acp-events.js";import{defaultAcpRuntimePool as D}from"../../client/acp-runtime-pool.js";import{resolveCodexAcpLaunch as U}from"./launch.js";import{cancelCodexRuntime as Z}from"./cancellation.js";import{CodexPromptPerformanceTracker as ee,CodexUsageLoopDetector as q,codexRawResponseUsageFromAcpSessionUpdate as te,codexTokenUsageFromAcpSessionUpdate as oe,readCodexContextMaintenancePolicy as $}from"./context-maintenance.js";import{ensureCodexProviderRuntimeConfig as O}from"../../../core/codex-provider-runtime-config.js";import{codexControlToolLoopFromAcpSessionUpdate as se,controlToolLoopDetectionThresholds as re,controlToolLoopReady as ne,readCodexControlToolLoopPolicy as ie}from"./control-tool-loop.js";import{attachCodexDiagnostics as ae,CodexTurnDiagnosticsTracker as ce}from"./turn-diagnostics.js";import{AcpSessionInvalidatedError as W}from"../../session-errors.js";import{codexAcpUpdateIsMeaningfulProgress as le}from"./progress-evidence.js";const de={sessionResume:!0,liveSupplement:!0,imageInput:!0,fileAttachment:!0,filesystem:!0,terminal:!0,permissionRequest:!0,configOptions:!0,usage:!0,contextUsage:!0,contextCompaction:!0,plan:!0,diff:!0};class Ue{id="codex-acp";name="Codex ACP";capabilities=de;async createSession(e){return new ue(e.cwd,e.providerSessionId??null,e.timeoutMs)}async forkSession(e){if(!y.existsSync(e.cwd)||!y.statSync(e.cwd).isDirectory())throw new Error(`Project root does not exist or is not a directory: ${e.cwd}`);const s=e.sourceProviderSessionId.trim(),r=await O(),i=await D.acquire({providerId:"codex-acp",cwd:e.cwd,providerSessionId:s,runtimeIdentity:r?.runtimeIdentity||"default",env:F(r),...U(),onSessionUpdate:()=>{}});try{await N(i,r),i.initialized||(await i.client.initialize(6e4),i.markInitialized()),e.onStatus?.({phase:"forking",label:"正在分叉 Codex 会话",detail:s,updateType:"session/fork",atMs:Date.now()});const a=await i.client.forkSession({sessionId:s,cwd:e.cwd,config:z(e)},e.timeoutMs??12e4),t=A(a);if(!t)throw new Error("Codex ACP session/fork did not return sessionId");return{providerSessionId:t}}finally{i.release()}}}class ue{cwd;providerSessionId;defaultTimeoutMs;lease=null;supplementCallback=null;supplementRequests=new Set;sessionResponse=null;statusCallback=null;updateCallback=null;permissionCallback=null;userInputCallback=null;abortHandler=null;statusTimeline=[];updates=new X(16);content="";performanceTracker=null;usageLoopDetector=null;usagePatternReport=null;controlToolPatternReport=null;turnDiagnosticsTracker=null;usageLoopWarningActive=!1;controlToolLoopWarningActive=!1;controlToolLoopInterrupting=!1;cancellation=null;constructor(e,s,r=we()){this.cwd=e,this.providerSessionId=s,this.defaultTimeoutMs=r}async sendPrompt(e){if(!y.existsSync(e.cwd)||!y.statSync(e.cwd).isDirectory())throw new Error(`Project root does not exist or is not a directory: ${e.cwd}`);const s=Date.now();this.supplementCallback=e.onSupplementReady??null,this.statusCallback=e.onStatus??null,this.updateCallback=e.onUpdate??null,this.permissionCallback=e.onPermissionRequest??null,this.userInputCallback=e.onUserInputRequest??null,this.attachAbortSignal(e.signal),this.content="",this.updates.clear(),this.statusTimeline.length=0;const r=$(),i=ie();this.performanceTracker=new ee(s),this.usageLoopDetector=new q(r);const a=new ce;this.turnDiagnosticsTracker=a,this.usageLoopWarningActive=!1,this.controlToolLoopWarningActive=!1,this.controlToolLoopInterrupting=!1;let t={error:null,stopping:null,detection:null,controlDetection:null,interruption:null,kind:null},n=null,c=null,m=this.providerSessionId,v,C=null;try{this.emitStatus("initializing","Codex ACP 启动中","codex-acp");const h=await this.acquireRuntime(e.cwd);this.lease=h,n=h.client,c=h.started,await this.initialize(h),m=await this.establishSession(h,e),await this.applyConfig(n,m,e);const g=n,K=E(e.prompt,e.promptBlocks);let T=!1;for(this.emitStatus("thinking","思考中"),C=this.startSlowStatusTimer();;){const w=m;t={error:null,stopping:null,detection:null,controlDetection:null,interruption:null,kind:null},this.usageLoopDetector=new q(r),this.usageLoopWarningActive=!1,this.controlToolLoopWarningActive=!1,this.controlToolLoopInterrupting=!1,this.usagePatternReport=u=>{if(t.error)return;const _=Math.round(u.maxInputTokens/1e3),S=`同一 Turn 已连续请求至少 ${u.sampleCount} 次,单次输入约 ${_}k tokens`;if(a.recordUsageLoop(u.sampleCount,u.detectedAtMs,S),u.sampleCount<r.usageLoopInterruptSamples){this.usageLoopWarningActive=!0,this.emitStatus("thinking","Codex 出现低产出内部循环",S,"aca.performance.low_output_loop");return}const I=`Codex 低产出内部循环:同一 Turn 已连续请求至少 ${u.sampleCount} 次,单次输入约 ${_}k tokens,且未观察到有效进展`;t.error=new Error(I),t.detection=u,t.kind="low_output_loop",this.emitStatus("thinking","正在中断 Codex 低产出内部循环",S,"aca.performance.low_output_loop_interrupting"),t.stopping=this.stopRuntime(h,w).then(l=>{if(t.interruption=l,a.recordInterrupt({acknowledged:!l.forced,forcedRuntimeClose:l.forced,reason:l.reason}),l.forced){const d=l.reason||"取消请求失败";t.error=new Error(`${I};取消请求失败:${d};已关闭运行实例`)}}).catch(l=>{const d=l instanceof Error?l.message:String(l);a.recordInterrupt({acknowledged:!1,forcedRuntimeClose:!1,reason:d}),t.error=new Error(`${I};${d}`),this.emitStatus("failed","Codex 循环中断失败",d,"aca.performance.low_output_loop_interrupt_failed")})},this.controlToolPatternReport=u=>{if(t.error||!ne(u,i))return;const _=re(u,i);if(u.sampleCount<_.warningCalls)return;const S=u.kind==="list_agents_no_live"?"list_agents 查询不到运行中的子任务":u.kind==="wait_agent_no_live"?"无运行子任务时调用 wait_agent":u.kind==="send_message_no_progress"?"send_message 投递未产生进展":u.kind==="delegation_no_progress"?"重复创建或重启子任务且未产生进展":u.kind==="mixed_control_no_progress"?"混合控制工具且未产生进展":"短时 sleep",I=Math.max(1,Math.round(u.elapsedMs/6e4)),l=`同一 Turn 已连续 ${u.sampleCount} 次执行 ${S},持续约 ${I} 分钟`;if(a.recordControlLoop(u,l),u.sampleCount<_.interruptCalls){this.controlToolLoopWarningActive=!0,this.emitStatus("thinking","Codex 出现无进展控制工具循环",l,"aca.performance.control_tool_loop");return}const d=`Codex 无进展控制工具循环:${l}`;t.error=new Error(d),t.controlDetection=u,t.kind="control_tool_loop",this.controlToolLoopInterrupting=!0,this.emitStatus("thinking","正在中断 Codex 无进展控制工具循环",l,"aca.performance.control_tool_loop_interrupting"),t.stopping=this.stopRuntime(h,w).then(p=>{t.interruption=p,a.recordInterrupt({acknowledged:!p.forced,forcedRuntimeClose:p.forced,reason:p.reason});const x=p.forced?`${l};取消请求失败:${p.reason};已关闭运行实例`:l;p.forced&&(t.error=new Error(`${d};取消请求失败:${p.reason};已关闭运行实例`)),this.emitStatus("failed","Codex 已中断无进展控制工具循环",x,"aca.performance.control_tool_loop_interrupted")}).catch(p=>{const x=p instanceof Error?p.message:String(p);a.recordInterrupt({acknowledged:!1,forcedRuntimeClose:!1,reason:x}),t.error=new Error(`${d};${x}`),this.emitStatus("failed","Codex 控制工具循环中断失败",x,"aca.performance.control_tool_loop_interrupt_failed")})};let R;try{v=await this.promptWithAbort(g,{sessionId:w,prompt:K,timeoutMs:e.timeoutMs??this.defaultTimeoutMs,signal:e.signal})}catch(u){R=u}if(await t.stopping,t.error){const u=t.detection;if(t.kind==="low_output_loop"&&u?.source==="thread_usage"&&!T&&t.interruption?.forced===!1&&!e.signal?.aborted){T=!0,this.emitStatus("resuming","正在迁移异常 Codex 会话","保留本轮进度并创建干净线程","aca.performance.low_output_loop_recovering");try{const l=await g.recoverSession({sessionId:w,reason:"low_output_loop"}),d=A(l);if(!d||d===w)throw new Error("Codex recovery did not create a new session");m=d,this.providerSessionId=d,this.sessionResponse=f(l),h.setProviderSession({providerSessionId:d,sessionResponse:this.sessionResponse});const p={previousProviderSessionId:w,providerSessionId:d,reason:"low_output_loop",migratedAtMs:Date.now()};a.recordMigration(p),await e.onProviderSessionMigration?.(p),a.recordRecovery(p.migratedAtMs),this.content="",this.emitStatus("thinking","Codex 会话已迁移,继续执行",d,"aca.performance.low_output_loop_recovered_session");continue}catch(l){const d=l instanceof Error?l.message:String(l);t.error=new Error(`${t.error.message};自动迁移失败:${d}`)}}if(t.kind==="low_output_loop")throw this.emitStatus("failed","Codex 已中断低产出内部循环",t.error.message,"aca.performance.low_output_loop_interrupted"),new W(t.error.message,w,{source:u?.source??null,sampleCount:u?.sampleCount??null,recoveryAttempted:T});const S=t.controlDetection;if(t.interruption?.forced===!1&&!e.signal?.aborted){this.emitStatus("resuming","正在迁移异常 Codex 会话","保留本轮证据并为后续任务创建干净线程","aca.performance.control_tool_loop_migrating");try{const l=await g.recoverSession({sessionId:w,reason:"control_tool_loop"}),d=A(l);if(!d||d===w)throw new Error("Codex recovery did not create a new session");m=d,this.providerSessionId=d,this.sessionResponse=f(l),h.setProviderSession({providerSessionId:d,sessionResponse:this.sessionResponse});const p={previousProviderSessionId:w,providerSessionId:d,reason:"control_tool_loop",migratedAtMs:Date.now()};throw a.recordMigration(p),await e.onProviderSessionMigration?.(p),this.emitStatus("failed","Codex 循环已中断,会话已自动迁移",d,"aca.performance.control_tool_loop_migrated_session"),pe(t.error,d)}catch(l){if(me(l))throw l;const d=l instanceof Error?l.message:String(l);t.error=new Error(`${t.error.message};自动迁移失败:${d}`)}}throw new W(t.error.message,w,{source:"control_tool_loop",kind:S?.kind??null,sampleCount:S?.sampleCount??null,segmentId:S?.segmentId??null})}if(R)throw R;const b=H(v);b&&b!==m&&(m=b,this.providerSessionId=b,h.setProviderSession({providerSessionId:b,sessionResponse:this.sessionResponse}));break}}catch(h){if(h instanceof Error){const g=ae(h,a.snapshot());throw g.statusTimeline=[...this.statusTimeline],g.performance=this.performanceTracker?.snapshot(),g}throw h}finally{this.supplementCallback?.(null),this.supplementCallback=null,await Promise.allSettled([...this.supplementRequests]),await t.stopping,await this.cancellation?.catch(()=>{}),C&&clearInterval(C),this.usagePatternReport=null,this.controlToolPatternReport=null,this.turnDiagnosticsTracker=null,this.usageLoopDetector=null,this.usageLoopWarningActive=!1,this.controlToolLoopWarningActive=!1,this.controlToolLoopInterrupting=!1,this.abortHandler&&e.signal&&(e.signal.removeEventListener("abort",this.abortHandler),this.abortHandler=null),this.releaseLease()}return this.emitStatus("completed","已完成"),{providerSessionId:m,cliType:"builtin",agentType:"codex",effectiveConfig:{model:e.model??null,mode:e.mode??null,configOptionValues:e.configOptionValues??null,command:c.command,processPid:c.child.pid??null,provider:"codex-acp",protocol:"acp"},command:c.command,args:c.args,cwd:e.cwd,exitCode:c.child.exitCode,signal:c.child.signalCode,durationMs:Date.now()-s,stdout:n.rpc.stdout,stderr:n.rpc.stderr,content:this.content.trim(),updateCount:this.updates.count(),updates:this.updates.snapshot(),statusTimeline:[...this.statusTimeline],performance:this.performanceTracker?.snapshot(),contextUsage:ve(v),providerDiagnostics:a.snapshot(),promptResponse:v}}async compactContext(e){if(!y.existsSync(e.cwd)||!y.statSync(e.cwd).isDirectory())throw new Error(`Project root does not exist or is not a directory: ${e.cwd}`);const s=Date.now();this.statusCallback=e.onStatus??null,this.updateCallback=e.onUpdate??null,this.attachAbortSignal(e.signal);let r=this.providerSessionId,i;try{this.emitStatus("initializing","Codex ACP 启动中","codex-acp");const t=await this.acquireRuntime(e.cwd);this.lease=t,await this.initialize(t),r=await this.establishSession(t,e),await this.applyConfig(t.client,r,e);const n=Se(e.timeoutMs);i=await this.compactWithAbort(t.client,{sessionId:r,allowRollover:e.allowRollover===!0,timeoutMs:n,signal:e.signal});const c=H(i);c&&c!==r&&(r=c,this.providerSessionId=c,t.setProviderSession({providerSessionId:c,sessionResponse:this.sessionResponse}))}catch(t){if(this.lease&&he(t,e.signal)){const n=this.lease;this.lease=null,await n.dispose().catch(()=>{})}throw this.emitStatus("failed","上下文压缩失败",t instanceof Error?t.message:String(t)),t}finally{this.abortHandler&&e.signal&&(e.signal.removeEventListener("abort",this.abortHandler),this.abortHandler=null),this.releaseLease()}const a=f(i)??{};return{providerSessionId:r||this.providerSessionId||"",contextUsage:B(a.contextUsage),maintenance:f(a.maintenance)??{},durationMs:Date.now()-s}}async cancel(){this.providerSessionId&&this.lease&&await this.stopRuntime(this.lease,this.providerSessionId)}stopRuntime(e,s){if(this.cancellation)return this.cancellation;const r=Z(e,s);return this.cancellation=r,r.finally(()=>{this.cancellation===r&&(this.cancellation=null)}).catch(()=>{}),r}async close(){await this.cancellation?.catch(()=>{}),this.statusCallback=null,this.updateCallback=null,this.permissionCallback=null,this.userInputCallback=null,this.abortHandler&&(this.abortHandler=null),this.releaseLease()}async acquireRuntime(e){const s=U(),r=await O(),i=await D.acquire({providerId:"codex-acp",cwd:e,providerSessionId:this.providerSessionId,runtimeIdentity:r?.runtimeIdentity||"default",env:F(r),command:s.command,args:s.args,onSessionUpdate:a=>this.handleSessionUpdate(a),onClientRequest:async(a,t,n)=>{if(a==="session/request_permission")return this.requestPermission(t,n.id);if(a==="elicitation/create"||a==="session/request_user_input")return this.requestUserInput(a,t,n.id);throw new Error(`Unsupported Codex ACP client request: ${a}`)}});try{return await N(i,r),i}catch(a){throw await i.dispose().catch(()=>{}),a}}async initialize(e){e.initialized||(await e.client.initialize(6e4),e.markInitialized())}async establishSession(e,s){const r=e.client,i=z(s);if(e.providerSessionId&&e.sessionEstablished)return this.providerSessionId=e.providerSessionId,this.sessionResponse=e.sessionResponse,e.providerSessionId;if(this.providerSessionId)try{this.emitStatus("resuming","Codex ACP 会话恢复中","session/load");const n=await r.loadSession({sessionId:this.providerSessionId,cwd:s.cwd,config:i},6e4);this.sessionResponse=f(n);const c=A(n)||this.providerSessionId;return this.providerSessionId=c,e.setProviderSession({providerSessionId:c,sessionResponse:this.sessionResponse}),c}catch(n){if(s.requireSessionResume){const c=n instanceof Error?n.message:String(n);throw new Error(`无法恢复导入的 Codex ACP Session ${this.providerSessionId}:${c}`)}this.providerSessionId=null}this.emitStatus("acp","Codex ACP 会话创建中","session/new");const a=await r.newSession({cwd:s.cwd,config:i},12e4);this.sessionResponse=f(a);const t=A(a);if(!t)throw new Error("Codex ACP session/new did not return sessionId");return this.providerSessionId=t,e.setProviderSession({providerSessionId:t,sessionResponse:this.sessionResponse}),t}async applyConfig(e,s,r){r.model&&await e.setSessionConfigOption({sessionId:s,configId:"model",value:r.model},6e4).catch(()=>{});const i=M(r.mode);i&&await e.setSessionConfigOption({sessionId:s,configId:"approvalPolicy",value:i},6e4).catch(()=>{});const a=P(r.mode);a&&await e.setSessionConfigOption({sessionId:s,configId:"sandbox",value:a},6e4).catch(()=>{});for(const[t,n]of Object.entries(r.configOptionValues??{}))["model","mode","sandbox","approvalPolicy"].includes(t)||await e.setSessionConfigOption({sessionId:s,configId:t,value:n},6e4).catch(()=>{})}handleSessionUpdate(e){const s=Y(e),r=String(s?.update.sessionUpdate||"");if(r==="aca_supplement_target"){const n=s.update,c=this.lease?.client,m=String(n.threadId||""),v=String(n.turnId||"");!c||n.ready!==!0||!m||!v?this.supplementCallback?.(null):this.supplementCallback?.({threadId:m,turnId:v,send:(C,h)=>{const g=c.rpc.request("aca/supplement",{sessionId:m,expectedTurnId:v,deliveryId:C,prompt:E("",h)},65e3);return this.supplementRequests.add(g),g.finally(()=>this.supplementRequests.delete(g)).catch(()=>{}),g},receipt:C=>c.rpc.request("aca/supplement_status",{deliveryId:C},5e3)});return}const i=Q(e);if(!i&&r==="aca_codex_subtask_drain"){const n=String(s?.update.label||"子任务收尾等待超时"),c=String(s?.update.detail||"仍有子任务未结束");this.emitStatus("exploring",n,c,r)}else if(!i&&r==="aca_codex_control_tool_loop"){const n=se(e);n?.state==="observed"&&this.controlToolPatternReport?.(n.detection),n?.state==="recovered"&&this.controlToolLoopWarningActive&&!this.controlToolLoopInterrupting&&(this.controlToolLoopWarningActive=!1,this.turnDiagnosticsTracker?.recordRecovery(),this.emitStatus("thinking","Codex 已恢复执行","无进展控制工具循环已结束","aca.performance.control_tool_loop_recovered"))}else if(!i&&r==="aca_codex_control_tool_steer"){const n=fe(s?.update);n&&this.turnDiagnosticsTracker?.recordControlToolSteering(n)}else if(le(s?.update))this.recordMeaningfulProgress();else if(!i&&r==="aca_codex_raw_response_usage"){const n=te(e);n&&this.turnDiagnosticsTracker?.recordUsage(n);const c=n?this.usageLoopDetector?.recordUsage(n):null;c&&this.usagePatternReport?.(c)}else if(!i&&r==="usage_update"){const n=oe(e);n&&this.turnDiagnosticsTracker?.recordUsage(n);const c=n?this.usageLoopDetector?.recordUsage(n):null;c&&this.usagePatternReport?.(c)}this.updates.push({method:"session/update",params:e});const a=G(e);a&&(a.type==="agent_message_chunk"&&!i&&(this.content+=a.text??""),this.updateCallback?.(a));const t=J(e);t&&this.emitStatus(t.phase,t.label,t.detail,t.updateType)}recordMeaningfulProgress(){this.performanceTracker?.recordEvent();const e=this.usageLoopDetector?.recordMeaningfulEvent();this.usageLoopWarningActive&&(this.usageLoopWarningActive=!1,e&&this.turnDiagnosticsTracker?.recordRecovery(),this.emitStatus("thinking","Codex 已恢复执行","已重新执行工具或产生有效输出","aca.performance.loop_recovered"))}startSlowStatusTimer(){const e=$().slowEventThresholdMs,s=setInterval(()=>{const r=this.performanceTracker,i=Date.now();if(!r?.shouldReportSlow(i,e))return;const a=r.snapshot(i),t=Math.max(1,Math.round(a.silentForMs/6e4)),n=this.usageLoopWarningActive||this.controlToolLoopWarningActive?"Codex 可能卡在内部循环":"Codex 长时间没有有效进展";this.emitStatus("thinking",n,`已 ${t} 分钟没有执行工具或产生有效输出`,"aca.performance.slow")},Math.min(3e4,Math.max(1e3,Math.floor(e/4))));return s.unref(),s}attachAbortSignal(e){if(e){if(this.abortHandler=()=>{this.cancel().catch(()=>{})},e.aborted)throw new Error("Codex ACP prompt cancelled");e.addEventListener("abort",this.abortHandler,{once:!0})}}async promptWithAbort(e,s){return L({signal:s.signal,cancelledMessage:"Codex ACP prompt cancelled",startRequest:()=>e.prompt({sessionId:s.sessionId,prompt:s.prompt},s.timeoutMs),onAbort:async()=>{await this.lease?.dispose()}})}async compactWithAbort(e,s){return L({signal:s.signal,cancelledMessage:"Codex ACP 上下文压缩已取消",startRequest:()=>e.compactSession({sessionId:s.sessionId,allowRollover:s.allowRollover,timeoutMs:s.timeoutMs},s.timeoutMs),onAbort:async()=>{await this.lease?.dispose()}})}async requestPermission(e,s){if(!this.permissionCallback)return{outcome:{outcome:"cancelled"}};const r=e&&typeof e=="object"&&!Array.isArray(e)?e:{},i=r.request&&typeof r.request=="object"&&!Array.isArray(r.request)?r.request:r,t=(Array.isArray(i.options)?i.options:[]).map((n,c)=>{const m=n&&typeof n=="object"&&!Array.isArray(n)?n:{};return{optionId:String(m.optionId??m.id??c),kind:String(m.kind??"choice"),...typeof m.label=="string"?{label:m.label}:{}}});return this.emitStatus("requestPermission","等待授权",ge(i),"session/request_permission"),this.permissionCallback({requestId:String(i.requestId??s??i.id??`codex-acp-permission-${Date.now()}`),params:e,method:typeof i.method=="string"?i.method:"session/request_permission",expiresAtMs:typeof i.expiresAtMs=="number"?i.expiresAtMs:void 0,options:t})}async requestUserInput(e,s,r){return this.emitStatus("requestPermission","等待回答",void 0,e),V("codex-acp",e,s,this.userInputCallback,r)}emitStatus(e,s,r,i){const a={phase:e,label:s,...r?{detail:r}:{},...i?{updateType:i}:{},atMs:Date.now()},t=this.statusTimeline[this.statusTimeline.length-1];t&&t.phase===a.phase&&t.label===a.label&&t.detail===a.detail&&t.updateType===a.updateType||(this.statusTimeline.push(a),this.statusCallback?.(a))}releaseLease(){this.lease?.release(),this.lease=null}}function pe(o,e){const s=o;return s.controlLoopMigratedProviderSessionId=e,s}function me(o){if(!o||typeof o!="object"||Array.isArray(o))return null;const e=o.controlLoopMigratedProviderSessionId;return typeof e=="string"&&e.trim()?e.trim():null}async function N(o,e){o.runtimeConfigured||(await o.client.configureRuntime({codexProvider:e?{baseUrl:e.baseUrl,apiKey:e.apiKey,revision:e.revision}:null}),o.markRuntimeConfigured())}function F(o){return o?{OPENAI_API_KEY:o.apiKey,OPENAI_BASE_URL:o.baseUrl}:{}}function he(o,e){if(e?.aborted)return!0;const s=o instanceof Error?o.message:String(o||"");return/compact.*timed out|request timed out.*compact|adapter closed|aborted|cancelled/i.test(s)}function A(o){if(!o||typeof o!="object"||Array.isArray(o))return null;const e=o.sessionId;return typeof e=="string"&&e.trim()?e.trim():null}function f(o){return o&&typeof o=="object"&&!Array.isArray(o)?o:null}function fe(o){const e=f(o),s=f(e?._meta),r=f(s?.aca),i=f(r?.controlToolSteering);if(!i)return null;const a=Number(i.attempt),t=Number(i.maxAttempts);if(!Number.isInteger(a)||a<=0||!Number.isInteger(t)||t<=0)return null;const n=Number(i.steeredAtMs),c=Number(i.failedAtMs);return{segmentId:Math.max(0,Math.floor(Number(i.segmentId)||0)),attempt:a,maxAttempts:t,succeeded:Number.isFinite(n)&&n>0,observedAtMs:Number.isFinite(n)&&n>0?n:Number.isFinite(c)&&c>0?c:Date.now()}}function P(o){const e=String(o||"").trim();return e?e==="read-only"?"read-only":["agent-full-access","full-access","bypassPermissions","danger-full-access"].includes(e)?"danger-full-access":"workspace-write":null}function M(o){const e=String(o||"").trim();return e?["agent-full-access","full-access","bypassPermissions","danger-full-access"].includes(e)?"never":e==="acceptEdits"||e==="accept-edits"?"on-failure":"on-request":null}function z(o){return{...o.model?{model:o.model}:{},...M(o.mode)?{approvalPolicy:M(o.mode)}:{},...P(o.mode)?{sandbox:P(o.mode)}:{},...o.configOptionValues??{}}}function ge(o){const e=o.params&&typeof o.params=="object"&&!Array.isArray(o.params)?o.params:o;for(const s of["command","cwd","reason","grantRoot","method"]){const r=e[s];if(typeof r=="string"&&r.trim())return j(r)}}function qe(o){for(const e of["prompt","message","question","title"]){const s=o[e];if(typeof s=="string"&&s.trim())return j(s)}}function j(o){const e=o.replace(/\s+/g," ").trim();return e.length>120?`${e.slice(0,117)}...`:e}function we(){const o=Number.parseInt(process.env.ACA_CODEX_ACP_PROMPT_TIMEOUT_MS??process.env.ACA_ACP_PROMPT_TIMEOUT_MS??"",10);return Number.isInteger(o)&&o>=3e4?o:720*60*1e3}function Se(o){const e=Number.isInteger(o)?Number(o):Number.parseInt(process.env.ACA_CODEX_ACP_COMPACT_TIMEOUT_MS??"",10);return Number.isFinite(e)&&e>=3e4?Math.min(e,6e5):12e4}function H(o){if(!o||typeof o!="object"||Array.isArray(o))return null;const e=o;return typeof e.sessionId=="string"&&e.sessionId.trim()?e.sessionId:null}function ve(o){const e=f(o),s=f(e?._meta);return B(s?.acaContextUsage)}function B(o){const e=f(o);if(!e)return null;const s=k(e.contextWindow),r=k(e.usedTokens),i=k(e.inputTokens),a=String(e.state||"unknown"),t=["active","compacted","new_thread"].includes(a)?a:"unknown";return{usedTokens:r,inputTokens:i,contextWindow:s,ratio:s>0?Math.max(0,k(e.ratio)||r/s):0,observedAtMs:k(e.observedAtMs)||Date.now(),state:t,maintenanceAction:typeof e.maintenanceAction=="string"?e.maintenanceAction:null}}function k(o){return typeof o=="number"&&Number.isFinite(o)&&o>=0?o:0}export{Ue as CodexAcpProvider,de as codexAcpCapabilities};
|
|
1
|
+
import{handleProviderInput as V}from"../../../interactions/provider-input.js";import y from"node:fs";import{runAbortableRequest as L}from"../../client/abortable-request.js";import{BoundedItemBuffer as X}from"../../client/bounded-buffer.js";import{promptBlocksToAcpContent as E}from"../../client/acp-content.js";import{acpSessionUpdateFromParams as Y,eventFromAcpSessionUpdate as G,statusFromAcpSessionUpdate as J,subtaskContextFromAcpSessionUpdate as Q}from"../../client/acp-events.js";import{defaultAcpRuntimePool as D}from"../../client/acp-runtime-pool.js";import{resolveCodexAcpLaunch as U}from"./launch.js";import{cancelCodexRuntime as Z}from"./cancellation.js";import{CodexPromptPerformanceTracker as ee,CodexUsageLoopDetector as q,codexRawResponseUsageFromAcpSessionUpdate as te,codexTokenUsageFromAcpSessionUpdate as oe,readCodexContextMaintenancePolicy as $}from"./context-maintenance.js";import{ensureCodexProviderRuntimeConfig as O}from"../../../core/codex-provider-runtime-config.js";import{codexControlToolLoopFromAcpSessionUpdate as se,controlToolLoopDetectionThresholds as ne,controlToolLoopReady as re,readCodexControlToolLoopPolicy as ie}from"./control-tool-loop.js";import{attachCodexDiagnostics as ae,CodexTurnDiagnosticsTracker as ce}from"./turn-diagnostics.js";import{AcpSessionInvalidatedError as W}from"../../session-errors.js";import{codexAcpUpdateIsMeaningfulProgress as le}from"./progress-evidence.js";const de={sessionResume:!0,liveSupplement:!0,imageInput:!0,fileAttachment:!0,filesystem:!0,terminal:!0,permissionRequest:!0,configOptions:!0,usage:!0,contextUsage:!0,contextCompaction:!0,plan:!0,diff:!0};class Ue{id="codex-acp";name="Codex ACP";capabilities=de;async createSession(e){return new ue(e.cwd,e.providerSessionId??null,e.timeoutMs)}async forkSession(e){if(!y.existsSync(e.cwd)||!y.statSync(e.cwd).isDirectory())throw new Error(`Project root does not exist or is not a directory: ${e.cwd}`);const s=e.sourceProviderSessionId.trim(),n=await O(),i=await D.acquire({providerId:"codex-acp",cwd:e.cwd,providerSessionId:s,runtimeIdentity:n?.runtimeIdentity||"default",env:F(n),...U(),onSessionUpdate:()=>{}});try{await N(i,n),i.initialized||(await i.client.initialize(6e4),i.markInitialized()),e.onStatus?.({phase:"forking",label:"正在分叉 Codex 会话",detail:s,updateType:"session/fork",atMs:Date.now()});const a=await i.client.forkSession({sessionId:s,cwd:e.cwd,config:z(e)},e.timeoutMs??12e4),t=A(a);if(!t)throw new Error("Codex ACP session/fork did not return sessionId");return{providerSessionId:t}}finally{i.release()}}}class ue{cwd;providerSessionId;defaultTimeoutMs;lease=null;supplementCallback=null;supplementRequests=new Set;sessionResponse=null;statusCallback=null;updateCallback=null;permissionCallback=null;userInputCallback=null;abortHandler=null;statusTimeline=[];updates=new X(16);content="";performanceTracker=null;usageLoopDetector=null;usagePatternReport=null;controlToolPatternReport=null;turnDiagnosticsTracker=null;usageLoopWarningActive=!1;controlToolLoopWarningActive=!1;controlToolLoopInterrupting=!1;cancellation=null;constructor(e,s,n=we()){this.cwd=e,this.providerSessionId=s,this.defaultTimeoutMs=n}async sendPrompt(e){if(!y.existsSync(e.cwd)||!y.statSync(e.cwd).isDirectory())throw new Error(`Project root does not exist or is not a directory: ${e.cwd}`);const s=Date.now();this.supplementCallback=e.onSupplementReady??null,this.statusCallback=e.onStatus??null,this.updateCallback=e.onUpdate??null,this.permissionCallback=e.onPermissionRequest??null,this.userInputCallback=e.onUserInputRequest??null,this.elicitationEnabled=e.elicitation??!!e.onUserInputRequest,this.elicitationComplete=e.onElicitationComplete,this.attachAbortSignal(e.signal),this.content="",this.updates.clear(),this.statusTimeline.length=0;const n=$(),i=ie();this.performanceTracker=new ee(s),this.usageLoopDetector=new q(n);const a=new ce;this.turnDiagnosticsTracker=a,this.usageLoopWarningActive=!1,this.controlToolLoopWarningActive=!1,this.controlToolLoopInterrupting=!1;let t={error:null,stopping:null,detection:null,controlDetection:null,interruption:null,kind:null},r=null,c=null,p=this.providerSessionId,C,v=null;try{this.emitStatus("initializing","Codex ACP 启动中","codex-acp");const h=await this.acquireRuntime(e.cwd);this.lease=h,r=h.client,c=h.started,await this.initialize(h),p=await this.establishSession(h,e),await this.applyConfig(r,p,e);const g=r,K=E(e.prompt,e.promptBlocks);let T=!1;for(this.emitStatus("thinking","思考中"),v=this.startSlowStatusTimer();;){const w=p;t={error:null,stopping:null,detection:null,controlDetection:null,interruption:null,kind:null},this.usageLoopDetector=new q(n),this.usageLoopWarningActive=!1,this.controlToolLoopWarningActive=!1,this.controlToolLoopInterrupting=!1,this.usagePatternReport=u=>{if(t.error)return;const _=Math.round(u.maxInputTokens/1e3),S=`同一 Turn 已连续请求至少 ${u.sampleCount} 次,单次输入约 ${_}k tokens`;if(a.recordUsageLoop(u.sampleCount,u.detectedAtMs,S),u.sampleCount<n.usageLoopInterruptSamples){this.usageLoopWarningActive=!0,this.emitStatus("thinking","Codex 出现低产出内部循环",S,"aca.performance.low_output_loop");return}const I=`Codex 低产出内部循环:同一 Turn 已连续请求至少 ${u.sampleCount} 次,单次输入约 ${_}k tokens,且未观察到有效进展`;t.error=new Error(I),t.detection=u,t.kind="low_output_loop",this.emitStatus("thinking","正在中断 Codex 低产出内部循环",S,"aca.performance.low_output_loop_interrupting"),t.stopping=this.stopRuntime(h,w).then(l=>{if(t.interruption=l,a.recordInterrupt({acknowledged:!l.forced,forcedRuntimeClose:l.forced,reason:l.reason}),l.forced){const d=l.reason||"取消请求失败";t.error=new Error(`${I};取消请求失败:${d};已关闭运行实例`)}}).catch(l=>{const d=l instanceof Error?l.message:String(l);a.recordInterrupt({acknowledged:!1,forcedRuntimeClose:!1,reason:d}),t.error=new Error(`${I};${d}`),this.emitStatus("failed","Codex 循环中断失败",d,"aca.performance.low_output_loop_interrupt_failed")})},this.controlToolPatternReport=u=>{if(t.error||!re(u,i))return;const _=ne(u,i);if(u.sampleCount<_.warningCalls)return;const S=u.kind==="list_agents_no_live"?"list_agents 查询不到运行中的子任务":u.kind==="wait_agent_no_live"?"无运行子任务时调用 wait_agent":u.kind==="send_message_no_progress"?"send_message 投递未产生进展":u.kind==="delegation_no_progress"?"重复创建或重启子任务且未产生进展":u.kind==="mixed_control_no_progress"?"混合控制工具且未产生进展":"短时 sleep",I=Math.max(1,Math.round(u.elapsedMs/6e4)),l=`同一 Turn 已连续 ${u.sampleCount} 次执行 ${S},持续约 ${I} 分钟`;if(a.recordControlLoop(u,l),u.sampleCount<_.interruptCalls){this.controlToolLoopWarningActive=!0,this.emitStatus("thinking","Codex 出现无进展控制工具循环",l,"aca.performance.control_tool_loop");return}const d=`Codex 无进展控制工具循环:${l}`;t.error=new Error(d),t.controlDetection=u,t.kind="control_tool_loop",this.controlToolLoopInterrupting=!0,this.emitStatus("thinking","正在中断 Codex 无进展控制工具循环",l,"aca.performance.control_tool_loop_interrupting"),t.stopping=this.stopRuntime(h,w).then(m=>{t.interruption=m,a.recordInterrupt({acknowledged:!m.forced,forcedRuntimeClose:m.forced,reason:m.reason});const x=m.forced?`${l};取消请求失败:${m.reason};已关闭运行实例`:l;m.forced&&(t.error=new Error(`${d};取消请求失败:${m.reason};已关闭运行实例`)),this.emitStatus("failed","Codex 已中断无进展控制工具循环",x,"aca.performance.control_tool_loop_interrupted")}).catch(m=>{const x=m instanceof Error?m.message:String(m);a.recordInterrupt({acknowledged:!1,forcedRuntimeClose:!1,reason:x}),t.error=new Error(`${d};${x}`),this.emitStatus("failed","Codex 控制工具循环中断失败",x,"aca.performance.control_tool_loop_interrupt_failed")})};let R;try{C=await this.promptWithAbort(g,{sessionId:w,prompt:K,timeoutMs:e.timeoutMs??this.defaultTimeoutMs,signal:e.signal})}catch(u){R=u}if(await t.stopping,t.error){const u=t.detection;if(t.kind==="low_output_loop"&&u?.source==="thread_usage"&&!T&&t.interruption?.forced===!1&&!e.signal?.aborted){T=!0,this.emitStatus("resuming","正在迁移异常 Codex 会话","保留本轮进度并创建干净线程","aca.performance.low_output_loop_recovering");try{const l=await g.recoverSession({sessionId:w,reason:"low_output_loop"}),d=A(l);if(!d||d===w)throw new Error("Codex recovery did not create a new session");p=d,this.providerSessionId=d,this.sessionResponse=f(l),h.setProviderSession({providerSessionId:d,sessionResponse:this.sessionResponse});const m={previousProviderSessionId:w,providerSessionId:d,reason:"low_output_loop",migratedAtMs:Date.now()};a.recordMigration(m),await e.onProviderSessionMigration?.(m),a.recordRecovery(m.migratedAtMs),this.content="",this.emitStatus("thinking","Codex 会话已迁移,继续执行",d,"aca.performance.low_output_loop_recovered_session");continue}catch(l){const d=l instanceof Error?l.message:String(l);t.error=new Error(`${t.error.message};自动迁移失败:${d}`)}}if(t.kind==="low_output_loop")throw this.emitStatus("failed","Codex 已中断低产出内部循环",t.error.message,"aca.performance.low_output_loop_interrupted"),new W(t.error.message,w,{source:u?.source??null,sampleCount:u?.sampleCount??null,recoveryAttempted:T});const S=t.controlDetection;if(t.interruption?.forced===!1&&!e.signal?.aborted){this.emitStatus("resuming","正在迁移异常 Codex 会话","保留本轮证据并为后续任务创建干净线程","aca.performance.control_tool_loop_migrating");try{const l=await g.recoverSession({sessionId:w,reason:"control_tool_loop"}),d=A(l);if(!d||d===w)throw new Error("Codex recovery did not create a new session");p=d,this.providerSessionId=d,this.sessionResponse=f(l),h.setProviderSession({providerSessionId:d,sessionResponse:this.sessionResponse});const m={previousProviderSessionId:w,providerSessionId:d,reason:"control_tool_loop",migratedAtMs:Date.now()};throw a.recordMigration(m),await e.onProviderSessionMigration?.(m),this.emitStatus("failed","Codex 循环已中断,会话已自动迁移",d,"aca.performance.control_tool_loop_migrated_session"),pe(t.error,d)}catch(l){if(me(l))throw l;const d=l instanceof Error?l.message:String(l);t.error=new Error(`${t.error.message};自动迁移失败:${d}`)}}throw new W(t.error.message,w,{source:"control_tool_loop",kind:S?.kind??null,sampleCount:S?.sampleCount??null,segmentId:S?.segmentId??null})}if(R)throw R;const b=H(C);b&&b!==p&&(p=b,this.providerSessionId=b,h.setProviderSession({providerSessionId:b,sessionResponse:this.sessionResponse}));break}}catch(h){if(h instanceof Error){const g=ae(h,a.snapshot());throw g.statusTimeline=[...this.statusTimeline],g.performance=this.performanceTracker?.snapshot(),g}throw h}finally{this.supplementCallback?.(null),this.supplementCallback=null,await Promise.allSettled([...this.supplementRequests]),await t.stopping,await this.cancellation?.catch(()=>{}),v&&clearInterval(v),this.usagePatternReport=null,this.controlToolPatternReport=null,this.turnDiagnosticsTracker=null,this.usageLoopDetector=null,this.usageLoopWarningActive=!1,this.controlToolLoopWarningActive=!1,this.controlToolLoopInterrupting=!1,this.abortHandler&&e.signal&&(e.signal.removeEventListener("abort",this.abortHandler),this.abortHandler=null),this.releaseLease()}return this.emitStatus("completed","已完成"),{providerSessionId:p,cliType:"builtin",agentType:"codex",effectiveConfig:{model:e.model??null,mode:e.mode??null,configOptionValues:e.configOptionValues??null,command:c.command,processPid:c.child.pid??null,provider:"codex-acp",protocol:"acp"},command:c.command,args:c.args,cwd:e.cwd,exitCode:c.child.exitCode,signal:c.child.signalCode,durationMs:Date.now()-s,stdout:r.rpc.stdout,stderr:r.rpc.stderr,content:this.content.trim(),updateCount:this.updates.count(),updates:this.updates.snapshot(),statusTimeline:[...this.statusTimeline],performance:this.performanceTracker?.snapshot(),contextUsage:Ce(C),providerDiagnostics:a.snapshot(),promptResponse:C}}async compactContext(e){if(!y.existsSync(e.cwd)||!y.statSync(e.cwd).isDirectory())throw new Error(`Project root does not exist or is not a directory: ${e.cwd}`);const s=Date.now();this.statusCallback=e.onStatus??null,this.updateCallback=e.onUpdate??null,this.attachAbortSignal(e.signal);let n=this.providerSessionId,i;try{this.emitStatus("initializing","Codex ACP 启动中","codex-acp");const t=await this.acquireRuntime(e.cwd);this.lease=t,await this.initialize(t),n=await this.establishSession(t,e),await this.applyConfig(t.client,n,e);const r=Se(e.timeoutMs);i=await this.compactWithAbort(t.client,{sessionId:n,allowRollover:e.allowRollover===!0,timeoutMs:r,signal:e.signal});const c=H(i);c&&c!==n&&(n=c,this.providerSessionId=c,t.setProviderSession({providerSessionId:c,sessionResponse:this.sessionResponse}))}catch(t){if(this.lease&&he(t,e.signal)){const r=this.lease;this.lease=null,await r.dispose().catch(()=>{})}throw this.emitStatus("failed","上下文压缩失败",t instanceof Error?t.message:String(t)),t}finally{this.abortHandler&&e.signal&&(e.signal.removeEventListener("abort",this.abortHandler),this.abortHandler=null),this.releaseLease()}const a=f(i)??{};return{providerSessionId:n||this.providerSessionId||"",contextUsage:B(a.contextUsage),maintenance:f(a.maintenance)??{},durationMs:Date.now()-s}}async cancel(){this.providerSessionId&&this.lease&&await this.stopRuntime(this.lease,this.providerSessionId)}stopRuntime(e,s){if(this.cancellation)return this.cancellation;const n=Z(e,s);return this.cancellation=n,n.finally(()=>{this.cancellation===n&&(this.cancellation=null)}).catch(()=>{}),n}async close(){await this.cancellation?.catch(()=>{}),this.statusCallback=null,this.updateCallback=null,this.permissionCallback=null,this.userInputCallback=null,this.abortHandler&&(this.abortHandler=null),this.releaseLease()}elicitationEnabled=!1;elicitationComplete;async acquireRuntime(e){const s=U(),n=await O(),i=await D.acquire({providerId:"codex-acp",elicitation:this.elicitationEnabled,onElicitationComplete:this.elicitationComplete,cwd:e,providerSessionId:this.providerSessionId,runtimeIdentity:n?.runtimeIdentity||"default",env:F(n),command:s.command,args:s.args,onSessionUpdate:a=>this.handleSessionUpdate(a),onClientRequest:async(a,t,r)=>{if(a==="session/request_permission")return this.requestPermission(t,r.id);if(a==="elicitation/create"||a==="session/request_user_input")return this.requestUserInput(a,t,r.id);throw new Error(`Unsupported Codex ACP client request: ${a}`)}});try{return await N(i,n),i}catch(a){throw await i.dispose().catch(()=>{}),a}}async initialize(e){e.initialized||(await e.client.initialize(6e4),e.markInitialized())}async establishSession(e,s){const n=e.client,i=z(s);if(e.providerSessionId&&e.sessionEstablished)return this.providerSessionId=e.providerSessionId,this.sessionResponse=e.sessionResponse,e.providerSessionId;if(this.providerSessionId)try{this.emitStatus("resuming","Codex ACP 会话恢复中","session/load");const r=await n.loadSession({sessionId:this.providerSessionId,cwd:s.cwd,config:i},6e4);this.sessionResponse=f(r);const c=A(r)||this.providerSessionId;return this.providerSessionId=c,e.setProviderSession({providerSessionId:c,sessionResponse:this.sessionResponse}),c}catch(r){if(s.requireSessionResume){const c=r instanceof Error?r.message:String(r);throw new Error(`无法恢复导入的 Codex ACP Session ${this.providerSessionId}:${c}`)}this.providerSessionId=null}this.emitStatus("acp","Codex ACP 会话创建中","session/new");const a=await n.newSession({cwd:s.cwd,config:i},12e4);this.sessionResponse=f(a);const t=A(a);if(!t)throw new Error("Codex ACP session/new did not return sessionId");return this.providerSessionId=t,e.setProviderSession({providerSessionId:t,sessionResponse:this.sessionResponse}),t}async applyConfig(e,s,n){n.model&&await e.setSessionConfigOption({sessionId:s,configId:"model",value:n.model},6e4).catch(()=>{});const i=M(n.mode);i&&await e.setSessionConfigOption({sessionId:s,configId:"approvalPolicy",value:i},6e4).catch(()=>{});const a=P(n.mode);a&&await e.setSessionConfigOption({sessionId:s,configId:"sandbox",value:a},6e4).catch(()=>{});for(const[t,r]of Object.entries(n.configOptionValues??{}))["model","mode","sandbox","approvalPolicy"].includes(t)||await e.setSessionConfigOption({sessionId:s,configId:t,value:r},6e4).catch(()=>{})}handleSessionUpdate(e){const s=Y(e),n=String(s?.update.sessionUpdate||"");if(n==="aca_supplement_target"){const r=s.update,c=this.lease?.client,p=String(r.threadId||""),C=String(r.turnId||"");!c||r.ready!==!0||!p||!C?this.supplementCallback?.(null):this.supplementCallback?.({threadId:p,turnId:C,send:(v,h)=>{const g=c.rpc.request("aca/supplement",{sessionId:p,expectedTurnId:C,deliveryId:v,prompt:E("",h)},65e3);return this.supplementRequests.add(g),g.finally(()=>this.supplementRequests.delete(g)).catch(()=>{}),g},receipt:v=>c.rpc.request("aca/supplement_status",{deliveryId:v},5e3)});return}const i=Q(e);if(!i&&n==="aca_codex_subtask_drain"){const r=String(s?.update.label||"子任务收尾等待超时"),c=String(s?.update.detail||"仍有子任务未结束");this.emitStatus("exploring",r,c,n)}else if(!i&&n==="aca_codex_control_tool_loop"){const r=se(e);r?.state==="observed"&&this.controlToolPatternReport?.(r.detection),r?.state==="recovered"&&this.controlToolLoopWarningActive&&!this.controlToolLoopInterrupting&&(this.controlToolLoopWarningActive=!1,this.turnDiagnosticsTracker?.recordRecovery(),this.emitStatus("thinking","Codex 已恢复执行","无进展控制工具循环已结束","aca.performance.control_tool_loop_recovered"))}else if(!i&&n==="aca_codex_control_tool_steer"){const r=fe(s?.update);r&&this.turnDiagnosticsTracker?.recordControlToolSteering(r)}else if(le(s?.update))this.recordMeaningfulProgress();else if(!i&&n==="aca_codex_raw_response_usage"){const r=te(e);r&&this.turnDiagnosticsTracker?.recordUsage(r);const c=r?this.usageLoopDetector?.recordUsage(r):null;c&&this.usagePatternReport?.(c)}else if(!i&&n==="usage_update"){const r=oe(e);r&&this.turnDiagnosticsTracker?.recordUsage(r);const c=r?this.usageLoopDetector?.recordUsage(r):null;c&&this.usagePatternReport?.(c)}this.updates.push({method:"session/update",params:e});const a=G(e);a&&(a.type==="agent_message_chunk"&&!i&&(this.content+=a.text??""),this.updateCallback?.(a));const t=J(e);t&&this.emitStatus(t.phase,t.label,t.detail,t.updateType)}recordMeaningfulProgress(){this.performanceTracker?.recordEvent();const e=this.usageLoopDetector?.recordMeaningfulEvent();this.usageLoopWarningActive&&(this.usageLoopWarningActive=!1,e&&this.turnDiagnosticsTracker?.recordRecovery(),this.emitStatus("thinking","Codex 已恢复执行","已重新执行工具或产生有效输出","aca.performance.loop_recovered"))}startSlowStatusTimer(){const e=$().slowEventThresholdMs,s=setInterval(()=>{const n=this.performanceTracker,i=Date.now();if(!n?.shouldReportSlow(i,e))return;const a=n.snapshot(i),t=Math.max(1,Math.round(a.silentForMs/6e4)),r=this.usageLoopWarningActive||this.controlToolLoopWarningActive?"Codex 可能卡在内部循环":"Codex 长时间没有有效进展";this.emitStatus("thinking",r,`已 ${t} 分钟没有执行工具或产生有效输出`,"aca.performance.slow")},Math.min(3e4,Math.max(1e3,Math.floor(e/4))));return s.unref(),s}attachAbortSignal(e){if(e){if(this.abortHandler=()=>{this.cancel().catch(()=>{})},e.aborted)throw new Error("Codex ACP prompt cancelled");e.addEventListener("abort",this.abortHandler,{once:!0})}}async promptWithAbort(e,s){return L({signal:s.signal,cancelledMessage:"Codex ACP prompt cancelled",startRequest:()=>e.prompt({sessionId:s.sessionId,prompt:s.prompt},s.timeoutMs),onAbort:async()=>{await this.lease?.dispose()}})}async compactWithAbort(e,s){return L({signal:s.signal,cancelledMessage:"Codex ACP 上下文压缩已取消",startRequest:()=>e.compactSession({sessionId:s.sessionId,allowRollover:s.allowRollover,timeoutMs:s.timeoutMs},s.timeoutMs),onAbort:async()=>{await this.lease?.dispose()}})}async requestPermission(e,s){if(!this.permissionCallback)return{outcome:{outcome:"cancelled"}};const n=e&&typeof e=="object"&&!Array.isArray(e)?e:{},i=n.request&&typeof n.request=="object"&&!Array.isArray(n.request)?n.request:n,t=(Array.isArray(i.options)?i.options:[]).map((r,c)=>{const p=r&&typeof r=="object"&&!Array.isArray(r)?r:{};return{optionId:typeof p.optionId=="string"?p.optionId:"",kind:String(p.kind??"choice"),...typeof p.name=="string"?{label:p.name}:typeof p.label=="string"?{label:p.label}:{}}}).filter(r=>r.optionId);return this.emitStatus("requestPermission","等待授权",ge(i),"session/request_permission"),this.permissionCallback({requestId:String(i.requestId??s??i.id??`codex-acp-permission-${Date.now()}`),params:e,method:typeof i.method=="string"?i.method:"session/request_permission",expiresAtMs:typeof i.expiresAtMs=="number"?i.expiresAtMs:void 0,options:t})}async requestUserInput(e,s,n){return this.emitStatus("requestPermission","等待回答",void 0,e),V("codex-acp",e,s,this.userInputCallback,n)}emitStatus(e,s,n,i){const a={phase:e,label:s,...n?{detail:n}:{},...i?{updateType:i}:{},atMs:Date.now()},t=this.statusTimeline[this.statusTimeline.length-1];t&&t.phase===a.phase&&t.label===a.label&&t.detail===a.detail&&t.updateType===a.updateType||(this.statusTimeline.push(a),this.statusCallback?.(a))}releaseLease(){this.lease?.release(),this.lease=null}}function pe(o,e){const s=o;return s.controlLoopMigratedProviderSessionId=e,s}function me(o){if(!o||typeof o!="object"||Array.isArray(o))return null;const e=o.controlLoopMigratedProviderSessionId;return typeof e=="string"&&e.trim()?e.trim():null}async function N(o,e){o.runtimeConfigured||(await o.client.configureRuntime({codexProvider:e?{baseUrl:e.baseUrl,apiKey:e.apiKey,revision:e.revision}:null}),o.markRuntimeConfigured())}function F(o){return o?{OPENAI_API_KEY:o.apiKey,OPENAI_BASE_URL:o.baseUrl}:{}}function he(o,e){if(e?.aborted)return!0;const s=o instanceof Error?o.message:String(o||"");return/compact.*timed out|request timed out.*compact|adapter closed|aborted|cancelled/i.test(s)}function A(o){if(!o||typeof o!="object"||Array.isArray(o))return null;const e=o.sessionId;return typeof e=="string"&&e.trim()?e.trim():null}function f(o){return o&&typeof o=="object"&&!Array.isArray(o)?o:null}function fe(o){const e=f(o),s=f(e?._meta),n=f(s?.aca),i=f(n?.controlToolSteering);if(!i)return null;const a=Number(i.attempt),t=Number(i.maxAttempts);if(!Number.isInteger(a)||a<=0||!Number.isInteger(t)||t<=0)return null;const r=Number(i.steeredAtMs),c=Number(i.failedAtMs);return{segmentId:Math.max(0,Math.floor(Number(i.segmentId)||0)),attempt:a,maxAttempts:t,succeeded:Number.isFinite(r)&&r>0,observedAtMs:Number.isFinite(r)&&r>0?r:Number.isFinite(c)&&c>0?c:Date.now()}}function P(o){const e=String(o||"").trim();return e?e==="read-only"?"read-only":["agent-full-access","full-access","bypassPermissions","danger-full-access"].includes(e)?"danger-full-access":"workspace-write":null}function M(o){const e=String(o||"").trim();return e?["agent-full-access","full-access","bypassPermissions","danger-full-access"].includes(e)?"never":e==="acceptEdits"||e==="accept-edits"?"on-failure":"on-request":null}function z(o){return{...o.model?{model:o.model}:{},...M(o.mode)?{approvalPolicy:M(o.mode)}:{},...P(o.mode)?{sandbox:P(o.mode)}:{},...o.configOptionValues??{}}}function ge(o){const e=o.params&&typeof o.params=="object"&&!Array.isArray(o.params)?o.params:o;for(const s of["command","cwd","reason","grantRoot","method"]){const n=e[s];if(typeof n=="string"&&n.trim())return j(n)}}function qe(o){for(const e of["prompt","message","question","title"]){const s=o[e];if(typeof s=="string"&&s.trim())return j(s)}}function j(o){const e=o.replace(/\s+/g," ").trim();return e.length>120?`${e.slice(0,117)}...`:e}function we(){const o=Number.parseInt(process.env.ACA_CODEX_ACP_PROMPT_TIMEOUT_MS??process.env.ACA_ACP_PROMPT_TIMEOUT_MS??"",10);return Number.isInteger(o)&&o>=3e4?o:720*60*1e3}function Se(o){const e=Number.isInteger(o)?Number(o):Number.parseInt(process.env.ACA_CODEX_ACP_COMPACT_TIMEOUT_MS??"",10);return Number.isFinite(e)&&e>=3e4?Math.min(e,6e5):12e4}function H(o){if(!o||typeof o!="object"||Array.isArray(o))return null;const e=o;return typeof e.sessionId=="string"&&e.sessionId.trim()?e.sessionId:null}function Ce(o){const e=f(o),s=f(e?._meta);return B(s?.acaContextUsage)}function B(o){const e=f(o);if(!e)return null;const s=k(e.contextWindow),n=k(e.usedTokens),i=k(e.inputTokens),a=String(e.state||"unknown"),t=["active","compacted","new_thread"].includes(a)?a:"unknown";return{usedTokens:n,inputTokens:i,contextWindow:s,ratio:s>0?Math.max(0,k(e.ratio)||n/s):0,observedAtMs:k(e.observedAtMs)||Date.now(),state:t,maintenanceAction:typeof e.maintenanceAction=="string"?e.maintenanceAction:null}}function k(o){return typeof o=="number"&&Number.isFinite(o)&&o>=0?o:0}export{Ue as CodexAcpProvider,de as codexAcpCapabilities};
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{mimoCommands as y}from"./commands.js";import{assertMimoPromptCompleted as
|
|
2
|
-
`),this.providerSessionId=null}}if(
|
|
1
|
+
import{mimoCommands as y}from"./commands.js";import{assertMimoPromptCompleted as v,diagnoseMimoEmptyResponse as M,mimoError as b,MimoRunError as g}from"./errors.js";import{enterMimoTask as A}from"../../../core/mimo-auth/gate.js";import{resolveInstalledLaunch as R}from"../../../core/provider-install-paths.js";import{handleProviderInput as x}from"../../../interactions/provider-input.js";import m from"node:fs";import h from"node:path";import{runAbortableRequest as E}from"../../client/abortable-request.js";import{BoundedItemBuffer as _}from"../../client/bounded-buffer.js";import{mimoContextUsage as P}from"./usage.js";import{defaultAcpRuntimePool as T}from"../../client/acp-runtime-pool.js";import{providerEventFromAcpSessionUpdate as U,providerStatusFromAcpSessionUpdate as q}from"../standard-acp-events.js";const O={sessionResume:!0,imageInput:!0,fileAttachment:!0,filesystem:!0,terminal:!0,permissionRequest:!0,configOptions:!0,usage:!0,contextUsage:!0,contextCompaction:!0,plan:!0,diff:!1};class oe{id="mimocode";name="MimoCode";capabilities=O;async createSession(e){return new k(e.cwd,e.providerSessionId??null,e.timeoutMs)}}class k{cwd;providerSessionId;defaultTimeoutMs;busy=!1;commands=null;effectiveModel=null;lease=null;acpSessionResponse=null;statusCallback=null;updateCallback=null;permissionCallback=null;userInputCallback=null;canExecuteTools=!0;abortHandler=null;statusTimeline=[];updates=new _(16);content="";latestContextUsage=null;contextWindow=0;constructor(e,s,t=W()){this.cwd=e,this.providerSessionId=s,this.defaultTimeoutMs=t}async compactContext(e){if(!this.providerSessionId)throw new Error("MiMo 当前没有可压缩的会话。");const s=await this.sendPrompt({...e,prompt:"/compact",requireSessionResume:!0});if(!s.providerSessionId)throw new Error("MiMo 未返回压缩后的会话标识。");return{providerSessionId:s.providerSessionId,contextUsage:s.contextUsage?{...s.contextUsage,state:"compacted",maintenanceAction:"compact"}:null,maintenance:{action:"compact",provider:"mimocode",protocol:"session/prompt"},durationMs:s.durationMs}}async sendPrompt(e){if(this.busy)throw new Error("MiMo 会话正在执行另一项操作。");this.busy=!0;try{return await this.runPrompt(e)}finally{this.busy=!1}}async runPrompt(e){if(!m.existsSync(e.cwd)||!m.statSync(e.cwd).isDirectory())throw new Error(`Project root does not exist or is not a directory: ${e.cwd}`);const s=Date.now();this.statusCallback=e.onStatus??null,this.updateCallback=e.onUpdate??null,this.permissionCallback=e.onPermissionRequest??null,this.userInputCallback=e.onUserInputRequest??null,this.elicitationEnabled=e.elicitation??!!e.onUserInputRequest,this.elicitationComplete=e.onElicitationComplete,this.canExecuteTools=e.canExecuteTools!==!1,this.content="",this.updates.clear(),this.statusTimeline.length=0,this.latestContextUsage=null,this.contextWindow=j(e.contextWindow);let t=null,o=null,n=this.providerSessionId,r;const l=A();try{this.attachAbortSignal(e.signal),this.emitStatus("initializing","MimoCode ACP 启动中","mimo acp");const a=await this.acquireRuntime(e.cwd);this.lease=a,t=a.client,o=a.started,await this.initialize(a),n=await this.establishSession(a,e.cwd,e.requireSessionResume===!0),await this.applyConfig(t,n,e),this.emitStatus("thinking","思考中");const c=t,u=n;if(!c||!u)throw new Error("MimoCode ACP session is unavailable");if(r=await E({signal:e.signal,cancelledMessage:"MimoCode prompt cancelled",startRequest:()=>c.prompt({sessionId:u,prompt:z(e.prompt,e.promptBlocks)},e.timeoutMs??this.defaultTimeoutMs),onAbort:async()=>{c.cancel(u),await this.lease?.dispose()}}),v(r),!this.content.trim()){const f=await M({cwd:e.cwd,sessionId:u,startedAt:s,signal:e.signal});if(e.signal?.aborted)throw new g("cancelled","小米 MiMo 任务已取消。");if(f)throw f;if(!/^\s*\//.test(e.prompt))throw new g("incomplete","小米 MiMo 未返回回复正文,本轮未正常完成。请检查登录状态、所选模型和 Worker 网络后重试。")}}catch(a){await this.lease?.dispose();const c=b(a);throw this.emitStatus("failed","执行失败",c.message),Object.assign(c,{statusTimeline:[...this.statusTimeline],providerDiagnostics:{mimo:{availableCommands:this.commands}}})}finally{this.abortHandler&&e.signal&&(e.signal.removeEventListener("abort",this.abortHandler),this.abortHandler=null),this.releaseLease(),l()}return this.emitStatus("completed","已完成"),{providerSessionId:n,cliType:"builtin",agentType:"mimo",effectiveConfig:{model:this.effectiveModel||e.model||C(),mode:e.mode||null,configOptionValues:e.configOptionValues??null,command:o.command,processPid:o.child.pid??null,provider:"mimocode",protocol:"acp"},command:o.command,args:o.args,cwd:e.cwd,exitCode:o.child.exitCode,signal:o.child.signalCode,durationMs:Date.now()-s,stdout:t.rpc.stdout,stderr:t.rpc.stderr,content:this.content.trim(),updateCount:this.updates.count(),updates:this.updates.snapshot(),statusTimeline:[...this.statusTimeline],contextUsage:this.latestContextUsage,providerDiagnostics:{mimo:{...this.commands!==null?{availableCommands:this.commands}:{}}},promptResponse:r}}async cancel(){this.providerSessionId&&this.lease?.client.cancel(this.providerSessionId)}async close(){this.statusCallback=null,this.updateCallback=null,this.permissionCallback=null,this.userInputCallback=null,this.canExecuteTools=!0,this.abortHandler&&(this.abortHandler=null),this.releaseLease()}elicitationEnabled=!1;elicitationComplete;async acquireRuntime(e){const{command:s,args:t}=R("mimo"),o=[...t,"acp","--cwd",e];return T.acquire({providerId:"mimocode",elicitation:this.elicitationEnabled,onElicitationComplete:this.elicitationComplete,elicitationProvider:"mimo",cwd:e,providerSessionId:this.providerSessionId,command:s,args:o,onSessionUpdate:n=>this.handleSessionUpdate(n),onClientRequest:(n,r,l)=>this.handleClientRequest(n,r,l.id)})}async initialize(e){e.initialized||(await e.client.initialize(6e4),e.markInitialized())}async establishSession(e,s,t=!1){const o=e.client;if(e.providerSessionId&&e.sessionEstablished)return this.providerSessionId=e.providerSessionId,this.acpSessionResponse=e.sessionResponse,this.commands=y(e.sessionResponse?.availableCommands),this.commands!==null&&this.handleSessionUpdate({sessionId:e.providerSessionId,update:{sessionUpdate:"available_commands_update",availableCommands:this.commands}}),e.providerSessionId;if(this.providerSessionId)try{this.emitStatus("resuming","MimoCode 会话恢复中","session/resume");const l=await o.resumeSession({sessionId:this.providerSessionId,cwd:s},6e4);this.acpSessionResponse=d(l);const a=S(l)||this.providerSessionId;return this.providerSessionId=a,e.setProviderSession({providerSessionId:a,sessionResponse:this.acpSessionResponse}),a}catch(l){try{this.emitStatus("resuming","MimoCode 会话加载中","session/load");const a=await o.loadSession({sessionId:this.providerSessionId,cwd:s},6e4);this.acpSessionResponse=d(a);const c=S(a)||this.providerSessionId;return this.providerSessionId=c,e.setProviderSession({providerSessionId:c,sessionResponse:this.acpSessionResponse}),c}catch{if(t)throw new Error("MiMo 历史会话恢复失败,未执行压缩。请先确认原会话可继续对话。");this.appendStderr(`MimoCode resume failed, creating new session: ${L(l)}
|
|
2
|
+
`),this.providerSessionId=null}}if(t)throw new Error("MiMo 历史会话缺少 Session ID,未执行压缩。");this.emitStatus("acp","MimoCode 会话创建中","session/new");const n=await o.newSession({cwd:s},12e4);this.acpSessionResponse=d(n);const r=S(n);if(!r)throw new Error("MimoCode ACP session/new did not return sessionId");return this.providerSessionId=r,e.setProviderSession({providerSessionId:r,sessionResponse:this.acpSessionResponse}),r}async applyConfig(e,s,t){const o=w(this.acpSessionResponse,"model")||"model",n=w(this.acpSessionResponse,"mode")||"mode",r=t.model||C(),l=r==="default"?null:D(r,t.configOptionValues,this.acpSessionResponse);if(this.effectiveModel=l||F(this.acpSessionResponse),l){const c=await e.setSessionConfigOption({sessionId:s,configId:o,value:l},6e4).catch(u=>{throw new Error("MiMo 拒绝了模型或思考档位设置,请刷新模型列表并重新选择后重试。",{cause:u})});this.acpSessionResponse={...this.acpSessionResponse,...d(c)},this.lease?.setProviderSession({providerSessionId:s,sessionResponse:this.acpSessionResponse})}const a=B(t.mode);a&&await e.setSessionMode({sessionId:s,modeId:a},6e4).catch(async()=>{await e.setSessionConfigOption({sessionId:s,configId:n,value:a},6e4).catch(c=>{throw new Error("MiMo 拒绝了运行模式设置,请重新选择后重试。",{cause:c})})});for(const[c,u]of Object.entries(t.configOptionValues??{}))["variant","reasoning_effort","effort","model","mode"].includes(c)||await e.setSessionConfigOption({sessionId:s,configId:c,value:u},6e4).catch(f=>{throw new Error("MiMo 拒绝了配置选项,请更新模型配置后重试。",{cause:f})})}handleSessionUpdate(e){const s=d(e)?.sessionId;if(this.providerSessionId&&s&&s!==this.providerSessionId)return;this.updates.push({method:"session/update",params:e});const t=d(d(e)?.update);if(t?.sessionUpdate==="available_commands_update"){const r=y(t.availableCommands);r!==null&&(this.commands=r,this.acpSessionResponse={...this.acpSessionResponse,availableCommands:r},this.providerSessionId&&this.lease?.setProviderSession({providerSessionId:this.providerSessionId,sessionResponse:this.acpSessionResponse}))}t?.sessionUpdate==="config_option_update"&&Array.isArray(t.configOptions)&&(this.acpSessionResponse={...this.acpSessionResponse,configOptions:t.configOptions},this.providerSessionId&&this.lease?.setProviderSession({providerSessionId:this.providerSessionId,sessionResponse:this.acpSessionResponse})),this.latestContextUsage=P(e,this.contextWindow)??this.latestContextUsage;const o=U("mimo",e);o&&(o.type==="agent_message_chunk"&&(this.content+=o.text??""),this.updateCallback?.(o));const n=q("mimo",e);n&&this.emitStatus(n.phase,n.label,n.detail,n.updateType)}async handleClientRequest(e,s,t){if(!this.canExecuteTools&&(e==="fs/read_text_file"||e==="fs/write_text_file"))throw new Error(`Chat Room role policy disabled tool execution: ${e}`);switch(e){case"fs/read_text_file":return this.readTextFile(s);case"fs/write_text_file":return this.writeTextFile(s);case"session/request_permission":return this.requestPermission(s,t);case"elicitation/create":case"session/request_user_input":return this.requestUserInput(e,s,t);default:throw new Error(`Unsupported ACP client request: ${e}`)}}readTextFile(e){const s=I(this.cwd,p(e,"path")||p(e,"filePath"));return{content:m.readFileSync(s,"utf8")}}writeTextFile(e){const s=I(this.cwd,p(e,"path")||p(e,"filePath")),t=p(e,"content")??p(e,"text")??"";return m.mkdirSync(h.dirname(s),{recursive:!0}),m.writeFileSync(s,t,"utf8"),{}}async requestPermission(e,s){if(!this.permissionCallback)return{outcome:{outcome:"cancelled"}};const t=e&&typeof e=="object"&&!Array.isArray(e)?e:{},n=(Array.isArray(t.options)?t.options:[]).map((r,l)=>{const a=r&&typeof r=="object"&&!Array.isArray(r)?r:{};return{optionId:typeof a.optionId=="string"?a.optionId:"",kind:String(a.kind??a.outcome??"choice"),...typeof a.name=="string"?{label:a.name}:typeof a.label=="string"?{label:a.label}:{}}}).filter(r=>r.optionId);return this.permissionCallback({requestId:String(t.requestId??s??t.id??`mimo-permission-${Date.now()}`),params:e,expiresAtMs:typeof t.expiresAtMs=="number"?t.expiresAtMs:void 0,options:n})}async requestUserInput(e,s,t){return this.emitStatus("requestPermission","等待回答",void 0,e),x("mimo",e,s,this.userInputCallback,t)}attachAbortSignal(e){if(e){if(this.abortHandler=()=>{this.cancel()},e.aborted)throw new Error("MimoCode prompt cancelled");e.addEventListener("abort",this.abortHandler,{once:!0})}}emitStatus(e,s,t,o){const n={phase:e,label:s,...t?{detail:t}:{},...o?{updateType:o}:{},atMs:Date.now()},r=this.statusTimeline[this.statusTimeline.length-1];r&&r.phase===n.phase&&r.label===n.label&&r.detail===n.detail&&r.updateType===n.updateType||(this.statusTimeline.push(n),this.statusCallback?.(n))}requireClient(){if(!this.lease)throw new Error("MimoCode ACP client is not started");return this.lease.client}appendStderr(e){const s=this.lease?.client;s&&(s.rpc.stderr+=e)}releaseLease(){this.lease?.release(),this.lease=null}}function S(i){if(!i||typeof i!="object"||Array.isArray(i))return null;const e=i.sessionId;return typeof e=="string"&&e.trim()?e.trim():null}function d(i){return i&&typeof i=="object"&&!Array.isArray(i)?i:null}function j(i){return typeof i=="number"&&Number.isFinite(i)&&i>0?i:0}function w(i,e){const s=i?.configOptions;if(!Array.isArray(s))return null;for(const t of s){if(!t||typeof t!="object"||Array.isArray(t))continue;const o=t;if(o.category===e&&typeof o.id=="string"&&o.id.trim())return o.id.trim()}return null}function D(i,e,s){const t=H(e?.variant,e?.reasoning_effort,e?.effort);if(!t||i.endsWith(`/${t}`))return i;const o=`${i}/${t}`;if(V(s,o))return o;throw new Error("当前 MiMo 模型不支持所选思考档位,请重新选择。")}function F(i){const e=i?.configOptions;if(Array.isArray(e))for(const t of e){const o=d(t);if(o?.category==="model"&&typeof o.currentValue=="string")return o.currentValue}const s=d(i?.models)?.currentModelId;return typeof s=="string"?s:null}function V(i,e){const s=i?.configOptions;if(!Array.isArray(s))return!1;for(const t of s){if(!t||typeof t!="object"||Array.isArray(t))continue;const o=t;if(!(o.category!=="model"||!Array.isArray(o.options))&&o.options.some(n=>$(n)===e))return!0}return!1}function $(i){if(typeof i=="string"&&i.trim())return i.trim();if(!i||typeof i!="object"||Array.isArray(i))return null;const e=i;for(const s of["value","id","modelId","model_id"]){const t=e[s];if(typeof t=="string"&&t.trim())return t.trim()}return null}function p(i,e){if(!i||typeof i!="object"||Array.isArray(i))return null;const s=i[e];return typeof s=="string"&&s.trim()?s:null}function I(i,e){if(!e)throw new Error("ACP file request did not include a path");const s=h.resolve(i,e),t=h.resolve(i);if(s!==t&&!s.startsWith(`${t}${h.sep}`))throw new Error(`ACP file request is outside project root: ${e}`);return s}function B(i){const e=String(i||"").trim();return e?["agent","agent-full-access","full-access","bypassPermissions","danger-full-access"].includes(e)?"build":e==="read-only"?"plan":e:null}function H(...i){for(const e of i)if(typeof e=="string"&&e.trim())return e.trim();return null}function L(i){return i instanceof Error?i.message:String(i)}function C(){return process.env.ACA_MIMO_DEFAULT_MODEL||"default"}function z(i,e){return(e?.length?e:[{type:"text",text:i}]).map(t=>t.type==="image"?{type:"image",data:t.data,mimeType:t.mimeType}:{type:"text",text:t.text})}function W(){const i=Number.parseInt(process.env.ACA_MIMO_PROMPT_TIMEOUT_MS??process.env.ACA_ACP_PROMPT_TIMEOUT_MS??"",10);return Number.isInteger(i)&&i>=3e4?i:720*60*1e3}export{oe as MimoCodeProvider,O as mimoCodeCapabilities};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{parseAcpCommands as w}from"../../client/commands.js";import{openCodeContextUsage as x}from"./usage.js";import{configureOpenCode as R,currentOpenCodeModel as O}from"./config.js";import{assertOpenCodeCompleted as A,diagnoseOpenCodeEmpty as
|
|
2
|
-
`),await this.lease?.dispose().catch(()=>{}),this.lease=null,this.providerSessionId=null,this.sessionResponse=null,this.resumedExistingSession=!1,this.emitStatus("resuming","OpenCode 会话已失效,正在新建","session/prompt invalid params");const m=await this.acquireRuntime(e.cwd);this.lease=m,t=m.client,o=m.started,await this.initialize(m),n=await this.establishSession(m,e),await this.applyConfig(t,n,e),this.emitStatus("thinking","思考中"),r=await this.promptWithAbort(t,{sessionId:n,prompt:
|
|
1
|
+
import{parseAcpCommands as w}from"../../client/commands.js";import{openCodeContextUsage as x}from"./usage.js";import{configureOpenCode as R,currentOpenCodeModel as O}from"./config.js";import{assertOpenCodeCompleted as A,diagnoseOpenCodeEmpty as E,openCodeError as _,OpenCodeRunError as h}from"./errors.js";import{enterOpenCodeTask as P}from"../../../core/opencode/gate.js";import{resolveInstalledLaunch as T}from"../../../core/provider-install-paths.js";import{handleProviderInput as q}from"../../../interactions/provider-input.js";import f from"node:fs";import C from"node:path";import{runAbortableRequest as U}from"../../client/abortable-request.js";import{BoundedItemBuffer as k}from"../../client/bounded-buffer.js";import{acquireOpenCodeRuntime as M}from"./runtime.js";import{monitorOpenCodeRetry as D}from"./retry.js";import{providerEventFromAcpSessionUpdate as S,providerStatusFromAcpSessionUpdate as F}from"../standard-acp-events.js";const N={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 le{id="opencode";name="OpenCode";capabilities=N;async createSession(e){return new $(e.cwd,e.providerSessionId??null,e.timeoutMs)}}class ${cwd;providerSessionId;defaultTimeoutMs;busy=!1;promptActive=!1;promptProgress=!1;commands=null;lease=null;sessionResponse=null;statusCallback=null;updateCallback=null;permissionCallback=null;userInputCallback=null;canExecuteTools=!0;abortHandler=null;statusTimeline=[];updates=new k(16);content="";latestContextUsage=null;contextWindow=0;resumedExistingSession=!1;constructor(e,s,t=J()){this.cwd=e,this.providerSessionId=s,this.defaultTimeoutMs=t}async compactContext(e){if(!this.providerSessionId)throw new h("incomplete","OpenCode 当前没有可压缩的会话。");const s=await this.sendPrompt({...e,prompt:"/compact",requireSessionResume:!0});if(!s.providerSessionId)throw new h("incomplete","OpenCode 未返回压缩后的会话标识。");return{providerSessionId:s.providerSessionId,durationMs:s.durationMs,contextUsage:s.contextUsage?{...s.contextUsage,state:"compacted",maintenanceAction:"compact"}:null,maintenance:{action:"compact",provider:"opencode",protocol:"session/prompt"}}}async sendPrompt(e){if(this.busy)throw new Error("OpenCode 会话正在执行另一项操作。");this.busy=!0;try{return await this.runPrompt(e)}finally{this.busy=!1}}async runPrompt(e){if(!f.existsSync(e.cwd)||!f.statSync(e.cwd).isDirectory())throw new Error(`Project root does not exist or is not a directory: ${e.cwd}`);const s=Date.now();this.statusCallback=e.onStatus??null,this.updateCallback=e.onUpdate??null,this.permissionCallback=e.onPermissionRequest??null,this.userInputCallback=e.onUserInputRequest??null,this.elicitationEnabled=e.elicitation??!!e.onUserInputRequest,this.elicitationComplete=e.onElicitationComplete,this.canExecuteTools=e.canExecuteTools!==!1,this.content="",this.updates.clear(),this.statusTimeline.length=0,this.latestContextUsage=null,this.contextWindow=z(e.contextWindow),this.promptProgress=!1;let t=null,o=null,n=this.providerSessionId,r;const c=P();try{this.attachAbortSignal(e.signal),this.emitStatus("initializing","OpenCode ACP 启动中","opencode acp");const d=await this.acquireRuntime(e.cwd);this.lease=d,t=d.client,o=d.started,await this.initialize(d),n=await this.establishSession(d,e),await this.applyConfig(t,n,e),this.emitStatus("thinking","思考中");const a=V(e.prompt,e.promptBlocks);try{r=await this.promptWithAbort(t,{sessionId:n,prompt:a,timeoutMs:e.timeoutMs??this.defaultTimeoutMs,signal:e.signal})}catch(u){if(e.requireSessionResume||!this.resumedExistingSession||!j(u)||this.promptProgress)throw u;this.appendStderr(`OpenCode resumed session rejected session/prompt with Invalid params; creating a fresh session and retrying once.
|
|
2
|
+
`),await this.lease?.dispose().catch(()=>{}),this.lease=null,this.providerSessionId=null,this.sessionResponse=null,this.resumedExistingSession=!1,this.emitStatus("resuming","OpenCode 会话已失效,正在新建","session/prompt invalid params");const m=await this.acquireRuntime(e.cwd);this.lease=m,t=m.client,o=m.started,await this.initialize(m),n=await this.establishSession(m,e),await this.applyConfig(t,n,e),this.emitStatus("thinking","思考中"),r=await this.promptWithAbort(t,{sessionId:n,prompt:a,timeoutMs:e.timeoutMs??this.defaultTimeoutMs,signal:e.signal})}if(A(r),!this.content.trim()){const u=await E(e.cwd,n,s,e.signal);if(u)throw u;if(!e.prompt.trim().startsWith("/"))throw new h("incomplete","OpenCode 未返回回复正文,请检查模型认证及已有执行结果。")}}catch(d){const a=_(e.signal?.aborted?new Error("cancelled"):d);throw this.emitStatus("failed",a.message),a}finally{c(),this.abortHandler&&e.signal&&(e.signal.removeEventListener("abort",this.abortHandler),this.abortHandler=null),this.releaseLease()}return this.emitStatus("completed","已完成"),{providerSessionId:n,cliType:"builtin",agentType:"opencode",providerDiagnostics:this.commands===null?{}:{opencode:{availableCommands:this.commands}},effectiveConfig:{model:O(this.sessionResponse)||g(e.model)||null,mode:e.mode||null,configOptionValues:e.configOptionValues??null,command:o.command,processPid:o.child.pid??null,provider:"opencode",protocol:"acp"},command:o.command,args:o.args,cwd:e.cwd,exitCode:o.child.exitCode,signal:o.child.signalCode,durationMs:Date.now()-s,stdout:t.rpc.stdout,stderr:t.rpc.stderr,content:this.content.trim(),updateCount:this.updates.count(),updates:this.updates.snapshot(),statusTimeline:[...this.statusTimeline],contextUsage:this.latestContextUsage,promptResponse:r}}async cancel(){this.providerSessionId&&this.lease?.client.cancel(this.providerSessionId)}async close(){this.statusCallback=null,this.updateCallback=null,this.permissionCallback=null,this.userInputCallback=null,this.canExecuteTools=!0,this.abortHandler&&(this.abortHandler=null),this.releaseLease()}elicitationEnabled=!1;elicitationComplete;async acquireRuntime(e){const{command:s,args:t}=T("opencode");return M({providerId:"opencode",elicitation:this.elicitationEnabled,onElicitationComplete:this.elicitationComplete,cwd:e,providerSessionId:this.providerSessionId,command:s,args:[...t,"acp","--cwd",e],onSessionUpdate:o=>this.handleSessionUpdate(o),onClientRequest:(o,n,r)=>this.handleClientRequest(o,n,r.id)})}async initialize(e){e.initialized||(await e.client.initialize(6e4),e.markInitialized())}async establishSession(e,s){const t=e.client,o=L(s);if(this.resumedExistingSession=!1,e.providerSessionId&&e.sessionEstablished)return this.providerSessionId=e.providerSessionId,this.sessionResponse=e.sessionResponse,this.commands=w(this.sessionResponse?.acaOpenCodeCommands),this.commands!==null&&this.handleSessionUpdate({sessionId:e.providerSessionId,update:{sessionUpdate:"available_commands_update",availableCommands:this.commands}}),this.resumedExistingSession=!0,e.providerSessionId;if(this.providerSessionId)try{this.emitStatus("resuming","OpenCode 会话恢复中","session/load");const c=await t.loadSession({sessionId:this.providerSessionId,cwd:s.cwd},6e4);this.sessionResponse=l(c);const d=v(c)||this.providerSessionId;return this.providerSessionId=d,e.setProviderSession({providerSessionId:d,sessionResponse:this.sessionResponse}),this.resumedExistingSession=!0,d}catch{if(s.requireSessionResume)throw new h("incomplete","OpenCode 原会话恢复失败,已取消压缩,请检查会话状态。");this.providerSessionId=null}if(s.requireSessionResume)throw new h("incomplete","OpenCode 原会话不可用,已取消压缩。");this.emitStatus("acp","OpenCode 会话创建中","session/new");const n=await t.newSession({cwd:s.cwd,config:o},12e4);this.sessionResponse=l(n);const r=v(n);if(!r)throw new Error("OpenCode ACP session/new did not return sessionId");return this.providerSessionId=r,e.setProviderSession({providerSessionId:r,sessionResponse:this.sessionResponse}),r}async applyConfig(e,s,t){this.sessionResponse=await R(e,s,this.sessionResponse,t,g(t.model),y(t.mode)),this.sessionResponse={...this.sessionResponse,acaOpenCodeCommands:this.commands},this.lease?.setSessionResponse(this.sessionResponse)}handleSessionUpdate(e){const s=l(e);if(this.providerSessionId&&s?.sessionId&&s.sessionId!==this.providerSessionId)return;const t=l(s?.update),o=t?.sessionUpdate==="available_commands_update"||t?.sessionUpdate==="config_options_update";if(!this.promptActive&&!o)return;t?.sessionUpdate==="available_commands_update"&&(this.commands=w(t.availableCommands),this.sessionResponse={...this.sessionResponse,acaOpenCodeCommands:this.commands},this.lease?.setSessionResponse(this.sessionResponse)),t?.sessionUpdate==="config_options_update"&&Array.isArray(t.configOptions)&&(this.sessionResponse={...this.sessionResponse,configOptions:t.configOptions}),this.promptActive&&!o&&t?.sessionUpdate!=="usage_update"&&(this.promptProgress=!0),this.updates.push({method:"session/update",params:e}),this.latestContextUsage=x(e,this.contextWindow)??this.latestContextUsage;const n=S("opencode",e);n&&(n.type==="agent_message_chunk"&&(this.content+=n.text??""),this.updateCallback?.(n));const r=F("opencode",e);r&&this.emitStatus(r.phase,r.label,r.detail,r.updateType)}async handleClientRequest(e,s,t){if(!this.canExecuteTools&&(e==="fs/read_text_file"||e==="fs/write_text_file"))throw new Error(`Chat Room role policy disabled tool execution: ${e}`);switch(e){case"fs/read_text_file":return this.readTextFile(s);case"fs/write_text_file":return this.writeTextFile(s);case"session/request_permission":return this.requestPermission(s,t);case"elicitation/create":case"session/request_user_input":return this.requestUserInput(e,s,t);default:throw new Error(`Unsupported OpenCode ACP client request: ${e}`)}}readTextFile(e){const s=b(this.cwd,p(e,"path")||p(e,"filePath"));return{content:f.readFileSync(s,"utf8")}}writeTextFile(e){const s=b(this.cwd,p(e,"path")||p(e,"filePath")),t=p(e,"content")??p(e,"text")??"";return f.mkdirSync(C.dirname(s),{recursive:!0}),f.writeFileSync(s,t,"utf8"),{}}attachAbortSignal(e){if(e){if(this.abortHandler=()=>{this.cancel()},e.aborted)throw new Error("OpenCode prompt cancelled");e.addEventListener("abort",this.abortHandler,{once:!0})}}async promptWithAbort(e,s){this.promptActive=!0,this.promptProgress=!1;const t=D({lease:this.lease,sessionId:s.sessionId,onNotice:o=>this.emitRuntimeNotice(o.label,o.detail),onRecovered:()=>this.emitRuntimeNotice("OpenCode 已恢复,继续执行")});try{return await Promise.race([t.failure,U({signal:s.signal,cancelledMessage:"OpenCode prompt cancelled",startRequest:()=>e.prompt({sessionId:s.sessionId,prompt:s.prompt},s.timeoutMs),onAbort:async()=>{e.cancel(s.sessionId),await this.lease?.dispose()}})])}catch(o){throw e.cancel(s.sessionId),await this.lease?.dispose().catch(()=>{}),o}finally{this.promptActive=!1,await t.stop()}}emitRuntimeNotice(e,s){const t=`${e}${s?`:${s}`:""}
|
|
3
3
|
|
|
4
|
-
`,o={sessionId:this.providerSessionId,update:{sessionUpdate:"agent_progress_chunk",content:{type:"text",text:t}}},n=S("opencode",o);this.updates.push({method:"session/update",params:o}),n&&this.updateCallback?.(n),e!=="OpenCode 状态监测暂不可用"&&(this.promptProgress=!0),this.emitStatus("thinking",e,s,"session.status")}async requestPermission(e,s){if(!this.permissionCallback)return{outcome:{outcome:"cancelled"}};const t=l(e)??{},o=l(t.request)??t,r=(Array.isArray(o.options)?o.options:[]).map((c,
|
|
4
|
+
`,o={sessionId:this.providerSessionId,update:{sessionUpdate:"agent_progress_chunk",content:{type:"text",text:t}}},n=S("opencode",o);this.updates.push({method:"session/update",params:o}),n&&this.updateCallback?.(n),e!=="OpenCode 状态监测暂不可用"&&(this.promptProgress=!0),this.emitStatus("thinking",e,s,"session.status")}async requestPermission(e,s){if(!this.permissionCallback)return{outcome:{outcome:"cancelled"}};const t=l(e)??{},o=l(t.request)??t,r=(Array.isArray(o.options)?o.options:[]).map((c,d)=>{const a=l(c)??{};return{optionId:typeof a.optionId=="string"?a.optionId:"",kind:String(a.kind??a.outcome??"choice"),...typeof a.name=="string"?{label:a.name}:typeof a.label=="string"?{label:a.label}:{}}}).filter(c=>c.optionId);return this.emitStatus("requestPermission","等待授权",H(o),"session/request_permission"),this.permissionCallback({requestId:String(o.requestId??s??o.id??`opencode-permission-${Date.now()}`),params:e,expiresAtMs:typeof o.expiresAtMs=="number"?o.expiresAtMs:void 0,options:r})}async requestUserInput(e,s,t){return this.emitStatus("requestPermission","等待回答",void 0,e),q("opencode",e,s,this.userInputCallback,t)}emitStatus(e,s,t,o){const n={phase:e,label:s,...t?{detail:t}:{},...o?{updateType:o}:{},atMs:Date.now()},r=this.statusTimeline[this.statusTimeline.length-1];r&&r.phase===n.phase&&r.label===n.label&&r.detail===n.detail&&r.updateType===n.updateType||(this.statusTimeline.push(n),this.statusCallback?.(n))}appendStderr(e){const s=this.lease?.client;s&&(s.rpc.stderr+=e)}releaseLease(){this.lease?.release(),this.lease=null}}function v(i){const s=l(i)?.sessionId;return typeof s=="string"&&s.trim()?s.trim():null}function l(i){return i&&typeof i=="object"&&!Array.isArray(i)?i:null}function z(i){return typeof i=="number"&&Number.isFinite(i)&&i>0?i:0}function L(i){const e=g(i.model),s=y(i.mode);return{...W(i.configOptionValues),...e?{model:e}:{},...s?{mode:s}:{}}}function W(i){const e={};for(const[s,t]of Object.entries(i??{}))["model","mode","reasoning_effort","effort","variant"].includes(s)||(e[s]=t);return e}function y(i){const e=String(i||"").trim();return e?["agent-full-access","full-access","bypassPermissions","danger-full-access"].includes(e)?"build":e==="read-only"?"plan":e:null}function p(i,e){const t=l(i)?.[e];return typeof t=="string"&&t.trim()?t:null}function b(i,e){if(!e)throw new Error("ACP file request did not include a path");const s=C.resolve(i,e),t=C.resolve(i);if(s!==t&&!s.startsWith(`${t}${C.sep}`))throw new Error(`ACP file request is outside project root: ${e}`);return s}function H(i){const e=l(i.params)??i;for(const s of["command","cwd","reason","grantRoot","method"]){const t=e[s];if(typeof t=="string"&&t.trim())return I(t)}}function ce(i){for(const e of["prompt","message","question","title"]){const s=i[e];if(typeof s=="string"&&s.trim())return I(s)}}function I(i){const e=i.replace(/\s+/g," ").trim();return e.length>120?`${e.slice(0,117)}...`:e}function B(i){return i instanceof Error?i.message:String(i)}function j(i){return/\binvalid params\b/i.test(B(i))}function V(i,e){return(e?.length?e:[{type:"text",text:i}]).map(t=>t.type==="image"?{type:"image",data:t.data,mimeType:t.mimeType}:{type:"text",text:t.text})}function G(){return process.env.ACA_OPENCODE_DEFAULT_MODEL||""}function g(i){const e=String(i||G()).trim();return e==="default"||e==="opencode-default"?"":e}function J(){const i=Number.parseInt(process.env.ACA_OPENCODE_PROMPT_TIMEOUT_MS??process.env.ACA_ACP_PROMPT_TIMEOUT_MS??"",10);return Number.isInteger(i)&&i>=3e4?i:720*60*1e3}export{le as OpenCodeProvider,N as openCodeCapabilities};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{eventFromAcpSessionUpdate as
|
|
1
|
+
import{eventFromAcpSessionUpdate as y,statusFromAcpSessionUpdate as h}from"../client/acp-events.js";function P(t,n){return y(d(t,n))}function L(t,n){return h(d(t,n))}function d(t,n){const o=I(n);if(!o)return n;const e=o.update,s=A(e);if(!s)return n;const i={...e,sessionUpdate:s};return(s==="tool_call"||s==="tool_call_update")&&U(i,t,e),s==="plan"&&w(i),(s==="agent_message_chunk"||s==="agent_progress_chunk"||s==="agent_thought_chunk")&&(i.content=C(e.content??e.text??e.delta)),{...o.params,...o.sessionId?{sessionId:o.sessionId}:{},update:i}}function w(t){const n=l(t.plan,t.taskPlan,t.task_plan),o=z(t.entries,t.items,t.steps,t.todos,t.tasks,t.checklist,n?.entries,n?.items,n?.steps,n?.todos,n?.tasks,n?.checklist);o&&!Array.isArray(t.entries)&&(t.entries=o);const e=r(t.planId,t.plan_id,t.taskPlanId,t.task_plan_id,n?.id);e&&!t.planId&&(t.planId=e);const s=r(t.markdown,t.planText,t.plan_text,n?.markdown);s&&!t.markdown&&(t.markdown=s)}function I(t){if(!t||typeof t!="object"||Array.isArray(t))return null;const n=t,o=l(n.update,n.sessionUpdate,n.session_update,n.event,n.notification)??n,e=o===n&&!S(n)?l(n.data,n.payload):o;return e?{params:n,sessionId:r(n.sessionId,n.session_id),update:e}:null}function A(t){const o=r(t.sessionUpdate,t.session_update,t.updateType,t.update_type,t.eventType,t.event,t.type)?.toLowerCase().replace(/[-./ ]+/g,"_");return o?["agent_message_chunk","message_chunk","assistant_message","assistant_text","text_delta"].includes(o)?"agent_message_chunk":["agent_progress_chunk","progress_chunk","progress"].includes(o)?"agent_progress_chunk":["agent_thought_chunk","thought_chunk","reasoning_chunk","reasoning"].includes(o)?"agent_thought_chunk":["tool_call","tool_use","toolcall","tool_use_start","tool_call_start"].includes(o)?"tool_call":["tool_call_update","tool_use_update","toolcall_update","tool_result","tool_call_result","tool_use_result"].includes(o)?"tool_call_update":["plan","plan_update","plan_updated","plan_changed","task_plan","task_plan_update","todo","todo_update","todo_updated","tasks"].includes(o)?"plan":["plan_removed","plan_deleted","todo_removed","todo_deleted"].includes(o)?"plan_removed":o:null}function U(t,n,o){const e=l(t.tool,t.toolCall,t.tool_call,t.toolUse,t.tool_use,t.data),s=r(t.toolCallId,t.tool_call_id,t.callId,t.call_id,t.itemId,t.item_id,t.id,e?.toolCallId,e?.tool_call_id,e?.id)||`${n}.${Date.now()}`,i=r(t.title,t.name,t.toolName,t.tool_name,e?.title,e?.name,e?.toolName,e?.tool_name),u=r(t.kind,t.toolKind,t.tool_kind,e?.kind,i)||"other",g=r(o.sessionUpdate,o.session_update,o.updateType,o.update_type,o.eventType,o.event,o.type)?.toLowerCase().replace(/[-./ ]+/g,"_"),k=r(t.status,t.state,t.phase,e?.status,e?.state)||(["tool_result","tool_call_result","tool_use_result"].includes(g||"")?"completed":"in_progress"),_=m(t.rawInput,t.raw_input,t.input,t.arguments,t.args,t.params,e?.rawInput,e?.raw_input,e?.input,e?.arguments,e?.args,e?.params,x(t)),a=m(t.rawOutput,t.raw_output,t.output,t.result,t.error,t.aggregatedOutput,t.aggregated_output,t.resultText,e?.rawOutput,e?.raw_output,e?.output,e?.result,e?.error);t.toolCallId=s,t.kind=u;const f=T(u,i,_);f&&(t.title=f),t.status=k,_!==void 0&&(t.rawInput=_),a!==void 0&&(t.rawOutput=a),t.content===void 0&&a!==void 0&&(t.content=p(a)),t._meta={...c(t._meta)?t._meta:{},provider_normalized:n}}function T(t,n,o){const e=l(o),s=r(e?.path,e?.filePath,e?.file_path);if(s&&(t.toLowerCase().includes("read")||t.toLowerCase().includes("view")))return`Read file ${s}`;if(s&&t.toLowerCase().includes("list"))return`List files in ${s}`;if(n)return n}function x(t){const n={};for(const o of["command","query","path","filePath","file_path","url","cwd"])t[o]!==void 0&&(n[o]=t[o]);return Object.keys(n).length?n:void 0}function p(t){return typeof t=="string"?{type:"content",content:t}:(Array.isArray(t)||c(t),t)}function C(t){return typeof t=="string"?{type:"text",text:t}:c(t)&&typeof t.text=="string"?t:p(t)}function S(t){return["sessionUpdate","session_update","updateType","update_type","eventType","event","type"].some(n=>t[n]!==void 0)}function l(...t){for(const n of t)if(c(n))return n;return null}function z(...t){return t.find(Array.isArray)}function c(t){return!!(t&&typeof t=="object"&&!Array.isArray(t))}function m(...t){return t.find(n=>n!=null)}function r(...t){for(const n of t)if(typeof n=="string"&&n.trim())return n.trim()}export{d as normalizeProviderSessionUpdate,P as providerEventFromAcpSessionUpdate,L as providerStatusFromAcpSessionUpdate};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{randomUUID as b}from"node:crypto";import{defaultAcpRuntimePool as
|
|
1
|
+
import{randomUUID as b}from"node:crypto";import{defaultAcpRuntimePool as y}from"../../client/acp-runtime-pool.js";import{managementActivity as A}from"../../../core/management/activity.js";import{beginWorkBuddyAuthChange as g,enterWorkBuddyTask as I}from"./auth-gate.js";import{clearWorkBuddyCapabilities as u}from"./config.js";import{resetWorkBuddyDiscovery as d}from"./discovery.js";import{saveWorkBuddyRegion as M,workBuddyEnvironmentCredentials as h,workBuddyRegion as E}from"./auth-environment.js";import{createWorkBuddyAuthTransport as c,safeWorkBuddyAuthUrl as v}from"./auth-transport.js";class T{timeoutMs;changed;flow;cached;queue=Promise.resolve();stopping=!1;constructor(r=5*6e4,e=async()=>{}){this.timeoutMs=r,this.changed=e}execute(r,e={}){const s=this.queue.then(async()=>{if(this.stopping)throw new Error("Worker 正在停止。");if(r==="status")return this.status();if(r==="start")return this.start(e.region);if(r==="cancel")return this.cancel(String(e.flowId||""));if(r==="logout")return this.logout();throw new Error("不支持的腾讯账号操作。")});return this.queue=s.catch(()=>{}),s}async stop(){this.stopping=!0,await this.queue,this.flow&&await this.cancel(this.flow.snapshot.flowId)}base(r){return{state:r,region:E()||"internal",regionLocked:process.env.CODEBUDDY_INTERNET_ENVIRONMENT!==void 0}}async status(){if(this.flow)return{...this.flow.snapshot};if(h())return{...this.base("environment"),message:"当前使用 Worker 环境变量凭据;网页不能修改或退出这类凭据。"};if(this.cached&&this.cached.until>Date.now())return{...this.cached.snapshot};const r=I();let e;try{e=c(),await e.initialize();const s=await e.account();return this.remember({...this.base(s?"authenticated":"signed_out"),account:s})}catch(s){return this.remember({...this.base("error"),message:p(s)})}finally{try{await e?.close()}finally{r()}}}async start(r){if(this.flow)return{...this.flow.snapshot};if(h())return this.status();if(!["internal","external","ioa"].includes(String(r)))throw new Error("请选择中国版、国际版或 iOA。");const e=r;if(process.env.CODEBUDDY_INTERNET_ENVIRONMENT!==void 0&&e!==E())throw new Error("登录版本由 Worker 环境变量固定,请使用对应版本。");const s=g(),k=A.track("workbuddy-login"),t={stopped:!1,snapshot:{...this.base("starting"),region:e,flowId:b(),expiresAtMs:Date.now()+this.timeoutMs}};this.flow=t,this.cached=void 0;const f=setTimeout(()=>{t.stopped||(t.stopped=!0,t.snapshot={...t.snapshot,state:"expired",authUrl:void 0,message:"登录已超时,请重新发起。"},t.transport?.close())},this.timeoutMs);return f.unref(),t.done=(async()=>{try{if(u(),d(),await y.retireProviderRuntimes("workbuddy"),t.stopped)return;t.transport=c(e,a=>{if(!t.stopped)try{t.snapshot={...t.snapshot,state:"waiting",authUrl:v(a,e)}}catch(w){t.stopped=!0,t.snapshot={...t.snapshot,state:"error",authUrl:void 0,expiresAtMs:void 0,message:w instanceof Error&&w.message==="auth_region_mismatch"?"官方授权链接与所选登录版本不一致,请重新发起登录,并检查 CLI 的版本和环境配置。":"官方返回的授权链接无法安全打开,请升级 CLI 或使用本机终端登录。"},t.transport?.close()}},!1,!0);const o=await t.transport.initialize();if(t.stopped)return;const n=await t.transport.account();if(t.stopped)return;const l=e==="ioa"?"iOA":e;if(!Array.isArray(o.authMethods)||!o.authMethods.some(a=>a?.id===l))throw new Error("auth_method_unsupported");const m=n||await t.transport.authenticate(l,Math.max(100,t.snapshot.expiresAtMs-Date.now()));if(t.stopped)return;if(!m)throw new Error("auth_no_account");M(e),u(),d(),t.snapshot={...t.snapshot,state:"authenticated",account:m,authUrl:void 0,expiresAtMs:void 0}}catch(o){if(!t.stopped){const n=Date.now()>=(t.snapshot.expiresAtMs??1/0);t.snapshot={...t.snapshot,state:n?"expired":"error",authUrl:void 0,message:n?"登录已超时,请重新发起。":p(o)}}}finally{clearTimeout(f);try{await t.transport?.close()}finally{t.stopped=!0,this.remember(t.snapshot),this.flow===t&&(this.flow=void 0),s(),k()}t.snapshot.state==="authenticated"&&this.changed().catch(()=>{})}})(),{...t.snapshot}}async cancel(r){const e=this.flow;return!e||e.snapshot.flowId!==r?this.status():(e.stopped=!0,e.snapshot={...e.snapshot,state:"cancelled",authUrl:void 0,message:"已停止等待授权。如已在腾讯页面完成授权,请刷新登录状态。"},await e.transport?.close(),await e.done,{...e.snapshot})}async logout(){if(h())return this.status();if(this.flow)throw new Error("请先取消当前登录,再退出账号。");const r=g();let e;try{return await y.retireProviderRuntimes("workbuddy"),e=c(void 0,void 0,!0),await e.logout(),u(),d(),this.remember(this.base("signed_out"))}catch(s){return this.remember({...this.base("error"),message:p(s)})}finally{try{await e?.close()}finally{r()}this.changed().catch(()=>{})}}remember(r){return this.cached={until:Date.now()+1e4,snapshot:r},{...r}}}function p(i){const r=i instanceof Error?i.message:"";return/ENOENT/.test(r)?"未找到 CodeBuddy CLI,请在 Worker 机器安装 @tencent-ai/codebuddy-code,或配置 ACA_WORKBUDDY_COMMAND。":/method.*not.*found|unsupported|not.*supported/i.test(r)?"当前 CodeBuddy CLI 不支持所需认证接口,请升级 CLI 后重试。":/timeout|timed out/i.test(r)?"腾讯认证服务响应超时,请检查 Worker 网络后重试。":"腾讯账号操作未完成,请检查 Worker 网络和 CodeBuddy CLI 安装后重试。"}export{T as WorkBuddyAuthManager};
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import
|
|
2
|
-
`))>=0;){const
|
|
3
|
-
`)})}}}export{
|
|
1
|
+
import{createConnectionInteractions as W}from"../../../interactions/connection-interactions.js";import{handleProviderInput as B}from"../../../interactions/provider-input.js";import{ElicitationTracker as j}from"../../../interactions/elicitation-tracker.js";import v from"node:fs";import C from"node:os";import x from"node:path";import{randomUUID as O}from"node:crypto";import{startAcpProcess as P,closeAcpProcess as A}from"../../client/acp-process.js";import{JsonLineRpcClient as N}from"../../client/json-rpc.js";import{resolveWorkBuddyLaunch as J}from"./launch.js";import{record as n}from"./config.js";import{workBuddyRegionEnv as L}from"./auth-environment.js";function E(u){const s=n(u),t=typeof s.userId=="string"?s.userId.slice(0,200):"",d=[s.userNickname,s.userName].find(c=>typeof c=="string"&&c.trim());return t||d?{id:t,name:typeof d=="string"?d.slice(0,200):t}:void 0}function Z(u,s){if(typeof u!="string"||u.length>8192)throw new Error("invalid_auth_url");const t=new URL(u),d=["tencent.com","codebuddy.ai","codebuddy.cn"];if(t.protocol!=="https:"||t.username||t.password||t.port&&t.port!=="443"||!d.some(w=>t.hostname===w||t.hostname.endsWith(`.${w}`)))throw new Error("invalid_auth_url");const c=t.hostname==="codebuddy.ai"||t.hostname.endsWith(".codebuddy.ai"),f=t.hostname==="codebuddy.cn"||t.hostname.endsWith(".codebuddy.cn")||t.hostname==="copilot.tencent.com";if(s==="internal"&&c||s==="external"&&f)throw new Error("auth_region_mismatch");return t.href}function ee(u,s,t=!1,d=!1){const c=J(!1),f=v.mkdtempSync(x.join(C.tmpdir(),"aca-workbuddy-auth-")),w=t?c.args.flatMap(e=>e==="--acp"?["--print","--input-format","stream-json","--output-format","stream-json","--verbose"]:[e]):c.args;let o;try{o=P(c.command,[...w,"--no-session-persistence"],f,{...c.env,...L(u),BROWSER:x.join(f,"no-local-browser")})}catch(e){throw v.rmSync(f,{recursive:!0,force:!0}),e}const l=!t&&d?W("workbuddy","workbuddy-auth"):void 0,g=new j;let R;const S=()=>R??=Promise.all([A(o.child),l?.close()]).then(()=>{}).finally(()=>v.rmSync(f,{recursive:!0,force:!0}));o.child.once("close",()=>{l?.close()});const h=t?null:new N(o.child,{includeJsonRpc:!0,onNotification(e,i){e==="elicitation/complete"&&g.complete(i),e==="_codebuddy.ai/authUrl"&&s?.(String(n(i).authUrl||""))},async onRequest(e,i,p){if(e!=="elicitation/create"||!l?.supported)throw Object.assign(new Error("Unsupported authentication client request"),{rpcCode:-32602});const a=n(i);if(a.sessionId!=null||!h.hasPendingRequest(a.requestId))throw Object.assign(new Error("Invalid authentication elicitation scope"),{rpcCode:-32602});const b=g.begin(i,p.id,"workbuddy",r=>{l.complete(r)});try{const r=await B("workbuddy",e,i,y=>l.input(y),p.id);return g.settle(b,r),r}catch(r){throw g.settle(b,null),r}}});return{close:S,async initialize(){return await l?.initialize(),n(await h.request("initialize",{protocolVersion:1,clientCapabilities:{fs:{readTextFile:!1,writeTextFile:!1},terminal:!1,...l?.supported?{elicitation:{form:{},url:{}}}:{}},clientInfo:{name:"aca-auth",version:"1"}},12e3))},async account(){return E(n(await h.request("_codebuddy.ai/getUserInfo",{},12e3)).userInfo)},async billingIdentity(){const e=n(n(await h.request("_codebuddy.ai/getUserInfo",{},12e3)).userInfo);return{account:E(e),token:typeof e.accessToken=="string"?e.accessToken:typeof e.token=="string"?e.token:"",enterpriseId:typeof e.enterpriseId=="string"?e.enterpriseId:""}},async authenticate(e,i){const p=n(await h.request("authenticate",{methodId:e},i));return E(n(p._meta)["codebuddy.ai/userinfo"])},async logout(){const e=O();await new Promise((i,p)=>{let a="";const b=setTimeout(()=>r(new Error("logout_timeout")),15e3),r=m=>{clearTimeout(b),o.child.stdout.off("data",U),o.child.off("error",y),o.child.off("close",q),m?p(m):i()},y=m=>r(m),q=()=>r(new Error("logout_process_exited")),U=m=>{if(a+=m.toString(),Buffer.byteLength(a)>1024*1024)return r(new Error("invalid_auth_response"));let _;for(;(_=a.indexOf(`
|
|
2
|
+
`))>=0;){const T=a.slice(0,_);a=a.slice(_+1);let k;try{k=n(JSON.parse(T))}catch{continue}const I=n(k.response);if(!(k.type!=="control_response"||I.request_id!==e))return r(I.subtype==="success"&&n(I.response).success===!0?void 0:new Error("logout_failed"))}};o.child.stdout.on("data",U),o.child.stderr.resume(),o.child.on("error",y),o.child.on("close",q),o.child.stdin.on("error",y),o.child.stdin.write(`${JSON.stringify({type:"control_request",request_id:e,request:{subtype:"logout"}})}
|
|
3
|
+
`)})}}}export{ee as createWorkBuddyAuthTransport,E as publicWorkBuddyAccount,Z as safeWorkBuddyAuthUrl};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{handleProviderInput as y}from"../../../interactions/provider-input.js";import{INTERACTION_TIMEOUT_MS as f,validateInteractionAnswers as w}from"../../../interactions/types.js";import{record as d}from"./config.js";async function g(n,s,c){const o=d(n),a=d(o.schema);if(o.inputType!=="question"||!Array.isArray(a.questions))throw new Error("Unsupported WorkBuddy question schema");const r=a.questions.map(t=>{const e=d(t),m=Array.isArray(e.options)?e.options.map(q=>{const
|
|
1
|
+
import{handleProviderInput as y}from"../../../interactions/provider-input.js";import{INTERACTION_TIMEOUT_MS as f,validateInteractionAnswers as w}from"../../../interactions/types.js";import{record as d}from"./config.js";async function g(n,s,c){const o=d(n),a=d(o.schema);if(o.inputType!=="question"||!Array.isArray(a.questions))throw new Error("Unsupported WorkBuddy question schema");const r=a.questions.map(t=>{const e=d(t),m=Array.isArray(e.options)?e.options.map(q=>{const p=d(q),l=String(p.value??p.label??"");return{value:l,label:String(p.label??l),description:typeof p.description=="string"?p.description:void 0}}):void 0;return{id:String(e.id??""),label:String(e.question??""),description:typeof e.header=="string"?e.header:void 0,type:e.multiSelect===!0?"string[]":"string",required:!0,allowOther:!0,options:m}});if(!r.length||r.length>100||r.some(t=>!t.id||!t.label||["__proto__","constructor","prototype"].includes(t.id))||new Set(r.map(t=>t.id)).size!==r.length)throw new Error("Unsupported WorkBuddy question fields");const u={version:1,kind:"questions",provider:"workbuddy",method:"_codebuddy.ai/question",requestId:String(s),runtimeId:"",expiresAtMs:Date.now()+f,prompt:String(o.prompt||"请回答以下问题"),fields:r},i=await c.onUserInputRequest?.({requestId:String(s),prompt:u.prompt,params:n,interaction:u});if(!i||i.outcome.outcome!=="submitted")return{outcome:"cancelled"};if(Object.keys(w(u,i.outcome.answers)).length)throw new Error("Invalid WorkBuddy question answers");return{outcome:"submitted",answers:Object.fromEntries(Object.entries(i.outcome.answers).map(([t,e])=>[t,e.value]))}}async function S(n,s,c,o){if(n==="_codebuddy.ai/question")return g(s,c,o);if(n==="elicitation/create")return y("workbuddy",n,s,o.onUserInputRequest??null,c);if(n!=="session/request_permission")throw new Error(`Unsupported WorkBuddy ACP client request: ${n}`);const a=d(s),r={outcome:{outcome:"cancelled"}};if(o.canExecuteTools===!1||!o.onPermissionRequest)return r;const u=(Array.isArray(a.options)?a.options:[]).map(d).map(e=>({optionId:String(e.optionId||""),kind:String(e.kind||"choice"),label:String(e.name||e.label||e.optionId||"")})).filter(e=>e.optionId),i=await o.onPermissionRequest({requestId:String(c),params:s,options:u}),t=i.outcome;return t.outcome==="selected"&&!u.some(e=>e.optionId===t.optionId)?r:i}export{g as handleWorkBuddyQuestion,S as handleWorkBuddyRequest};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{workBuddyCredits as B}from"../../../core/workbuddy-credits/worker.js";import h from"node:fs";import{enterWorkBuddySession as A}from"./session-lock.js";import{forkWorkBuddySession as E}from"./fork.js";import{runAbortableRequest as W}from"../../client/abortable-request.js";import{defaultAcpRuntimePool as m}from"../../client/acp-runtime-pool.js";import{BoundedItemBuffer as f}from"../../client/bounded-buffer.js";import{contextUsageFromAcpSessionUpdate as M}from"../../client/context-usage.js";import{providerEventFromAcpSessionUpdate as w,providerStatusFromAcpSessionUpdate as R}from"../standard-acp-events.js";import{resolveWorkBuddyLaunch as U,workBuddyTimeout as x}from"./launch.js";import{applyWorkBuddyConfig as q,record as n,rememberWorkBuddyCapabilities as y}from"./config.js";import{handleWorkBuddyRequest as D}from"./interactions.js";import{WorkBuddyEventRouter as g}from"./events.js";import{WorkBuddySupplementIdentity as v,WorkBuddySupplements as _,workBuddyPromptContent as O}from"./supplements.js";import{assertWorkBuddyPromptCompleted as P,workBuddyError as S}from"./errors.js";import{WorkBuddyTurnCredits as I,workBuddyContextUsage as T}from"./usage.js";import{workBuddyCommands as k}from"./commands.js";const c=new WeakMap,z={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 os{id="workbuddy";name="腾讯 Code Buddy";capabilities=z;async createSession(s){return new F(s.cwd,s.providerSessionId??null,s.timeoutMs)}async forkSession(s){return E(s)}}class F{cwd;providerSessionId;timeoutMs;lease=null;input=null;acceptingOutput=!1;busy=!1;content="";usage=null;response={};commands=new Set;commandCatalog=null;credits=new I;technicalError=null;router=new g;supplements=null;supplementIdentity=new v;updates=new f(16);statuses=new f(64);firstEventAtMs=null;lastEventAtMs=null;constructor(s,t,o=x()){this.cwd=s,this.providerSessionId=t,this.timeoutMs=o}async sendPrompt(s){return this.run(s,!1)}async compactContext(s){if(!this.providerSessionId)throw new Error("WorkBuddy 当前没有可压缩的会话。");const t=await this.run({...s,cwd:s.cwd,prompt:"/compact",requireSessionResume:!0},!0);return{providerSessionId:t.providerSessionId,contextUsage:t.contextUsage??null,maintenance:{action:"compact",provider:"workbuddy",protocol:"session/prompt",stopReason:n(t.promptResponse).stopReason},durationMs:t.durationMs}}async run(s,t){if(this.busy)throw new Error("WorkBuddy 会话正在执行另一轮任务。");if(h.realpathSync(s.cwd)!==h.realpathSync(this.cwd))throw new Error("WorkBuddy 会话不能切换工作目录。");if(s.requireSessionResume&&!this.providerSessionId)throw new Error("WorkBuddy 历史会话缺少 Session ID,禁止新建会话。");if(/^\s*\/(fork|branch|fork-bg)(?:\s|$)/i.test(s.prompt))throw new Error("请使用会话菜单中的“从当前会话分叉”,以便 ACA 正确保存新会话。");const o=A(this.providerSessionId);this.busy=!0,this.input=s,this.content="",this.usage=null,this.response={},this.commands.clear(),this.commandCatalog=null,this.credits=new I,this.technicalError=null,this.router=new g,this.supplementIdentity=new v,this.updates.clear(),this.statuses.clear(),this.firstEventAtMs=this.lastEventAtMs=null;const i=Date.now();let a;try{return await W({signal:s.signal,cancelledMessage:"WorkBuddy 任务已取消",onAbort:()=>this.cancelAndDispose(),startRequest:async()=>{this.status("initializing","WorkBuddy ACP 启动中");const l=U(s.canExecuteTools!==!1);if(this.providerSessionId&&await m.retireSessionRuntimes({providerId:"workbuddy",cwd:this.cwd,providerSessionId:this.providerSessionId,keepRuntimeIdentity:l.runtimeIdentity}),s.signal?.aborted)throw new Error("WorkBuddy 任务已取消");const e=await m.acquire({providerId:"workbuddy",cwd:this.cwd,providerSessionId:this.providerSessionId,...l,onSessionUpdate:r=>this.handleUpdate(r),onClientRequest:(r,u,C)=>this.handleRequest(r,u,C.id)});if(this.lease=e,s.signal?.aborted)throw await e.dispose(),new Error("WorkBuddy 任务已取消");await this.initialize(e),await this.establishSession(e),o.bind(this.providerSessionId);const d=n(n(c.get(e.client)?.agentCapabilities).promptCapabilities);if(s.promptBlocks?.some(r=>r.type==="image")&&d.image!==!0)throw new Error("当前 CodeBuddy ACP 版本未声明图片输入支持。");if(t||(this.response=await q(e.client,this.providerSessionId,this.response,s)),e.setSessionResponse(this.response),t&&!this.commands.has("compact"))throw new Error("当前 WorkBuddy 会话未提供 /compact 命令,无法压缩上下文。");this.acceptingOutput=!0,this.publishCommands(),this.status("thinking",t?"正在压缩上下文":"思考中"),a=B.beginUsage();const p=await e.client.prompt({sessionId:this.providerSessionId,prompt:O(s.prompt,s.promptBlocks)},s.timeoutMs??this.timeoutMs);if(s.signal?.aborted)throw new Error("WorkBuddy 任务已取消");if(this.credits.identifyResponse(p),this.technicalError)throw S(this.technicalError);return P(p),t&&this.usage&&(this.usage={...this.usage,state:"compacted",maintenanceAction:"compact"}),this.status("completed",t?"上下文压缩完成":"已完成"),{providerSessionId:this.providerSessionId,cliType:"builtin",agentType:"workbuddy",effectiveConfig:{model:s.model||null,mode:s.mode||"default",configOptions:this.response.configOptions,provider:"workbuddy",protocol:"acp",command:e.started.command,processPid:e.started.child.pid},command:e.started.command,args:e.started.args,cwd:this.cwd,exitCode:e.started.child.exitCode,signal:e.started.child.signalCode,durationMs:Date.now()-i,stdout:e.client.rpc.stdout,stderr:e.client.rpc.stderr,content:this.content.trim(),updateCount:this.updates.count(),updates:this.updates.snapshot(),statusTimeline:this.statuses.snapshot(),contextUsage:this.usage,promptResponse:p,providerDiagnostics:this.diagnostics(),performance:{startedAtMs:i,firstEventAtMs:this.firstEventAtMs,lastEventAtMs:this.lastEventAtMs,eventCount:this.updates.count(),firstEventLatencyMs:this.firstEventAtMs===null?null:this.firstEventAtMs-i,silentForMs:Date.now()-(this.lastEventAtMs??i),durationMs:Date.now()-i}}}})}catch(l){await this.lease?.dispose();const e=S(l);throw this.status(e.failure.category==="cancelled"?"cancelled":"failed",e.message),Object.assign(e,{providerDiagnostics:this.diagnostics(),statusTimeline:this.statuses.snapshot()})}finally{a?.(),this.acceptingOutput=!1,s.onSupplementReady?.(null);try{await this.supplements?.close()}finally{o.release()}this.supplements=null,this.lease?.release(),this.lease=null,this.input=null,this.busy=!1}}async initialize(s){if(s.initialized)return;const t=n(await s.client.rpc.request("initialize",{protocolVersion:1,clientInfo:{name:"aca",version:"0.1.0"},clientCapabilities:{fs:{readTextFile:!1,writeTextFile:!1},terminal:!1,elicitation:{form:{}},_meta:{"codebuddy.ai":{question:!0}}}},6e4));if(t.protocolVersion!==1)throw new Error("WorkBuddy ACP 协议版本不兼容。");c.set(s.client,t),s.markInitialized()}async establishSession(s){if(s.sessionEstablished&&s.providerSessionId){this.providerSessionId=s.providerSessionId,this.response=s.sessionResponse??{};const t=this.response.acaAvailableCommands;Array.isArray(t)&&(this.commandCatalog=k(t),this.commands=new Set(this.commandCatalog.map(o=>o.name)));return}if(this.providerSessionId){if(n(c.get(s.client)?.agentCapabilities).loadSession!==!0)throw new Error("当前 WorkBuddy 版本不支持恢复会话,禁止静默新建。");if(this.status("resuming","WorkBuddy 会话恢复中"),this.response=n(await s.client.loadSession({sessionId:this.providerSessionId,cwd:this.cwd},12e4)),this.response.sessionId&&this.response.sessionId!==this.providerSessionId)throw new Error("WorkBuddy 恢复返回了不同的会话 ID。")}else{if(this.status("acp","WorkBuddy 会话创建中"),this.response=n(await s.client.newSession({cwd:this.cwd},12e4)),typeof this.response.sessionId!="string"||!this.response.sessionId.trim())throw new Error("WorkBuddy session/new 未返回会话 ID。");this.providerSessionId=this.response.sessionId}this.commandCatalog&&(this.response.acaAvailableCommands=this.commandCatalog),y(this.response),s.setProviderSession({providerSessionId:this.providerSessionId,sessionResponse:this.response})}handleUpdate(s){const t=n(s),o=n(t.update);if(this.providerSessionId&&t.sessionId!==this.providerSessionId)return;const i=this.router.route(s),a=i.child?null:this.supplementIdentity.observe(s);if(!i.child&&o.sessionUpdate==="available_commands_update"&&Array.isArray(o.availableCommands)&&(this.commandCatalog=k(o.availableCommands),this.commands=new Set(this.commandCatalog.map(p=>p.name)),this.response.acaAvailableCommands=this.commandCatalog,this.lease?.setSessionResponse(this.response)),!i.child&&o.sessionUpdate==="config_option_update"&&Array.isArray(o.configOptions)&&(this.response={...this.response,configOptions:o.configOptions},this.lease?.setSessionResponse(this.response),y(this.response)),!this.acceptingOutput||!this.input)return;if(this.updates.push({method:"session/update",params:i.params}),this.firstEventAtMs??=Date.now(),this.lastEventAtMs=Date.now(),!i.child){this.credits.observe(s);const p=M(s,this.input.contextWindow);this.usage=T(s,p)??this.usage;const r=n(o._meta)["codebuddy.ai/error"];r&&(this.technicalError=r);const u=a;!this.supplements&&typeof u=="string"&&u&&this.lease&&this.providerSessionId&&(this.supplements=new _(this.lease.client,this.providerSessionId,u),this.input.onSupplementReady?.(this.supplements.channel))}const l=!i.child&&o.sessionUpdate==="available_commands_update"?{...i.params,update:{...o,availableCommands:this.commandCatalog??[]}}:i.params,e=w("workbuddy",l);e&&(!i.child&&e.type==="agent_message_chunk"&&(this.content+=e.text??""),this.input.onUpdate?.(e));const d=R("workbuddy",i.params);d&&this.status(d.phase,d.label,d.detail,d.updateType)}diagnostics(){return{workbuddy:{creditUsage:this.credits.snapshot(),...this.commandCatalog!==null?{availableCommands:this.commandCatalog}:{}}}}publishCommands(){if(this.commandCatalog===null)return;const s=w("workbuddy",{sessionId:this.providerSessionId,update:{sessionUpdate:"available_commands_update",availableCommands:this.commandCatalog}});s&&this.input?.onUpdate?.(s)}async handleRequest(s,t,o){if(!this.input||!this.acceptingOutput)throw new Error("WorkBuddy 当前没有可交互的执行轮次。");const i=n(t).sessionId;if((s!=="elicitation/create"||i!==void 0)&&i!==this.providerSessionId)throw new Error("WorkBuddy 交互请求会话不匹配。");this.status("requestPermission",s==="session/request_permission"?"等待授权":"等待回答",void 0,s);try{return await D(s,t,o,this.input)}finally{this.acceptingOutput&&this.status("thinking","处理中")}}status(s,t,o,i){const a={phase:s,label:t,detail:o,updateType:i,atMs:Date.now()};this.statuses.push(a),this.input?.onStatus?.(a)}async cancel(){await this.cancelAndDispose()}async cancelAndDispose(){this.acceptingOutput=!1,this.input?.onSupplementReady?.(null),this.providerSessionId&&this.lease?.client.cancel(this.providerSessionId),await this.lease?.dispose()}async close(){this.busy?await this.cancelAndDispose():this.lease?.release()}}export{os as WorkBuddyProvider,z as workBuddyCapabilities};
|
|
1
|
+
import{workBuddyCredits as B}from"../../../core/workbuddy-credits/worker.js";import h from"node:fs";import{enterWorkBuddySession as E}from"./session-lock.js";import{forkWorkBuddySession as A}from"./fork.js";import{runAbortableRequest as R}from"../../client/abortable-request.js";import{defaultAcpRuntimePool as m}from"../../client/acp-runtime-pool.js";import{BoundedItemBuffer as f}from"../../client/bounded-buffer.js";import{contextUsageFromAcpSessionUpdate as W}from"../../client/context-usage.js";import{providerEventFromAcpSessionUpdate as w,providerStatusFromAcpSessionUpdate as M}from"../standard-acp-events.js";import{resolveWorkBuddyLaunch as U,workBuddyTimeout as q}from"./launch.js";import{applyWorkBuddyConfig as x,record as n,rememberWorkBuddyCapabilities as y}from"./config.js";import{handleWorkBuddyRequest as D}from"./interactions.js";import{WorkBuddyEventRouter as g}from"./events.js";import{WorkBuddySupplementIdentity as v,WorkBuddySupplements as _,workBuddyPromptContent as O}from"./supplements.js";import{assertWorkBuddyPromptCompleted as P,workBuddyError as S}from"./errors.js";import{WorkBuddyTurnCredits as I,workBuddyContextUsage as z}from"./usage.js";import{workBuddyCommands as k}from"./commands.js";const c=new WeakMap,T={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 os{id="workbuddy";name="腾讯 Code Buddy";capabilities=T;async createSession(s){return new F(s.cwd,s.providerSessionId??null,s.timeoutMs)}async forkSession(s){return A(s)}}class F{cwd;providerSessionId;timeoutMs;lease=null;input=null;acceptingOutput=!1;busy=!1;content="";usage=null;response={};commands=new Set;commandCatalog=null;credits=new I;technicalError=null;router=new g;supplements=null;supplementIdentity=new v;updates=new f(16);statuses=new f(64);firstEventAtMs=null;lastEventAtMs=null;constructor(s,e,t=q()){this.cwd=s,this.providerSessionId=e,this.timeoutMs=t}async sendPrompt(s){return this.run(s,!1)}async compactContext(s){if(!this.providerSessionId)throw new Error("WorkBuddy 当前没有可压缩的会话。");const e=await this.run({...s,cwd:s.cwd,prompt:"/compact",requireSessionResume:!0},!0);return{providerSessionId:e.providerSessionId,contextUsage:e.contextUsage??null,maintenance:{action:"compact",provider:"workbuddy",protocol:"session/prompt",stopReason:n(e.promptResponse).stopReason},durationMs:e.durationMs}}async run(s,e){if(this.busy)throw new Error("WorkBuddy 会话正在执行另一轮任务。");if(h.realpathSync(s.cwd)!==h.realpathSync(this.cwd))throw new Error("WorkBuddy 会话不能切换工作目录。");if(s.requireSessionResume&&!this.providerSessionId)throw new Error("WorkBuddy 历史会话缺少 Session ID,禁止新建会话。");if(/^\s*\/(fork|branch|fork-bg)(?:\s|$)/i.test(s.prompt))throw new Error("请使用会话菜单中的“从当前会话分叉”,以便 ACA 正确保存新会话。");const t=E(this.providerSessionId);this.busy=!0,this.input=s,this.content="",this.usage=null,this.response={},this.commands.clear(),this.commandCatalog=null,this.credits=new I,this.technicalError=null,this.router=new g,this.supplementIdentity=new v,this.updates.clear(),this.statuses.clear(),this.firstEventAtMs=this.lastEventAtMs=null;const o=Date.now();let a;try{return await R({signal:s.signal,cancelledMessage:"WorkBuddy 任务已取消",onAbort:()=>this.cancelAndDispose(),startRequest:async()=>{this.status("initializing","WorkBuddy ACP 启动中");const p=U(s.canExecuteTools!==!1);if(this.providerSessionId&&await m.retireSessionRuntimes({providerId:"workbuddy",cwd:this.cwd,providerSessionId:this.providerSessionId,keepRuntimeIdentity:p.runtimeIdentity}),s.signal?.aborted)throw new Error("WorkBuddy 任务已取消");const i=await m.acquire({providerId:"workbuddy",cwd:this.cwd,elicitation:s.elicitation??!!s.onUserInputRequest,onElicitationComplete:s.onElicitationComplete,providerSessionId:this.providerSessionId,...p,normalizeClientRequest:(r,u)=>this.normalizeLegacyRequest(r,u),onSessionUpdate:r=>this.handleUpdate(r),onClientRequest:(r,u,C)=>this.handleRequest(r,u,C.id)});if(this.lease=i,s.signal?.aborted)throw await i.dispose(),new Error("WorkBuddy 任务已取消");await this.initialize(i),await this.establishSession(i),t.bind(this.providerSessionId);const d=n(n(c.get(i.client)?.agentCapabilities).promptCapabilities);if(s.promptBlocks?.some(r=>r.type==="image")&&d.image!==!0)throw new Error("当前 CodeBuddy ACP 版本未声明图片输入支持。");if(e||(this.response=await x(i.client,this.providerSessionId,this.response,s)),i.setSessionResponse(this.response),e&&!this.commands.has("compact"))throw new Error("当前 WorkBuddy 会话未提供 /compact 命令,无法压缩上下文。");this.acceptingOutput=!0,this.publishCommands(),this.status("thinking",e?"正在压缩上下文":"思考中"),a=B.beginUsage();const l=await i.client.prompt({sessionId:this.providerSessionId,prompt:O(s.prompt,s.promptBlocks)},s.timeoutMs??this.timeoutMs);if(s.signal?.aborted)throw new Error("WorkBuddy 任务已取消");if(this.credits.identifyResponse(l),this.technicalError)throw S(this.technicalError);return P(l),e&&this.usage&&(this.usage={...this.usage,state:"compacted",maintenanceAction:"compact"}),this.status("completed",e?"上下文压缩完成":"已完成"),{providerSessionId:this.providerSessionId,cliType:"builtin",agentType:"workbuddy",effectiveConfig:{model:s.model||null,mode:s.mode||"default",configOptions:this.response.configOptions,provider:"workbuddy",protocol:"acp",command:i.started.command,processPid:i.started.child.pid},command:i.started.command,args:i.started.args,cwd:this.cwd,exitCode:i.started.child.exitCode,signal:i.started.child.signalCode,durationMs:Date.now()-o,stdout:i.client.rpc.stdout,stderr:i.client.rpc.stderr,content:this.content.trim(),updateCount:this.updates.count(),updates:this.updates.snapshot(),statusTimeline:this.statuses.snapshot(),contextUsage:this.usage,promptResponse:l,providerDiagnostics:this.diagnostics(),performance:{startedAtMs:o,firstEventAtMs:this.firstEventAtMs,lastEventAtMs:this.lastEventAtMs,eventCount:this.updates.count(),firstEventLatencyMs:this.firstEventAtMs===null?null:this.firstEventAtMs-o,silentForMs:Date.now()-(this.lastEventAtMs??o),durationMs:Date.now()-o}}}})}catch(p){await this.lease?.dispose();const i=S(p);throw this.status(i.failure.category==="cancelled"?"cancelled":"failed",i.message),Object.assign(i,{providerDiagnostics:this.diagnostics(),statusTimeline:this.statuses.snapshot()})}finally{a?.(),this.acceptingOutput=!1,s.onSupplementReady?.(null);try{await this.supplements?.close()}finally{t.release()}this.supplements=null,this.lease?.release(),this.lease=null,this.input=null,this.busy=!1}}async initialize(s){if(s.initialized)return;const e=n(await s.client.rpc.request("initialize",{protocolVersion:1,clientInfo:{name:"aca",version:"0.1.0"},clientCapabilities:{fs:{readTextFile:!1,writeTextFile:!1},terminal:!1,...this.input?.elicitation??!!this.input?.onUserInputRequest?{elicitation:{form:{},url:{}}}:{},_meta:{"codebuddy.ai":{question:!0}}}},6e4));if(e.protocolVersion!==1)throw new Error("WorkBuddy ACP 协议版本不兼容。");c.set(s.client,e),s.markInitialized()}async establishSession(s){if(s.sessionEstablished&&s.providerSessionId){this.providerSessionId=s.providerSessionId,this.response=s.sessionResponse??{};const e=this.response.acaAvailableCommands;Array.isArray(e)&&(this.commandCatalog=k(e),this.commands=new Set(this.commandCatalog.map(t=>t.name)));return}if(this.providerSessionId){if(n(c.get(s.client)?.agentCapabilities).loadSession!==!0)throw new Error("当前 WorkBuddy 版本不支持恢复会话,禁止静默新建。");if(this.status("resuming","WorkBuddy 会话恢复中"),this.response=n(await s.client.loadSession({sessionId:this.providerSessionId,cwd:this.cwd},12e4)),this.response.sessionId&&this.response.sessionId!==this.providerSessionId)throw new Error("WorkBuddy 恢复返回了不同的会话 ID。")}else{if(this.status("acp","WorkBuddy 会话创建中"),this.response=n(await s.client.newSession({cwd:this.cwd},12e4)),typeof this.response.sessionId!="string"||!this.response.sessionId.trim())throw new Error("WorkBuddy session/new 未返回会话 ID。");this.providerSessionId=this.response.sessionId}this.commandCatalog&&(this.response.acaAvailableCommands=this.commandCatalog),y(this.response),s.setProviderSession({providerSessionId:this.providerSessionId,sessionResponse:this.response})}handleUpdate(s){const e=n(s),t=n(e.update);if(this.providerSessionId&&e.sessionId!==this.providerSessionId)return;const o=this.router.route(s),a=o.child?null:this.supplementIdentity.observe(s);if(!o.child&&t.sessionUpdate==="available_commands_update"&&Array.isArray(t.availableCommands)&&(this.commandCatalog=k(t.availableCommands),this.commands=new Set(this.commandCatalog.map(l=>l.name)),this.response.acaAvailableCommands=this.commandCatalog,this.lease?.setSessionResponse(this.response)),!o.child&&t.sessionUpdate==="config_option_update"&&Array.isArray(t.configOptions)&&(this.response={...this.response,configOptions:t.configOptions},this.lease?.setSessionResponse(this.response),y(this.response)),!this.acceptingOutput||!this.input)return;if(this.updates.push({method:"session/update",params:o.params}),this.firstEventAtMs??=Date.now(),this.lastEventAtMs=Date.now(),!o.child){this.credits.observe(s);const l=W(s,this.input.contextWindow);this.usage=z(s,l)??this.usage;const r=n(t._meta)["codebuddy.ai/error"];r&&(this.technicalError=r);const u=a;!this.supplements&&typeof u=="string"&&u&&this.lease&&this.providerSessionId&&(this.supplements=new _(this.lease.client,this.providerSessionId,u),this.input.onSupplementReady?.(this.supplements.channel))}const p=!o.child&&t.sessionUpdate==="available_commands_update"?{...o.params,update:{...t,availableCommands:this.commandCatalog??[]}}:o.params,i=w("workbuddy",p);i&&(!o.child&&i.type==="agent_message_chunk"&&(this.content+=i.text??""),this.input.onUpdate?.(i));const d=M("workbuddy",o.params);d&&this.status(d.phase,d.label,d.detail,d.updateType)}diagnostics(){return{workbuddy:{creditUsage:this.credits.snapshot(),...this.commandCatalog!==null?{availableCommands:this.commandCatalog}:{}}}}publishCommands(){if(this.commandCatalog===null)return;const s=w("workbuddy",{sessionId:this.providerSessionId,update:{sessionUpdate:"available_commands_update",availableCommands:this.commandCatalog}});s&&this.input?.onUpdate?.(s)}normalizeLegacyRequest(s,e){const t=n(e);return s==="elicitation/create"&&t.mode===void 0&&t.requestedSchema&&this.input&&this.providerSessionId?{...t,mode:"form",...t.sessionId==null&&t.requestId==null?{sessionId:this.providerSessionId}:{}}:e}async handleRequest(s,e,t){if(!this.input)throw new Error("WorkBuddy 当前没有可交互的执行轮次。");const o=n(e).sessionId;if((s!=="elicitation/create"||o!==void 0)&&o!==this.providerSessionId)throw new Error("WorkBuddy 交互请求会话不匹配。");this.status("requestPermission",s==="session/request_permission"?"等待授权":"等待回答",void 0,s);try{return await D(s,e,t,this.input)}finally{this.acceptingOutput&&this.status("thinking","处理中")}}status(s,e,t,o){const a={phase:s,label:e,detail:t,updateType:o,atMs:Date.now()};this.statuses.push(a),this.input?.onStatus?.(a)}async cancel(){await this.cancelAndDispose()}async cancelAndDispose(){this.acceptingOutput=!1,this.input?.onSupplementReady?.(null),this.providerSessionId&&this.lease?.client.cancel(this.providerSessionId),await this.lease?.dispose()}async close(){this.busy?await this.cancelAndDispose():this.lease?.release()}}export{os as WorkBuddyProvider,T as workBuddyCapabilities};
|
package/dist/core/job-worker.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{createJobInteractions as Y}from"../interactions/job-interactions.js";import{createJobSupplementReceiver as Z}from"./job-supplements.js";import{buildPromptWithAttachments as ee,buildPromptBlocks as te,materializeJobAttachments as ne,summarizeMaterializedAttachments as L}from"./job-attachments.js";import{managementActivity as g}from"./management/activity.js";import{managedClaim as oe}from"./management/claims.js";import{startJobArtifactMonitor as ie,mergeArtifactFiles as D}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 A}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{resolveJobExecutionConfig as fe}from"./job-execution-config.js";import{createAcpEventBatcher as he}from"./acp-event-batcher.js";import{createToolLifecycleTrace as we}from"../acp/tool-lifecycle-trace.js";import{shouldBufferAcpEvent as Ie}from"./acp-event-upload-filter.js";import{userFacingAcpError as Se}from"../acp/error-display.js";import{ensureCodexProviderRuntimeRevision as be}from"./codex-provider-runtime-config.js";import{acpSessionInvalidationFromError as ye}from"../acp/session-errors.js";const ve=1500,q=5e3,K=900,Ae=1e4,_e=0,Te=12e3;function Ce(e){return ue(e)}function ge(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 mt(e,t={}){let n=!1,s=null,i=null,l=0,w=0;const I=new Set,a=new Map,y=new Map,_=async()=>{if(n||g.paused||g.stopping||Date.now()<w)return;const m=ke();if(!X(I.size,m))return;const S=g.enter("poll");if(S)try{const u=t.loadConfig?.()??le();if(!u.token)return;for(;!n&&!g.paused&&!g.stopping&&X(I.size,m);){const v=t.claimNextJob?await t.claimNextJob(u,[...a.keys()]):await oe("job",()=>A("POST","/api/client/jobs/claim",{machineId:u.machineId,workspaceId:u.defaultWorkspaceId,activeSessionIds:[...a.keys()]}),{workspaceId:u.defaultWorkspaceId,activeSessionIds:[...a.keys()]});if(l=0,w=0,!v.item)break;const p=v.item;if(n){await(t.cancelClaimedJob?t.cancelClaimedJob(p,u):N(p.job_id,u.machineId,"cancelled")).catch(()=>{});break}const b=p.session_id,f=a.get(b),$=g.track("job");I.add(p.job_id);const k=new AbortController;let j;j=(async()=>{f&&(e.warn(`claimed job=${p.job_id} for already active session=${b}; waiting for the previous run to release its ACP runtime`),await Re(f,p,u,k)),!k.signal.aborted&&await(t.runJob??je)(p,u,e,k)})().catch(R=>{g.uncertain.add("job-finalize"),e.warn(`job ${p.job_id} failed outside handler: ${R instanceof Error?R.message:String(R)}`)}).finally(()=>{$(),I.delete(p.job_id),a.get(b)===j&&a.delete(b),y.delete(p.job_id)}),a.set(b,j),y.set(p.job_id,{controller:k,promise:j})}}catch(u){if(!n){l+=1;const v=Le(l);w=Date.now()+v,e.warn(`job worker failed, retry in ${v}ms: ${u instanceof Error?u.message:String(u)}`)}}finally{S()}},E=()=>{if(n||s)return;const m=_();s=m,m.finally(()=>{s===m&&(s=null)})};E();const c=setInterval(()=>{E()},t.claimIntervalMs??ve);return{timer:c,stop:()=>i||(i=(async()=>{if(n)return;n=!0,clearInterval(c);for(const S of y.values())S.controller.abort();await s?.catch(()=>{});for(const S of y.values())S.controller.abort();const m=[...y.values()].map(S=>S.promise);m.length!==0&&await Promise.race([Promise.allSettled(m).then(()=>{}),Be(Te)])})(),i)}}async function Re(e,t,n,s){let i=!1;const w=setInterval(()=>{i||s.signal.aborted||(i=!0,Promise.allSettled([A("POST",`/api/client/jobs/${encodeURIComponent(t.job_id)}/heartbeat`,{machineId:n.machineId}),H(t.job_id,s)]).finally(()=>{i=!1}))},q);try{await Promise.race([e,Pe(s.signal)])}finally{clearInterval(w)}}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?_e:t===0?0:Math.max(1,Math.min(t,8))}function X(e,t){return t===0||e<t}async function je(e,t,n,s){const i=e.job_id,l=e.session_id,w=e.workspace_id,I=e.project_id??null,a={...e.input,...fe(e.input??{})},y=Y(i,t.machineId,String(a.acpRun?.agentType||"codex"),s.signal),_=String(a.control?.type||"").toLowerCase();if(!_&&!a.message?.trim()&&(a.attachments??[]).length===0&&!a.sessionReferences?.length){await N(i,t.machineId,"job input missing message");return}const E=t.projects.find(r=>r.projectId===I),c=Ce(E?.rootPath||e.project_root_path||"");if(!c){await N(i,t.machineId,"job project root is unknown");return}ge(e)&&ae.mkdirSync(c,{recursive:!0}),n.info(`claimed job=${i} session=${l} project=${E?.name??I??"unknown"}`);let m=!1,S=!1;const u=setInterval(()=>{m||(m=!0,A("POST",`/api/client/jobs/${encodeURIComponent(i)}/heartbeat`,{machineId:t.machineId}).catch(()=>{}).finally(()=>{m=!1})),S||(S=!0,H(i,s).catch(()=>{}).finally(()=>{S=!1}))},q);let v=0;const p=we(r=>n.info(r)),b=he(i,t.machineId,{warn:r=>n.warn(r),trace:p}),f=Ne(i,t.machineId),$=new Set;let k=null,j,R;const W=async()=>j?.finish().catch(r=>(g.uncertain.add("job-artifacts"),n.warn(`artifact collection failed job=${i}: ${String(r)}`),[]))??[];try{await f.sendNow({phase:"initializing",label:"准备工作区",detail:e.worktree_id?"worktree":"project root"}).catch(()=>{});const r=de({projectId:I,sessionId:l,projectRoot:c,worktree:e});k=r,await Ue(i,t.machineId,r);const T=Me(a.acpRun);if(await y.initialize(),xe(T.agentType)&&await be(e.codex_provider_revision),_==="compact"){if(!e.acp_session_id)throw new Error("Current session does not have an ACP context to compact");const o=await re({agentType:x(T.agentType,"codex"),cliType:x(T.cliType,"builtin"),acpSessionId:e.acp_session_id,cwd:r.cwd,model:a.model,mode:a.mode,configOptionValues:a.configOptionValues,timeoutMs:a.timeoutMs,allowRollover:a.control?.allowRollover===!0,signal:s.signal,onStatus:h=>{f.report({phase:h.phase,label:h.label,detail:h.detail,updateType:h.updateType,raw:h})}});await f.flush(),await A("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:J(r)}}),n.info(`completed context compact job=${i}`);return}if(_==="fork"){const o=String(a.control?.sourceAcpSessionId||"").trim();if(!o)throw new Error("Source session does not have an ACP context to fork");await f.sendNow({phase:"forking",label:"正在分叉会话",detail:a.control?.sourceSessionId?`源会话 ${a.control.sourceSessionId}`:void 0}).catch(()=>{});const h=await se({agentType:x(T.agentType,"codex"),cliType:x(T.cliType,"builtin"),sourceProviderSessionId:o,cwd:r.cwd,model:a.model,mode:a.mode,configOptionValues:a.configOptionValues,timeoutMs:a.timeoutMs,signal:s.signal,onStatus:M=>{f.report({phase:M.phase,label:M.label,detail:M.detail,updateType:M.updateType,raw:M})}});await f.flush(),await A("POST",`/api/client/jobs/${encodeURIComponent(i)}/complete`,{machineId:t.machineId,acpSessionId:h.acpSessionId,suppressTurn:!0,result:{ok:!0,control:{type:"fork",sourceSessionId:a.control?.sourceSessionId||null},acpSessionId:h.acpSessionId,worktree:J(r)}}),n.info(`completed session fork job=${i}`);return}const C=await ne({cwd:r.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:L(C)}}).catch(()=>{}));const O=ee(a,C),P=te(O,C);j=await ie(r.cwd,o=>n.warn(o)).catch(o=>{n.warn(`artifact monitor unavailable job=${i}: ${String(o)}`)}),a.automation||(R=Z({jobId:i,sessionId:l,cwd:r.cwd,logger:n,signal:s.signal}));const d=await ce({agentType:x(T.agentType,"codex"),cliType:x(T.cliType,"builtin"),acpSessionId:e.acp_session_id??null,requireSessionResume:Ee(e.session_raw),prompt:O,promptBlocks:P,cwd:r.cwd,model:a.model,mode:a.mode,configOptionValues:a.configOptionValues,canExecuteTools:a.canExecuteTools,timeoutMs:a.timeoutMs,contextWindow:$e(T.contextWindow),signal:s.signal,onSupplementReady:o=>R?.setChannel(o),onStatus:o=>{f.report({phase:o.phase,label:o.label,detail:o.detail,updateType:o.updateType,raw:o})},onUpdate:o=>{v+=1,(o.type==="tool_call"||o.type==="tool_call_update"&&["completed","failed","cancelled"].includes(o.status||""))&&p("worker_event",{jobId:i,uploadRunId:b.uploadRunId,toolCallId:o.toolCallId,status:o.status,sequence:v});for(const M of pe(o.raw))$.add(M);const h={turnId:e.user_turn_id??null,eventType:o.type,label:o.label,text:o.text,status:o.status,toolCallId:o.toolCallId,raw:Fe(o.raw,v)};Ie(h)&&b.push(h)},onPermissionRequest:o=>{const h=me(a,o);return h?Promise.resolve(h):y.permission(o)},onUserInputRequest:o=>y.input(o),onProviderSessionMigration:async o=>{const h=await A("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 b.flush().catch(()=>{}),await f.sendNow({phase:"resuming",label:"Codex 会话已迁移",detail:o.providerSessionId,updateType:"aca.performance.provider_session_migrated",raw:{migration:o,server:{migrated:h.migrated===!0}}}).catch(()=>{})}}),Q=await W(),z=await b.finish();z.complete||g.uncertain.add("job-event-upload"),await f.flush(),await R?.stop();const B=D(V({cwd:r.cwd,evidencePaths:$}),Q),F=Oe(d.promptResponse);if(!(Je(F)||!!d.content)){await N(i,t.machineId,`${d.agentType} stopped: ${F??"unknown"}`,{acpSessionId:d.acpSessionId,stopReason:F,eventUpload:z,files:B,exitCode:d.exitCode,stderr:De(d.stderr,4e3),worktree:J(r)});return}await A("POST",`/api/client/jobs/${encodeURIComponent(i)}/complete`,{machineId:t.machineId,acpSessionId:d.acpSessionId,assistantText:d.content,result:{ok:!0,eventUpload:z,acpSessionId:d.acpSessionId,stopReason:F,durationMs:d.durationMs,acpRun:a.acpRun??null,effectiveConfig:d.effectiveConfig??null,performance:d.performance??null,usage:d.contextUsage??null,providerDiagnostics:d.providerDiagnostics??null,updateCount:d.updateCount,statusTimeline:d.statusTimeline,worktree:J(r),attachments:L(C),files:B}}),n.info(`completed job=${i}`)}catch(r){if(await R?.stop(),We(s.signal)){b.discard(),f.discard(),n.warn(`stopped local job=${i} because ACA Server already finalized it`);return}const T=await W(),C=await b.finish();C.complete||g.uncertain.add("job-event-upload"),await f.flush();const O=s.signal.aborted?"cancelled":r instanceof Error?r.message:String(r),P=ye(r),d=D(k?V({cwd:k.cwd,evidencePaths:$}):[],T);await N(i,t.machineId,Se(O),{rawError:O,eventUpload:C,files:d,providerDiagnostics:U(r,"providerDiagnostics"),failure:U(r,"failure"),statusTimeline:U(r,"statusTimeline"),performance:U(r,"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 y.close(),await R?.stop(),j?.dispose(),b.discard(),clearInterval(u),await f.flush()}}function Ee(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 Me(e){return!e||typeof e!="object"||Array.isArray(e)?{}:e}function xe(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 x(...e){for(const t of e)if(typeof t=="string"&&t.trim())return t.trim();return""}function Oe(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 Je(e){return e?["end_turn","stop","completed","complete"].includes(e):!1}function Ne(e,t){let n=null,s=0,i="",l=null,w=Promise.resolve(),I=!1;const a=c=>JSON.stringify({phase:c.phase??"",label:c.label??"",detail:c.detail??"",updateType:c.updateType??""}),y=async c=>{await A("POST",`/api/client/jobs/${encodeURIComponent(e)}/presence`,{machineId:t,phase:c.phase,label:c.label,detail:c.detail,updateType:c.updateType,raw:c.raw}),s=Date.now(),i=a(c)},_=()=>{if(l&&(clearTimeout(l),l=null),I)return n=null,w;if(!n)return w;const c=n;return n=null,w=w.then(()=>y(c)).catch(()=>{}),w},E=c=>{l||(l=setTimeout(()=>{_()},c),l.unref())};return{report(c){if(I)return;const m=Date.now(),u=a(c)!==i,v=m-s>=Ae;if(!u&&!v)return;n=c;const p=m-s;if(p>=K||s===0){_();return}E(K-p)},sendNow(c){return I?w:(n=c,_())},flush:_,discard(){I=!0,n=null,l&&clearTimeout(l),l=null}}}function Fe(e,t){return e&&typeof e=="object"&&!Array.isArray(e)?{...e,acaSequence:t}:{value:e,acaSequence:t}}async function Ue(e,t,n){!n.usedWorktree||!n.worktreeId||await A("POST",`/api/client/jobs/${encodeURIComponent(e)}/worktree`,{machineId:t,baseCommit:n.baseCommit??null,worktreePath:n.cwd,status:n.status??"active",raw:n.raw??J(n)})}function J(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 H(e,t){if(t.signal.aborted)return;const n=await A("GET",`/api/client/jobs/${encodeURIComponent(e)}`);if(Number(n.item.cancel_requested??0)===1){t.abort();return}const s=ze(n.item);s&&t.abort(new G(e,s))}function ze(e){const t=String(e.status||"").toLowerCase();return t&&!["claimed","running"].includes(t)?t:null}class G 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 G}async function N(e,t,n,s,i){await A("POST",`/api/client/jobs/${encodeURIComponent(e)}/fail`,{machineId:t,error:n,result:s,...i?.invalidateAcpSession?{invalidateAcpSession:!0,invalidatedAcpSessionId:i.invalidatedAcpSessionId}:{}})}function Be(e){return new Promise(t=>setTimeout(t,e))}function Le(e){return Math.min(1e4,500*2**Math.min(5,Math.max(0,e-1)))+Math.floor(Math.random()*250)}function De(e,t){return e.length<=t?e:`${e.slice(0,t)}...`}function U(e,t){return!e||typeof e!="object"||Array.isArray(e)?null:e[t]??null}export{ze as serverFinalizedJobStatus,mt as startJobWorkerLoop};
|
|
1
|
+
import{createJobInteractions as Y}from"../interactions/job-interactions.js";import{createJobSupplementReceiver as Z}from"./job-supplements.js";import{buildPromptWithAttachments as ee,buildPromptBlocks as te,materializeJobAttachments as ne,summarizeMaterializedAttachments as L}from"./job-attachments.js";import{managementActivity as g}from"./management/activity.js";import{managedClaim as oe}from"./management/claims.js";import{startJobArtifactMonitor as ie,mergeArtifactFiles as D}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 A}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{resolveJobExecutionConfig as fe}from"./job-execution-config.js";import{createAcpEventBatcher as he}from"./acp-event-batcher.js";import{createToolLifecycleTrace as we}from"../acp/tool-lifecycle-trace.js";import{shouldBufferAcpEvent as Ie}from"./acp-event-upload-filter.js";import{userFacingAcpError as Se}from"../acp/error-display.js";import{ensureCodexProviderRuntimeRevision as be}from"./codex-provider-runtime-config.js";import{acpSessionInvalidationFromError as ye}from"../acp/session-errors.js";const ve=1500,q=5e3,K=900,Ae=1e4,_e=0,Ce=12e3;function Te(e){return ue(e)}function ge(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 mt(e,t={}){let n=!1,s=null,i=null,l=0,I=0;const S=new Set,a=new Map,w=new Map,_=async()=>{if(n||g.paused||g.stopping||Date.now()<I)return;const m=ke();if(!X(S.size,m))return;const b=g.enter("poll");if(b)try{const u=t.loadConfig?.()??le();if(!u.token)return;for(;!n&&!g.paused&&!g.stopping&&X(S.size,m);){const v=t.claimNextJob?await t.claimNextJob(u,[...a.keys()]):await oe("job",()=>A("POST","/api/client/jobs/claim",{machineId:u.machineId,workspaceId:u.defaultWorkspaceId,activeSessionIds:[...a.keys()]}),{workspaceId:u.defaultWorkspaceId,activeSessionIds:[...a.keys()]});if(l=0,I=0,!v.item)break;const p=v.item;if(n){await(t.cancelClaimedJob?t.cancelClaimedJob(p,u):N(p.job_id,u.machineId,"cancelled")).catch(()=>{});break}const y=p.session_id,f=a.get(y),$=g.track("job");S.add(p.job_id);const k=new AbortController;let j;j=(async()=>{f&&(e.warn(`claimed job=${p.job_id} for already active session=${y}; waiting for the previous run to release its ACP runtime`),await Re(f,p,u,k)),!k.signal.aborted&&await(t.runJob??je)(p,u,e,k)})().catch(R=>{g.uncertain.add("job-finalize"),e.warn(`job ${p.job_id} failed outside handler: ${R instanceof Error?R.message:String(R)}`)}).finally(()=>{$(),S.delete(p.job_id),a.get(y)===j&&a.delete(y),w.delete(p.job_id)}),a.set(y,j),w.set(p.job_id,{controller:k,promise:j})}}catch(u){if(!n){l+=1;const v=Le(l);I=Date.now()+v,e.warn(`job worker failed, retry in ${v}ms: ${u instanceof Error?u.message:String(u)}`)}}finally{b()}},E=()=>{if(n||s)return;const m=_();s=m,m.finally(()=>{s===m&&(s=null)})};E();const c=setInterval(()=>{E()},t.claimIntervalMs??ve);return{timer:c,stop:()=>i||(i=(async()=>{if(n)return;n=!0,clearInterval(c);for(const b of w.values())b.controller.abort();await s?.catch(()=>{});for(const b of w.values())b.controller.abort();const m=[...w.values()].map(b=>b.promise);m.length!==0&&await Promise.race([Promise.allSettled(m).then(()=>{}),Be(Ce)])})(),i)}}async function Re(e,t,n,s){let i=!1;const I=setInterval(()=>{i||s.signal.aborted||(i=!0,Promise.allSettled([A("POST",`/api/client/jobs/${encodeURIComponent(t.job_id)}/heartbeat`,{machineId:n.machineId}),H(t.job_id,s)]).finally(()=>{i=!1}))},q);try{await Promise.race([e,Pe(s.signal)])}finally{clearInterval(I)}}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?_e:t===0?0:Math.max(1,Math.min(t,8))}function X(e,t){return t===0||e<t}async function je(e,t,n,s){const i=e.job_id,l=e.session_id,I=e.workspace_id,S=e.project_id??null,a={...e.input,...fe(e.input??{})},w=Y(i,t.machineId,String(a.acpRun?.agentType||"codex"),s.signal),_=String(a.control?.type||"").toLowerCase();if(!_&&!a.message?.trim()&&(a.attachments??[]).length===0&&!a.sessionReferences?.length){await N(i,t.machineId,"job input missing message");return}const E=t.projects.find(r=>r.projectId===S),c=Te(E?.rootPath||e.project_root_path||"");if(!c){await N(i,t.machineId,"job project root is unknown");return}ge(e)&&ae.mkdirSync(c,{recursive:!0}),n.info(`claimed job=${i} session=${l} project=${E?.name??S??"unknown"}`);let m=!1,b=!1;const u=setInterval(()=>{m||(m=!0,A("POST",`/api/client/jobs/${encodeURIComponent(i)}/heartbeat`,{machineId:t.machineId}).catch(()=>{}).finally(()=>{m=!1})),b||(b=!0,H(i,s).catch(()=>{}).finally(()=>{b=!1}))},q);let v=0;const p=we(r=>n.info(r)),y=he(i,t.machineId,{warn:r=>n.warn(r),trace:p}),f=Ne(i,t.machineId),$=new Set;let k=null,j,R;const W=async()=>j?.finish().catch(r=>(g.uncertain.add("job-artifacts"),n.warn(`artifact collection failed job=${i}: ${String(r)}`),[]))??[];try{await f.sendNow({phase:"initializing",label:"准备工作区",detail:e.worktree_id?"worktree":"project root"}).catch(()=>{});const r=de({projectId:S,sessionId:l,projectRoot:c,worktree:e});k=r,await Ue(i,t.machineId,r);const C=Me(a.acpRun);if(await w.initialize(),xe(C.agentType)&&await be(e.codex_provider_revision),_==="compact"){if(!e.acp_session_id)throw new Error("Current session does not have an ACP context to compact");const o=await re({agentType:x(C.agentType,"codex"),cliType:x(C.cliType,"builtin"),acpSessionId:e.acp_session_id,cwd:r.cwd,model:a.model,mode:a.mode,configOptionValues:a.configOptionValues,timeoutMs:a.timeoutMs,allowRollover:a.control?.allowRollover===!0,signal:s.signal,onStatus:h=>{f.report({phase:h.phase,label:h.label,detail:h.detail,updateType:h.updateType,raw:h})}});await f.flush(),await A("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:J(r)}}),n.info(`completed context compact job=${i}`);return}if(_==="fork"){const o=String(a.control?.sourceAcpSessionId||"").trim();if(!o)throw new Error("Source session does not have an ACP context to fork");await f.sendNow({phase:"forking",label:"正在分叉会话",detail:a.control?.sourceSessionId?`源会话 ${a.control.sourceSessionId}`:void 0}).catch(()=>{});const h=await se({agentType:x(C.agentType,"codex"),cliType:x(C.cliType,"builtin"),sourceProviderSessionId:o,cwd:r.cwd,model:a.model,mode:a.mode,configOptionValues:a.configOptionValues,timeoutMs:a.timeoutMs,signal:s.signal,onStatus:M=>{f.report({phase:M.phase,label:M.label,detail:M.detail,updateType:M.updateType,raw:M})}});await f.flush(),await A("POST",`/api/client/jobs/${encodeURIComponent(i)}/complete`,{machineId:t.machineId,acpSessionId:h.acpSessionId,suppressTurn:!0,result:{ok:!0,control:{type:"fork",sourceSessionId:a.control?.sourceSessionId||null},acpSessionId:h.acpSessionId,worktree:J(r)}}),n.info(`completed session fork job=${i}`);return}const T=await ne({cwd:r.cwd,sessionId:l,jobId:i,attachments:a.attachments??[]});T.length>0&&(n.info(`materialized ${T.length} attachment(s) for job=${i}`),await f.sendNow({phase:"initializing",label:"附件已写入本机",detail:T.map(o=>o.relativePath).join(", "),raw:{attachments:L(T)}}).catch(()=>{}));const O=ee(a,T),P=te(O,T);j=await ie(r.cwd,o=>n.warn(o)).catch(o=>{n.warn(`artifact monitor unavailable job=${i}: ${String(o)}`)}),a.automation||(R=Z({jobId:i,sessionId:l,cwd:r.cwd,logger:n,signal:s.signal}));const d=await ce({agentType:x(C.agentType,"codex"),cliType:x(C.cliType,"builtin"),acpSessionId:e.acp_session_id??null,requireSessionResume:Ee(e.session_raw),prompt:O,promptBlocks:P,cwd:r.cwd,model:a.model,mode:a.mode,configOptionValues:a.configOptionValues,canExecuteTools:a.canExecuteTools,timeoutMs:a.timeoutMs,contextWindow:$e(C.contextWindow),signal:s.signal,onSupplementReady:o=>R?.setChannel(o),onStatus:o=>{f.report({phase:o.phase,label:o.label,detail:o.detail,updateType:o.updateType,raw:o})},onUpdate:o=>{v+=1,(o.type==="tool_call"||o.type==="tool_call_update"&&["completed","failed","cancelled"].includes(o.status||""))&&p("worker_event",{jobId:i,uploadRunId:y.uploadRunId,toolCallId:o.toolCallId,status:o.status,sequence:v});for(const M of pe(o.raw))$.add(M);const h={turnId:e.user_turn_id??null,eventType:o.type,label:o.label,text:o.text,status:o.status,toolCallId:o.toolCallId,raw:Fe(o.raw,v)};Ie(h)&&y.push(h)},onPermissionRequest:o=>{const h=me(a,o);return h?Promise.resolve(h):w.permission(o)},onUserInputRequest:o=>w.input(o),elicitation:w.elicitation,onElicitationComplete:o=>{w.complete(o)},onProviderSessionMigration:async o=>{const h=await A("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 y.flush().catch(()=>{}),await f.sendNow({phase:"resuming",label:"Codex 会话已迁移",detail:o.providerSessionId,updateType:"aca.performance.provider_session_migrated",raw:{migration:o,server:{migrated:h.migrated===!0}}}).catch(()=>{})}}),Q=await W(),z=await y.finish();z.complete||g.uncertain.add("job-event-upload"),await f.flush(),await R?.stop();const B=D(V({cwd:r.cwd,evidencePaths:$}),Q),F=Oe(d.promptResponse);if(!(Je(F)||!!d.content)){await N(i,t.machineId,`${d.agentType} stopped: ${F??"unknown"}`,{acpSessionId:d.acpSessionId,stopReason:F,eventUpload:z,files:B,exitCode:d.exitCode,stderr:De(d.stderr,4e3),worktree:J(r)});return}await A("POST",`/api/client/jobs/${encodeURIComponent(i)}/complete`,{machineId:t.machineId,acpSessionId:d.acpSessionId,assistantText:d.content,result:{ok:!0,eventUpload:z,acpSessionId:d.acpSessionId,stopReason:F,durationMs:d.durationMs,acpRun:a.acpRun??null,effectiveConfig:d.effectiveConfig??null,performance:d.performance??null,usage:d.contextUsage??null,providerDiagnostics:d.providerDiagnostics??null,updateCount:d.updateCount,statusTimeline:d.statusTimeline,worktree:J(r),attachments:L(T),files:B}}),n.info(`completed job=${i}`)}catch(r){if(await R?.stop(),We(s.signal)){y.discard(),f.discard(),n.warn(`stopped local job=${i} because ACA Server already finalized it`);return}const C=await W(),T=await y.finish();T.complete||g.uncertain.add("job-event-upload"),await f.flush();const O=s.signal.aborted?"cancelled":r instanceof Error?r.message:String(r),P=ye(r),d=D(k?V({cwd:k.cwd,evidencePaths:$}):[],C);await N(i,t.machineId,Se(O),{rawError:O,eventUpload:T,files:d,providerDiagnostics:U(r,"providerDiagnostics"),failure:U(r,"failure"),statusTimeline:U(r,"statusTimeline"),performance:U(r,"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 w.close(),await R?.stop(),j?.dispose(),y.discard(),clearInterval(u),await f.flush()}}function Ee(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 Me(e){return!e||typeof e!="object"||Array.isArray(e)?{}:e}function xe(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 x(...e){for(const t of e)if(typeof t=="string"&&t.trim())return t.trim();return""}function Oe(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 Je(e){return e?["end_turn","stop","completed","complete"].includes(e):!1}function Ne(e,t){let n=null,s=0,i="",l=null,I=Promise.resolve(),S=!1;const a=c=>JSON.stringify({phase:c.phase??"",label:c.label??"",detail:c.detail??"",updateType:c.updateType??""}),w=async c=>{await A("POST",`/api/client/jobs/${encodeURIComponent(e)}/presence`,{machineId:t,phase:c.phase,label:c.label,detail:c.detail,updateType:c.updateType,raw:c.raw}),s=Date.now(),i=a(c)},_=()=>{if(l&&(clearTimeout(l),l=null),S)return n=null,I;if(!n)return I;const c=n;return n=null,I=I.then(()=>w(c)).catch(()=>{}),I},E=c=>{l||(l=setTimeout(()=>{_()},c),l.unref())};return{report(c){if(S)return;const m=Date.now(),u=a(c)!==i,v=m-s>=Ae;if(!u&&!v)return;n=c;const p=m-s;if(p>=K||s===0){_();return}E(K-p)},sendNow(c){return S?I:(n=c,_())},flush:_,discard(){S=!0,n=null,l&&clearTimeout(l),l=null}}}function Fe(e,t){return e&&typeof e=="object"&&!Array.isArray(e)?{...e,acaSequence:t}:{value:e,acaSequence:t}}async function Ue(e,t,n){!n.usedWorktree||!n.worktreeId||await A("POST",`/api/client/jobs/${encodeURIComponent(e)}/worktree`,{machineId:t,baseCommit:n.baseCommit??null,worktreePath:n.cwd,status:n.status??"active",raw:n.raw??J(n)})}function J(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 H(e,t){if(t.signal.aborted)return;const n=await A("GET",`/api/client/jobs/${encodeURIComponent(e)}`);if(Number(n.item.cancel_requested??0)===1){t.abort();return}const s=ze(n.item);s&&t.abort(new G(e,s))}function ze(e){const t=String(e.status||"").toLowerCase();return t&&!["claimed","running"].includes(t)?t:null}class G 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 G}async function N(e,t,n,s,i){await A("POST",`/api/client/jobs/${encodeURIComponent(e)}/fail`,{machineId:t,error:n,result:s,...i?.invalidateAcpSession?{invalidateAcpSession:!0,invalidatedAcpSessionId:i.invalidatedAcpSessionId}:{}})}function Be(e){return new Promise(t=>setTimeout(t,e))}function Le(e){return Math.min(1e4,500*2**Math.min(5,Math.max(0,e-1)))+Math.floor(Math.random()*250)}function De(e,t){return e.length<=t?e:`${e.slice(0,t)}...`}function U(e,t){return!e||typeof e!="object"||Array.isArray(e)?null:e[t]??null}export{ze as serverFinalizedJobStatus,mt as startJobWorkerLoop};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{randomUUID as v}from"node:crypto";import{setTimeout as y}from"node:timers/promises";import{acaServerRequest as I,AcaServerHttpError as f}from"../core/aca-server-client.js";import{loadAcaConfig as T}from"../core/aca-config.js";function P(m,b){const w=v(),l=T(),S={serverUrl:l.serverUrl,token:l.token},i=new AbortController,a=`/api/client/interactions/connections/${w}`,u=new Set;let o=!1,g,d,p=!1;const r=(t,e,s,n=!1)=>I(t,e,s,{...S,timeoutMs:5e3,...n?{}:{signal:i.signal}}),h=()=>g??=(async()=>{if(!l.token)return;let t;try{t=await r("GET","/api/interactions/capabilities")}catch(e){if(e instanceof f&&e.status===404)return;throw e}!(t.interactionVersions??[t.interactionVersion]).includes(2)||!t.requestScope||(await r("POST",a,{action:"open",provider:m,key:b}),o=!0,d=setInterval(()=>{p||i.signal.aborted||(p=!0,r("POST",a,{action:"heartbeat"}).catch(e=>{e instanceof f&&e.status<500&&i.abort()}).finally(()=>{p=!1}))},1e4),d.unref())})();return{initialize:h,get supported(){return o},async input(t){if(await h(),!o)throw Object.assign(new Error("ACA Server 不支持会话外交互,请先升级 Server。"),{rpcCode:-32602});const e=t.interaction;if(!e||e.scope?.requestId===void 0||e.scope.sessionId!==void 0)throw Object.assign(new Error("Invalid request-scoped elicitation"),{rpcCode:-32602});const s={...e,provider:m,runtimeId:w};let n;for(;!i.signal.aborted&&Date.now()<s.expiresAtMs;){try{if(n=(n?await r("GET",`/api/client/connection-interactions/${n.permission_id}`):await r("POST",`${a}/requests`,{interaction:s})).item,n.status!=="pending")return n.status==="resolved"&&n.response?n.response:{outcome:{outcome:"cancelled"}}}catch(c){if(i.signal.aborted)break;if(c instanceof f&&c.status<500)throw c}await y(Math.min(1e3,Math.max(1,s.expiresAtMs-Date.now())),void 0,{signal:i.signal}).catch(()=>{})}return{outcome:{outcome:"cancelled"}}},async complete(t){if(!o||i.signal.aborted)return;const e=r("POST",`${a}/complete`,t).catch(()=>{});u.add(e),await e,u.delete(e)},async close(){await Promise.allSettled([...u]),i.abort(),clearInterval(d),await g?.catch(()=>{}),o&&await r("POST",a,{action:"close"},!0).catch(()=>{})}}}export{P as createConnectionInteractions};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
class c{pending=new Map;begin(e,t,i,r){const n=e;if(!(n?.mode!=="url"||typeof n.elicitationId!="string")){for(const[s,o]of this.pending)o.expires<=Date.now()&&this.pending.delete(s);if(this.pending.has(n.elicitationId)||this.pending.size>=100)throw Object.assign(new Error("Duplicate or excessive outstanding elicitation IDs"),{rpcCode:-32602});return this.pending.set(n.elicitationId,{requestId:String(t),provider:i,expires:Date.now()+6e5,accepted:!1,completed:!1,notify:r??(()=>{})}),n.elicitationId}}settle(e,t){if(!e)return;const i=this.pending.get(e);if(i){if(t?.action!=="accept"){this.pending.delete(e);return}i.accepted=!0,i.completed&&this.deliver(e)}}complete(e){const t=e?.elicitationId;if(typeof t!="string")return;const i=this.pending.get(t);if(!i||i.expires<=Date.now()){this.pending.delete(t);return}i.completed=!0,i.accepted&&this.deliver(t)}deliver(e){const t=this.pending.get(e);this.pending.delete(e),t.notify({requestId:t.requestId,provider:t.provider,elicitationId:e})}}export{c as ElicitationTracker};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
const d=a=>a&&typeof a=="object"&&!Array.isArray(a)?a:{};function h(a){const u=t=>Object.fromEntries(Object.entries(d(t)).filter(([f,e])=>e!==null&&f!=="_meta")),s=u(a);if(s.type!==void 0&&s.type!=="object")throw new Error("Unsupported elicitation schema: expected an object");const l=new Set(["type","properties","required","title","description","$schema","additionalProperties"]);if(Object.keys(s).some(t=>!l.has(t))||s.additionalProperties!==void 0&&s.additionalProperties!==!1||s.required!==void 0&&!Array.isArray(s.required))throw new Error("Unsupported form root validation");const c=Array.isArray(s.required)?s.required:[];if(c.some(t=>typeof t!="string"||!Object.hasOwn(d(s.properties),t)))throw new Error("Unsupported required field");return Object.entries(d(s.properties)).map(([t,f])=>{if(["__proto__","constructor","prototype"].includes(t))throw new Error("Unsupported field ID");const e=u(f),i=e.type==="array"?"string[]":e.type;if(!["string","number","integer","boolean","string[]"].includes(String(i)))throw new Error(`Unsupported form field type: ${t}`);const g=new Set(["type","title","description","enum","enumNames","oneOf","anyOf","items","default","minLength","maxLength","minimum","maximum","minItems","maxItems","uniqueItems","format","pattern"]);for(const r of Object.keys(e))if(!g.has(r))throw new Error(`Unsupported form validation: ${t}.${r}`);const n=i==="string[]"?u(e.items):e;if(i==="string[]"&&(n.type!=="string"&&!(n.type===void 0&&Array.isArray(n.anyOf))||Object.keys(n).some(r=>!["type","enum","oneOf","anyOf","enumNames"].includes(r))))throw new Error(`Unsupported array item schema: ${t}`);if(["enum","oneOf","anyOf"].filter(r=>n[r]!==void 0).length>1)throw new Error(`Unsupported combined enum: ${t}`);for(const r of["enum","oneOf","anyOf"])if(n[r]!==void 0&&!Array.isArray(n[r]))throw new Error(`Unsupported enum schema: ${t}`);let m;if(Array.isArray(n.enum)){if(!n.enum.every(r=>typeof r=="string"))throw new Error(`Unsupported non-string enum: ${t}`);m=n.enum.map((r,o)=>({value:String(r),label:String(Array.isArray(n.enumNames)?n.enumNames[o]??r:r)}))}else(Array.isArray(n.oneOf)||Array.isArray(n.anyOf))&&(m=(Array.isArray(n.oneOf)?n.oneOf:n.anyOf).map(r=>{const o=u(r);if(typeof o.const!="string"||Object.keys(o).some(p=>!["const","title","description"].includes(p)))throw new Error(`Unsupported enum option: ${t}`);return{value:o.const,label:String(o.title||o.const),...typeof o.description=="string"?{description:o.description}:{}}}));if(i==="string[]"&&!m)throw new Error(`Unsupported free-form array: ${t}`);if(m&&(!m.length||new Set(m.map(r=>r.value)).size!==m.length||!["string","string[]"].includes(String(i))))throw new Error(`Unsupported enum field: ${t}`);if(e.format==="password")throw new Error("Unsupported secret in form mode; use URL mode");if(e.format&&!["email","uri","date","date-time"].includes(String(e.format)))throw new Error(`Unsupported form format: ${t}`);for(const r of["minLength","maxLength","minimum","maximum","minItems","maxItems"]){const o=e[r],p=["minimum","maximum"].includes(r)?["number","integer"].includes(String(i)):r.endsWith("Length")?i==="string":i==="string[]";if(o!==void 0&&(!p||typeof o!="number"||!Number.isFinite(o)||!["minimum","maximum"].includes(r)&&(!Number.isSafeInteger(o)||o<0)))throw new Error(`Unsupported form bound: ${t}.${r}`)}if(e.format!==void 0&&i!=="string"||e.items!==void 0&&i!=="string[]"||e.uniqueItems!==void 0&&(i!=="string[]"||typeof e.uniqueItems!="boolean"))throw new Error(`Unsupported form constraints: ${t}`);for(const[r,o]of[["minimum","maximum"],["minLength","maxLength"],["minItems","maxItems"]]){const p=e[r],y=e[o];if(typeof p=="number"&&typeof y=="number"&&p>y)throw new Error(`Unsupported inverted bounds: ${t}`)}if(e.pattern!==void 0){if(i!=="string"||typeof e.pattern!="string"||e.pattern.length>4096)throw new Error(`Unsupported form pattern: ${t}`);try{new RegExp(e.pattern,"u")}catch{throw new Error(`Unsupported invalid pattern: ${t}`)}}const w=Object.fromEntries(["minLength","maxLength","minimum","maximum","minItems","maxItems"].filter(r=>typeof e[r]=="number").map(r=>[r,e[r]]));return{id:t,label:typeof e.title=="string"&&e.title?e.title:t,description:typeof e.description=="string"?e.description:void 0,type:i,required:c.includes(t),options:m,...typeof e.pattern=="string"?{pattern:e.pattern}:{},...w,...typeof e.format=="string"?{format:e.format}:{},...e.default!==void 0?{defaultValue:e.default}:{}}})}export{h as schemaFields};
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{randomUUID as
|
|
1
|
+
import{randomUUID as I}from"node:crypto";import{setTimeout as x}from"node:timers/promises";import{acaServerRequest as l,AcaServerHttpError as M}from"../core/aca-server-client.js";import{INTERACTION_TIMEOUT_MS as E}from"./types.js";function U(m,s,u,a){const o=I(),h=new AbortController,n=AbortSignal.any([a,h.signal]),f=`/api/client/jobs/${encodeURIComponent(m)}`,S=new Set;let d=!1,p=0,g;const v=()=>g??=(async()=>{try{const t=await l("GET","/api/interactions/capabilities",void 0,{signal:n}),e=t.interactionVersions??[t.interactionVersion];p=e.includes(2)?2:e.includes(1)?1:0,d=p>=1}catch(t){if(!(t instanceof M)||t.status!==404)throw t}d&&await l("POST",`${f}/interactions/runtime`,{machineId:s,runtimeId:o},{signal:n})})();async function A(t,e){if(await v(),e&&!d)throw new Error("ACA Server 不支持结构化回答,请先更新 Server;本次请求未提交给旧接口。");const w=e?t.interaction:void 0;if(w&&w.version>p)throw new Error("ACA Server 不支持此交互版本,请先更新 Server。");const b=w?{...w,runtimeId:o}:{version:1,kind:"approval",provider:u,method:t.method??"session/request_permission",requestId:t.requestId,runtimeId:o,expiresAtMs:t.expiresAtMs??Date.now()+E,prompt:C(t.params),options:t.options},y={machineId:s,request:{...t,...e?{kind:"elicitation"}:{},...d?{interaction:b}:{}}};let i;for(;!n.aborted&&Date.now()<b.expiresAtMs;){try{const r={signal:n,timeoutMs:Math.max(1,Math.min(15e3,b.expiresAtMs-Date.now()))};if(i?i=(await l("GET",`/api/client/permissions/${encodeURIComponent(i.permission_id)}`,void 0,r)).item:i=(await l("POST",`${f}/permissions`,y,r)).item,i.status!=="pending"){const c=i.response?.outcome;return i.status==="resolved"&&c?.outcome==="selected"&&!e&&t.options.some(T=>T.optionId===c.optionId)?{outcome:c}:i.status==="resolved"&&(c?.outcome==="submitted"||c?.outcome==="declined")&&e?{outcome:c}:{outcome:{outcome:"cancelled"}}}}catch(r){if(n.aborted)break;if(r instanceof M&&r.status<500){if([404,409,410].includes(r.status))break;throw r}}await x(Math.min(1e3,Math.max(1,b.expiresAtMs-Date.now())),void 0,{signal:n}).catch(()=>{})}return{outcome:{outcome:"cancelled"}}}return{initialize:v,get elicitation(){return p>=2},permission:t=>A(t,!1),input:t=>A(t,!0),complete:async t=>{if(p<2||n.aborted)return;const e=l("POST",`${f}/interactions/complete`,{...t,runtimeId:o},{timeoutMs:5e3}).catch(()=>{});S.add(e),await e,S.delete(e)},close:async()=>{await Promise.allSettled([...S]),h.abort(),d&&await l("POST",`${f}/interactions/runtime`,{machineId:s,runtimeId:o,closed:!0},{timeoutMs:3e3}).catch(()=>{})}}}function C(m){const s=m&&typeof m=="object"?m:{},u=s.request??s,a=u.params??u;return[a.reason,a.description,a.command,a.title].filter(o=>typeof o=="string"&&o.trim()).join(`
|
|
2
2
|
`)||"Agent 请求继续执行。"}export{U as createJobInteractions};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{randomUUID as
|
|
1
|
+
import{schemaFields as O}from"./form-schema.js";import{randomUUID as x}from"node:crypto";import{INTERACTION_TIMEOUT_MS as E,validateInteractionAnswers as b,validateInteractionUrl as U}from"./types.js";const h="item/tool/requestUserInput",w="mcpServer/elicitation/request";function A(t){return t===h||t===w}function p(t){return t&&typeof t=="object"&&!Array.isArray(t)?t:{}}function _(t,c,r,n){const u=p(r),e=Object.keys(p(u.request)).length?p(u.request):u,o=typeof e.method=="string"?e.method:c;if(!(o==="elicitation/create"||(t==="native-codex"||t==="codex-acp")&&A(o)))throw new Error(`Unsupported user input method: ${o}`);if(o==="elicitation/create"&&!["form","url"].includes(String(e.mode)))throw new Error("Unsupported elicitation mode: explicit form or url required");const s=String(n??e.requestId??e.id??x()),f=String(e.prompt??e.message??e.question??""),q=typeof e.sessionId=="string"?{sessionId:e.sessionId,...typeof e.toolCallId=="string"?{toolCallId:e.toolCallId}:{}}:typeof e.requestId=="string"||typeof e.requestId=="number"?{requestId:e.requestId}:void 0;if(e.mode==="url"){if(!U(e.url)||typeof e.elicitationId!="string"||!e.elicitationId||e.elicitationId.length>256)throw new Error("Unsupported elicitation URL or ID");return{requestId:s,prompt:f,params:r,interaction:{version:2,kind:"url",provider:t,method:o,requestId:s,runtimeId:"",expiresAtMs:Date.now()+E,prompt:f,scope:q,url:e.url,elicitationId:e.elicitationId}}}let l,I="questions";if(o!==h&&e.requestedSchema){if(e.mode&&!["form","openai/form","openaiForm"].includes(String(e.mode)))throw new Error("Unsupported elicitation mode");I="form",l=O(e.requestedSchema)}else if(o===h&&Array.isArray(e.questions))l=e.questions.map(a=>{const i=p(a),g=Array.isArray(i.options)?i.options.map(S=>{const m=p(S),y=String(m.label??m.value??"");return{value:String(m.value??m.label??""),label:y,description:typeof m.description=="string"?m.description:void 0,recommended:m.recommended===!0||/\(Recommended\)|(推荐)|\(推荐\)/i.test(y)}}):void 0;if(i.multiple===!0||i.multiSelect===!0)throw new Error("Unsupported multi-select question protocol");return{id:String(i.id??""),label:String(i.question??i.title??""),description:typeof i.header=="string"?i.header:void 0,type:"string",required:!0,secret:i.isSecret===!0,allowOther:i.isOther===!0||!g?.length,options:g?.length?g:void 0}});else throw new Error("Unsupported user input payload: missing questions or form schema");if(l.length>100||l.some(a=>!a.id||!a.label||["__proto__","constructor","prototype"].includes(a.id))||new Set(l.map(a=>a.id)).size!==l.length)throw new Error("Unsupported user input fields: missing/duplicate IDs or too many fields");return{requestId:s,prompt:f,params:r,interaction:{version:I==="questions"?1:2,kind:I,provider:t,method:o,requestId:s,runtimeId:"",expiresAtMs:Date.now()+E,prompt:f,fields:l,scope:q}}}async function C(t,c,r,n,u){const e=p(r);if(t==="codex-acp"&&e.acaInteractionBridge===1){const s=p(e.request);return n?n(s):{outcome:{outcome:"cancelled"}}}let o;try{o=_(t,c,r,u)}catch(s){throw Object.assign(s instanceof Error?s:new Error(String(s)),{rpcCode:-32602})}const d=n?await n(o):{outcome:{outcome:"cancelled"}};return v(o,d)}function v(t,c){const r=t.interaction;if(!r)throw new Error("Missing structured interaction contract");const n=c.outcome.outcome==="submitted",u=c.outcome.outcome==="submitted"?c.outcome.answers:void 0;if(n&&r.kind!=="url"&&Object.keys(b(r,u)).length)throw new Error("Invalid structured answers");const e=n?"accept":c.outcome.outcome==="declined"?"decline":"cancel";if(r.kind==="url")return{action:e,...r.method===w?{content:null,_meta:null}:{}};if(r.kind==="form")return{action:e,content:n?Object.fromEntries(Object.entries(u).map(([o,d])=>[o,d.value])):null,...r.method===w?{_meta:null}:{}};if(r.method===h)return{answers:n?Object.fromEntries(Object.entries(u).map(([o,d])=>[o,{answers:Array.isArray(d.value)?d.value:[String(d.value)]}])):{}};throw new Error(`Unsupported interaction response method: ${r.method}`)}export{w as CODEX_FORM_METHOD,h as CODEX_QUESTIONS_METHOD,C as handleProviderInput,A as isCodexInputMethod,_ as normalizeProviderInput,p as object,v as providerInputResponse};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
const
|
|
1
|
+
const u=6e5;function y(s,r){const i=Object.create(null);if(!r||typeof r!="object"||Array.isArray(r))return{_form:"请填写回答后提交。"};const f=r,d=s.fields??[];for(const e of Object.keys(f))d.some(n=>n.id===e)||(i[e]="回答包含未知字段。");for(const e of d){const n=Object.hasOwn(f,e.id)?f[e.id]:void 0;if(n===void 0){e.required&&(i[e.id]="此项为必填。");continue}if(!n||typeof n!="object"||Array.isArray(n)||Object.keys(n).some(o=>o!=="type"&&o!=="value")){i[e.id]="回答格式不正确。";continue}const t=n.value,a=e.type==="string[]"?Array.isArray(t)&&t.every(o=>typeof o=="string"):e.type==="integer"?typeof t=="number"&&Number.isSafeInteger(t):typeof t===e.type&&(typeof t!="number"||Number.isFinite(t));if(n.type!==e.type||!a){i[e.id]="回答类型不正确。";continue}if(typeof t=="string"){if(e.required&&s.kind!=="form"&&!t.trim()&&(i[e.id]="此项为必填。"),[...t].length<(e.minLength??0)&&(i[e.id]=`至少填写 ${e.minLength} 个字符。`),[...t].length>Math.min(e.maxLength??16e3,16e3)&&(i[e.id]="填写内容过长。"),e.pattern)try{new RegExp(e.pattern,"u").test(t)||(i[e.id]="填写内容不符合要求的格式。")}catch{i[e.id]="此字段的格式规则无效,请取消后联系 Agent。"}if(e.format==="date-time"&&(!/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:\d{2})$/i.test(t)||!Number.isFinite(Date.parse(t))||new Date(t.slice(0,10)).toISOString().slice(0,10)!==t.slice(0,10))&&(i[e.id]="请输入包含时区的有效日期时间。"),e.format==="email"&&!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(t)&&(i[e.id]="请输入有效的邮箱地址。"),e.format==="uri")try{new URL(t)}catch{i[e.id]="请输入完整的网址。"}e.format==="date"&&(!/^\d{4}-\d{2}-\d{2}$/.test(t)||!Number.isFinite(Date.parse(t))||new Date(t).toISOString().slice(0,10)!==t)&&(i[e.id]="请输入有效的日期。")}typeof t=="number"&&(t<(e.minimum??-1/0)||t>(e.maximum??1/0))&&(i[e.id]="数值超出允许范围。"),Array.isArray(t)&&(([...t].length<(e.minItems??(e.required&&s.kind!=="form"?1:0))||t.length>(e.maxItems??100))&&(i[e.id]="选择数量不符合要求。"),new Set(t).size!==t.length&&(i[e.id]="不能重复选择同一项。")),e.options&&!e.allowOther&&(Array.isArray(t)?t:[t]).some(c=>!e.options.some(m=>m.value===c))&&(i[e.id]="请选择提供的选项。")}return i}function p(s){if(typeof s!="string"||s.length>8192||/[\u0000-\u0020\u007f\\]/u.test(s))return!1;try{const r=new URL(s);return["https:","http:"].includes(r.protocol)&&!!r.hostname&&!r.username&&!r.password}catch{return!1}}export{u as INTERACTION_TIMEOUT_MS,y as validateInteractionAnswers,p as validateInteractionUrl};
|
package/dist/version.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
const o="0.1.
|
|
1
|
+
const o="0.1.97";export{o as AICA_VERSION};
|