@yuandc/aica 0.1.54 → 0.1.56
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/providers/codex/codex-path.js +1 -1
- package/dist/acp/providers/codex-acp/adapter.js +4 -4
- package/dist/acp/providers/codex-acp/control-tool-loop.js +1 -1
- package/dist/acp/providers/codex-acp/control-tool-steering.js +1 -1
- package/dist/acp/providers/codex-acp/notification-routing.js +1 -1
- package/dist/acp/providers/codex-acp/provider.js +1 -1
- package/dist/acp/providers/codex-acp/subtask-executions.js +1 -0
- package/dist/acp/providers/codex-acp/subtask-routing.js +1 -1
- package/dist/commands/start.js +1 -1
- package/dist/core/acp-event-coalescer.js +1 -1
- package/dist/core/machine-filesystem-worker.js +4 -4
- package/dist/core/terminal/native.js +1 -0
- package/dist/core/terminal/service.js +1 -0
- package/dist/core/terminal/shell.js +19 -0
- package/dist/core/terminal/worker.js +1 -0
- package/dist/version.js +1 -1
- package/package.json +5 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
import d from"node:fs";import{createRequire as T}from"node:module";import F from"node:os";import L from"node:path";const
|
|
1
|
+
import d from"node:fs";import{createRequire as T}from"node:module";import{isSea as F}from"node:sea";import g from"node:os";import L from"node:path";const O=T(F()?process.execPath:import.meta.url);function q(n={}){const e=n.env??process.env,r=n.platform??process.platform,c=n.arch??process.arch,o=n.homeDir??g.homedir(),i=n.execPath??process.execPath,t=r==="win32"?L.win32:L.posix,u=l(e.CODEX_PATH);if(u){const s=C(u,e,r,t);return s?E(s,r,c,t)??s:u}const f=n.bundledCodexLauncher===void 0?S():l(n.bundledCodexLauncher??void 0),a=x([f,C("codex",e,r,t),...k(e,r,o,i,t)]);let y=null;for(const s of a){if(!s||!w(s,r))continue;const m=$(s,r,c,t);if(m)return m;if(!D(s))return s;y??=s}for(const s of M(e,r,o,i,t)){const m=j(s,r,c,t);if(m)return m}return y??"codex"}function S(){try{return O.resolve("@openai/codex/bin/codex.js")}catch{return null}}function k(n,e,r,c,o){const i=e==="win32"?"codex.exe":"codex",t=e==="win32"?"codex.cmd":"codex",u=l(n.npm_config_prefix),f=l(n.APPDATA),a=l(n.LOCALAPPDATA);return x([o.join(r,".codex","packages","standalone","current","bin",i),n.NVM_BIN?o.join(n.NVM_BIN,i):null,n.VOLTA_HOME?o.join(n.VOLTA_HOME,"bin",i):null,u?o.join(u,e==="win32"?"":"bin",t):null,o.join(o.dirname(c),t),e==="win32"&&f?o.join(f,"npm",t):null,e==="win32"&&a?o.join(a,"npm",t):null,e!=="win32"?o.join(r,".local","bin",i):null,e==="darwin"?"/opt/homebrew/bin/codex":null,e!=="win32"?"/usr/local/bin/codex":null,e!=="win32"?"/usr/bin/codex":null])}function M(n,e,r,c,o){const i=x([l(n.npm_config_prefix),l(n.NVM_BIN)?o.dirname(l(n.NVM_BIN)):null,o.dirname(o.dirname(c)),e==="win32"?l(n.APPDATA):null,e==="win32"?l(n.LOCALAPPDATA):null,e!=="win32"?o.join(r,".local"):null,e!=="win32"?"/usr/local":null,e!=="win32"?"/usr":null]);return x(i.flatMap(t=>_(t,e,o)))}function $(n,e,r,c){const o=B(n,e,c);for(const i of o){const t=j(i,e,r,c);if(t)return t}return null}function E(n,e,r,c){for(const o of R(n,c)){const i=j(o,e,r,c);if(i)return i}return null}function B(n,e,r){const c=[];let o=P(n)??n;o=r.dirname(o);for(let i=0;i<8;i+=1){r.basename(o)==="codex"&&r.basename(r.dirname(o))==="@openai"&&c.push(o),c.push(..._(o,e,r));const t=r.dirname(o);if(t===o)break;o=t}return x(c)}function R(n,e){const r=[];let c=e.dirname(P(n)??n);for(let o=0;o<8;o+=1){e.basename(c)==="codex"&&e.basename(e.dirname(c))==="@openai"&&r.push(c);const i=e.dirname(c);if(i===c)break;c=i}return x(r)}function _(n,e,r){return e==="win32"?[r.join(n,"node_modules","@openai","codex")]:[r.join(n,"lib","node_modules","@openai","codex"),r.join(n,"node_modules","@openai","codex")]}function j(n,e,r,c){if(!N(n))return null;const o=`codex-${e}-${r==="x64"?"x64":r}`,i=c.join(n,"node_modules","@openai",o),t=c.dirname(n),u=c.basename(n)==="codex"&&c.basename(t)==="@openai"?c.join(t,o):null;return b(i,e,c)??(u?b(u,e,c):null)??b(n,e,c)}function b(n,e,r,c=0){if(c>8||!N(n))return null;let o;try{o=d.readdirSync(n,{withFileTypes:!0})}catch{return null}const i=e==="win32"?"codex.exe":"codex";for(const t of o){const u=r.join(n,t.name);if(t.isFile()&&t.name.toLowerCase()===i&&w(u,e)&&!D(u))return u}for(const t of o){if(!t.isDirectory())continue;const u=b(r.join(n,t.name),e,r,c+1);if(u)return u}return null}function C(n,e,r,c){if(c.isAbsolute(n)||n.includes("/")||n.includes("\\"))return w(n,r)?n:null;const o=r==="win32"?n.includes(".")?[""]:(e.PATHEXT||".EXE;.CMD;.BAT;.COM").split(";").map(t=>t.toLowerCase()):[""],i=r==="win32"?";":":";for(const t of(e.PATH||"").split(i)){const u=l(t);if(u)for(const f of o){const a=c.join(u,`${n}${f}`);if(w(a,r))return a}}return null}function w(n,e){try{return d.accessSync(n,e==="win32"?d.constants.F_OK:d.constants.X_OK),d.statSync(n).isFile()}catch{return!1}}function N(n){try{return d.statSync(n).isDirectory()}catch{return!1}}function D(n){const e=P(n)??n;if(/\.(?:js|mjs|cjs|cmd|bat)$/i.test(e))return!0;try{const r=d.readFileSync(e,{encoding:"utf8",flag:"r"}).slice(0,256);return/^#!.*\bnode\b/m.test(r)}catch{return!1}}function P(n){try{return d.realpathSync(n)}catch{return null}}function l(n){return n?.trim().replace(/^(?:"(.*)"|'(.*)')$/,"$1$2")||null}function x(n){return[...new Set(n.filter(e=>e!=null))]}export{q as resolveCodexPath};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{JsonLineRpcClient as
|
|
2
|
-
`);this.lineBuffer=t.pop()??"";for(const s of t){const
|
|
3
|
-
`),await this.shutdown(),process.exit(1)}}async handleRequest(e,t){switch(e){case"initialize":return this.initializeResponse();case"aca/runtime_config":return this.applyRuntimeConfig(t);case"session/new":return this.newSession(t);case"session/resume":case"session/load":return this.resumeSession(t);case"session/fork":return this.forkSession(t);case"session/prompt":return this.prompt(t);case"session/compact":return this.manualCompact(t);case"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=u(e.codexProvider);if(Object.keys(t).length===0)return this.managedProvider=null,{configured:!1};const s=a(t,"baseUrl"),n=a(t,"apiKey"),o=Number(t.revision||0);if(!s||!n||!Number.isSafeInteger(o)||o<1)throw new Error("Invalid Codex managed provider runtime config");return this.managedProvider={baseUrl:s,apiKey:n,revision:o},{configured:!0,revision:o}}async newSession(e){const t=a(e,"cwd")||process.cwd();this.applyInitialConfig(e),await this.ensureCodex(t);const s=await this.requireRpc().request("thread/start",{cwd:t,approvalsReviewer:"user",...this.codexConfig(),ephemeral:!1,experimentalRawEvents:!0,threadSource:"aca-codex-acp"},6e4),n=b(s);if(!n)throw new Error("Codex thread/start did not return thread id");return this.currentSessionId=n,this.currentCwd=t,this.resetSubtaskState(),this.captureThreadMetadata(s),this.sessionResponse(n)}async resumeSession(e){const t=a(e,"sessionId");if(!t)throw new Error("sessionId is required");const s=a(e,"cwd")||this.currentCwd;this.applyInitialConfig(e),await this.ensureCodex(s);const n=await this.requireRpc().request("thread/resume",{threadId:t,cwd:s,approvalsReviewer:"user",...this.codexConfig()},6e4);return this.currentSessionId=b(n)||t,this.currentCwd=s,this.resetSubtaskState(n),await this.subscribeKnownSubtasks(!1),this.captureThreadMetadata(n),this.sessionResponse(this.currentSessionId)}async forkSession(e){const t=a(e,"sessionId");if(!t)throw new Error("sessionId is required");const s=a(e,"cwd")||this.currentCwd;this.applyInitialConfig(e),await this.ensureCodex(s);const n=await this.requireRpc().request("thread/fork",{threadId:t,cwd:s,approvalsReviewer:"user",ephemeral:!1,threadSource:"aca-codex-acp-fork",...this.codexConfig()},12e4),o=b(n);if(!o)throw new Error("Codex thread/fork did not return thread id");return this.currentSessionId=o,this.currentCwd=s,this.resetSubtaskState(n),this.captureThreadMetadata(n),this.sessionResponse(o)}async prompt(e){this.promptCancelled=!1;const t=a(e,"sessionId")||this.currentSessionId;let s=this.resolveSessionAlias(t);if(!s)throw new Error("sessionId is required");const n=Array.isArray(e.prompt)?e.prompt:[],o=this.requireRpc();this.currentSessionId&&this.currentSessionId!==s&&this.resetSubtaskState(),this.currentSessionId=s;const r=await this.maintainContext("before",{userRequest:this.previousUserText,assistantResponse:this.previousAssistantText,affectedFiles:[...this.previousAffectedFiles]},{allowRollover:!0});if(ue(r),this.promptCancelled||this.shutdownPromise)throw new Error("Codex prompt cancelled");s=this.currentSessionId||s,this.promptActive=!0,this.currentTurnId=null,this.controlToolLoop.reset(),this.controlToolSteering.reset(),this.currentPromptText=Y(n),this.currentAssistantText="",this.currentAffectedFiles.clear(),this.connectionRecovery.reset(),this.agentMessagePhases.clear(),this.planDeltas.reset(),this.pendingSubtaskNotifications=[];let c;try{this.turnStartPromise=o.request("turn/start",{threadId:s,clientUserMessageId:`aca-acp-${Date.now()}-${Math.random().toString(16).slice(2)}`,input:Z(n),cwd:this.currentCwd,...ee(this.config)},D()),c=await this.turnStartPromise;const d=String(c?.turn?.id??"");this.currentTurnId=d||null;const l=d?await this.waitForTurnCompleted(d,D()):c?.turn,h=await this.subtaskRouter.waitForIdle(fe());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 I of this.currentAffectedFiles)this.previousAffectedFiles.add(I);const p=await this.maintainContext("after",{userRequest:this.previousUserText,assistantResponse:this.previousAssistantText,affectedFiles:[...this.previousAffectedFiles]},{allowRollover:!0});return{stopReason:"end_turn",sessionId:this.currentSessionId,turn:l,_meta:{acaContextUsage:q(p)??ce(this.latestTokenUsage),acaContextMaintenance:p,acaSubtaskDrain:h}}}catch(d){const l=this.connectionRecovery.fail(d);throw l&&this.notifySessionUpdate(l),d}finally{this.controlToolLoop.reset(),this.promptActive=!1,this.currentTurnId=null,this.turnStartPromise=null,this.pendingSubtaskNotifications=[]}}resetSubtaskState(e){this.subtaskRouter.reset(),this.subtaskAgentMessagePhases.clear(),this.subtaskPlanDeltas.clear(),this.subscribedSubtaskThreadIds.clear(),this.pendingSubtaskSubscriptions.clear(),this.hydratedSubtaskThreadIds.clear(),this.seenSubtaskItemIds.clear(),this.pendingSubtaskNotifications=[],e!==void 0&&this.subtaskRouter.restoreFromSessionSnapshot(e,this.currentSessionId)}async subscribeKnownSubtasks(e=!0){const t=this.subtaskRouter.routesForSubscription().map(s=>this.subscribeSubtask(s,e));await Promise.allSettled(t)}subscribeSubtask(e,t){const s=e.sourceThreadId;if(!s||s===this.currentSessionId||this.subscribedSubtaskThreadIds.has(s))return Promise.resolve();const n=this.pendingSubtaskSubscriptions.get(s);if(n)return n;const o=this.requireRpc().request("thread/resume",{threadId:s,approvalsReviewer:"user",excludeTurns:!1},6e4).then(r=>{this.subscribedSubtaskThreadIds.add(s);const c=this.subtaskRouter.restoreFromSessionSnapshot(r,this.currentSessionId);t&&this.hydrateSubtaskHistory(r,e),c>0&&this.subscribeKnownSubtasks(t)}).catch(()=>{}).finally(()=>{this.pendingSubtaskSubscriptions.delete(s)});return this.pendingSubtaskSubscriptions.set(s,o),o}async cancel(e){const t=this.resolveSessionAlias(a(e,"sessionId")||this.currentSessionId);if(!t||!this.rpc)return;if(t!==this.currentSessionId)throw new Error("Cannot cancel a different Codex session");this.promptCancelled=!0,this.subtaskRouter.stopWaiting();const s=this.turnStartPromise?await X(this.turnStartPromise):null,n=this.currentTurnId||a(m(s,"turn"),"id");if(await this.rpc.request("turn/interrupt",{threadId:t,turnId:n},5e3),n){const o=this.pendingTurnCompletions.get(n);o&&(this.pendingTurnCompletions.delete(n),clearTimeout(o.timer),o.reject(new Error("Codex prompt cancelled")))}}async manualCompact(e){const t=a(e,"sessionId")||this.currentSessionId,s=this.resolveSessionAlias(t);if(!s)throw new Error("sessionId is required");if(this.promptActive||this.maintenanceActive)throw new Error("Codex session is busy");this.currentSessionId=s;const n=k(),o=await this.maintainContext("manual",{userRequest:this.previousUserText,assistantResponse:this.previousAssistantText,affectedFiles:[...this.previousAffectedFiles]},{force:!0,allowRollover:e.allowRollover===!0,compactTimeoutMs:ae(e,n.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:q(o)}}async recoverSession(e){const t=a(e,"sessionId")||this.currentSessionId,s=this.resolveSessionAlias(t);if(!s)throw new Error("sessionId is required");const n=a(e,"reason");if(n!=="low_output_loop"&&n!=="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]}),{...u(this.sessionResponse(this.currentSessionId)),previousSessionId:s,recoveryReason:n}}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=j(e,this.managedProvider),n=new $(s.child,{peerName:"Codex app-server",onNotification:(o,r)=>this.handleCodexNotification(o,r),onRequest:async(o,r)=>this.handleCodexRequest(o,r)});this.started=s,this.rpc=n,this.initialized=!1,await n.request("initialize",{clientInfo:{name:"aca-codex-acp",version:"0.1.0"},capabilities:{experimentalApi:!0,requestAttestation:!1}},6e4),this.initialized=!0})();this.codexStartupPromise=t;try{await t}catch(s){throw this.rpc&&this.started&&!this.initialized&&await T(this.started.child,1e3),this.rpc=null,this.started=null,s}finally{this.codexStartupPromise===t&&(this.codexStartupPromise=null)}}handleCodexNotification(e,t){const s=R(t);if(this.maintenanceActive&&(!s.threadId||s.threadId===this.currentSessionId)){this.captureContextNotification(e,t);return}if(!J(t,this.currentSessionId,this.currentTurnId)){if(!this.promptActive||!s.threadId||s.threadId===this.currentSessionId)return;const d=this.subtaskRouter.routeFor(s.threadId);if(!d){this.bufferPendingSubtaskNotification(e,t,s.threadId);return}this.handleSubtaskNotification(e,t,d),this.flushPendingSubtaskNotifications();return}const n=this.subtaskRouter.observe(t,this.currentSessionId);if(n>0&&this.subscribeKnownSubtasks(!0),this.captureContextNotification(e,t),this.maintenanceActive||!this.promptActive&&e!=="turn/completed")return;this.rpc?.refreshPendingRequestTimeout("turn/start"),this.captureAgentMessagePhase(e,t);const o=this.connectionRecovery.beforeNotification(e,t);e==="turn/completed"&&this.resolveTurnCompleted(t);const r=e==="rawResponseItem/completed"?le(this.controlToolLoop,t):[],c=e==="rawResponse/completed"?de(t):e==="rawResponseItem/completed"?r.map(({update:d})=>d):x(e,t,{agentMessagePhases:this.agentMessagePhases,planDeltas:this.planDeltas});for(const d of r)this.maybeSteerControlToolLoop(d.event);for(const d of[...o,...c]){const l=P(d,e,t,this.agentMessagePhases);A(l)&&this.notifyControlToolProgress(),this.captureUpdateEvidence(l),this.notifySessionUpdate(l)}n>0&&this.flushPendingSubtaskNotifications()}handleSubtaskNotification(e,t,s){if(!this.promptActive)return;(e==="rawResponseItem/completed"||e==="item/completed")&&Q(u(u(t).item))&&this.notifyControlToolProgress(),this.rememberSubtaskItem(s.sourceThreadId,t),this.rpc?.refreshPendingRequestTimeout("turn/start");const n=this.subtaskAgentMessagePhases.get(s.sourceThreadId)??new Map;this.subtaskAgentMessagePhases.set(s.sourceThreadId,n);const o=this.subtaskPlanDeltas.get(s.sourceThreadId)??new y;this.subtaskPlanDeltas.set(s.sourceThreadId,o),this.captureAgentMessagePhase(e,t,n),this.subtaskRouter.observe(t,this.currentSessionId)>0&&this.subscribeKnownSubtasks(!0);const c=x(e,t,{agentMessagePhases:n,planDeltas:o}),d=R(t).turnId;for(const l of c){const h=P(l,e,t,n);if(!he(h))continue;A(h)&&this.notifyControlToolProgress();const p=E(h,s,d);w(p,this.currentAffectedFiles),this.notifySessionUpdate(p)}}hydrateSubtaskHistory(e,t){if(!(!this.promptActive||this.hydratedSubtaskThreadIds.has(t.sourceThreadId))){this.hydratedSubtaskThreadIds.add(t.sourceThreadId);for(const s of H(e)){const n=N(t.sourceThreadId,s.itemId);if(this.seenSubtaskItemIds.has(n))continue;this.seenSubtaskItemIds.add(n);const o=E(s.update,t,s.sourceTurnId),r=u(o._meta);o._meta={...r,aca:{...u(r.aca),historyReplay:!0}},w(o,this.currentAffectedFiles),this.notifySessionUpdate(o)}}}rememberSubtaskItem(e,t){const s=u(t),n=u(s.item),o=a(n,"id")||a(s,"itemId");o&&this.seenSubtaskItemIds.add(N(e,o))}bufferPendingSubtaskNotification(e,t,s){this.pendingSubtaskNotifications.push({method:e,params:t,threadId:s}),this.pendingSubtaskNotifications.length>200&&this.pendingSubtaskNotifications.shift()}flushPendingSubtaskNotifications(){if(this.pendingSubtaskNotifications.length===0)return;let e=!0;for(;e&&this.pendingSubtaskNotifications.length>0;){e=!1;const t=[];for(const s of this.pendingSubtaskNotifications){const n=this.subtaskRouter.routeFor(s.threadId);if(!n){t.push(s);continue}e=!0,this.handleSubtaskNotification(s.method,s.params,n)}this.pendingSubtaskNotifications=t}}notifySessionUpdate(e){this.notify("session/update",{sessionId:this.currentSessionId,update:e})}async handleCodexRequest(e,t){if(O(e))return B(e,t,async s=>{const n=await this.requestParent("session/request_permission",{sessionId:this.currentSessionId,request:{...s,params:t,method:e}}),o=u(n),r=u(o.outcome);return r.outcome==="selected"&&typeof r.optionId=="string"?{outcome:{outcome:"selected",optionId:r.optionId}}:{outcome:{outcome:"cancelled"}}});if(pe(e)){const s=await this.requestParent("elicitation/create",{sessionId:this.currentSessionId,request:{...u(t),method:e}}),n=u(s),o=u(n.outcome);return o.outcome==="submitted"?{outcome:{outcome:"submitted",value:typeof o.value=="string"?o.value:""}}:{outcome:{outcome:"cancelled"}}}return{}}captureAgentMessagePhase(e,t,s=this.agentMessagePhases){if(e!=="item/started"&&e!=="item/completed")return;const n=m(t,"item");if(a(n,"type")!=="agentMessage")return;const o=a(n,"id");o&&s.set(o,a(n,"phase"))}waitForTurnCompleted(e,t){const s=this.completedTurns.get(e);return s?Promise.resolve(s):new Promise((n,o)=>{const 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:n,reject:o,timer:r})})}resolveTurnCompleted(e){const t=m(e,"turn"),s=a(t,"id");if(!s)return;this.completedTurns.set(s,t);const n=this.pendingTurnCompletions.get(s);if(n){if(this.pendingTurnCompletions.delete(s),clearTimeout(n.timer),a(t,"status")==="failed"){n.reject(new Error(`Codex turn failed: ${JSON.stringify(t.error??{})}`));return}n.resolve(t)}}async shutdown(){return this.shutdownPromise?this.shutdownPromise:(this.shutdownPromise=this.performShutdown(),this.shutdownPromise)}async performShutdown(){this.pendingCompaction&&(clearTimeout(this.pendingCompaction.timer),this.pendingCompaction.reject(new Error("Codex ACP adapter closed")),this.pendingCompaction=null);for(const t of this.pendingParentRequests.values())clearTimeout(t.timer),t.reject(new Error("Codex ACP adapter closed"));this.pendingParentRequests.clear();for(const t of this.pendingTurnCompletions.values())clearTimeout(t.timer),t.reject(new Error("Codex ACP adapter closed"));this.pendingTurnCompletions.clear();const e=this.started?.child;!e||e.exitCode!==null||e.signalCode!==null||(e.stdin.end(),!await T(e,2e3)&&(e.kill("SIGTERM"),!await T(e,1e3)&&(e.kill("SIGKILL"),await T(e,1e3))))}sessionResponse(e){return{sessionId:e,modes:{currentModeId:"workspace-write",availableModes:[{id:"read-only",name:"Read Only"},{id:"workspace-write",name:"Workspace Write"},{id:"danger-full-access",name:"Full Access"}]},configOptions:[{id:"model",category:"model",type:"string"},{id:"approvalPolicy",category:"approval",type:"string"},{id:"sandbox",category:"mode",type:"string"}]}}requireRpc(){if(!this.rpc)throw new Error("Codex app-server is not started");return this.rpc}notify(e,t){this.write({jsonrpc:"2.0",method:e,params:t})}requestParent(e,t,s=600*1e3){const n=this.nextParentRequestId++;return new Promise((o,r)=>{const c=setTimeout(()=>{this.pendingParentRequests.delete(n),r(new Error(`ACP client request timed out: ${e}`))},s);c.unref(),this.pendingParentRequests.set(n,{resolve:o,reject:r,timer:c}),this.write({jsonrpc:"2.0",id:n,method:e,params:t})})}handleParentResponse(e){const t=Number(e.id),s=this.pendingParentRequests.get(t);if(s){if(this.pendingParentRequests.delete(t),clearTimeout(s.timer),"error"in e){const n=u(e.error);s.reject(new Error(a(n,"message")||"ACP client request failed"));return}s.resolve(e.result)}}setConfigOption(e){const t=a(e,"configId"),s=e.value;if(!t)throw new Error("configId is required");if(typeof s!="string"&&typeof s!="boolean")throw new Error("config value must be string or boolean");return this.config[t]=s,this.notify("session/update",{sessionId:this.currentSessionId,update:{sessionUpdate:"config_option_update",configId:t,value:s}}),{}}setMode(e){const t=a(e,"modeId");if(!t)throw new Error("modeId is required");return this.config.sandbox=t,this.notify("session/update",{sessionId:this.currentSessionId,update:{sessionUpdate:"current_mode_update",modeId:t}}),{}}applyInitialConfig(e){const t=m(e,"config");for(const[s,n]of Object.entries(t))(typeof n=="string"||typeof n=="boolean")&&(this.config[s]=n)}codexConfig(){return{...typeof this.config.model=="string"&&this.config.model?{model:this.config.model}:{},approvalPolicy:typeof this.config.approvalPolicy=="string"?this.config.approvalPolicy:"on-request",sandbox:typeof this.config.sandbox=="string"?this.config.sandbox:"workspace-write"}}captureContextNotification(e,t){if(e==="thread/tokenUsage/updated"&&(this.latestTokenUsage=W(t)??this.latestTokenUsage),e==="thread/compacted"&&this.pendingCompaction&&this.resolveCompaction(),e==="item/completed"){const s=m(t,"item"),n=a(u(t),"threadId");a(s,"type")==="contextCompaction"&&(!n||n===this.currentSessionId)&&this.resolveCompaction(),a(s,"type")==="agentMessage"&&a(s,"phase")==="final_answer"&&(this.currentAssistantText=g(s)||this.currentAssistantText)}}resolveCompaction(){if(!this.pendingCompaction)return;const e=this.pendingCompaction;this.pendingCompaction=null,clearTimeout(e.timer),e.resolve()}captureUpdateEvidence(e){String(e.sessionUpdate||"")==="agent_message_chunk"&&(this.currentAssistantText+=g(e.content)),w(e,this.currentAffectedFiles)}captureThreadMetadata(e){const t=m(e,"thread");this.currentRolloutPath=a(t,"path")||null,this.threadTurnCount=Array.isArray(t.turns)?t.turns.length:0;const s=re(t.turns);s.userRequest&&(this.previousUserText=s.userRequest),s.assistantResponse&&(this.previousAssistantText=s.assistantResponse),this.previousAffectedFiles.clear();for(const n of s.affectedFiles)this.previousAffectedFiles.add(n);this.latestTokenUsage=null}async maintainContext(e,t,s={}){const n=k(),o=this.contextState(),r=v(o,n);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("正在整理会话上下文",U(o)),this.maintenanceActive=!0;let d=!1,l="",h=o,p=r,I=!1,C="";try{const f=o.usage?.observedAtMs??0;await this.compactThread(c,s.compactTimeoutMs??n.compactTimeoutMs),await this.waitForTokenUsageAfter(f,2e3)}catch(f){d=!0,l=f instanceof Error?f.message:String(f)}try{if(e!=="manual"&&this.promptCancelled||this.shutdownPromise)throw new Error("Codex prompt cancelled");if(h=this.contextState(),p=v(h,n,{compactAttempted:!0,compactFailed:d}),p.rollover&&s.allowRollover!==!1){this.notifyContextStatus("正在创建轻量会话",p.reasons.join(", "));try{await this.rotateThread(c,t),I=!0}catch(f){C=f instanceof Error?f.message:String(f),this.notifyContextStatus("轻量会话创建失败",C)}}else d||this.notifyContextStatus("会话上下文已压缩",U(h))}finally{this.maintenanceActive=!1}return this.maintenanceResult({action:I?"rollover":C?"rollover_failed":d?"compact_failed":"compact",stage:e,reasons:p.reasons,beforeState:o,afterState:h,compactError:[l,C].filter(Boolean).join("; "),previousThreadId:c,rolloverRecommended:p.rollover&&s.allowRollover===!1})}compactThread(e,t){return this.pendingCompaction?Promise.reject(new Error("Codex context compaction is already running")):new Promise((s,n)=>{const o=setTimeout(()=>{this.pendingCompaction?.timer===o&&(this.pendingCompaction=null),n(new Error("Codex thread compaction timed out"))},t);o.unref(),this.pendingCompaction={resolve:s,reject:n,timer:o},this.requireRpc().request("thread/compact/start",{threadId:e},t).catch(r=>{this.pendingCompaction?.timer===o&&(this.pendingCompaction=null,clearTimeout(o),n(r instanceof Error?r:new Error(String(r))))})})}async rotateThread(e,t){const s=z({previousThreadId:e,cwd:this.currentCwd,userRequest:t.userRequest,assistantResponse:t.assistantResponse,affectedFiles:t.affectedFiles}),n=await this.requireRpc().request("thread/start",{cwd:this.currentCwd,approvalsReviewer:"user",...this.codexConfig(),developerInstructions:s,ephemeral:!1,experimentalRawEvents:!0,threadSource:"aca-codex-acp-rollover"},6e4),o=b(n);if(!o)throw new Error("Codex rollover thread/start did not return thread id");this.sessionAliases.set(e,o),this.currentSessionId=o,this.resetSubtaskState(n),this.captureThreadMetadata(n)}async waitForTokenUsageAfter(e,t){const s=Date.now()+t;for(;Date.now()<s;){if((this.latestTokenUsage?.observedAtMs??0)>e)return;await me(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:G(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(_(e))}maybeSteerControlToolLoop(e){const t=se(e.detection.kind,this.controlToolPolicy),s=this.controlToolSteering.next(e,t.warningCalls);if(!s)return;const{threadId:n,turnId:o}=e.detection;!n||!o||this.requireRpc().request("turn/steer",{threadId:n,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(M(e,s,{succeeded:!0}))}).catch(r=>{this.notifySessionUpdate(M(e,s,{succeeded:!1,error:r}))})}write(e){process.stdout.write(`${JSON.stringify(e)}
|
|
4
|
-
`)}}function
|
|
1
|
+
import{JsonLineRpcClient as D}from"../../client/json-rpc.js";import{startCodexAppServer as $}from"../codex/codex-process.js";import{CodexConnectionRecoveryTracker as L}from"../codex/connection-events.js";import{codexAcpSessionUpdatesFromNotification as C}from"../codex/events.js";import{CodexPlanDeltaState as y}from"../codex/plan-events.js";import{isCodexApprovalRequest as O,resolveCodexApprovalRequest as j}from"../codex/permissions.js";import{buildCodexContinuityInstructions as B,codexTokenUsageFromRawResponseCompleted as K,codexTokenUsageFromNotification as W,decideCodexContextMaintenance as k,readCodexContextMaintenancePolicy as v,rolloutFileSize as z}from"./context-maintenance.js";import{codexNotificationBelongsToCurrentTurn as G,codexNotificationRouting as R}from"./notification-routing.js";import{completedSubtaskHistoryUpdates as J}from"./subtask-history.js";import{CodexSubtaskRouter as H}from"./subtask-routing.js";import{decorateSubtaskUpdate as A}from"./subtask-executions.js";import{waitForCancellableTurnStart as V}from"./cancellation.js";import{codexAcpUpdateIsMeaningfulProgress as P,codexRawItemIsMeaningfulProgress as X,withCodexProgressMetadata as _}from"./progress-evidence.js";import{acpPromptText as Q,acpPromptToCodexInput as Y,turnConfigFromConfig as Z}from"./turn-input.js";import{CodexControlToolLoopDetector as ee,codexControlToolLoopAcpUpdate as M,controlToolLoopDetectionThresholds as te,controlToolLoopReady as se,readCodexControlToolLoopPolicy as oe}from"./control-tool-loop.js";import{CodexControlToolSteeringLimiter as ne,codexControlToolSteeringAcpUpdate as q}from"./control-tool-steering.js";class ie{lineBuffer="";started=null;rpc=null;codexStartupPromise=null;initialized=!1;currentSessionId=null;currentCwd=process.cwd();promptActive=!1;currentTurnId=null;turnStartPromise=null;promptCancelled=!1;nextParentRequestId=1;pendingParentRequests=new Map;completedTurns=new Map;pendingTurnCompletions=new Map;agentMessagePhases=new Map;planDeltas=new y;subtaskRouter=new H;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 L;latestTokenUsage=null;currentRolloutPath=null;threadTurnCount=0;currentPromptText="";currentAssistantText="";previousUserText="";previousAssistantText="";previousAffectedFiles=new Set;maintenanceActive=!1;pendingCompaction=null;shutdownPromise=null;managedProvider=null;controlToolLoop;controlToolPolicy=oe();controlToolSteering=new ne(this.controlToolPolicy.steeringMaxAttempts);constructor(){this.controlToolLoop=new ee({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 o=s.trim();if(!o)continue;let n;try{n=JSON.parse(o)}catch{continue}this.handleMessage(n)}}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,d(e.params));return}try{const o=await this.handleRequest(t,d(e.params));this.write({jsonrpc:"2.0",id:s,result:o})}catch(o){this.write({jsonrpc:"2.0",id:s,error:{code:-32603,message:o instanceof Error?o.message:String(o)}})}}}async handleNotification(e,t){if(e==="session/cancel")try{await this.cancel(t)}catch(s){process.stderr.write(`Codex cancel failed: ${s instanceof Error?s.message:String(s)}
|
|
3
|
+
`),await this.shutdown(),process.exit(1)}}async handleRequest(e,t){switch(e){case"initialize":return this.initializeResponse();case"aca/runtime_config":return this.applyRuntimeConfig(t);case"session/new":return this.newSession(t);case"session/resume":case"session/load":return this.resumeSession(t);case"session/fork":return this.forkSession(t);case"session/prompt":return this.prompt(t);case"session/compact":return this.manualCompact(t);case"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=d(e.codexProvider);if(Object.keys(t).length===0)return this.managedProvider=null,{configured:!1};const s=a(t,"baseUrl"),o=a(t,"apiKey"),n=Number(t.revision||0);if(!s||!o||!Number.isSafeInteger(n)||n<1)throw new Error("Invalid Codex managed provider runtime config");return this.managedProvider={baseUrl:s,apiKey:o,revision:n},{configured:!0,revision:n}}async newSession(e){const t=a(e,"cwd")||process.cwd();this.applyInitialConfig(e),await this.ensureCodex(t);const s=await this.requireRpc().request("thread/start",{cwd:t,approvalsReviewer:"user",...this.codexConfig(),ephemeral:!1,experimentalRawEvents:!0,threadSource:"aca-codex-acp"},6e4),o=x(s);if(!o)throw new Error("Codex thread/start did not return thread id");return this.currentSessionId=o,this.currentCwd=t,this.resetSubtaskState(),this.captureThreadMetadata(s),this.sessionResponse(o)}async resumeSession(e){const t=a(e,"sessionId");if(!t)throw new Error("sessionId is required");const s=a(e,"cwd")||this.currentCwd;this.applyInitialConfig(e),await this.ensureCodex(s);const o=await this.requireRpc().request("thread/resume",{threadId:t,cwd:s,approvalsReviewer:"user",...this.codexConfig()},6e4);return this.currentSessionId=x(o)||t,this.currentCwd=s,this.resetSubtaskState(o),await this.subscribeKnownSubtasks(!1),this.captureThreadMetadata(o),this.sessionResponse(this.currentSessionId)}async forkSession(e){const t=a(e,"sessionId");if(!t)throw new Error("sessionId is required");const s=a(e,"cwd")||this.currentCwd;this.applyInitialConfig(e),await this.ensureCodex(s);const o=await this.requireRpc().request("thread/fork",{threadId:t,cwd:s,approvalsReviewer:"user",ephemeral:!1,threadSource:"aca-codex-acp-fork",...this.codexConfig()},12e4),n=x(o);if(!n)throw new Error("Codex thread/fork did not return thread id");return this.currentSessionId=n,this.currentCwd=s,this.resetSubtaskState(o),this.captureThreadMetadata(o),this.sessionResponse(n)}async prompt(e){this.promptCancelled=!1;const t=a(e,"sessionId")||this.currentSessionId;let s=this.resolveSessionAlias(t);if(!s)throw new Error("sessionId is required");const o=Array.isArray(e.prompt)?e.prompt:[],n=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(ue(r),this.promptCancelled||this.shutdownPromise)throw new Error("Codex prompt cancelled");s=this.currentSessionId||s,this.promptActive=!0,this.currentTurnId=null,this.controlToolLoop.reset(),this.controlToolSteering.reset(),this.currentPromptText=Q(o),this.currentAssistantText="",this.currentAffectedFiles.clear(),this.connectionRecovery.reset(),this.agentMessagePhases.clear(),this.planDeltas.reset(),this.pendingSubtaskNotifications=[],this.flushSubtaskStates();let c;try{this.turnStartPromise=n.request("turn/start",{threadId:s,clientUserMessageId:`aca-acp-${Date.now()}-${Math.random().toString(16).slice(2)}`,input:Y(o),cwd:this.currentCwd,...Z(this.config)},N()),c=await this.turnStartPromise;const u=String(c?.turn?.id??"");this.currentTurnId=u||null;const l=u?await this.waitForTurnCompleted(u,N()):c?.turn,h=await this.subtaskRouter.waitForIdle(fe());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:U(f)??ce(this.latestTokenUsage),acaContextMaintenance:f,acaSubtaskDrain:h}}}catch(u){const l=this.connectionRecovery.fail(u);throw l&&this.notifySessionUpdate(l),u}finally{this.subtaskRouter.executions.loseObservation(this.promptCancelled?"父任务已中断,子执行状态需重新确认":"本轮观测结束,未确认子执行终态"),this.flushSubtaskStates(),this.controlToolLoop.reset(),this.promptActive=!1,this.currentTurnId=null,this.turnStartPromise=null,this.pendingSubtaskNotifications=[]}}resetSubtaskState(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 o=this.pendingSubtaskSubscriptions.get(s);if(o)return o;const n=this.subtaskGeneration,r=this.requireRpc().request("thread/resume",{threadId:s,approvalsReviewer:"user",excludeTurns:!1},6e4).then(c=>{if(n!==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(a(e,"sessionId")||this.currentSessionId);if(!t||!this.rpc)return;if(t!==this.currentSessionId)throw new Error("Cannot cancel a different Codex session");this.promptCancelled=!0,this.subtaskRouter.stopWaiting();const s=this.turnStartPromise?await V(this.turnStartPromise):null,o=this.currentTurnId||a(w(s,"turn"),"id");if(await this.rpc.request("turn/interrupt",{threadId:t,turnId:o},5e3),o){const n=this.pendingTurnCompletions.get(o);n&&(this.pendingTurnCompletions.delete(o),clearTimeout(n.timer),n.reject(new Error("Codex prompt cancelled")))}}async manualCompact(e){const t=a(e,"sessionId")||this.currentSessionId,s=this.resolveSessionAlias(t);if(!s)throw new Error("sessionId is required");if(this.promptActive||this.maintenanceActive)throw new Error("Codex session is busy");this.currentSessionId=s;const o=v(),n=await this.maintainContext("manual",{userRequest:this.previousUserText,assistantResponse:this.previousAssistantText,affectedFiles:[...this.previousAffectedFiles]},{force:!0,allowRollover:e.allowRollover===!0,compactTimeoutMs:ae(e,o.compactTimeoutMs)}),r=String(n.action||"");if(r==="compact_failed"||r==="rollover_failed"){const c=String(n.compactError||"Codex 上下文压缩未完成");throw new Error(`Codex 上下文压缩失败:${c}`)}return{sessionId:this.currentSessionId,maintenance:n,contextUsage:U(n)}}async recoverSession(e){const t=a(e,"sessionId")||this.currentSessionId,s=this.resolveSessionAlias(t);if(!s)throw new Error("sessionId is required");const o=a(e,"reason");if(o!=="low_output_loop"&&o!=="control_tool_loop")throw new Error("Unsupported Codex recovery reason");if(this.promptActive||this.maintenanceActive)throw new Error("Codex session is busy");if(s!==this.currentSessionId)throw new Error("Cannot recover a different Codex session");const n=new Set([...this.previousAffectedFiles,...this.currentAffectedFiles]);return await this.rotateThread(s,{userRequest:this.currentPromptText||this.previousUserText,assistantResponse:this.currentAssistantText||this.previousAssistantText,affectedFiles:[...n]}),{...d(this.sessionResponse(this.currentSessionId)),previousSessionId:s,recoveryReason:o}}async closeSession(e){await this.shutdown()}async ensureCodex(e){if(this.rpc&&this.started&&this.started.child.exitCode===null&&this.started.child.signalCode===null)return;if(this.codexStartupPromise){await this.codexStartupPromise;return}const t=(async()=>{if(this.rpc&&this.started&&this.started.child.exitCode===null&&this.started.child.signalCode===null)return;const s=$(e,this.managedProvider),o=new D(s.child,{peerName:"Codex app-server",onNotification:(n,r)=>this.handleCodexNotification(n,r),onRequest:async(n,r)=>this.handleCodexRequest(n,r)});this.started=s,this.rpc=o,s.child.once("exit",()=>{this.started===s&&(this.subtaskRouter.executions.loseObservation("执行连接断开,未确认子代理终态"),this.flushSubtaskStates(),this.subtaskRouter.stopWaiting())}),this.initialized=!1,await o.request("initialize",{clientInfo:{name:"aca-codex-acp",version:"0.1.0"},capabilities:{experimentalApi:!0,requestAttestation:!1}},6e4),this.initialized=!0})();this.codexStartupPromise=t;try{await t}catch(s){throw this.rpc&&this.started&&!this.initialized&&await T(this.started.child,1e3),this.rpc=null,this.started=null,s}finally{this.codexStartupPromise===t&&(this.codexStartupPromise=null)}}handleCodexNotification(e,t){const s=R(t);if(this.maintenanceActive&&(!s.threadId||s.threadId===this.currentSessionId)){this.captureContextNotification(e,t);return}if(!G(t,this.currentSessionId,this.currentTurnId)){if(!this.promptActive||!s.threadId||s.threadId===this.currentSessionId)return;const u=this.subtaskRouter.routeFor(s.threadId);if(!u){this.bufferPendingSubtaskNotification(e,t,s.threadId);return}this.handleSubtaskNotification(e,t,u),this.flushPendingSubtaskNotifications();return}const o=this.subtaskRouter.observe(t,this.currentSessionId);if(this.flushSubtaskStates(),o>0&&this.subscribeKnownSubtasks(!0),this.captureContextNotification(e,t),this.maintenanceActive||!this.promptActive&&e!=="turn/completed")return;this.rpc?.refreshPendingRequestTimeout("turn/start"),this.captureAgentMessagePhase(e,t);const n=this.connectionRecovery.beforeNotification(e,t);e==="turn/completed"&&this.resolveTurnCompleted(t);const r=e==="rawResponseItem/completed"?le(this.controlToolLoop,t):[],c=e==="rawResponse/completed"?de(t):e==="rawResponseItem/completed"?r.map(({update:u})=>u):C(e,t,{agentMessagePhases:this.agentMessagePhases,planDeltas:this.planDeltas});for(const u of r)this.maybeSteerControlToolLoop(u.event);for(const u of[...n,...c]){const l=_(u,e,t,this.agentMessagePhases);P(l)&&this.notifyControlToolProgress(),this.captureUpdateEvidence(l),this.notifySessionUpdate(l)}o>0&&this.flushPendingSubtaskNotifications()}handleSubtaskNotification(e,t,s){if(!this.promptActive)return;const o=this.subtaskRouter.acceptNotification(s,e,t);if(this.flushSubtaskStates(),!o)return;(e==="rawResponseItem/completed"||e==="item/completed")&&X(d(d(t).item))&&this.notifyControlToolProgress(),this.rememberSubtaskItem(s.sourceThreadId,t),this.rpc?.refreshPendingRequestTimeout("turn/start");const n=this.subtaskRouter.executions.get(s.sourceThreadId)?.runId||s.sourceThreadId,r=this.subtaskAgentMessagePhases.get(n)??new Map;this.subtaskAgentMessagePhases.set(n,r);const c=this.subtaskPlanDeltas.get(n)??new y;this.subtaskPlanDeltas.set(n,c),this.captureAgentMessagePhase(e,t,r);const u=this.subtaskRouter.observe(t,this.currentSessionId);this.flushSubtaskStates(),u>0&&this.subscribeKnownSubtasks(!0);const l=C(e,t,{agentMessagePhases:r,planDeltas:c}),h=R(t).turnId;for(const f of l){const p=_(f,e,t,r);if(!he(p))continue;a(p,"title")&&this.subtaskRouter.executions.action(s.sourceThreadId,a(p,"title")),P(p)&&(this.notifyControlToolProgress(),this.subtaskRouter.executions.progress(s.sourceThreadId,a(p,"title")||"收到执行结果"));const g=A(p,s,h,this.subtaskRouter.executions.get(s.sourceThreadId));b(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 J(e)){const o=F(t.sourceThreadId,s.itemId);if(this.seenSubtaskItemIds.has(o))continue;this.seenSubtaskItemIds.add(o);const n=A(s.update,t,s.sourceTurnId,{runId:`history.${t.sourceThreadId}.${s.sourceTurnId}`,state:"unknown",outputIncomplete:!0,reason:"历史过程回放,不代表当前执行状态"}),r=d(n._meta);n._meta={...r,aca:{...d(r.aca),historyReplay:!0}},b(n,this.currentAffectedFiles),this.notifySessionUpdate(n)}}}rememberSubtaskItem(e,t){const s=d(t),o=d(s.item),n=a(o,"id")||a(s,"itemId");n&&this.seenSubtaskItemIds.add(F(e,n))}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 o=this.subtaskRouter.routeFor(s.threadId);if(!o){t.push(s);continue}e=!0,this.handleSubtaskNotification(s.method,s.params,o)}this.pendingSubtaskNotifications=t}}notifySessionUpdate(e){this.notify("session/update",{sessionId:this.currentSessionId,update:e})}flushSubtaskStates(){if(this.promptActive)for(const e of this.subtaskRouter.executions.drainUpdates())this.notifySessionUpdate(e)}async handleCodexRequest(e,t){if(O(e))return j(e,t,async s=>{const o=await this.requestParent("session/request_permission",{sessionId:this.currentSessionId,request:{...s,params:t,method:e}}),n=d(o),r=d(n.outcome);return r.outcome==="selected"&&typeof r.optionId=="string"?{outcome:{outcome:"selected",optionId:r.optionId}}:{outcome:{outcome:"cancelled"}}});if(pe(e)){const s=await this.requestParent("elicitation/create",{sessionId:this.currentSessionId,request:{...d(t),method:e}}),o=d(s),n=d(o.outcome);return n.outcome==="submitted"?{outcome:{outcome:"submitted",value:typeof n.value=="string"?n.value:""}}:{outcome:{outcome:"cancelled"}}}return{}}captureAgentMessagePhase(e,t,s=this.agentMessagePhases){if(e!=="item/started"&&e!=="item/completed")return;const o=w(t,"item");if(a(o,"type")!=="agentMessage")return;const n=a(o,"id");n&&s.set(n,a(o,"phase"))}waitForTurnCompleted(e,t){const s=this.completedTurns.get(e);return s?Promise.resolve(s):new Promise((o,n)=>{const r=setTimeout(()=>{this.pendingTurnCompletions.delete(e),n(new Error("Codex app-server request timed out while waiting for turn/completed"))},t);r.unref(),this.pendingTurnCompletions.set(e,{resolve:o,reject:n,timer:r})})}resolveTurnCompleted(e){const t=w(e,"turn"),s=a(t,"id");if(!s)return;this.completedTurns.set(s,t);const o=this.pendingTurnCompletions.get(s);if(o){if(this.pendingTurnCompletions.delete(s),clearTimeout(o.timer),a(t,"status")==="failed"){o.reject(new Error(`Codex turn failed: ${JSON.stringify(t.error??{})}`));return}o.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 T(e,2e3)&&(e.kill("SIGTERM"),!await T(e,1e3)&&(e.kill("SIGKILL"),await T(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 o=this.nextParentRequestId++;return new Promise((n,r)=>{const c=setTimeout(()=>{this.pendingParentRequests.delete(o),r(new Error(`ACP client request timed out: ${e}`))},s);c.unref(),this.pendingParentRequests.set(o,{resolve:n,reject:r,timer:c}),this.write({jsonrpc:"2.0",id:o,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 o=d(e.error);s.reject(new Error(a(o,"message")||"ACP client request failed"));return}s.resolve(e.result)}}setConfigOption(e){const t=a(e,"configId"),s=e.value;if(!t)throw new Error("configId is required");if(typeof s!="string"&&typeof s!="boolean")throw new Error("config value must be string or boolean");return this.config[t]=s,this.notify("session/update",{sessionId:this.currentSessionId,update:{sessionUpdate:"config_option_update",configId:t,value:s}}),{}}setMode(e){const t=a(e,"modeId");if(!t)throw new Error("modeId is required");return this.config.sandbox=t,this.notify("session/update",{sessionId:this.currentSessionId,update:{sessionUpdate:"current_mode_update",modeId:t}}),{}}applyInitialConfig(e){const t=w(e,"config");for(const[s,o]of Object.entries(t))(typeof o=="string"||typeof o=="boolean")&&(this.config[s]=o)}codexConfig(){return{...typeof this.config.model=="string"&&this.config.model?{model:this.config.model}:{},approvalPolicy:typeof this.config.approvalPolicy=="string"?this.config.approvalPolicy:"on-request",sandbox:typeof this.config.sandbox=="string"?this.config.sandbox:"workspace-write"}}captureContextNotification(e,t){if(e==="thread/tokenUsage/updated"&&(this.latestTokenUsage=W(t)??this.latestTokenUsage),e==="thread/compacted"&&this.pendingCompaction&&this.resolveCompaction(),e==="item/completed"){const s=w(t,"item"),o=a(d(t),"threadId");a(s,"type")==="contextCompaction"&&(!o||o===this.currentSessionId)&&this.resolveCompaction(),a(s,"type")==="agentMessage"&&a(s,"phase")==="final_answer"&&(this.currentAssistantText=S(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+=S(e.content)),b(e,this.currentAffectedFiles)}captureThreadMetadata(e){const t=w(e,"thread");this.currentRolloutPath=a(t,"path")||null,this.threadTurnCount=Array.isArray(t.turns)?t.turns.length:0;const s=re(t.turns);s.userRequest&&(this.previousUserText=s.userRequest),s.assistantResponse&&(this.previousAssistantText=s.assistantResponse),this.previousAffectedFiles.clear();for(const o of s.affectedFiles)this.previousAffectedFiles.add(o);this.latestTokenUsage=null}async maintainContext(e,t,s={}){const o=v(),n=this.contextState(),r=k(n,o);if(!r.compact&&!s.force||!this.currentSessionId)return this.maintenanceResult({stage:e,action:"none",reasons:r.reasons,beforeState:n});const c=this.currentSessionId;this.notifyContextStatus("正在整理会话上下文",E(n)),this.maintenanceActive=!0;let u=!1,l="",h=n,f=r,p=!1,g="";try{const m=n.usage?.observedAtMs??0;await this.compactThread(c,s.compactTimeoutMs??o.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=k(h,o,{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("会话上下文已压缩",E(h))}finally{this.maintenanceActive=!1}return this.maintenanceResult({action:p?"rollover":g?"rollover_failed":u?"compact_failed":"compact",stage:e,reasons:f.reasons,beforeState:n,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,o)=>{const n=setTimeout(()=>{this.pendingCompaction?.timer===n&&(this.pendingCompaction=null),o(new Error("Codex thread compaction timed out"))},t);n.unref(),this.pendingCompaction={resolve:s,reject:o,timer:n},this.requireRpc().request("thread/compact/start",{threadId:e},t).catch(r=>{this.pendingCompaction?.timer===n&&(this.pendingCompaction=null,clearTimeout(n),o(r instanceof Error?r:new Error(String(r))))})})}async rotateThread(e,t){const s=B({previousThreadId:e,cwd:this.currentCwd,userRequest:t.userRequest,assistantResponse:t.assistantResponse,affectedFiles:t.affectedFiles}),o=await this.requireRpc().request("thread/start",{cwd:this.currentCwd,approvalsReviewer:"user",...this.codexConfig(),developerInstructions:s,ephemeral:!1,experimentalRawEvents:!0,threadSource:"aca-codex-acp-rollover"},6e4),n=x(o);if(!n)throw new Error("Codex rollover thread/start did not return thread id");this.sessionAliases.set(e,n),this.currentSessionId=n,this.resetSubtaskState(o),this.captureThreadMetadata(o)}async waitForTokenUsageAfter(e,t){const s=Date.now()+t;for(;Date.now()<s;){if((this.latestTokenUsage?.observedAtMs??0)>e)return;await me(50)}}resolveSessionAlias(e){if(!e)return null;let t=e;const s=new Set;for(;this.sessionAliases.has(t)&&!s.has(t);)s.add(t),t=this.sessionAliases.get(t)??t;return t}contextState(){return{usage:this.latestTokenUsage,rolloutBytes:z(this.currentRolloutPath),turnCount:this.threadTurnCount}}maintenanceResult(e){return{stage:e.stage,action:e.action,reasons:e.reasons,previousThreadId:e.previousThreadId??null,sessionId:this.currentSessionId,before:e.beforeState,after:e.afterState??null,compactError:e.compactError||null,rolloverRecommended:e.rolloverRecommended===!0}}notifyContextStatus(e,t){this.notify("session/update",{sessionId:this.currentSessionId,update:{sessionUpdate:"aca_context_maintenance",label:e,detail:t}})}notifyControlToolProgress(){const e=this.controlToolLoop.recordProgress();e&&this.notifySessionUpdate(M(e))}maybeSteerControlToolLoop(e){if(!se(e.detection,this.controlToolPolicy))return;const t=te(e.detection,this.controlToolPolicy),s=this.controlToolSteering.next(e,t.warningCalls);if(!s)return;const{threadId:o,turnId:n}=e.detection;!o||!n||this.requireRpc().request("turn/steer",{threadId:o,expectedTurnId:n,clientUserMessageId:`aca-loop-steer-${Date.now()}-${Math.random().toString(16).slice(2)}`,input:[{type:"text",text:s.text,text_elements:[]}]},6e4).then(()=>{this.notifySessionUpdate(q(e,s,{succeeded:!0}))}).catch(r=>{this.notifySessionUpdate(q(e,s,{succeeded:!1,error:r}))})}write(e){process.stdout.write(`${JSON.stringify(e)}
|
|
4
|
+
`)}}function x(i){const e=w(i,"thread");return a(e,"id")||null}function w(i,e){const t=d(i);return d(t[e])}function d(i){return i&&typeof i=="object"&&!Array.isArray(i)?i:{}}function S(i){if(typeof i=="string")return i;if(Array.isArray(i))return i.map(S).filter(Boolean).join("");if(!i||typeof i!="object")return"";const e=d(i);for(const t of["text","message","content","output_text"]){const s=S(e[t]);if(s)return s}return""}function b(i,e){if(Array.isArray(i)){for(const n of i)b(n,e);return}const t=d(i),s=a(t,"type").toLowerCase(),o=a(t,"path");if(o&&["diff","resource_link","content"].includes(s)&&e.add(o),Array.isArray(t.locations))for(const n of t.locations){const r=a(d(n),"path");r&&e.add(r)}for(const n of[t.content,t.update,t.rawInput,t.rawOutput])n&&n!==i&&b(n,e)}function re(i){if(!Array.isArray(i)||i.length===0)return{userRequest:"",assistantResponse:"",affectedFiles:[]};const e=i.at(-1),t=new Set;b(e,t);let s="",o="";const n=r=>{if(Array.isArray(r)){for(const l of r)n(l);return}if(!r||typeof r!="object")return;const c=d(r),u=a(c,"type").toLowerCase();u==="usermessage"&&(s=S(c)||s),u==="agentmessage"&&(o=S(c)||o);for(const l of["items","content","input"])c[l]&&n(c[l])};return n(e),{userRequest:s,assistantResponse:o,affectedFiles:[...t]}}function ae(i,e){const t=I(i,"timeoutMs");return!Number.isFinite(t)||t<=0?e:Math.max(3e4,Math.min(59e4,t-1e4))}function U(i){const e=String(i.action||"none"),t=d(i.after),s=d(i.before),o=d(t.usage),n=d(s.usage),r=Object.keys(o).length>0?o:n,c=I(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=I(r,"totalTokens"),l=I(r,"inputTokens");return{usedTokens:u||l,inputTokens:l,contextWindow:c,ratio:c>0?(u||l)/c:0,observedAtMs:I(r,"observedAtMs")||Date.now(),state:e==="compact"?"compacted":"active",maintenanceAction:e}}function ce(i){return i?{usedTokens:i.totalTokens||i.inputTokens,inputTokens:i.inputTokens,contextWindow:i.contextWindow,ratio:i.contextWindow>0?(i.totalTokens||i.inputTokens)/i.contextWindow:0,observedAtMs:i.observedAtMs,state:"active",maintenanceAction:null}:null}function E(i){const e=i.usage?`${i.usage.inputTokens} tokens`:"token unknown",t=i.rolloutBytes>0?`${Math.round(i.rolloutBytes/1024/1024)}MB rollout`:"rollout unknown";return`${e}, ${t}, ${i.turnCount} turns`}function ue(i){const e=String(i.action||"none");if(e!=="compact_failed"&&e!=="rollover_failed")return;const t=String(i.compactError||"Codex 上下文维护未完成");throw new Error(`Codex 自动上下文维护失败:${t}`)}function I(i,e){const t=d(i)[e];return typeof t=="number"&&Number.isFinite(t)?t:0}function de(i){return K(i)?[{sessionUpdate:"aca_codex_raw_response_usage",_meta:{codex:{rawResponse:i}}}]:[]}function le(i,e){const t=i.observeRawResponseItem(e);return t?[{event:t,update:M(t)}]:[]}function he(i){return["agent_message_chunk","agent_progress_chunk","agent_thought_chunk","tool_call","tool_call_update","plan","plan_update","plan_removed"].includes(String(i.sessionUpdate||""))}function F(i,e){return`${i}:${e}`}function a(i,e){const s=d(i)[e];return typeof s=="string"?s:""}function pe(i){const e=i.toLowerCase();return e.includes("elicitation")||e.includes("requestuserinput")||e.includes("request_user_input")||e.includes("input/request")}function N(){const i=Number.parseInt(process.env.ACA_ACP_PROMPT_TIMEOUT_MS??"",10);return Number.isInteger(i)&&i>=3e4?i:360*60*1e3}function fe(){const i=Number.parseInt(process.env.ACA_CODEX_SUBTASK_DRAIN_TIMEOUT_MS??"",10);return Number.isInteger(i)&&i>=1e3?Math.min(i,600*1e3):600*1e3}function T(i,e){return i.exitCode!==null||i.signalCode!==null?Promise.resolve(!0):new Promise(t=>{const s=setTimeout(()=>t(!1),e);s.unref(),i.once("close",()=>{clearTimeout(s),t(!0)})})}function me(i){return new Promise(e=>setTimeout(e,i))}function ge(){new ie}const we=process.argv[1]||"";/(?:^|[\\/])adapter\.(?:js|ts)$/.test(we)&&ge();export{ge as runCodexAcpAdapter};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{codexRawItemIsMeaningfulProgress as
|
|
1
|
+
import{codexRawItemIsMeaningfulProgress as E,toolOutputIsMeaningfulProgress as T}from"./progress-evidence.js";const L="aca_codex_control_tool_loop";class X{options;pendingControls=new Map;pendingWork=new Map;runningCommands=new Map;latest=null;nextSegmentId=1;progressVersion=0;constructor(n={}){this.options=n}observeRawResponseItem(n,r=Date.now()){const s=c(n),e=c(s.item),i=l(e,"type"),a=I(s.threadId),u=I(s.turnId);if(i==="function_call"||i==="custom_tool_call"){const o=P(e,a,u,this.options.shortSleepMaxDurationMs??x().shortSleepMaxDurationMs,this.options.hasActiveSubtasks?.()===!0,this.progressVersion);if(o)return this.pendingControls.set(o.callId,o),null;const g=l(e,"call_id")||l(e,"callId");return g&&this.pendingWork.set(g,{toolName:D(e),arguments:S(e.arguments??e.input)}),null}if(i==="function_call_output"||i==="custom_tool_call_output"){const o=l(e,"call_id")||l(e,"callId"),g=o?this.pendingControls.get(o):void 0;if(!g){const f=o?this.pendingWork.get(o):void 0;return o&&this.pendingWork.delete(o),this.updateRunningCommands(f,e.output),T(e.output)?this.recordProgress():null}return this.pendingControls.delete(o),g.progressVersion!==this.progressVersion||g.kind==="list_agents_no_live"&&!R(e.output)||g.kind==="wait_agent_no_live"&&(g.activeSubtasksAtCall||this.options.hasActiveSubtasks?.())?null:{state:"observed",detection:this.recordControlCall(g,r)}}return E(e)?this.recordProgress():null}recordProgress(){this.progressVersion+=1;const n=this.latest;return this.latest=null,n?{state:"recovered",detection:n}:null}reset(){this.pendingControls.clear(),this.pendingWork.clear(),this.runningCommands.clear(),this.latest=null,this.nextSegmentId=1,this.progressVersion=0}recordControlCall(n,r){const s=this.latest,e=s&&M(s.threadId,n.threadId)&&M(s.turnId,n.turnId),i=e&&s.kind!==n.kind?"mixed_control_no_progress":n.kind,a={kind:i,toolName:n.toolName,arguments:n.arguments,signature:i,sampleCount:e?s.sampleCount+1:1,firstObservedAtMs:e?s.firstObservedAtMs:r,detectedAtMs:r,elapsedMs:e?Math.max(0,r-s.firstObservedAtMs):0,segmentId:e?s.segmentId:this.nextSegmentId++,activeToolNames:this.activeToolNames(),containsDelegation:n.kind==="delegation_no_progress"||!!(e&&s.containsDelegation),threadId:n.threadId,turnId:n.turnId};return this.latest=a,a}activeToolNames(){return[...new Set([...[...this.pendingWork.values()].map(n=>n.toolName),...[...this.runningCommands.entries()].map(([n,r])=>`${r}(${n})`)].filter(Boolean))].sort()}updateRunningCommands(n,r){if(!(n&&!b(n.toolName))){for(const s of m(n?.arguments))this.runningCommands.delete(s);for(const s of m(r))this.runningCommands.set(s,n?.toolName||"后台命令")}}}function x(t=process.env){const n=d(t.ACA_CODEX_LIST_AGENTS_LOOP_WARNING_CALLS,3),r=d(t.ACA_CODEX_WAIT_AGENT_LOOP_WARNING_CALLS,3),s=d(t.ACA_CODEX_SEND_MESSAGE_LOOP_WARNING_CALLS,3),e=d(t.ACA_CODEX_SHORT_SLEEP_LOOP_WARNING_CALLS,5),i=d(t.ACA_CODEX_DELEGATION_LOOP_WARNING_CALLS,6);return{listAgentsWarningCalls:n,listAgentsInterruptCalls:Math.max(n+1,d(t.ACA_CODEX_LIST_AGENTS_LOOP_INTERRUPT_CALLS,5)),waitAgentWarningCalls:r,waitAgentInterruptCalls:Math.max(r+1,d(t.ACA_CODEX_WAIT_AGENT_LOOP_INTERRUPT_CALLS,5)),sendMessageWarningCalls:s,sendMessageInterruptCalls:Math.max(s+1,d(t.ACA_CODEX_SEND_MESSAGE_LOOP_INTERRUPT_CALLS,8)),shortSleepWarningCalls:e,shortSleepInterruptCalls:Math.max(e+1,d(t.ACA_CODEX_SHORT_SLEEP_LOOP_INTERRUPT_CALLS,8)),shortSleepMaxDurationMs:d(t.ACA_CODEX_SHORT_SLEEP_MAX_DURATION_MS,1e3),steeringMaxAttempts:d(t.ACA_CODEX_CONTROL_TOOL_STEERING_MAX_ATTEMPTS,3),delegationWarningCalls:i,delegationInterruptCalls:Math.max(i+1,d(t.ACA_CODEX_DELEGATION_LOOP_INTERRUPT_CALLS,12)),delegationMinElapsedMs:d(t.ACA_CODEX_DELEGATION_LOOP_MIN_ELAPSED_MS,6e4)}}function w(t,n){return t==="delegation_no_progress"?{warningCalls:n.delegationWarningCalls,interruptCalls:n.delegationInterruptCalls}:t==="mixed_control_no_progress"?{warningCalls:Math.min(n.listAgentsWarningCalls,n.waitAgentWarningCalls,n.sendMessageWarningCalls,n.shortSleepWarningCalls),interruptCalls:Math.max(n.listAgentsInterruptCalls,n.waitAgentInterruptCalls,n.sendMessageInterruptCalls,n.shortSleepInterruptCalls)}:t==="list_agents_no_live"?{warningCalls:n.listAgentsWarningCalls,interruptCalls:n.listAgentsInterruptCalls}:t==="wait_agent_no_live"?{warningCalls:n.waitAgentWarningCalls,interruptCalls:n.waitAgentInterruptCalls}:t==="send_message_no_progress"?{warningCalls:n.sendMessageWarningCalls,interruptCalls:n.sendMessageInterruptCalls}:{warningCalls:n.shortSleepWarningCalls,interruptCalls:n.shortSleepInterruptCalls}}function y(t,n){return!t.containsDelegation||t.elapsedMs>=n.delegationMinElapsedMs}function U(t,n){const r=w(t.kind,n);return t.containsDelegation?{warningCalls:Math.max(r.warningCalls,n.delegationWarningCalls),interruptCalls:Math.max(r.interruptCalls,n.delegationInterruptCalls)}:r}function $(t){return{sessionUpdate:L,_meta:{codex:{controlToolLoop:t}}}}function V(t){const n=c(t),r=c(n.update);if(l(r,"sessionUpdate")!==L)return null;const s=c(r._meta),e=c(s.codex),i=c(e.controlToolLoop),a=c(i.detection),u=l(i,"state"),o=l(a,"kind");return u!=="observed"&&u!=="recovered"||o!=="list_agents_no_live"&&o!=="wait_agent_no_live"&&o!=="send_message_no_progress"&&o!=="delegation_no_progress"&&o!=="mixed_control_no_progress"&&o!=="short_sleep"?null:{state:u,detection:{kind:o,toolName:l(a,"toolName"),arguments:a.arguments,signature:l(a,"signature"),sampleCount:C(a.sampleCount),firstObservedAtMs:C(a.firstObservedAtMs),detectedAtMs:C(a.detectedAtMs),elapsedMs:C(a.elapsedMs),segmentId:C(a.segmentId),activeToolNames:k(a.activeToolNames),containsDelegation:a.containsDelegation===!0||o==="delegation_no_progress",threadId:I(a.threadId),turnId:I(a.turnId)}}}function D(t){const n=l(t,"name"),r=l(t,"namespace");return r?`${r}.${n}`:n}function m(t,n=0){if(n>5)return[];if(typeof t=="string"){const e=h(t);if(e!==null)return m(e,n+1);const i=t.match(/Script running with cell ID\s+([^\s.]+)/i);return i?.[1]?[`cell:${i[1]}`]:[]}if(Array.isArray(t))return t.flatMap(e=>m(e,n+1));const r=c(t),s=[];for(const e of["cell_id","cellId"]){const i=r[e];typeof i=="string"&&i.trim()&&s.push(`cell:${i.trim()}`)}for(const e of["session_id","sessionId"]){const i=r[e];(typeof i=="string"||typeof i=="number")&&String(i).trim()&&s.push(`session:${String(i).trim()}`)}for(const e of["output","content","result","structuredContent"])e in r&&s.push(...m(r[e],n+1));return[...new Set(s)]}function b(t){const n=t.split(".").at(-1)||t;return["exec","execcommand","wait","writestdin"].includes(n.replace(/[^a-z0-9]/gi,"").toLowerCase())}function P(t,n,r,s,e,i){const a=l(t,"call_id")||l(t,"callId");if(!a)return null;const u=l(t,"name"),o=l(t,"namespace")||(u.includes(".")?u.slice(0,u.lastIndexOf(".")):""),g=u.includes(".")?u:o?`${o}.${u}`:u,f=S(t.arguments??t.input),N=u.split(".").at(-1)||u,p=N.replace(/[^a-z0-9]/gi,"").toLowerCase();let _=null;if(["spawnagent","followuptask","resumeagent"].includes(p)&&(!o||o==="collaboration"))_="delegation_no_progress";else if(p==="listagents"&&(!o||o==="collaboration"))_="list_agents_no_live";else if((p==="waitagent"||p==="wait")&&(!o||o==="collaboration"))_="wait_agent_no_live";else if(p==="sendmessage"&&(!o||o==="collaboration"))_="send_message_no_progress";else if(N==="sleep"&&(!o||o==="clock")){const O=W(c(f).duration_ms);O>=0&&O<=s&&(_="short_sleep")}return _?{callId:a,kind:_,toolName:g,arguments:f,threadId:n,turnId:r,activeSubtasksAtCall:e,progressVersion:i}:null}function R(t){const n=A(t,0);return n?!n.some(r=>{const s=c(r),e=String(s.agent_name??s.agentName??"").trim(),i=String(s.agent_status??s.agentStatus??s.status??"").trim().toLowerCase();return e==="/root"?!1:["pending","pendinginit","running","active","in_progress","inprogress"].includes(i)}):!1}function A(t,n){if(n>4)return null;if(typeof t=="string"){const s=h(t);return s===null?null:A(s,n+1)}if(Array.isArray(t)){for(const s of t){const e=c(s),i=A(e.text??e.content??s,n+1);if(i)return i}return null}const r=c(t);if(Array.isArray(r.agents))return r.agents;for(const s of["structuredContent","output","content","result"]){if(!(s in r))continue;const e=A(r[s],n+1);if(e)return e}return null}function S(t){return typeof t!="string"?t??{}:h(t)??t.trim()}function h(t){try{return JSON.parse(t)}catch{return null}}function M(t,n){return!t||!n||t===n}function d(t,n){const r=Number.parseInt(t??"",10);return Number.isInteger(r)&&r>0?r:n}function C(t){return typeof t=="number"&&Number.isFinite(t)&&t>=0?Math.floor(t):0}function W(t){return typeof t=="number"&&Number.isFinite(t)?t:Number.NaN}function c(t){return t&&typeof t=="object"&&!Array.isArray(t)?t:{}}function l(t,n){const r=t[n];return typeof r=="string"?r:""}function I(t){return typeof t=="string"&&t.trim()?t.trim():null}function k(t){return Array.isArray(t)?t.filter(n=>typeof n=="string"&&n.trim().length>0):[]}export{X as CodexControlToolLoopDetector,$ as codexControlToolLoopAcpUpdate,V as codexControlToolLoopFromAcpSessionUpdate,U as controlToolLoopDetectionThresholds,y as controlToolLoopReady,w as controlToolLoopThresholds,x as readCodexControlToolLoopPolicy};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
function c(e,t,s){const r=s.succeeded?"steeredAtMs":"failedAtMs";return{sessionUpdate:"aca_codex_control_tool_steer",label:s.succeeded?"已发送 Codex 工具循环纠偏提示":"Codex 工具循环纠偏发送失败",detail:s.succeeded?t.text:s.error instanceof Error?s.error.message:String(s.error),_meta:{aca:{controlToolSteering:{...e.detection,attempt:t.attempt,maxAttempts:t.maxAttempts,[r]:Date.now()}}}}}class g{maxAttempts;steeredStages=new Set;attemptsBySegment=new Map;attemptsByTurn=new Map;constructor(t){this.maxAttempts=t}next(t,s){const{threadId:r,turnId:n}=t.detection;if(!r||!n)return null;const o=`${r}:${n}`,i=this.attemptsByTurn.get(o)??0;if(i>=this.maxAttempts)return null;const d=`${o}:${t.detection.segmentId}`,l=(this.attemptsBySegment.get(d)??0)+1,a=p(t,s,l);if(!a||this.steeredStages.has(a.key))return null;const m=i+1;return this.steeredStages.add(a.key),this.attemptsBySegment.set(d,l),this.attemptsByTurn.set(o,m),{...a,attempt:m,maxAttempts:this.maxAttempts}}reset(){this.steeredStages.clear(),this.attemptsBySegment.clear(),this.attemptsByTurn.clear()}}function p(e,t,s=1){const r=t+Math.max(0,s-1)*2;if(e.state!=="observed"||e.detection.sampleCount<r)return null;const n=e.detection;return!n.threadId||!n.turnId?null:{key:`${n.threadId}:${n.turnId}:${n.segmentId}:${s}`,text:u(n)}}function u(e){const t=e.activeToolNames.length>0?` 当前仍有普通工具或后台命令等待收取结果(${e.activeToolNames.join("、")});请使用该工具对应的 wait、write_stdin 或结果收取机制继续等待,不能用子代理查询工具轮询普通命令。`:"";return e.kind==="list_agents_no_live"?`系统检测到当前没有运行中的子代理。不要继续调用 list_agents;请直接继续执行原始用户任务,并用实际工作成果推进任务。${t}`:e.kind==="wait_agent_no_live"?`系统检测到当前没有可等待的子代理。不要继续调用 wait_agent;请直接继续执行原始用户任务,并用实际工作成果推进任务。${t}`:e.kind==="send_message_no_progress"?`系统检测到连续投递子任务消息没有产生新进展。send_message 只投递消息,不会为已结束的子任务启动新 Turn;不要继续轮换 target 发送消息。需要让已结束子任务重新执行时使用 followup_task,否则请直接汇总已有结果并完成原始用户任务。${t}`:e.kind==="mixed_control_no_progress"
|
|
1
|
+
function c(e,t,s){const r=s.succeeded?"steeredAtMs":"failedAtMs";return{sessionUpdate:"aca_codex_control_tool_steer",label:s.succeeded?"已发送 Codex 工具循环纠偏提示":"Codex 工具循环纠偏发送失败",detail:s.succeeded?t.text:s.error instanceof Error?s.error.message:String(s.error),_meta:{aca:{controlToolSteering:{...e.detection,attempt:t.attempt,maxAttempts:t.maxAttempts,[r]:Date.now()}}}}}class g{maxAttempts;steeredStages=new Set;attemptsBySegment=new Map;attemptsByTurn=new Map;constructor(t){this.maxAttempts=t}next(t,s){const{threadId:r,turnId:n}=t.detection;if(!r||!n)return null;const o=`${r}:${n}`,i=this.attemptsByTurn.get(o)??0;if(i>=this.maxAttempts)return null;const d=`${o}:${t.detection.segmentId}`,l=(this.attemptsBySegment.get(d)??0)+1,a=p(t,s,l);if(!a||this.steeredStages.has(a.key))return null;const m=i+1;return this.steeredStages.add(a.key),this.attemptsBySegment.set(d,l),this.attemptsByTurn.set(o,m),{...a,attempt:m,maxAttempts:this.maxAttempts}}reset(){this.steeredStages.clear(),this.attemptsBySegment.clear(),this.attemptsByTurn.clear()}}function p(e,t,s=1){const r=t+Math.max(0,s-1)*2;if(e.state!=="observed"||e.detection.sampleCount<r)return null;const n=e.detection;return!n.threadId||!n.turnId?null:{key:`${n.threadId}:${n.turnId}:${n.segmentId}:${s}`,text:u(n)}}function u(e){const t=e.activeToolNames.length>0?` 当前仍有普通工具或后台命令等待收取结果(${e.activeToolNames.join("、")});请使用该工具对应的 wait、write_stdin 或结果收取机制继续等待,不能用子代理查询工具轮询普通命令。`:"";return e.kind==="list_agents_no_live"?`系统检测到当前没有运行中的子代理。不要继续调用 list_agents;请直接继续执行原始用户任务,并用实际工作成果推进任务。${t}`:e.kind==="wait_agent_no_live"?`系统检测到当前没有可等待的子代理。不要继续调用 wait_agent;请直接继续执行原始用户任务,并用实际工作成果推进任务。${t}`:e.kind==="send_message_no_progress"?`系统检测到连续投递子任务消息没有产生新进展。send_message 只投递消息,不会为已结束的子任务启动新 Turn;不要继续轮换 target 发送消息。需要让已结束子任务重新执行时使用 followup_task,否则请直接汇总已有结果并完成原始用户任务。${t}`:e.kind==="mixed_control_no_progress"?`系统检测到交替委派、查询、等待或投递子任务消息仍未产生实际进展。切换工具或 target 不能推进任务;停止重复创建或重启子任务,请收取真实运行任务的结果,或直接执行原始用户任务。${t}`:e.kind==="delegation_no_progress"?`系统检测到反复创建或重新启动子代理,却没有收到有效工作成果。停止重复委派;已有任务仍在运行时等待其结果,已结束时汇总已有成果,必要时由主代理直接推进原始任务。不要以再次启动子代理代替完成任务。${t}`:`系统检测到连续短时 sleep 没有带来进展。不要继续短时轮询;请直接继续执行原始用户任务,或使用能等待真实运行任务完成的机制。${t}`}export{g as CodexControlToolSteeringLimiter,c as codexControlToolSteeringAcpUpdate,p as controlToolLoopSteeringInput};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
function u(t){const
|
|
1
|
+
const d=new Set(["thread/status/changed","thread/name/updated","thread/tokenUsage/updated","thread/archived","thread/unarchived","account/updated","account/rateLimits/updated"]);function i(t){return d.has(t)}function u(t){const e=a(t),n=a(e.turn);return{threadId:o(e,"threadId")||null,turnId:o(e,"turnId")||o(n,"id")||null}}function c(t,e,n){const r=u(t);return!(r.threadId&&e&&r.threadId!==e||r.turnId&&n&&r.turnId!==n)}function a(t){return t&&typeof t=="object"&&!Array.isArray(t)?t:{}}function o(t,e){const n=t[e];return typeof n=="string"?n:""}export{c as codexNotificationBelongsToCurrentTurn,i as codexNotificationIsSubtaskMetadata,u as codexNotificationRouting};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import _ from"node:fs";import{runAbortableRequest as L}from"../../client/abortable-request.js";import{BoundedItemBuffer as B}from"../../client/bounded-buffer.js";import{promptBlocksToAcpContent as K}from"../../client/acp-content.js";import{acpSessionUpdateFromParams as X,eventFromAcpSessionUpdate as Y,statusFromAcpSessionUpdate as G,subtaskContextFromAcpSessionUpdate as J}from"../../client/acp-events.js";import{defaultAcpRuntimePool as E}from"../../client/acp-runtime-pool.js";import{resolveCodexAcpLaunch as D}from"./launch.js";import{cancelCodexRuntime as Q}from"./cancellation.js";import{CodexPromptPerformanceTracker as Z,CodexUsageLoopDetector as U,codexRawResponseUsageFromAcpSessionUpdate as ee,codexTokenUsageFromAcpSessionUpdate as oe,readCodexContextMaintenancePolicy as q}from"./context-maintenance.js";import{ensureCodexProviderRuntimeConfig as $}from"../../../core/codex-provider-runtime-config.js";import{codexControlToolLoopFromAcpSessionUpdate as te,controlToolLoopThresholds as se,readCodexControlToolLoopPolicy as re}from"./control-tool-loop.js";import{attachCodexDiagnostics as ne,CodexTurnDiagnosticsTracker as ie}from"./turn-diagnostics.js";import{AcpSessionInvalidatedError as O}from"../../session-errors.js";import{codexAcpUpdateIsMeaningfulProgress as ae}from"./progress-evidence.js";const ce={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 Ee{id="codex-acp";name="Codex ACP";capabilities=ce;async createSession(e){return new le(e.cwd,e.providerSessionId??null,e.timeoutMs)}async forkSession(e){if(!_.existsSync(e.cwd)||!_.statSync(e.cwd).isDirectory())throw new Error(`Project root does not exist or is not a directory: ${e.cwd}`);const t=e.sourceProviderSessionId.trim(),r=await $(),a=await E.acquire({providerId:"codex-acp",cwd:e.cwd,providerSessionId:t,runtimeIdentity:r?.runtimeIdentity||"default",env:N(r),...D(),onSessionUpdate:()=>{}});try{await W(a,r),a.initialized||(await a.client.initialize(6e4),a.markInitialized()),e.onStatus?.({phase:"forking",label:"正在分叉 Codex 会话",detail:t,updateType:"session/fork",atMs:Date.now()});const n=await a.client.forkSession({sessionId:t,cwd:e.cwd,config:F(e)},e.timeoutMs??12e4),o=b(n);if(!o)throw new Error("Codex ACP session/fork did not return sessionId");return{providerSessionId:o}}finally{a.release()}}}class le{cwd;providerSessionId;defaultTimeoutMs;lease=null;sessionResponse=null;statusCallback=null;updateCallback=null;permissionCallback=null;userInputCallback=null;abortHandler=null;statusTimeline=[];updates=new B(16);content="";performanceTracker=null;usageLoopDetector=null;usagePatternReport=null;controlToolPatternReport=null;turnDiagnosticsTracker=null;usageLoopWarningActive=!1;controlToolLoopWarningActive=!1;controlToolLoopInterrupting=!1;cancellation=null;constructor(e,t,r=ge()){this.cwd=e,this.providerSessionId=t,this.defaultTimeoutMs=r}async sendPrompt(e){if(!_.existsSync(e.cwd)||!_.statSync(e.cwd).isDirectory())throw new Error(`Project root does not exist or is not a directory: ${e.cwd}`);const t=Date.now();this.statusCallback=e.onStatus??null,this.updateCallback=e.onUpdate??null,this.permissionCallback=e.onPermissionRequest??null,this.userInputCallback=e.onUserInputRequest??null,this.attachAbortSignal(e.signal),this.content="",this.updates.clear(),this.statusTimeline.length=0;const r=q(),a=re();this.performanceTracker=new Z(t),this.usageLoopDetector=new U(r);const n=new ie;this.turnDiagnosticsTracker=n,this.usageLoopWarningActive=!1,this.controlToolLoopWarningActive=!1,this.controlToolLoopInterrupting=!1;let o={error:null,stopping:null,detection:null,controlDetection:null,interruption:null,kind:null},i=null,c=null,w=this.providerSessionId,x,k=null;try{this.emitStatus("initializing","Codex ACP 启动中","codex-acp");const m=await this.acquireRuntime(e.cwd);this.lease=m,i=m.client,c=m.started,await this.initialize(m),w=await this.establishSession(m,e),await this.applyConfig(i,w,e);const S=i,V=K(e.prompt,e.promptBlocks);let T=!1;for(this.emitStatus("thinking","思考中"),k=this.startSlowStatusTimer();;){const f=w;o={error:null,stopping:null,detection:null,controlDetection:null,interruption:null,kind:null},this.usageLoopDetector=new U(r),this.usageLoopWarningActive=!1,this.controlToolLoopWarningActive=!1,this.controlToolLoopInterrupting=!1,this.usagePatternReport=u=>{if(o.error)return;const v=Math.round(u.maxInputTokens/1e3),g=`同一 Turn 已连续请求至少 ${u.sampleCount} 次,单次输入约 ${v}k tokens`;if(n.recordUsageLoop(u.sampleCount,u.detectedAtMs,g),u.sampleCount<r.usageLoopInterruptSamples){this.usageLoopWarningActive=!0,this.emitStatus("thinking","Codex 出现低产出内部循环",g,"aca.performance.low_output_loop");return}const C=`Codex 低产出内部循环:同一 Turn 已连续请求至少 ${u.sampleCount} 次,单次输入约 ${v}k tokens,且未观察到有效进展`;o.error=new Error(C),o.detection=u,o.kind="low_output_loop",this.emitStatus("thinking","正在中断 Codex 低产出内部循环",g,"aca.performance.low_output_loop_interrupting"),o.stopping=this.stopRuntime(m,f).then(l=>{if(o.interruption=l,n.recordInterrupt({acknowledged:!l.forced,forcedRuntimeClose:l.forced,reason:l.reason}),l.forced){const d=l.reason||"取消请求失败";o.error=new Error(`${C};取消请求失败:${d};已关闭运行实例`)}}).catch(l=>{const d=l instanceof Error?l.message:String(l);n.recordInterrupt({acknowledged:!1,forcedRuntimeClose:!1,reason:d}),o.error=new Error(`${C};${d}`),this.emitStatus("failed","Codex 循环中断失败",d,"aca.performance.low_output_loop_interrupt_failed")})},this.controlToolPatternReport=u=>{if(o.error)return;const v=se(u.kind,a);if(u.sampleCount<v.warningCalls)return;const g=u.kind==="list_agents_no_live"?"list_agents 查询不到运行中的子任务":u.kind==="wait_agent_no_live"?"无运行子任务时调用 wait_agent":u.kind==="send_message_no_progress"?"send_message 投递未产生进展":u.kind==="mixed_control_no_progress"?"混合控制工具且未产生进展":"短时 sleep",C=Math.max(1,Math.round(u.elapsedMs/6e4)),l=`同一 Turn 已连续 ${u.sampleCount} 次执行 ${g},持续约 ${C} 分钟`;if(n.recordControlLoop(u,l),u.sampleCount<v.interruptCalls){this.controlToolLoopWarningActive=!0,this.emitStatus("thinking","Codex 出现无进展控制工具循环",l,"aca.performance.control_tool_loop");return}const d=`Codex 无进展控制工具循环:${l}`;o.error=new Error(d),o.controlDetection=u,o.kind="control_tool_loop",this.controlToolLoopInterrupting=!0,this.emitStatus("thinking","正在中断 Codex 无进展控制工具循环",l,"aca.performance.control_tool_loop_interrupting"),o.stopping=this.stopRuntime(m,f).then(p=>{o.interruption=p,n.recordInterrupt({acknowledged:!p.forced,forcedRuntimeClose:p.forced,reason:p.reason});const I=p.forced?`${l};取消请求失败:${p.reason};已关闭运行实例`:l;p.forced&&(o.error=new Error(`${d};取消请求失败:${p.reason};已关闭运行实例`)),this.emitStatus("failed","Codex 已中断无进展控制工具循环",I,"aca.performance.control_tool_loop_interrupted")}).catch(p=>{const I=p instanceof Error?p.message:String(p);n.recordInterrupt({acknowledged:!1,forcedRuntimeClose:!1,reason:I}),o.error=new Error(`${d};${I}`),this.emitStatus("failed","Codex 控制工具循环中断失败",I,"aca.performance.control_tool_loop_interrupt_failed")})};let P;try{x=await this.promptWithAbort(S,{sessionId:f,prompt:V,timeoutMs:e.timeoutMs??this.defaultTimeoutMs,signal:e.signal})}catch(u){P=u}if(await o.stopping,o.error){const u=o.detection;if(o.kind==="low_output_loop"&&u?.source==="thread_usage"&&!T&&o.interruption?.forced===!1&&!e.signal?.aborted){T=!0,this.emitStatus("resuming","正在迁移异常 Codex 会话","保留本轮进度并创建干净线程","aca.performance.low_output_loop_recovering");try{const l=await S.recoverSession({sessionId:f,reason:"low_output_loop"}),d=b(l);if(!d||d===f)throw new Error("Codex recovery did not create a new session");w=d,this.providerSessionId=d,this.sessionResponse=h(l),m.setProviderSession({providerSessionId:d,sessionResponse:this.sessionResponse});const p={previousProviderSessionId:f,providerSessionId:d,reason:"low_output_loop",migratedAtMs:Date.now()};n.recordMigration(p),await e.onProviderSessionMigration?.(p),n.recordRecovery(p.migratedAtMs),this.content="",this.emitStatus("thinking","Codex 会话已迁移,继续执行",d,"aca.performance.low_output_loop_recovered_session");continue}catch(l){const d=l instanceof Error?l.message:String(l);o.error=new Error(`${o.error.message};自动迁移失败:${d}`)}}if(o.kind==="low_output_loop")throw this.emitStatus("failed","Codex 已中断低产出内部循环",o.error.message,"aca.performance.low_output_loop_interrupted"),new O(o.error.message,f,{source:u?.source??null,sampleCount:u?.sampleCount??null,recoveryAttempted:T});const g=o.controlDetection;if(o.interruption?.forced===!1&&!e.signal?.aborted){this.emitStatus("resuming","正在迁移异常 Codex 会话","保留本轮证据并为后续任务创建干净线程","aca.performance.control_tool_loop_migrating");try{const l=await S.recoverSession({sessionId:f,reason:"control_tool_loop"}),d=b(l);if(!d||d===f)throw new Error("Codex recovery did not create a new session");w=d,this.providerSessionId=d,this.sessionResponse=h(l),m.setProviderSession({providerSessionId:d,sessionResponse:this.sessionResponse});const p={previousProviderSessionId:f,providerSessionId:d,reason:"control_tool_loop",migratedAtMs:Date.now()};throw n.recordMigration(p),await e.onProviderSessionMigration?.(p),this.emitStatus("failed","Codex 循环已中断,会话已自动迁移",d,"aca.performance.control_tool_loop_migrated_session"),de(o.error,d)}catch(l){if(ue(l))throw l;const d=l instanceof Error?l.message:String(l);o.error=new Error(`${o.error.message};自动迁移失败:${d}`)}}throw new O(o.error.message,f,{source:"control_tool_loop",kind:g?.kind??null,sampleCount:g?.sampleCount??null,segmentId:g?.segmentId??null})}if(P)throw P;const y=z(x);y&&y!==w&&(w=y,this.providerSessionId=y,m.setProviderSession({providerSessionId:y,sessionResponse:this.sessionResponse}));break}}catch(m){if(m instanceof Error){const S=ne(m,n.snapshot());throw S.statusTimeline=[...this.statusTimeline],S.performance=this.performanceTracker?.snapshot(),S}throw m}finally{await o.stopping,await this.cancellation?.catch(()=>{}),k&&clearInterval(k),this.usagePatternReport=null,this.controlToolPatternReport=null,this.turnDiagnosticsTracker=null,this.usageLoopDetector=null,this.usageLoopWarningActive=!1,this.controlToolLoopWarningActive=!1,this.controlToolLoopInterrupting=!1,this.abortHandler&&e.signal&&(e.signal.removeEventListener("abort",this.abortHandler),this.abortHandler=null),this.releaseLease()}return this.emitStatus("completed","已完成"),{providerSessionId:w,cliType:"builtin",agentType:"codex",effectiveConfig:{model:e.model??null,mode:e.mode??null,configOptionValues:e.configOptionValues??null,command:c.command,processPid:c.child.pid??null,provider:"codex-acp",protocol:"acp"},command:c.command,args:c.args,cwd:e.cwd,exitCode:c.child.exitCode,signal:c.child.signalCode,durationMs:Date.now()-t,stdout:i.rpc.stdout,stderr:i.rpc.stderr,content:this.content.trim(),updateCount:this.updates.count(),updates:this.updates.snapshot(),statusTimeline:[...this.statusTimeline],performance:this.performanceTracker?.snapshot(),contextUsage:Se(x),providerDiagnostics:n.snapshot(),promptResponse:x}}async compactContext(e){if(!_.existsSync(e.cwd)||!_.statSync(e.cwd).isDirectory())throw new Error(`Project root does not exist or is not a directory: ${e.cwd}`);const t=Date.now();this.statusCallback=e.onStatus??null,this.updateCallback=e.onUpdate??null,this.attachAbortSignal(e.signal);let r=this.providerSessionId,a;try{this.emitStatus("initializing","Codex ACP 启动中","codex-acp");const o=await this.acquireRuntime(e.cwd);this.lease=o,await this.initialize(o),r=await this.establishSession(o,e),await this.applyConfig(o.client,r,e);const i=we(e.timeoutMs);a=await this.compactWithAbort(o.client,{sessionId:r,allowRollover:e.allowRollover===!0,timeoutMs:i,signal:e.signal});const c=z(a);c&&c!==r&&(r=c,this.providerSessionId=c,o.setProviderSession({providerSessionId:c,sessionResponse:this.sessionResponse}))}catch(o){if(this.lease&&pe(o,e.signal)){const i=this.lease;this.lease=null,await i.dispose().catch(()=>{})}throw this.emitStatus("failed","上下文压缩失败",o instanceof Error?o.message:String(o)),o}finally{this.abortHandler&&e.signal&&(e.signal.removeEventListener("abort",this.abortHandler),this.abortHandler=null),this.releaseLease()}const n=h(a)??{};return{providerSessionId:r||this.providerSessionId||"",contextUsage:H(n.contextUsage),maintenance:h(n.maintenance)??{},durationMs:Date.now()-t}}async cancel(){this.providerSessionId&&this.lease&&await this.stopRuntime(this.lease,this.providerSessionId)}stopRuntime(e,t){if(this.cancellation)return this.cancellation;const r=Q(e,t);return this.cancellation=r,r.finally(()=>{this.cancellation===r&&(this.cancellation=null)}).catch(()=>{}),r}async close(){await this.cancellation?.catch(()=>{}),this.statusCallback=null,this.updateCallback=null,this.permissionCallback=null,this.userInputCallback=null,this.abortHandler&&(this.abortHandler=null),this.releaseLease()}async acquireRuntime(e){const t=D(),r=await $(),a=await E.acquire({providerId:"codex-acp",cwd:e,providerSessionId:this.providerSessionId,runtimeIdentity:r?.runtimeIdentity||"default",env:N(r),command:t.command,args:t.args,onSessionUpdate:n=>this.handleSessionUpdate(n),onClientRequest:async(n,o)=>{if(n==="session/request_permission")return this.requestPermission(o);if(n==="elicitation/create"||n==="session/request_user_input")return this.requestUserInput(o);throw new Error(`Unsupported Codex ACP client request: ${n}`)}});try{return await W(a,r),a}catch(n){throw await a.dispose().catch(()=>{}),n}}async initialize(e){e.initialized||(await e.client.initialize(6e4),e.markInitialized())}async establishSession(e,t){const r=e.client,a=F(t);if(e.providerSessionId&&e.sessionEstablished)return this.providerSessionId=e.providerSessionId,this.sessionResponse=e.sessionResponse,e.providerSessionId;if(this.providerSessionId)try{this.emitStatus("resuming","Codex ACP 会话恢复中","session/load");const i=await r.loadSession({sessionId:this.providerSessionId,cwd:t.cwd,config:a},6e4);this.sessionResponse=h(i);const c=b(i)||this.providerSessionId;return this.providerSessionId=c,e.setProviderSession({providerSessionId:c,sessionResponse:this.sessionResponse}),c}catch(i){if(t.requireSessionResume){const c=i instanceof Error?i.message:String(i);throw new Error(`无法恢复导入的 Codex ACP Session ${this.providerSessionId}:${c}`)}this.providerSessionId=null}this.emitStatus("acp","Codex ACP 会话创建中","session/new");const n=await r.newSession({cwd:t.cwd,config:a},12e4);this.sessionResponse=h(n);const o=b(n);if(!o)throw new Error("Codex ACP session/new did not return sessionId");return this.providerSessionId=o,e.setProviderSession({providerSessionId:o,sessionResponse:this.sessionResponse}),o}async applyConfig(e,t,r){r.model&&await e.setSessionConfigOption({sessionId:t,configId:"model",value:r.model},6e4).catch(()=>{});const a=M(r.mode);a&&await e.setSessionConfigOption({sessionId:t,configId:"approvalPolicy",value:a},6e4).catch(()=>{});const n=R(r.mode);n&&await e.setSessionConfigOption({sessionId:t,configId:"sandbox",value:n},6e4).catch(()=>{});for(const[o,i]of Object.entries(r.configOptionValues??{}))["model","mode","sandbox","approvalPolicy"].includes(o)||await e.setSessionConfigOption({sessionId:t,configId:o,value:i},6e4).catch(()=>{})}handleSessionUpdate(e){const t=X(e),r=String(t?.update.sessionUpdate||""),a=J(e);if(!a&&r==="aca_codex_subtask_drain"){const i=String(t?.update.label||"子任务收尾等待超时"),c=String(t?.update.detail||"仍有子任务未结束");this.emitStatus("exploring",i,c,r)}else if(!a&&r==="aca_codex_control_tool_loop"){const i=te(e);i?.state==="observed"&&this.controlToolPatternReport?.(i.detection),i?.state==="recovered"&&this.controlToolLoopWarningActive&&!this.controlToolLoopInterrupting&&(this.controlToolLoopWarningActive=!1,this.turnDiagnosticsTracker?.recordRecovery(),this.emitStatus("thinking","Codex 已恢复执行","无进展控制工具循环已结束","aca.performance.control_tool_loop_recovered"))}else if(!a&&r==="aca_codex_control_tool_steer"){const i=me(t?.update);i&&this.turnDiagnosticsTracker?.recordControlToolSteering(i)}else if(ae(t?.update))this.recordMeaningfulProgress();else if(!a&&r==="aca_codex_raw_response_usage"){const i=ee(e);i&&this.turnDiagnosticsTracker?.recordUsage(i);const c=i?this.usageLoopDetector?.recordUsage(i):null;c&&this.usagePatternReport?.(c)}else if(!a&&r==="usage_update"){const i=oe(e);i&&this.turnDiagnosticsTracker?.recordUsage(i);const c=i?this.usageLoopDetector?.recordUsage(i):null;c&&this.usagePatternReport?.(c)}this.updates.push({method:"session/update",params:e});const n=Y(e);n&&(n.type==="agent_message_chunk"&&!a&&(this.content+=n.text??""),this.updateCallback?.(n));const o=G(e);o&&this.emitStatus(o.phase,o.label,o.detail,o.updateType)}recordMeaningfulProgress(){this.performanceTracker?.recordEvent();const e=this.usageLoopDetector?.recordMeaningfulEvent();this.usageLoopWarningActive&&(this.usageLoopWarningActive=!1,e&&this.turnDiagnosticsTracker?.recordRecovery(),this.emitStatus("thinking","Codex 已恢复执行","已重新执行工具或产生有效输出","aca.performance.loop_recovered"))}startSlowStatusTimer(){const e=q().slowEventThresholdMs,t=setInterval(()=>{const r=this.performanceTracker,a=Date.now();if(!r?.shouldReportSlow(a,e))return;const n=r.snapshot(a),o=Math.max(1,Math.round(n.silentForMs/6e4)),i=this.usageLoopWarningActive||this.controlToolLoopWarningActive?"Codex 可能卡在内部循环":"Codex 长时间没有有效进展";this.emitStatus("thinking",i,`已 ${o} 分钟没有执行工具或产生有效输出`,"aca.performance.slow")},Math.min(3e4,Math.max(1e3,Math.floor(e/4))));return t.unref(),t}attachAbortSignal(e){if(e){if(this.abortHandler=()=>{this.cancel().catch(()=>{})},e.aborted)throw new Error("Codex ACP prompt cancelled");e.addEventListener("abort",this.abortHandler,{once:!0})}}async promptWithAbort(e,t){return L({signal:t.signal,cancelledMessage:"Codex ACP prompt cancelled",startRequest:()=>e.prompt({sessionId:t.sessionId,prompt:t.prompt},t.timeoutMs),onAbort:async()=>{await this.lease?.dispose()}})}async compactWithAbort(e,t){return L({signal:t.signal,cancelledMessage:"Codex ACP 上下文压缩已取消",startRequest:()=>e.compactSession({sessionId:t.sessionId,allowRollover:t.allowRollover,timeoutMs:t.timeoutMs},t.timeoutMs),onAbort:async()=>{await this.lease?.dispose()}})}async requestPermission(e){if(!this.permissionCallback)return{outcome:{outcome:"cancelled"}};const t=e&&typeof e=="object"&&!Array.isArray(e)?e:{},r=t.request&&typeof t.request=="object"&&!Array.isArray(t.request)?t.request:t,n=(Array.isArray(r.options)?r.options:[]).map((o,i)=>{const c=o&&typeof o=="object"&&!Array.isArray(o)?o:{};return{optionId:String(c.optionId??c.id??i),kind:String(c.kind??"choice"),...typeof c.label=="string"?{label:c.label}:{}}});return this.emitStatus("requestPermission","等待授权",he(r),"session/request_permission"),this.permissionCallback({requestId:String(r.requestId??r.id??`codex-acp-permission-${Date.now()}`),params:e,options:n})}async requestUserInput(e){if(!this.userInputCallback)return{outcome:{outcome:"cancelled"}};const t=e&&typeof e=="object"&&!Array.isArray(e)?e:{},r=t.request&&typeof t.request=="object"&&!Array.isArray(t.request)?t.request:t;return this.emitStatus("requestPermission","等待输入",fe(r),"elicitation/create"),this.userInputCallback({requestId:String(r.requestId??r.id??`codex-acp-input-${Date.now()}`),prompt:String(r.prompt??r.message??r.question??""),params:e,...typeof r.defaultValue=="string"?{defaultValue:r.defaultValue}:{}})}emitStatus(e,t,r,a){const n={phase:e,label:t,...r?{detail:r}:{},...a?{updateType:a}:{},atMs:Date.now()},o=this.statusTimeline[this.statusTimeline.length-1];o&&o.phase===n.phase&&o.label===n.label&&o.detail===n.detail&&o.updateType===n.updateType||(this.statusTimeline.push(n),this.statusCallback?.(n))}releaseLease(){this.lease?.release(),this.lease=null}}function de(s,e){const t=s;return t.controlLoopMigratedProviderSessionId=e,t}function ue(s){if(!s||typeof s!="object"||Array.isArray(s))return null;const e=s.controlLoopMigratedProviderSessionId;return typeof e=="string"&&e.trim()?e.trim():null}async function W(s,e){s.runtimeConfigured||(await s.client.configureRuntime({codexProvider:e?{baseUrl:e.baseUrl,apiKey:e.apiKey,revision:e.revision}:null}),s.markRuntimeConfigured())}function N(s){return s?{OPENAI_API_KEY:s.apiKey,OPENAI_BASE_URL:s.baseUrl}:{}}function pe(s,e){if(e?.aborted)return!0;const t=s instanceof Error?s.message:String(s||"");return/compact.*timed out|request timed out.*compact|adapter closed|aborted|cancelled/i.test(t)}function b(s){if(!s||typeof s!="object"||Array.isArray(s))return null;const e=s.sessionId;return typeof e=="string"&&e.trim()?e.trim():null}function h(s){return s&&typeof s=="object"&&!Array.isArray(s)?s:null}function me(s){const e=h(s),t=h(e?._meta),r=h(t?.aca),a=h(r?.controlToolSteering);if(!a)return null;const n=Number(a.attempt),o=Number(a.maxAttempts);if(!Number.isInteger(n)||n<=0||!Number.isInteger(o)||o<=0)return null;const i=Number(a.steeredAtMs),c=Number(a.failedAtMs);return{segmentId:Math.max(0,Math.floor(Number(a.segmentId)||0)),attempt:n,maxAttempts:o,succeeded:Number.isFinite(i)&&i>0,observedAtMs:Number.isFinite(i)&&i>0?i:Number.isFinite(c)&&c>0?c:Date.now()}}function R(s){const e=String(s||"").trim();return e?e==="read-only"?"read-only":["agent-full-access","full-access","bypassPermissions","danger-full-access"].includes(e)?"danger-full-access":"workspace-write":null}function M(s){const e=String(s||"").trim();return e?["agent-full-access","full-access","bypassPermissions","danger-full-access"].includes(e)?"never":e==="acceptEdits"||e==="accept-edits"?"on-failure":"on-request":null}function F(s){return{...s.model?{model:s.model}:{},...M(s.mode)?{approvalPolicy:M(s.mode)}:{},...R(s.mode)?{sandbox:R(s.mode)}:{},...s.configOptionValues??{}}}function he(s){const e=s.params&&typeof s.params=="object"&&!Array.isArray(s.params)?s.params:s;for(const t of["command","cwd","reason","grantRoot","method"]){const r=e[t];if(typeof r=="string"&&r.trim())return j(r)}}function fe(s){for(const e of["prompt","message","question","title"]){const t=s[e];if(typeof t=="string"&&t.trim())return j(t)}}function j(s){const e=s.replace(/\s+/g," ").trim();return e.length>120?`${e.slice(0,117)}...`:e}function ge(){const s=Number.parseInt(process.env.ACA_CODEX_ACP_PROMPT_TIMEOUT_MS??process.env.ACA_ACP_PROMPT_TIMEOUT_MS??"",10);return Number.isInteger(s)&&s>=3e4?s:360*60*1e3}function we(s){const e=Number.isInteger(s)?Number(s):Number.parseInt(process.env.ACA_CODEX_ACP_COMPACT_TIMEOUT_MS??"",10);return Number.isFinite(e)&&e>=3e4?Math.min(e,6e5):12e4}function z(s){if(!s||typeof s!="object"||Array.isArray(s))return null;const e=s;return typeof e.sessionId=="string"&&e.sessionId.trim()?e.sessionId:null}function Se(s){const e=h(s),t=h(e?._meta);return H(t?.acaContextUsage)}function H(s){const e=h(s);if(!e)return null;const t=A(e.contextWindow),r=A(e.usedTokens),a=A(e.inputTokens),n=String(e.state||"unknown"),o=["active","compacted","new_thread"].includes(n)?n:"unknown";return{usedTokens:r,inputTokens:a,contextWindow:t,ratio:t>0?Math.max(0,A(e.ratio)||r/t):0,observedAtMs:A(e.observedAtMs)||Date.now(),state:o,maintenanceAction:typeof e.maintenanceAction=="string"?e.maintenanceAction:null}}function A(s){return typeof s=="number"&&Number.isFinite(s)&&s>=0?s:0}export{Ee as CodexAcpProvider,ce as codexAcpCapabilities};
|
|
1
|
+
import _ from"node:fs";import{runAbortableRequest as L}from"../../client/abortable-request.js";import{BoundedItemBuffer as B}from"../../client/bounded-buffer.js";import{promptBlocksToAcpContent as K}from"../../client/acp-content.js";import{acpSessionUpdateFromParams as X,eventFromAcpSessionUpdate as Y,statusFromAcpSessionUpdate as G,subtaskContextFromAcpSessionUpdate as J}from"../../client/acp-events.js";import{defaultAcpRuntimePool as E}from"../../client/acp-runtime-pool.js";import{resolveCodexAcpLaunch as D}from"./launch.js";import{cancelCodexRuntime as Q}from"./cancellation.js";import{CodexPromptPerformanceTracker as Z,CodexUsageLoopDetector as U,codexRawResponseUsageFromAcpSessionUpdate as ee,codexTokenUsageFromAcpSessionUpdate as oe,readCodexContextMaintenancePolicy as q}from"./context-maintenance.js";import{ensureCodexProviderRuntimeConfig as $}from"../../../core/codex-provider-runtime-config.js";import{codexControlToolLoopFromAcpSessionUpdate as te,controlToolLoopDetectionThresholds as se,controlToolLoopReady as re,readCodexControlToolLoopPolicy as ne}from"./control-tool-loop.js";import{attachCodexDiagnostics as ie,CodexTurnDiagnosticsTracker as ae}from"./turn-diagnostics.js";import{AcpSessionInvalidatedError as O}from"../../session-errors.js";import{codexAcpUpdateIsMeaningfulProgress as ce}from"./progress-evidence.js";const le={sessionResume:!0,imageInput:!0,fileAttachment:!0,filesystem:!0,terminal:!0,permissionRequest:!0,configOptions:!0,usage:!0,contextUsage:!0,contextCompaction:!0,plan:!0,diff:!0};class De{id="codex-acp";name="Codex ACP";capabilities=le;async createSession(e){return new de(e.cwd,e.providerSessionId??null,e.timeoutMs)}async forkSession(e){if(!_.existsSync(e.cwd)||!_.statSync(e.cwd).isDirectory())throw new Error(`Project root does not exist or is not a directory: ${e.cwd}`);const t=e.sourceProviderSessionId.trim(),r=await $(),a=await E.acquire({providerId:"codex-acp",cwd:e.cwd,providerSessionId:t,runtimeIdentity:r?.runtimeIdentity||"default",env:N(r),...D(),onSessionUpdate:()=>{}});try{await W(a,r),a.initialized||(await a.client.initialize(6e4),a.markInitialized()),e.onStatus?.({phase:"forking",label:"正在分叉 Codex 会话",detail:t,updateType:"session/fork",atMs:Date.now()});const n=await a.client.forkSession({sessionId:t,cwd:e.cwd,config:F(e)},e.timeoutMs??12e4),o=b(n);if(!o)throw new Error("Codex ACP session/fork did not return sessionId");return{providerSessionId:o}}finally{a.release()}}}class de{cwd;providerSessionId;defaultTimeoutMs;lease=null;sessionResponse=null;statusCallback=null;updateCallback=null;permissionCallback=null;userInputCallback=null;abortHandler=null;statusTimeline=[];updates=new B(16);content="";performanceTracker=null;usageLoopDetector=null;usagePatternReport=null;controlToolPatternReport=null;turnDiagnosticsTracker=null;usageLoopWarningActive=!1;controlToolLoopWarningActive=!1;controlToolLoopInterrupting=!1;cancellation=null;constructor(e,t,r=we()){this.cwd=e,this.providerSessionId=t,this.defaultTimeoutMs=r}async sendPrompt(e){if(!_.existsSync(e.cwd)||!_.statSync(e.cwd).isDirectory())throw new Error(`Project root does not exist or is not a directory: ${e.cwd}`);const t=Date.now();this.statusCallback=e.onStatus??null,this.updateCallback=e.onUpdate??null,this.permissionCallback=e.onPermissionRequest??null,this.userInputCallback=e.onUserInputRequest??null,this.attachAbortSignal(e.signal),this.content="",this.updates.clear(),this.statusTimeline.length=0;const r=q(),a=ne();this.performanceTracker=new Z(t),this.usageLoopDetector=new U(r);const n=new ae;this.turnDiagnosticsTracker=n,this.usageLoopWarningActive=!1,this.controlToolLoopWarningActive=!1,this.controlToolLoopInterrupting=!1;let o={error:null,stopping:null,detection:null,controlDetection:null,interruption:null,kind:null},i=null,c=null,w=this.providerSessionId,x,k=null;try{this.emitStatus("initializing","Codex ACP 启动中","codex-acp");const m=await this.acquireRuntime(e.cwd);this.lease=m,i=m.client,c=m.started,await this.initialize(m),w=await this.establishSession(m,e),await this.applyConfig(i,w,e);const S=i,V=K(e.prompt,e.promptBlocks);let T=!1;for(this.emitStatus("thinking","思考中"),k=this.startSlowStatusTimer();;){const f=w;o={error:null,stopping:null,detection:null,controlDetection:null,interruption:null,kind:null},this.usageLoopDetector=new U(r),this.usageLoopWarningActive=!1,this.controlToolLoopWarningActive=!1,this.controlToolLoopInterrupting=!1,this.usagePatternReport=u=>{if(o.error)return;const v=Math.round(u.maxInputTokens/1e3),g=`同一 Turn 已连续请求至少 ${u.sampleCount} 次,单次输入约 ${v}k tokens`;if(n.recordUsageLoop(u.sampleCount,u.detectedAtMs,g),u.sampleCount<r.usageLoopInterruptSamples){this.usageLoopWarningActive=!0,this.emitStatus("thinking","Codex 出现低产出内部循环",g,"aca.performance.low_output_loop");return}const C=`Codex 低产出内部循环:同一 Turn 已连续请求至少 ${u.sampleCount} 次,单次输入约 ${v}k tokens,且未观察到有效进展`;o.error=new Error(C),o.detection=u,o.kind="low_output_loop",this.emitStatus("thinking","正在中断 Codex 低产出内部循环",g,"aca.performance.low_output_loop_interrupting"),o.stopping=this.stopRuntime(m,f).then(l=>{if(o.interruption=l,n.recordInterrupt({acknowledged:!l.forced,forcedRuntimeClose:l.forced,reason:l.reason}),l.forced){const d=l.reason||"取消请求失败";o.error=new Error(`${C};取消请求失败:${d};已关闭运行实例`)}}).catch(l=>{const d=l instanceof Error?l.message:String(l);n.recordInterrupt({acknowledged:!1,forcedRuntimeClose:!1,reason:d}),o.error=new Error(`${C};${d}`),this.emitStatus("failed","Codex 循环中断失败",d,"aca.performance.low_output_loop_interrupt_failed")})},this.controlToolPatternReport=u=>{if(o.error||!re(u,a))return;const v=se(u,a);if(u.sampleCount<v.warningCalls)return;const g=u.kind==="list_agents_no_live"?"list_agents 查询不到运行中的子任务":u.kind==="wait_agent_no_live"?"无运行子任务时调用 wait_agent":u.kind==="send_message_no_progress"?"send_message 投递未产生进展":u.kind==="delegation_no_progress"?"重复创建或重启子任务且未产生进展":u.kind==="mixed_control_no_progress"?"混合控制工具且未产生进展":"短时 sleep",C=Math.max(1,Math.round(u.elapsedMs/6e4)),l=`同一 Turn 已连续 ${u.sampleCount} 次执行 ${g},持续约 ${C} 分钟`;if(n.recordControlLoop(u,l),u.sampleCount<v.interruptCalls){this.controlToolLoopWarningActive=!0,this.emitStatus("thinking","Codex 出现无进展控制工具循环",l,"aca.performance.control_tool_loop");return}const d=`Codex 无进展控制工具循环:${l}`;o.error=new Error(d),o.controlDetection=u,o.kind="control_tool_loop",this.controlToolLoopInterrupting=!0,this.emitStatus("thinking","正在中断 Codex 无进展控制工具循环",l,"aca.performance.control_tool_loop_interrupting"),o.stopping=this.stopRuntime(m,f).then(p=>{o.interruption=p,n.recordInterrupt({acknowledged:!p.forced,forcedRuntimeClose:p.forced,reason:p.reason});const I=p.forced?`${l};取消请求失败:${p.reason};已关闭运行实例`:l;p.forced&&(o.error=new Error(`${d};取消请求失败:${p.reason};已关闭运行实例`)),this.emitStatus("failed","Codex 已中断无进展控制工具循环",I,"aca.performance.control_tool_loop_interrupted")}).catch(p=>{const I=p instanceof Error?p.message:String(p);n.recordInterrupt({acknowledged:!1,forcedRuntimeClose:!1,reason:I}),o.error=new Error(`${d};${I}`),this.emitStatus("failed","Codex 控制工具循环中断失败",I,"aca.performance.control_tool_loop_interrupt_failed")})};let P;try{x=await this.promptWithAbort(S,{sessionId:f,prompt:V,timeoutMs:e.timeoutMs??this.defaultTimeoutMs,signal:e.signal})}catch(u){P=u}if(await o.stopping,o.error){const u=o.detection;if(o.kind==="low_output_loop"&&u?.source==="thread_usage"&&!T&&o.interruption?.forced===!1&&!e.signal?.aborted){T=!0,this.emitStatus("resuming","正在迁移异常 Codex 会话","保留本轮进度并创建干净线程","aca.performance.low_output_loop_recovering");try{const l=await S.recoverSession({sessionId:f,reason:"low_output_loop"}),d=b(l);if(!d||d===f)throw new Error("Codex recovery did not create a new session");w=d,this.providerSessionId=d,this.sessionResponse=h(l),m.setProviderSession({providerSessionId:d,sessionResponse:this.sessionResponse});const p={previousProviderSessionId:f,providerSessionId:d,reason:"low_output_loop",migratedAtMs:Date.now()};n.recordMigration(p),await e.onProviderSessionMigration?.(p),n.recordRecovery(p.migratedAtMs),this.content="",this.emitStatus("thinking","Codex 会话已迁移,继续执行",d,"aca.performance.low_output_loop_recovered_session");continue}catch(l){const d=l instanceof Error?l.message:String(l);o.error=new Error(`${o.error.message};自动迁移失败:${d}`)}}if(o.kind==="low_output_loop")throw this.emitStatus("failed","Codex 已中断低产出内部循环",o.error.message,"aca.performance.low_output_loop_interrupted"),new O(o.error.message,f,{source:u?.source??null,sampleCount:u?.sampleCount??null,recoveryAttempted:T});const g=o.controlDetection;if(o.interruption?.forced===!1&&!e.signal?.aborted){this.emitStatus("resuming","正在迁移异常 Codex 会话","保留本轮证据并为后续任务创建干净线程","aca.performance.control_tool_loop_migrating");try{const l=await S.recoverSession({sessionId:f,reason:"control_tool_loop"}),d=b(l);if(!d||d===f)throw new Error("Codex recovery did not create a new session");w=d,this.providerSessionId=d,this.sessionResponse=h(l),m.setProviderSession({providerSessionId:d,sessionResponse:this.sessionResponse});const p={previousProviderSessionId:f,providerSessionId:d,reason:"control_tool_loop",migratedAtMs:Date.now()};throw n.recordMigration(p),await e.onProviderSessionMigration?.(p),this.emitStatus("failed","Codex 循环已中断,会话已自动迁移",d,"aca.performance.control_tool_loop_migrated_session"),ue(o.error,d)}catch(l){if(pe(l))throw l;const d=l instanceof Error?l.message:String(l);o.error=new Error(`${o.error.message};自动迁移失败:${d}`)}}throw new O(o.error.message,f,{source:"control_tool_loop",kind:g?.kind??null,sampleCount:g?.sampleCount??null,segmentId:g?.segmentId??null})}if(P)throw P;const y=z(x);y&&y!==w&&(w=y,this.providerSessionId=y,m.setProviderSession({providerSessionId:y,sessionResponse:this.sessionResponse}));break}}catch(m){if(m instanceof Error){const S=ie(m,n.snapshot());throw S.statusTimeline=[...this.statusTimeline],S.performance=this.performanceTracker?.snapshot(),S}throw m}finally{await o.stopping,await this.cancellation?.catch(()=>{}),k&&clearInterval(k),this.usagePatternReport=null,this.controlToolPatternReport=null,this.turnDiagnosticsTracker=null,this.usageLoopDetector=null,this.usageLoopWarningActive=!1,this.controlToolLoopWarningActive=!1,this.controlToolLoopInterrupting=!1,this.abortHandler&&e.signal&&(e.signal.removeEventListener("abort",this.abortHandler),this.abortHandler=null),this.releaseLease()}return this.emitStatus("completed","已完成"),{providerSessionId:w,cliType:"builtin",agentType:"codex",effectiveConfig:{model:e.model??null,mode:e.mode??null,configOptionValues:e.configOptionValues??null,command:c.command,processPid:c.child.pid??null,provider:"codex-acp",protocol:"acp"},command:c.command,args:c.args,cwd:e.cwd,exitCode:c.child.exitCode,signal:c.child.signalCode,durationMs:Date.now()-t,stdout:i.rpc.stdout,stderr:i.rpc.stderr,content:this.content.trim(),updateCount:this.updates.count(),updates:this.updates.snapshot(),statusTimeline:[...this.statusTimeline],performance:this.performanceTracker?.snapshot(),contextUsage:ve(x),providerDiagnostics:n.snapshot(),promptResponse:x}}async compactContext(e){if(!_.existsSync(e.cwd)||!_.statSync(e.cwd).isDirectory())throw new Error(`Project root does not exist or is not a directory: ${e.cwd}`);const t=Date.now();this.statusCallback=e.onStatus??null,this.updateCallback=e.onUpdate??null,this.attachAbortSignal(e.signal);let r=this.providerSessionId,a;try{this.emitStatus("initializing","Codex ACP 启动中","codex-acp");const o=await this.acquireRuntime(e.cwd);this.lease=o,await this.initialize(o),r=await this.establishSession(o,e),await this.applyConfig(o.client,r,e);const i=Se(e.timeoutMs);a=await this.compactWithAbort(o.client,{sessionId:r,allowRollover:e.allowRollover===!0,timeoutMs:i,signal:e.signal});const c=z(a);c&&c!==r&&(r=c,this.providerSessionId=c,o.setProviderSession({providerSessionId:c,sessionResponse:this.sessionResponse}))}catch(o){if(this.lease&&me(o,e.signal)){const i=this.lease;this.lease=null,await i.dispose().catch(()=>{})}throw this.emitStatus("failed","上下文压缩失败",o instanceof Error?o.message:String(o)),o}finally{this.abortHandler&&e.signal&&(e.signal.removeEventListener("abort",this.abortHandler),this.abortHandler=null),this.releaseLease()}const n=h(a)??{};return{providerSessionId:r||this.providerSessionId||"",contextUsage:H(n.contextUsage),maintenance:h(n.maintenance)??{},durationMs:Date.now()-t}}async cancel(){this.providerSessionId&&this.lease&&await this.stopRuntime(this.lease,this.providerSessionId)}stopRuntime(e,t){if(this.cancellation)return this.cancellation;const r=Q(e,t);return this.cancellation=r,r.finally(()=>{this.cancellation===r&&(this.cancellation=null)}).catch(()=>{}),r}async close(){await this.cancellation?.catch(()=>{}),this.statusCallback=null,this.updateCallback=null,this.permissionCallback=null,this.userInputCallback=null,this.abortHandler&&(this.abortHandler=null),this.releaseLease()}async acquireRuntime(e){const t=D(),r=await $(),a=await E.acquire({providerId:"codex-acp",cwd:e,providerSessionId:this.providerSessionId,runtimeIdentity:r?.runtimeIdentity||"default",env:N(r),command:t.command,args:t.args,onSessionUpdate:n=>this.handleSessionUpdate(n),onClientRequest:async(n,o)=>{if(n==="session/request_permission")return this.requestPermission(o);if(n==="elicitation/create"||n==="session/request_user_input")return this.requestUserInput(o);throw new Error(`Unsupported Codex ACP client request: ${n}`)}});try{return await W(a,r),a}catch(n){throw await a.dispose().catch(()=>{}),n}}async initialize(e){e.initialized||(await e.client.initialize(6e4),e.markInitialized())}async establishSession(e,t){const r=e.client,a=F(t);if(e.providerSessionId&&e.sessionEstablished)return this.providerSessionId=e.providerSessionId,this.sessionResponse=e.sessionResponse,e.providerSessionId;if(this.providerSessionId)try{this.emitStatus("resuming","Codex ACP 会话恢复中","session/load");const i=await r.loadSession({sessionId:this.providerSessionId,cwd:t.cwd,config:a},6e4);this.sessionResponse=h(i);const c=b(i)||this.providerSessionId;return this.providerSessionId=c,e.setProviderSession({providerSessionId:c,sessionResponse:this.sessionResponse}),c}catch(i){if(t.requireSessionResume){const c=i instanceof Error?i.message:String(i);throw new Error(`无法恢复导入的 Codex ACP Session ${this.providerSessionId}:${c}`)}this.providerSessionId=null}this.emitStatus("acp","Codex ACP 会话创建中","session/new");const n=await r.newSession({cwd:t.cwd,config:a},12e4);this.sessionResponse=h(n);const o=b(n);if(!o)throw new Error("Codex ACP session/new did not return sessionId");return this.providerSessionId=o,e.setProviderSession({providerSessionId:o,sessionResponse:this.sessionResponse}),o}async applyConfig(e,t,r){r.model&&await e.setSessionConfigOption({sessionId:t,configId:"model",value:r.model},6e4).catch(()=>{});const a=M(r.mode);a&&await e.setSessionConfigOption({sessionId:t,configId:"approvalPolicy",value:a},6e4).catch(()=>{});const n=R(r.mode);n&&await e.setSessionConfigOption({sessionId:t,configId:"sandbox",value:n},6e4).catch(()=>{});for(const[o,i]of Object.entries(r.configOptionValues??{}))["model","mode","sandbox","approvalPolicy"].includes(o)||await e.setSessionConfigOption({sessionId:t,configId:o,value:i},6e4).catch(()=>{})}handleSessionUpdate(e){const t=X(e),r=String(t?.update.sessionUpdate||""),a=J(e);if(!a&&r==="aca_codex_subtask_drain"){const i=String(t?.update.label||"子任务收尾等待超时"),c=String(t?.update.detail||"仍有子任务未结束");this.emitStatus("exploring",i,c,r)}else if(!a&&r==="aca_codex_control_tool_loop"){const i=te(e);i?.state==="observed"&&this.controlToolPatternReport?.(i.detection),i?.state==="recovered"&&this.controlToolLoopWarningActive&&!this.controlToolLoopInterrupting&&(this.controlToolLoopWarningActive=!1,this.turnDiagnosticsTracker?.recordRecovery(),this.emitStatus("thinking","Codex 已恢复执行","无进展控制工具循环已结束","aca.performance.control_tool_loop_recovered"))}else if(!a&&r==="aca_codex_control_tool_steer"){const i=he(t?.update);i&&this.turnDiagnosticsTracker?.recordControlToolSteering(i)}else if(ce(t?.update))this.recordMeaningfulProgress();else if(!a&&r==="aca_codex_raw_response_usage"){const i=ee(e);i&&this.turnDiagnosticsTracker?.recordUsage(i);const c=i?this.usageLoopDetector?.recordUsage(i):null;c&&this.usagePatternReport?.(c)}else if(!a&&r==="usage_update"){const i=oe(e);i&&this.turnDiagnosticsTracker?.recordUsage(i);const c=i?this.usageLoopDetector?.recordUsage(i):null;c&&this.usagePatternReport?.(c)}this.updates.push({method:"session/update",params:e});const n=Y(e);n&&(n.type==="agent_message_chunk"&&!a&&(this.content+=n.text??""),this.updateCallback?.(n));const o=G(e);o&&this.emitStatus(o.phase,o.label,o.detail,o.updateType)}recordMeaningfulProgress(){this.performanceTracker?.recordEvent();const e=this.usageLoopDetector?.recordMeaningfulEvent();this.usageLoopWarningActive&&(this.usageLoopWarningActive=!1,e&&this.turnDiagnosticsTracker?.recordRecovery(),this.emitStatus("thinking","Codex 已恢复执行","已重新执行工具或产生有效输出","aca.performance.loop_recovered"))}startSlowStatusTimer(){const e=q().slowEventThresholdMs,t=setInterval(()=>{const r=this.performanceTracker,a=Date.now();if(!r?.shouldReportSlow(a,e))return;const n=r.snapshot(a),o=Math.max(1,Math.round(n.silentForMs/6e4)),i=this.usageLoopWarningActive||this.controlToolLoopWarningActive?"Codex 可能卡在内部循环":"Codex 长时间没有有效进展";this.emitStatus("thinking",i,`已 ${o} 分钟没有执行工具或产生有效输出`,"aca.performance.slow")},Math.min(3e4,Math.max(1e3,Math.floor(e/4))));return t.unref(),t}attachAbortSignal(e){if(e){if(this.abortHandler=()=>{this.cancel().catch(()=>{})},e.aborted)throw new Error("Codex ACP prompt cancelled");e.addEventListener("abort",this.abortHandler,{once:!0})}}async promptWithAbort(e,t){return L({signal:t.signal,cancelledMessage:"Codex ACP prompt cancelled",startRequest:()=>e.prompt({sessionId:t.sessionId,prompt:t.prompt},t.timeoutMs),onAbort:async()=>{await this.lease?.dispose()}})}async compactWithAbort(e,t){return L({signal:t.signal,cancelledMessage:"Codex ACP 上下文压缩已取消",startRequest:()=>e.compactSession({sessionId:t.sessionId,allowRollover:t.allowRollover,timeoutMs:t.timeoutMs},t.timeoutMs),onAbort:async()=>{await this.lease?.dispose()}})}async requestPermission(e){if(!this.permissionCallback)return{outcome:{outcome:"cancelled"}};const t=e&&typeof e=="object"&&!Array.isArray(e)?e:{},r=t.request&&typeof t.request=="object"&&!Array.isArray(t.request)?t.request:t,n=(Array.isArray(r.options)?r.options:[]).map((o,i)=>{const c=o&&typeof o=="object"&&!Array.isArray(o)?o:{};return{optionId:String(c.optionId??c.id??i),kind:String(c.kind??"choice"),...typeof c.label=="string"?{label:c.label}:{}}});return this.emitStatus("requestPermission","等待授权",fe(r),"session/request_permission"),this.permissionCallback({requestId:String(r.requestId??r.id??`codex-acp-permission-${Date.now()}`),params:e,options:n})}async requestUserInput(e){if(!this.userInputCallback)return{outcome:{outcome:"cancelled"}};const t=e&&typeof e=="object"&&!Array.isArray(e)?e:{},r=t.request&&typeof t.request=="object"&&!Array.isArray(t.request)?t.request:t;return this.emitStatus("requestPermission","等待输入",ge(r),"elicitation/create"),this.userInputCallback({requestId:String(r.requestId??r.id??`codex-acp-input-${Date.now()}`),prompt:String(r.prompt??r.message??r.question??""),params:e,...typeof r.defaultValue=="string"?{defaultValue:r.defaultValue}:{}})}emitStatus(e,t,r,a){const n={phase:e,label:t,...r?{detail:r}:{},...a?{updateType:a}:{},atMs:Date.now()},o=this.statusTimeline[this.statusTimeline.length-1];o&&o.phase===n.phase&&o.label===n.label&&o.detail===n.detail&&o.updateType===n.updateType||(this.statusTimeline.push(n),this.statusCallback?.(n))}releaseLease(){this.lease?.release(),this.lease=null}}function ue(s,e){const t=s;return t.controlLoopMigratedProviderSessionId=e,t}function pe(s){if(!s||typeof s!="object"||Array.isArray(s))return null;const e=s.controlLoopMigratedProviderSessionId;return typeof e=="string"&&e.trim()?e.trim():null}async function W(s,e){s.runtimeConfigured||(await s.client.configureRuntime({codexProvider:e?{baseUrl:e.baseUrl,apiKey:e.apiKey,revision:e.revision}:null}),s.markRuntimeConfigured())}function N(s){return s?{OPENAI_API_KEY:s.apiKey,OPENAI_BASE_URL:s.baseUrl}:{}}function me(s,e){if(e?.aborted)return!0;const t=s instanceof Error?s.message:String(s||"");return/compact.*timed out|request timed out.*compact|adapter closed|aborted|cancelled/i.test(t)}function b(s){if(!s||typeof s!="object"||Array.isArray(s))return null;const e=s.sessionId;return typeof e=="string"&&e.trim()?e.trim():null}function h(s){return s&&typeof s=="object"&&!Array.isArray(s)?s:null}function he(s){const e=h(s),t=h(e?._meta),r=h(t?.aca),a=h(r?.controlToolSteering);if(!a)return null;const n=Number(a.attempt),o=Number(a.maxAttempts);if(!Number.isInteger(n)||n<=0||!Number.isInteger(o)||o<=0)return null;const i=Number(a.steeredAtMs),c=Number(a.failedAtMs);return{segmentId:Math.max(0,Math.floor(Number(a.segmentId)||0)),attempt:n,maxAttempts:o,succeeded:Number.isFinite(i)&&i>0,observedAtMs:Number.isFinite(i)&&i>0?i:Number.isFinite(c)&&c>0?c:Date.now()}}function R(s){const e=String(s||"").trim();return e?e==="read-only"?"read-only":["agent-full-access","full-access","bypassPermissions","danger-full-access"].includes(e)?"danger-full-access":"workspace-write":null}function M(s){const e=String(s||"").trim();return e?["agent-full-access","full-access","bypassPermissions","danger-full-access"].includes(e)?"never":e==="acceptEdits"||e==="accept-edits"?"on-failure":"on-request":null}function F(s){return{...s.model?{model:s.model}:{},...M(s.mode)?{approvalPolicy:M(s.mode)}:{},...R(s.mode)?{sandbox:R(s.mode)}:{},...s.configOptionValues??{}}}function fe(s){const e=s.params&&typeof s.params=="object"&&!Array.isArray(s.params)?s.params:s;for(const t of["command","cwd","reason","grantRoot","method"]){const r=e[t];if(typeof r=="string"&&r.trim())return j(r)}}function ge(s){for(const e of["prompt","message","question","title"]){const t=s[e];if(typeof t=="string"&&t.trim())return j(t)}}function j(s){const e=s.replace(/\s+/g," ").trim();return e.length>120?`${e.slice(0,117)}...`:e}function we(){const s=Number.parseInt(process.env.ACA_CODEX_ACP_PROMPT_TIMEOUT_MS??process.env.ACA_ACP_PROMPT_TIMEOUT_MS??"",10);return Number.isInteger(s)&&s>=3e4?s:360*60*1e3}function Se(s){const e=Number.isInteger(s)?Number(s):Number.parseInt(process.env.ACA_CODEX_ACP_COMPACT_TIMEOUT_MS??"",10);return Number.isFinite(e)&&e>=3e4?Math.min(e,6e5):12e4}function z(s){if(!s||typeof s!="object"||Array.isArray(s))return null;const e=s;return typeof e.sessionId=="string"&&e.sessionId.trim()?e.sessionId:null}function ve(s){const e=h(s),t=h(e?._meta);return H(t?.acaContextUsage)}function H(s){const e=h(s);if(!e)return null;const t=A(e.contextWindow),r=A(e.usedTokens),a=A(e.inputTokens),n=String(e.state||"unknown"),o=["active","compacted","new_thread"].includes(n)?n:"unknown";return{usedTokens:r,inputTokens:a,contextWindow:t,ratio:t>0?Math.max(0,A(e.ratio)||r/t):0,observedAtMs:A(e.observedAtMs)||Date.now(),state:o,maintenanceAction:typeof e.maintenanceAction=="string"?e.maintenanceAction:null}}function A(s){return typeof s=="number"&&Number.isFinite(s)&&s>=0?s:0}export{De as CodexAcpProvider,le as codexAcpCapabilities};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{randomUUID as f}from"node:crypto";import{codexNotificationIsSubtaskMetadata as g}from"./notification-routing.js";class m{runs=new Map;starts=new Set;closedTurns=new Map;pending=new Map;reset(){this.runs.clear(),this.starts.clear(),this.closedTurns.clear(),this.pending.clear()}get(s){return this.runs.get(s)?.snapshot}hasStart(s,n){return this.starts.has(`${s}:${n}`)}activeThreadIds(){return[...this.runs].filter(([,s])=>d(s.snapshot.state)).map(([s])=>s)}start(s,n,t,i={}){const o=`${s.sourceThreadId}:${n}`;if(this.starts.has(o))return;this.starts.add(o);const a=this.runs.get(s.sourceThreadId);if(a&&d(a.snapshot.state)){this.change(a,{...i,state:a.snapshot.state==="running"?"running":t});return}const e={route:{...s},generation:(a?.generation??0)+1,snapshot:{...i,runId:f(),state:t,startedAtMs:Date.now()}};this.runs.set(s.sourceThreadId,e),this.pending.set(e.snapshot.runId,e)}observeState(s,n){let t=this.runs.get(s.sourceThreadId);if(!t)t={route:{...s},generation:1,snapshot:{runId:f(),state:"unknown"}},this.runs.set(s.sourceThreadId,t),this.pending.set(t.snapshot.runId,t);else if(u(t.snapshot.state))return;if(!(t.snapshot.state==="running"&&n==="pending")){if(t.generation>1&&u(n)){this.change(t,{state:"unknown",reason:t.outputRiskReason??"收到无法确认执行归属的结束通知"});return}this.change(t,{state:n,...u(n)?{endedAtMs:Date.now()}:{}})}}accept(s,n,t){if(g(n))return!1;const i=s.sourceThreadId,o=h(t.turn),a=p(t.turnId)||p(o.id);if(a&&this.closedTurns.get(i)?.has(a))return!1;let e=this.runs.get(i);if(n==="turn/started"&&a&&(e?.snapshot.sourceTurnId&&e.snapshot.sourceTurnId!==a&&d(e.snapshot.state)&&(this.closeTurn(i,e.snapshot.sourceTurnId),this.markOutputRisk(e,"新执行开始前未收到旧执行结束事件","unknown")),this.start(s,`turn:${a}`,"running"),e=this.runs.get(i)),e||(this.observeState(s,"unknown"),e=this.runs.get(i)),u(e.snapshot.state)||a&&e.snapshot.sourceTurnId&&a!==e.snapshot.sourceTurnId)return!1;if(e.generation>1&&!e.snapshot.sourceTurnId&&n!=="turn/started")return this.markOutputRisk(e,`部分事件缺少新执行归属,已隔离(${n.slice(0,120)})`),!1;if(!a&&e.generation>1&&n!=="turn/started")return this.markOutputRisk(e,`部分事件缺少新执行归属,已隔离(${n.slice(0,120)})`),!1;if(a&&!e.snapshot.sourceTurnId&&this.change(e,{sourceTurnId:a}),n==="turn/completed"){const c=p(o.status).toLowerCase(),l=c==="failed"?"failed":["interrupted","cancelled","canceled"].includes(c)?"interrupted":c==="completed"?"completed":"unknown";l==="unknown"?this.markOutputRisk(e,"结束通知未提供可识别的执行结果",l):this.change(e,{state:l,endedAtMs:Date.now(),reason:e.outputRiskReason}),a&&this.closeTurn(i,a)}return!0}progress(s,n){const t=this.runs.get(s);!t||u(t.snapshot.state)||(t.snapshot={...t.snapshot,lastProgressAtMs:Date.now(),...n?{lastAction:n}:{}})}action(s,n){const t=this.runs.get(s);t&&!u(t.snapshot.state)&&(t.snapshot={...t.snapshot,lastAction:n})}loseObservation(s){for(const n of this.runs.values())(d(n.snapshot.state)||n.snapshot.state==="unknown"&&n.snapshot.startedAtMs!==void 0)&&this.markOutputRisk(n,s,"unknown")}drainUpdates(){const s=[...this.pending.values()].map(({route:n,snapshot:t})=>I({sessionUpdate:"tool_call_update",toolCallId:`run.${t.runId}`,kind:"other",title:"子代理执行状态",status:t.state==="failed"?"failed":t.state==="interrupted"?"cancelled":t.state==="completed"?"completed":"in_progress",_meta:{aca:{subtaskLifecycle:!0,processKind:"subtask"}}},n,t.sourceTurnId||null,t));return this.pending.clear(),s}markOutputRisk(s,n,t){s.outputRiskReason??=n,this.change(s,{outputIncomplete:!0,reason:s.outputRiskReason,...t?{state:t}:{}})}closeTurn(s,n){const t=this.closedTurns.get(s)??new Set;t.add(n),this.closedTurns.set(s,t)}change(s,n){const t={...s.snapshot,...n};JSON.stringify(t)!==JSON.stringify(s.snapshot)&&(u(t.state)&&t.sourceTurnId&&this.closeTurn(s.route.sourceThreadId,t.sourceTurnId),s.snapshot=t,this.pending.set(t.runId,s))}}function I(r,s,n,t){const i={...r},o=t?.runId||s.sourceThreadId;["tool_call","tool_call_update"].includes(String(r.sessionUpdate))&&r.toolCallId&&(i.toolCallId=`subtask.${o}.${r.toolCallId}`),["plan","plan_update","plan_removed"].includes(String(r.sessionUpdate))&&(i.planId=`subtask.${o}.${r.planId||r.plan_id||r.id||"current"}`,delete i.plan_id);const a=h(r._meta);return i._meta={...a,aca:{...h(a.aca),subtask:{...s,sourceTurnId:n,...t?{runId:t.runId,run:{...t}}:{}}}},i}function u(r){return["completed","failed","interrupted"].includes(r)}function d(r){return r==="pending"||r==="running"}function h(r){return r&&typeof r=="object"?r:{}}function p(r){return typeof r=="string"?r:""}export{m as CodexSubtaskExecutions,I as decorateSubtaskUpdate};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
class
|
|
1
|
+
import{CodexSubtaskExecutions as A}from"./subtask-executions.js";class O{routes=new Map;executions=new A;idleWaiters=new Set;reset(){this.routes.clear(),this.executions.reset(),this.resolveIdleWaiters(!1)}routeFor(t){return t?this.routes.get(t)??null:null}routesForSubscription(){return[...this.routes.values()]}activeThreadIds(){return this.executions.activeThreadIds()}hasActiveSubtasks(){return this.activeThreadIds().length>0}waitForIdle(t){return this.activeThreadIds().length===0?Promise.resolve({timedOut:!1,activeThreadIds:[]}):new Promise(s=>{const i={resolve:s,timer:setTimeout(()=>{this.idleWaiters.delete(i),s({timedOut:!0,activeThreadIds:this.activeThreadIds()})},t)};i.timer.unref(),this.idleWaiters.add(i)})}stopWaiting(){this.resolveIdleWaiters(!1)}acceptNotification(t,e,s){const i=this.executions.accept(t,e,d(s));return this.hasActiveSubtasks()||this.resolveIdleWaiters(!1),i}restoreFromSessionSnapshot(t,e){const s=d(t),i=d(s.thread),n=a(i,"id")||e,o=d(s.initialTurnsPage),l=[...I(i,"turns"),...I(o,"data")];let u=0;for(const f of l){const h=d(f);for(const p of I(h,"items"))u+=this.observe({threadId:n,item:p},e,!0)}return u}observe(t,e,s=!1){const i=d(t),n=d(i.item);if(a(n,"type")==="subAgentActivity")return this.observeSubAgentActivity(n,a(i,"threadId"),e,s);if(!k(n))return 0;const o=a(n,"id");if(!o)return 0;const l=a(n,"senderThreadId")||a(i,"threadId"),u=this.routeFor(l||null),f=u?.parentToolCallId||o,h=Math.min(8,(u?.depth??0)+1),p=y(n),b=w(n.agentsStates);let T=0;for(const c of p){if(!c||c===e||c===l)continue;this.routes.has(c)||(this.routes.set(c,{parentToolCallId:f,sourceThreadId:c,depth:h}),T+=1);const m=b.get(c),S=this.routes.get(c);if(s){this.executions.get(c)||this.executions.observeState(S,"unknown");continue}a(n,"tool")==="spawnAgent"&&this.executions.start(S,o,"pending",{description:a(n,"prompt")||void 0}),m?this.updateStatus(c,m):a(n,"tool")==="spawnAgent"&&this.updateStatus(c,"pending")}return T}observeSubAgentActivity(t,e,s,i){const n=a(t,"kind"),o=a(t,"agentThreadId");if(!o||o===s||!i&&n==="started"&&this.executions.hasStart(o,a(t,"id")))return 0;if(n==="completed"||n==="interrupted")return i||this.updateStatus(o,n==="completed"?"completed":"interrupted"),0;if(n!=="started"&&n!=="interacted")return 0;const l=this.routeFor(e||null),u=l?.parentToolCallId||a(t,"id"),f=Math.min(8,(l?.depth??0)+1);if(!u)return 0;const h=this.routes.has(o)?0:1;if(h&&this.routes.set(o,{parentToolCallId:u,sourceThreadId:o,depth:f}),i)return this.executions.get(o)||this.executions.observeState(this.routes.get(o),"unknown"),h;if(n==="started"){const p=this.executions.get(o);p&&!["pending","running"].includes(p.state)&&this.routes.set(o,{parentToolCallId:u,sourceThreadId:o,depth:f}),this.executions.start(this.routes.get(o),a(t,"id"),"running",{name:a(t,"agentPath").split("/").filter(Boolean).at(-1)}),this.updateStatus(o,"running")}return h}updateStatus(t,e){const s=this.routes.get(t);s&&this.executions.observeState(s,e),this.hasActiveSubtasks()||this.resolveIdleWaiters(!1)}resolveIdleWaiters(t){const e=this.activeThreadIds();for(const s of this.idleWaiters)clearTimeout(s.timer),s.resolve({timedOut:t,activeThreadIds:e});this.idleWaiters.clear()}}function w(r){const t=new Map,e=d(r);for(const[s,i]of Object.entries(e)){const n=x(a(d(i),"status"));s.trim()&&n!=="unknown"&&t.set(s.trim(),n)}return t}function x(r){switch(r.toLowerCase()){case"pending":case"pendinginit":return"pending";case"running":case"inprogress":case"in_progress":return"running";case"completed":case"shutdown":return"completed";case"failed":case"errored":case"notfound":return"failed";case"cancelled":case"canceled":case"interrupted":return"interrupted";default:return"unknown"}}function y(r){const t=d(r),e=new Set;g(e,t.newThreadId),g(e,t.receiverThreadId),C(e,t.receiverThreadIds);const s=t.agentsStates;if(Array.isArray(s))for(const i of s)v(e,i);else{const i=d(s);for(const[n,o]of Object.entries(i))v(e,o),W(n)&&e.add(n)}return[...e]}function k(r){const t=a(d(r),"type");return t==="collabAgentToolCall"||t==="collabToolCall"}function v(r,t){const e=d(t);for(const s of["threadId","thread_id","receiverThreadId","newThreadId"])g(r,e[s])}function g(r,t){typeof t=="string"&&t.trim()&&r.add(t.trim())}function C(r,t){if(Array.isArray(t))for(const e of t)g(r,e)}function I(r,t){const e=r[t];return Array.isArray(e)?e:[]}function W(r){return r.length<8?!1:!["pending","running","completed","failed","cancelled","canceled"].includes(r.toLowerCase())}function d(r){return r&&typeof r=="object"&&!Array.isArray(r)?r:{}}function a(r,t){const e=r[t];return typeof e=="string"?e:""}export{O as CodexSubtaskRouter,y as collaborationChildThreadIds,k as isCollaborationItem};
|
package/dist/commands/start.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{Command as D}from"commander";import _ from"node:fs";import
|
|
1
|
+
import{Command as D}from"commander";import _ from"node:fs";import S from"node:os";import p from"node:path";import i from"node:process";import{randomUUID as x}from"node:crypto";import{Logger as j}from"../core/logger.js";import{loadCredentials as N,saveApiKeyCredential as O}from"../core/auth.js";import{getRuntimeStatePath as L}from"../core/paths.js";import{writeJsonFile as M}from"../core/fs.js";import{HEARTBEAT_INTERVAL_MS as U,startHeartbeatLoop as F}from"../core/heartbeat.js";import{startJobWorkerLoop as H}from"../core/job-worker.js";import{startFileTransferWorkerLoop as K}from"../core/file-transfer-worker.js";import{startTerminalWorker as V}from"../core/terminal/worker.js";import{startMachineFilesystemWorkerLoop as B}from"../core/machine-filesystem-worker.js";import{cleanupOrphanedAcpProcesses as G}from"../core/acp-orphan-cleanup.js";import{defaultAcpRuntimePool as y}from"../acp/client/acp-runtime-pool.js";import{isProcessAlive as u,readPidFile as h,writePidFile as J,removePidFile as C}from"../core/process.js";import{applyRuntimePathOptions as P}from"../core/runtime-options.js";import{loadAcaConfig as b,updateAcaConfig as E}from"../core/aca-config.js";import{acaServerRequest as q}from"../core/aca-server-client.js";import{restoreProjectsAfterLogin as z}from"../core/project-recovery.js";import{syncCodexProviderRuntimeConfig as Q}from"../core/codex-provider-runtime-config.js";import{ACA_WORKER_INSTANCE_ENV as T,ACA_WORKER_PID_ENV as X,ACA_WORKER_START_TICKS_ENV as Y,readProcessStartTicks as Z}from"../core/process-identity.js";import{printStartTips as ee,spawnDaemon as te}from"./daemon.js";function _e(){return new D("start").description("Start agent service in native mode").option("--aica-home <path>","Runtime home directory (defaults to ~/.aica or AICA_HOME)").option("--aica-config <path>","Config file path (defaults to <aica-home>/config.json)").option("--aca-home <path>","Deprecated alias for --aica-home").option("--aca-config <path>","Deprecated alias for --aica-config").option("--server <url>","ACA Server URL override").option("--machine-id <id>","Machine ID override for this runtime").option("--machine-name <name>","Machine name to register (defaults to hostname)").option("--cli-types <types...>","Specify CLI types to register, `claude` or `codex`").option("--debug","enable debug output").option("--heartbeat-log","output current timestamp every 5 seconds").option("--auth <api-key>","Use a CLI API key for non-interactive authentication").option("--token <enrollment-token>","Use a one-time Web-generated token to register this machine").option("-b, --background","在后台启动 Worker").action(async e=>{if(e.background){await re(e);return}await ne(e)})}async function re(e){P(e);const o=h();if(o&&u(o))throw new Error(`Worker 已在后台运行,PID ${o}。`);o&&C();const n=te(ie());if(n.unref(),!n.pid)throw new Error("后台 Worker 启动失败:未获得进程 PID。");if(!await oe(n.pid,3e3)&&!u(n.pid))throw new Error(`后台 Worker 启动失败,请检查 ${p.join(i.env.AICA_HOME||i.env.ACA_HOME||p.join(S.homedir(),".aica"),"logs")}。`);console.log(`Worker 已在后台启动,PID ${n.pid}。`),ee()}function ie(){const e=i.argv.findIndex((o,n)=>n>=2&&o==="start");return e<0?[]:i.argv.slice(e+1).filter(o=>o!=="--background"&&o!=="-b")}async function oe(e,o){const n=Date.now();for(;Date.now()-n<o;){if(h()===e)return!0;if(!u(e))return!1;await new Promise(t=>setTimeout(t,50))}return h()===e}async function ne(e){P(e);const o=i.env[T]||x(),n=Z(i.pid)||"";i.env[T]=o,i.env[X]=String(i.pid),i.env[Y]=n;const t=new j("start",e.debug?"debug":"info"),R=b(),g={...e.server?.trim()?{serverUrl:e.server.trim()}:{},...e.machineId?{machineId:e.machineId.trim()}:{},...e.machineName?{machineName:e.machineName.trim()}:{}};let s=Object.keys(g).length>0?E(g):R;const m=e.machineName?.trim()||s.machineName||S.hostname();if(e.token?.trim()){const r=await q("POST","/api/auth/machine-enroll",{token:e.token.trim(),machineId:s.machineId,machineName:m},{serverUrl:s.serverUrl});s=E({machineId:r.machineId,machineName:m,token:r.token,username:r.username}),t.info(`Machine enrollment completed for user ${r.username}`);try{const a=await z();a.restored>0&&t.info(`restored ${a.restored} local project(s) after machine enrollment`),a.unavailable>0&&t.warn(`skipped ${a.unavailable} unavailable project path(s) during machine enrollment recovery`),s=b()}catch(a){t.warn(`project recovery after machine enrollment failed: ${a instanceof Error?a.message:String(a)}`)}}const W=e.auth?O(e.auth,m):N();J(i.pid),$({pid:i.pid,workerInstanceId:o,workerStartTicks:n,phase:"running",startupStage:"local-compatible",machineId:s.machineId||W?.machine?.machineId||"local-machine",machineName:m,cliTypes:e.cliTypes??ae(),startedAt:new Date().toISOString(),heartbeatIntervalMs:U,issues:[{severity:"info",message:"aca is running in aca-server compatibility mode; ACP execution happens on this machine."}]}),t.info(`Starting aca local-compatible service on machine ${m}`),t.info(`PID ${i.pid}`);const l=await G();l.terminated>0&&t.warn(`terminated ${l.terminated} orphaned ACP process(es): ${l.pids.join(",")}`),Q().then(r=>t.info(r?`Codex Provider runtime config loaded revision=${r.revision}`:"Codex Provider runtime config is not configured")).catch(r=>t.warn(`Codex Provider runtime config sync failed: ${r instanceof Error?r.message:String(r)}`));let v=!1,f,w,I,k,A;const d=async r=>{if(v)return;v=!0,t.info("Shutting down"),f&&clearInterval(f),I?.stop(),A?.stop(),k?.stop(),await w?.stop().catch(c=>t.warn(`job worker shutdown failed: ${c instanceof Error?c.message:String(c)}`));const a=y.stats();await y.disposeAll().catch(c=>t.warn(`ACP runtime shutdown failed: ${c instanceof Error?c.message:String(c)}`)),t.info(`ACP runtimes stopped total=${a.total} inUse=${a.inUse} idle=${a.idle}`),$({pid:i.pid,phase:"stopped",stoppedAt:new Date().toISOString(),issues:[]}),C(),i.exit(r)};i.once("SIGINT",()=>{d(130)}),i.once("SIGTERM",()=>{d(0)}),i.once("uncaughtException",r=>{t.error(`uncaught exception: ${r.stack||r.message}`),d(1)}),i.once("unhandledRejection",r=>{const a=r instanceof Error?r.stack||r.message:String(r);t.error(`unhandled rejection: ${a}`),d(1)}),e.heartbeatLog&&setInterval(()=>t.info(`heartbeat: ${new Date().toISOString()}`),5e3),f=F(t),w=H(t),I=K(t),A=B(t),k=V(t),await new Promise(()=>{})}function ae(){return["codex","claude"].filter(e=>ce(e))}function ce(e){const o=i.env.PATH||"";for(const n of o.split(":"))try{if(n&&_.existsSync(p.join(n,e)))return!0}catch{}return!1}function $(e){M(L(),e)}export{_e as createStartCommand};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
const x=1024,k=2048,y=new Set(["agent_message_chunk","agent_progress_chunk","agent_thought_chunk","user_message_chunk"]),I=new Set(["usage_update","session_info_update"]);function q(e,t){const a=e.at(-1);if(!a)return e.push(t),!1;if(C(a,t)){const n=`${a.text??""}${t.text??""}`;if(n.length<=1024)return a.text=n,a.raw=
|
|
1
|
+
const x=1024,k=2048,y=new Set(["agent_message_chunk","agent_progress_chunk","agent_thought_chunk","user_message_chunk"]),I=new Set(["usage_update","session_info_update"]);function q(e,t){const a=e.at(-1);if(!a)return e.push(t),!1;if(C(a,t)){const n=`${a.text??""}${t.text??""}`;if(n.length<=1024)return a.text=n,a.raw=f(a.raw,t.raw,n),!0}if(S(a,t))return E(a,t),!0;if(h(a,t)){const n=T(a.raw);return a.label=t.label,a.text=t.text,a.status=t.status,a.toolCallId=t.toolCallId,a.raw=f(a.raw,t.raw,t.text,n),!0}return e.push(t),!1}function S(e,t){if(e.eventType!=="tool_call_update"||t.eventType!=="tool_call_update"||!e.toolCallId||e.toolCallId!==t.toolCallId||e.turnId!==t.turnId||d(e.raw)!==d(t.raw)||e.status!=="in_progress"||t.status!=="in_progress")return!1;const a=c(e.raw),n=c(t.raw);return!a||!n||a.terminalId!==n.terminalId?!1:a.data.length+n.data.length<=2048}function E(e,t){const a=c(e.raw),n=c(t.raw);if(!a||!n)return;e.label=t.label??e.label,e.raw=f(e.raw,t.raw,void 0);const u=r(e.raw),l=r(u?.update),s=r(l?._meta);!u||!l||!s||(u.update={...l,_meta:{...s,terminal_output_delta:{...n.raw,data:a.data+n.data}}})}function c(e){const t=r(r(e)?.update),a=r(r(t?._meta)?.terminal_output_delta);if(!a)return null;const n=typeof a?.data=="string"?a.data:"",u=typeof a?.terminal_id=="string"?a.terminal_id:"";return n&&u?{data:n,terminalId:u,raw:a}:null}function C(e,t){return y.has(e.eventType)&&e.eventType===t.eventType&&e.turnId===t.turnId&&d(e.raw)===d(t.raw)&&e.label===t.label&&e.status===t.status&&!e.toolCallId&&!t.toolCallId&&typeof e.text=="string"&&typeof t.text=="string"}function h(e,t){if(e.eventType!==t.eventType||e.turnId!==t.turnId||d(e.raw)!==d(t.raw))return!1;if(I.has(e.eventType))return!0;if(e.eventType!=="config_option_update")return!1;const a=_(e.raw),n=_(t.raw);return!!a&&a===n}function d(e){const t=r(r(e)?.update),a=r(t?._meta),n=r(a?.aca),u=r(n?.subtask),l=typeof u?.parentToolCallId=="string"?u.parentToolCallId:"",s=typeof u?.sourceThreadId=="string"?u.sourceThreadId:"",o=typeof u?.runId=="string"?u.runId:"";return l||s?`subtask:${l}:${s}:${o}`:"main"}function _(e){const t=A(e);for(const a of["configId","optionId","id","key","name"])if(typeof t?.[a]=="string"&&t[a].trim())return t[a].trim();return""}function f(e,t,a,n=T(e)){const u=r(t);if(!u)return t;const l=b(t),s=w(e)+w(t),o={...u,acaSequence:n,acaSequenceEnd:l,sourceEventCount:s};if(a!==void 0){const p=r(u.update);p&&(o.update={...p,content:{type:"text",text:a}})}return o}function A(e){return r(r(e)?.update)}function T(e){const t=r(e)?.acaSequence;return typeof t=="number"?t:void 0}function b(e){const t=r(e),a=t?.acaSequenceEnd;return typeof a=="number"?a:typeof t?.acaSequence=="number"?t.acaSequence:void 0}function w(e){const t=r(e)?.sourceEventCount;return typeof t=="number"&&t>0?t:1}function r(e){return e&&typeof e=="object"&&!Array.isArray(e)?e:null}export{q as appendCoalescedAcpEvent};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import{execFileSync as y}from"node:child_process";import{randomUUID as $}from"node:crypto";import l from"node:fs";import T from"node:os";import u from"node:path";import{acaServerDownloadToFile as Q,acaServerRequest as P}from"./aca-server-client.js";import{loadAcaConfig as Z,upsertLocalProject as q}from"./aca-config.js";import{startLongPollWorker as tt}from"./long-poll-worker.js";import{expandWorkerRuntimePath as et}from"./worker-runtime-paths.js";import{syncCodexProviderRuntimeConfig as rt}from"./codex-provider-runtime-config.js";import{defaultAcpRuntimePool as ot}from"../acp/client/acp-runtime-pool.js";const F=25e3,nt=F+1e4,E=300,it=3e4;let b=null;function Zt(t){return tt({run:async()=>{const e=Z();if(!e.token)throw new Error("Worker 尚未配置认证令牌");const r=await P("GET",`/api/client/machine-operations/claim?machineId=${encodeURIComponent(e.machineId)}&waitMs=${F}`,void 0,{timeoutMs:nt});if(r.item)try{const o=await st(r.item);await P("POST",`/api/client/machine-operations/${encodeURIComponent(r.item.requestId)}/complete`,{result:o})}catch(o){const n=o instanceof Error?o.message:String(o);t.warn(`machine filesystem request failed request=${r.item.requestId}: ${n}`),await P("POST",`/api/client/machine-operations/${encodeURIComponent(r.item.requestId)}/fail`,{message:n}).catch(()=>{})}},onError:(e,r)=>t.warn(`machine filesystem worker failed, retry in ${r}ms: ${e instanceof Error?e.message:String(e)}`)})}async function st(t){if(t.operation==="sync_codex_provider_config"){const e=await rt(),r=await ot.retireProviderRuntimes("codex-acp");return{configured:!!e,revision:e?.revision??null,retiredRuntimes:r}}if(t.operation==="filesystem_roots")return kt();if(t.operation==="list_directory")return Dt({directoryPath:String(t.payload.path||""),includeHidden:t.payload.includeHidden===!0});if(t.operation==="register_project")return Ft(t.payload);if(t.operation==="list_project_directory")return lt(t.payload);if(t.operation==="upload_project_file")return ut(t.payload);if(t.operation==="mutate_project_files")return ft(t.payload);if(t.operation==="get_project_git_status")return O(t.payload);if(t.operation==="get_project_git_diff")return yt(t.payload);if(t.operation==="get_project_git_log")return gt(t.payload);if(t.operation==="get_project_git_commit")return St(t.payload);if(t.operation==="get_worktree_status")return A(t.payload);if(t.operation==="commit_worktree")return at(t.payload);if(t.operation==="cleanup_worktree")return ct(t.payload);throw new Error("不支持的 Machine 文件系统操作。 ")}function A(t){const e=C(String(t.worktreePath||""));if(!l.existsSync(e))return{exists:!1,isRepository:!1,dirty:!1,changedFiles:0,ahead:0};const r=m(e);if(!w(r))return{exists:!0,isRepository:!1,dirty:bt(r),changedFiles:null,ahead:0};const o=O({rootPath:r}),n=String(t.baseCommit||"").trim(),s=n&&/^[0-9a-f]{7,64}$/i.test(n)?Et(r,n):0;return{exists:!0,isRepository:!0,dirty:Array.isArray(o.files)&&o.files.length>0,changedFiles:Array.isArray(o.files)?o.files.length:0,branch:o.branch,ahead:s,files:Array.isArray(o.files)?o.files.slice(0,50):[],truncated:o.truncated===!0}}function at(t){const e=m(String(t.worktreePath||"")),r=String(t.message||"").trim();if(!r)throw new Error("提交说明不能为空。");if(!w(e))throw new Error("当前 Worktree 不是 Git 仓库,无法提交。");y("git",["-C",e,"add","-A"],{stdio:"pipe",timeout:3e4});try{return y("git",["-C",e,"diff","--cached","--quiet"],{stdio:"pipe",timeout:1e4}),{committed:!1,reason:"no_changes",...A(t)}}catch(o){if(Number(o.status)!==1)throw o}return y("git",["-C",e,"commit","-m",r],{stdio:"pipe",timeout:6e4}),{committed:!0,commit:d(e,["rev-parse","HEAD"],8*1024).trim(),...A(t)}}function ct(t){const e=C(String(t.worktreePath||"")),r=m(String(t.projectRoot||""));if(h(e)===h(r))throw new Error("不能清理项目主目录。");if(!l.existsSync(e))return{removed:!1,reason:"not_found"};const o=m(e),n=String(t.branchName||"").trim(),s=w(o)&&w(r);if(s){if(y("git",["-C",r,"worktree","remove","--force",o],{stdio:"pipe",timeout:6e4}),t.deleteBranch===!0&&n)try{y("git",["-C",r,"branch","-D",n],{stdio:"pipe",timeout:3e4})}catch(i){const a=String(i.stderr||i);if(!/not found|not exist/i.test(a))throw i}}else l.rmSync(o,{recursive:!0,force:!0,maxRetries:2,retryDelay:200});return{removed:!0,branchDeleted:!!(s&&t.deleteBranch===!0&&n)}}function lt(t){const e=m(String(t.rootPath||"")),r=S(String(t.relativePath||"")),o=j(e,r,!0),n=Math.max(0,Number(t.offset||0)),s=Math.min(300,Math.max(20,Number(t.limit||200))),i=l.readdirSync(o,{withFileTypes:!0}).filter(c=>!c.isSymbolicLink()).map(c=>({name:c.name,path:K(e,u.join(o,c.name)),type:c.isDirectory()?"directory":c.isFile()?"file":"other",hidden:c.name.startsWith("."),extension:c.isFile()?u.extname(c.name).slice(1).toLowerCase():""})).filter(c=>c.type!=="other").sort((c,f)=>c.type!==f.type?c.type==="directory"?-1:1:c.name.localeCompare(f.name,void 0,{numeric:!0,sensitivity:"base"})),a=i.slice(n,n+s);return{path:r,rootKind:t.rootKind==="worktree"?"worktree":"project",items:a,total:i.length,nextOffset:n+a.length<i.length?n+a.length:null}}async function ut(t){const e=String(t.uploadId||"").trim();if(!e)throw new Error("项目上传任务缺少 uploadId。");const r=m(String(t.rootPath||"")),o=S(String(t.relativeDirectory||"")),n=j(r,o,!0),s=I(String(t.fileName||"")),i=Number(t.sizeBytes),a=String(t.sha256||"").trim().toLowerCase();if(!Number.isSafeInteger(i)||i<0)throw new Error("项目上传文件大小无效。");if(!/^[0-9a-f]{64}$/.test(a))throw new Error("项目上传文件摘要无效。");const c=u.join(n,`.aca-upload-${$()}.tmp`);try{const f=await Q(`/api/client/project-uploads/${encodeURIComponent(e)}`,c,{maxBytes:i,timeoutMs:3e5});if(f.sizeBytes!==i)throw new Error("项目上传文件大小校验失败。");if(f.sha256.toLowerCase()!==a)throw new Error("项目上传文件完整性校验失败。");const p=pt(r,o,s,c);return{name:u.basename(p),path:K(r,p),directory:o,size:f.sizeBytes,sha256:f.sha256,renamed:u.basename(p)!==s,rootKind:t.rootKind==="worktree"?"worktree":"project"}}finally{l.rmSync(c,{force:!0})}}function ft(t){const e=m(String(t.rootPath||"")),r=t.action&&typeof t.action=="object"?t.action:{};if(r.type==="delete")return dt(e,String(r.path||""),r.recursive===!0,t.rootKind);throw new Error("project_file_operation_unsupported")}function dt(t,e,r,o){const n=S(e);if(!n)throw new Error("project_file_root_protected");mt(n);const s=n.split("/"),i=s.slice(0,-1).join("/"),a=j(t,i,!0),c=u.join(a,s.at(-1)||"");let f;try{f=l.lstatSync(c)}catch(p){throw p.code==="ENOENT"?new Error("project_file_not_found"):p}if(f.isSymbolicLink())throw new Error("project_file_symbolic_link");if(!f.isFile()&&!f.isDirectory())throw new Error("project_file_type_unsupported");if(f.isDirectory()&&!r&&l.readdirSync(c).length>0)throw new Error("project_directory_not_empty");return f.isDirectory()&&r?l.rmSync(c,{recursive:!0,force:!1,maxRetries:2,retryDelay:150}):f.isDirectory()?l.rmdirSync(c):l.unlinkSync(c),{operation:"delete",path:n,entryType:f.isDirectory()?"directory":"file",parentPath:i,affectedDirectories:[i],rootKind:o==="worktree"?"worktree":"project"}}function mt(t){const e=t.split("/")[0].toLocaleLowerCase("en-US");if([".git",".aca",".aica"].includes(e))throw new Error("project_file_protected")}function pt(t,e,r,o){const n=m(t),s=S(e),i=j(n,s,!0),a=I(r);if(l.realpathSync.native(u.dirname(o))!==i)throw new Error("项目上传临时文件不在目标目录中。");const f=l.lstatSync(o);if(!f.isFile()||f.isSymbolicLink())throw new Error("项目上传临时文件无效。");for(let p=1;p<=1e4;p+=1){const J=p===1?a:ht(a,p),_=u.join(i,J);try{return l.linkSync(o,_),l.chmodSync(_,420),l.rmSync(o,{force:!0}),_}catch(v){if(L(v))continue;if(!wt(v))throw v;try{return l.copyFileSync(o,_,l.constants.COPYFILE_EXCL),l.chmodSync(_,420),l.rmSync(o,{force:!0}),_}catch(N){if(L(N))continue;throw N}}}throw new Error("同名文件过多,无法生成可用文件名。")}function I(t){const e=t.trim();if(!e||e==="."||e===".."||/[\\/\x00-\x1F]/.test(e))throw new Error("项目上传文件名无效。");if(process.platform==="win32"&&(/[<>:"|?*]/.test(e)||/[. ]$/.test(e)))throw new Error("项目上传文件名在 Windows 上无效。");return e.slice(0,180)}function ht(t,e){const r=u.extname(t);return`${r?t.slice(0,-r.length):t} (${e})${r}`}function L(t){return!!(t&&typeof t=="object"&&t.code==="EEXIST")}function wt(t){const e=t&&typeof t=="object"?t.code:"";return["EPERM","EACCES","EXDEV","ENOTSUP","EOPNOTSUPP"].includes(String(e||""))}const M=500,x=2*1024*1024,g=2*1024*1024;function O(t){const e=m(String(t.rootPath||""));if(!w(e))return{isRepository:!1,branch:null,files:[],ahead:0,behind:0};const o=d(e,["status","--porcelain=v1","-z","--untracked-files=all","--no-renames"],x).split("\0").filter(Boolean).slice(0,M),n=o.map(a=>Pt(a)).filter(a=>!!a),s=k(d(e,["diff","--no-ext-diff","--numstat","-z","--no-renames","HEAD","--","."],x));for(const a of n){const c=s.get(a.path);if(c)a.additions=c.additions,a.deletions=c.deletions;else if(a.status==="untracked"){const f=G(e,a.path);a.additions=f.additions,a.deletions=f.deletions}}const i=At(e);return{isRepository:!0,branch:vt(e),files:n,ahead:i.ahead,behind:i.behind,truncated:o.length>=M}}function yt(t){const e=m(String(t.rootPath||"")),r=S(String(t.relativePath||""));if(!r)throw new Error("Diff 文件路径不能为空。");if(!w(e))return{isRepository:!1,path:r,diff:"",additions:0,deletions:0};const o=B(e,r),n=xt(e,r),s=n?d(e,["diff","--no-ext-diff","--unified=80","HEAD","--",r],g):W(e,["diff","--no-ext-diff","--no-index","--unified=80",process.platform==="win32"?"NUL":"/dev/null",o],g),i=n?k(d(e,["diff","--no-ext-diff","--numstat","-z","--no-renames","HEAD","--",r],32*1024)).get(r):G(e,r);return{isRepository:!0,path:r,diff:U(s),additions:i?.additions??0,deletions:i?.deletions??0,truncated:s.length>g}}function gt(t){const e=m(String(t.rootPath||""));if(!w(e))return{isRepository:!1,items:[]};if(!jt(e))return{isRepository:!0,items:[]};const r=Math.min(200,Math.max(20,Number(t.limit||100)));return{isRepository:!0,items:d(e,["log",`--max-count=${r}`,"--date=iso-strict","--pretty=format:%H%x1f%h%x1f%an%x1f%ae%x1f%aI%x1f%D%x1f%s%x1f%b%x1e"],x).split("").map(i=>_t(i)).filter(i=>!!i),limit:r}}function St(t){const e=m(String(t.rootPath||"")),r=String(t.commit||"").trim();if(!/^[0-9a-f]{7,64}$/i.test(r))throw new Error("Git 提交标识无效。");if(!w(e))return{isRepository:!1,commit:r,diff:"",additions:0,deletions:0};d(e,["cat-file","-e",`${r}^{commit}`],8*1024);const o=d(e,["show","--no-ext-diff","--no-color","--format=","--stat","--patch","--unified=40",r],g),n=[...k(d(e,["show","--no-ext-diff","--numstat","-z","--no-renames","--format=",r],x)).values()];return{isRepository:!0,commit:r,diff:U(o),additions:n.reduce((s,i)=>s+(i.additions||0),0),deletions:n.reduce((s,i)=>s+(i.deletions||0),0),changedFiles:n.length,truncated:o.length>g}}function _t(t){const e=t.replace(/^\s+|\s+$/g,"");if(!e)return null;const r=e.split(""),o=String(r[0]||"").trim();return o?{hash:o,shortHash:String(r[1]||o.slice(0,8)).trim(),authorName:String(r[2]||"").trim(),authorEmail:String(r[3]||"").trim(),authoredAt:String(r[4]||"").trim(),refs:String(r[5]||"").trim(),subject:String(r[6]||"").trim(),body:r.slice(7).join("").trim()}:null}function Pt(t){if(t.length<4)return null;const e=t[0]||" ",r=t[1]||" ",o=S(t.slice(3));return o?{path:o,status:e==="?"&&r==="?"?"untracked":e==="U"||r==="U"?"conflicted":e==="D"||r==="D"?"deleted":e==="A"||r==="A"?"added":e==="R"||r==="R"?"renamed":"modified",indexStatus:e,worktreeStatus:r,staged:e!==" "&&e!=="?",additions:null,deletions:null}:null}function k(t){const e=new Map;for(const r of t.split("\0")){if(!r)continue;const o=H(r);if(!o||!o.path)continue;const n=S(o.path);n&&e.set(n,{additions:o.additions,deletions:o.deletions})}return e}function G(t,e){const r=B(t,e),n=W(t,["diff","--no-ext-diff","--no-index","--numstat","-z","--",process.platform==="win32"?"NUL":"/dev/null",r],32*1024).split("\0",1)[0]||"",s=H(n);return s?{additions:s.additions,deletions:s.deletions}:{additions:null,deletions:null}}function H(t){const e=t.indexOf(" "),r=e<0?-1:t.indexOf(" ",e+1);if(e<0||r<0)return null;const o=t.slice(0,e),n=t.slice(e+1,r);return{additions:/^\d+$/.test(o)?Number(o):null,deletions:/^\d+$/.test(n)?Number(n):null,path:t.slice(r+1)}}function w(t){try{return d(t,["rev-parse","--is-inside-work-tree"],8*1024).trim()==="true"}catch(e){if(String(e.code||"")==="ENOENT")throw new Error("设备未安装 Git。");return!1}}function Et(t,e){try{const r=d(t,["rev-list","--count",`${e}..HEAD`],8192).trim(),o=Number(r);return Number.isSafeInteger(o)&&o>0?o:0}catch{return 0}}function bt(t){try{return l.readdirSync(t).length>0}catch{return!1}}function xt(t,e){try{return d(t,["ls-files","--error-unmatch","--",e],8*1024),!0}catch{return!1}}function jt(t){try{return d(t,["rev-parse","--verify","HEAD"],8*1024),!0}catch{return!1}}function vt(t){try{return d(t,["symbolic-ref","--quiet","--short","HEAD"],8*1024).trim()||null}catch{try{return`HEAD (${d(t,["rev-parse","--short","HEAD"],8*1024).trim()})`}catch{return null}}}function At(t){try{const e=d(t,["rev-list","--left-right","--count","HEAD...@{upstream}"],8192).trim().split(/\s+/).map(Number);return{ahead:Number.isFinite(e[0])?e[0]:0,behind:Number.isFinite(e[1])?e[1]:0}}catch{return{ahead:0,behind:0}}}function B(t,e){const r=u.resolve(t,...e.split("/")),o=u.relative(t,r);if(o.startsWith("..")||u.isAbsolute(o))throw new Error("Diff 路径超出项目范围。");return r}function d(t,e,r){return y("git",["-C",t,...e],{encoding:"utf8",timeout:2e4,maxBuffer:r,stdio:["ignore","pipe","pipe"]})}function W(t,e,r){try{return d(t,e,r)}catch(o){const n=String(o.stdout||"");if(n)return n;if(Number(o.status)===1)return"";throw o}}function U(t){return t.length<=g?t:`${t.slice(0,g)}
|
|
1
|
+
import{activeTerminalService as Q}from"./terminal/service.js";import{execFileSync as y}from"node:child_process";import{randomUUID as $}from"node:crypto";import l from"node:fs";import T from"node:os";import u from"node:path";import{acaServerDownloadToFile as Z,acaServerRequest as P}from"./aca-server-client.js";import{loadAcaConfig as q,upsertLocalProject as tt}from"./aca-config.js";import{startLongPollWorker as et}from"./long-poll-worker.js";import{expandWorkerRuntimePath as rt}from"./worker-runtime-paths.js";import{syncCodexProviderRuntimeConfig as ot}from"./codex-provider-runtime-config.js";import{defaultAcpRuntimePool as nt}from"../acp/client/acp-runtime-pool.js";const F=25e3,it=F+1e4,E=300,st=3e4;let b=null;function te(t){return et({run:async()=>{const e=q();if(!e.token)throw new Error("Worker 尚未配置认证令牌");const r=await P("GET",`/api/client/machine-operations/claim?machineId=${encodeURIComponent(e.machineId)}&waitMs=${F}`,void 0,{timeoutMs:it});if(r.item)try{const o=await at(r.item);await P("POST",`/api/client/machine-operations/${encodeURIComponent(r.item.requestId)}/complete`,{result:o})}catch(o){const n=o instanceof Error?o.message:String(o);t.warn(`machine filesystem request failed request=${r.item.requestId}: ${n}`),await P("POST",`/api/client/machine-operations/${encodeURIComponent(r.item.requestId)}/fail`,{message:n}).catch(()=>{})}},onError:(e,r)=>t.warn(`machine filesystem worker failed, retry in ${r}ms: ${e instanceof Error?e.message:String(e)}`)})}async function at(t){if(t.operation==="sync_codex_provider_config"){const e=await ot(),r=await nt.retireProviderRuntimes("codex-acp");return{configured:!!e,revision:e?.revision??null,retiredRuntimes:r}}if(t.operation==="filesystem_roots")return Dt();if(t.operation==="list_directory")return Rt({directoryPath:String(t.payload.path||""),includeHidden:t.payload.includeHidden===!0});if(t.operation==="register_project")return It(t.payload);if(t.operation==="list_project_directory")return ut(t.payload);if(t.operation==="upload_project_file")return ft(t.payload);if(t.operation==="mutate_project_files")return dt(t.payload);if(t.operation==="get_project_git_status")return O(t.payload);if(t.operation==="get_project_git_diff")return gt(t.payload);if(t.operation==="get_project_git_log")return St(t.payload);if(t.operation==="get_project_git_commit")return _t(t.payload);if(t.operation==="get_worktree_status")return k(t.payload);if(t.operation==="commit_worktree")return ct(t.payload);if(t.operation==="cleanup_worktree")return lt(t.payload);throw new Error("不支持的 Machine 文件系统操作。 ")}function k(t){const e=C(String(t.worktreePath||""));if(!l.existsSync(e))return{exists:!1,isRepository:!1,dirty:!1,changedFiles:0,ahead:0};const r=m(e);if(!w(r))return{exists:!0,isRepository:!1,dirty:xt(r),changedFiles:null,ahead:0};const o=O({rootPath:r}),n=String(t.baseCommit||"").trim(),s=n&&/^[0-9a-f]{7,64}$/i.test(n)?bt(r,n):0;return{exists:!0,isRepository:!0,dirty:Array.isArray(o.files)&&o.files.length>0,changedFiles:Array.isArray(o.files)?o.files.length:0,branch:o.branch,ahead:s,files:Array.isArray(o.files)?o.files.slice(0,50):[],truncated:o.truncated===!0}}function ct(t){const e=m(String(t.worktreePath||"")),r=String(t.message||"").trim();if(!r)throw new Error("提交说明不能为空。");if(!w(e))throw new Error("当前 Worktree 不是 Git 仓库,无法提交。");y("git",["-C",e,"add","-A"],{stdio:"pipe",timeout:3e4});try{return y("git",["-C",e,"diff","--cached","--quiet"],{stdio:"pipe",timeout:1e4}),{committed:!1,reason:"no_changes",...k(t)}}catch(o){if(Number(o.status)!==1)throw o}return y("git",["-C",e,"commit","-m",r],{stdio:"pipe",timeout:6e4}),{committed:!0,commit:d(e,["rev-parse","HEAD"],8*1024).trim(),...k(t)}}function lt(t){const e=C(String(t.worktreePath||"")),r=m(String(t.projectRoot||""));if(h(e)===h(r))throw new Error("不能清理项目主目录。");if(!l.existsSync(e))return{removed:!1,reason:"not_found"};const o=m(e);if(Q?.hasRunningInDirectory(o,String(t.sessionId||"")))throw new Error("请先结束此目录中的会话终端,再清理 Worktree。");const n=String(t.branchName||"").trim(),s=w(o)&&w(r);if(s){if(y("git",["-C",r,"worktree","remove","--force",o],{stdio:"pipe",timeout:6e4}),t.deleteBranch===!0&&n)try{y("git",["-C",r,"branch","-D",n],{stdio:"pipe",timeout:3e4})}catch(i){const a=String(i.stderr||i);if(!/not found|not exist/i.test(a))throw i}}else l.rmSync(o,{recursive:!0,force:!0,maxRetries:2,retryDelay:200});return{removed:!0,branchDeleted:!!(s&&t.deleteBranch===!0&&n)}}function ut(t){const e=m(String(t.rootPath||"")),r=S(String(t.relativePath||"")),o=j(e,r,!0),n=Math.max(0,Number(t.offset||0)),s=Math.min(300,Math.max(20,Number(t.limit||200))),i=l.readdirSync(o,{withFileTypes:!0}).filter(c=>!c.isSymbolicLink()).map(c=>({name:c.name,path:K(e,u.join(o,c.name)),type:c.isDirectory()?"directory":c.isFile()?"file":"other",hidden:c.name.startsWith("."),extension:c.isFile()?u.extname(c.name).slice(1).toLowerCase():""})).filter(c=>c.type!=="other").sort((c,f)=>c.type!==f.type?c.type==="directory"?-1:1:c.name.localeCompare(f.name,void 0,{numeric:!0,sensitivity:"base"})),a=i.slice(n,n+s);return{path:r,rootKind:t.rootKind==="worktree"?"worktree":"project",items:a,total:i.length,nextOffset:n+a.length<i.length?n+a.length:null}}async function ft(t){const e=String(t.uploadId||"").trim();if(!e)throw new Error("项目上传任务缺少 uploadId。");const r=m(String(t.rootPath||"")),o=S(String(t.relativeDirectory||"")),n=j(r,o,!0),s=I(String(t.fileName||"")),i=Number(t.sizeBytes),a=String(t.sha256||"").trim().toLowerCase();if(!Number.isSafeInteger(i)||i<0)throw new Error("项目上传文件大小无效。");if(!/^[0-9a-f]{64}$/.test(a))throw new Error("项目上传文件摘要无效。");const c=u.join(n,`.aca-upload-${$()}.tmp`);try{const f=await Z(`/api/client/project-uploads/${encodeURIComponent(e)}`,c,{maxBytes:i,timeoutMs:3e5});if(f.sizeBytes!==i)throw new Error("项目上传文件大小校验失败。");if(f.sha256.toLowerCase()!==a)throw new Error("项目上传文件完整性校验失败。");const p=ht(r,o,s,c);return{name:u.basename(p),path:K(r,p),directory:o,size:f.sizeBytes,sha256:f.sha256,renamed:u.basename(p)!==s,rootKind:t.rootKind==="worktree"?"worktree":"project"}}finally{l.rmSync(c,{force:!0})}}function dt(t){const e=m(String(t.rootPath||"")),r=t.action&&typeof t.action=="object"?t.action:{};if(r.type==="delete")return mt(e,String(r.path||""),r.recursive===!0,t.rootKind);throw new Error("project_file_operation_unsupported")}function mt(t,e,r,o){const n=S(e);if(!n)throw new Error("project_file_root_protected");pt(n);const s=n.split("/"),i=s.slice(0,-1).join("/"),a=j(t,i,!0),c=u.join(a,s.at(-1)||"");let f;try{f=l.lstatSync(c)}catch(p){throw p.code==="ENOENT"?new Error("project_file_not_found"):p}if(f.isSymbolicLink())throw new Error("project_file_symbolic_link");if(!f.isFile()&&!f.isDirectory())throw new Error("project_file_type_unsupported");if(f.isDirectory()&&!r&&l.readdirSync(c).length>0)throw new Error("project_directory_not_empty");return f.isDirectory()&&r?l.rmSync(c,{recursive:!0,force:!1,maxRetries:2,retryDelay:150}):f.isDirectory()?l.rmdirSync(c):l.unlinkSync(c),{operation:"delete",path:n,entryType:f.isDirectory()?"directory":"file",parentPath:i,affectedDirectories:[i],rootKind:o==="worktree"?"worktree":"project"}}function pt(t){const e=t.split("/")[0].toLocaleLowerCase("en-US");if([".git",".aca",".aica"].includes(e))throw new Error("project_file_protected")}function ht(t,e,r,o){const n=m(t),s=S(e),i=j(n,s,!0),a=I(r);if(l.realpathSync.native(u.dirname(o))!==i)throw new Error("项目上传临时文件不在目标目录中。");const f=l.lstatSync(o);if(!f.isFile()||f.isSymbolicLink())throw new Error("项目上传临时文件无效。");for(let p=1;p<=1e4;p+=1){const J=p===1?a:wt(a,p),_=u.join(i,J);try{return l.linkSync(o,_),l.chmodSync(_,420),l.rmSync(o,{force:!0}),_}catch(v){if(L(v))continue;if(!yt(v))throw v;try{return l.copyFileSync(o,_,l.constants.COPYFILE_EXCL),l.chmodSync(_,420),l.rmSync(o,{force:!0}),_}catch(N){if(L(N))continue;throw N}}}throw new Error("同名文件过多,无法生成可用文件名。")}function I(t){const e=t.trim();if(!e||e==="."||e===".."||/[\\/\x00-\x1F]/.test(e))throw new Error("项目上传文件名无效。");if(process.platform==="win32"&&(/[<>:"|?*]/.test(e)||/[. ]$/.test(e)))throw new Error("项目上传文件名在 Windows 上无效。");return e.slice(0,180)}function wt(t,e){const r=u.extname(t);return`${r?t.slice(0,-r.length):t} (${e})${r}`}function L(t){return!!(t&&typeof t=="object"&&t.code==="EEXIST")}function yt(t){const e=t&&typeof t=="object"?t.code:"";return["EPERM","EACCES","EXDEV","ENOTSUP","EOPNOTSUPP"].includes(String(e||""))}const M=500,x=2*1024*1024,g=2*1024*1024;function O(t){const e=m(String(t.rootPath||""));if(!w(e))return{isRepository:!1,branch:null,files:[],ahead:0,behind:0};const o=d(e,["status","--porcelain=v1","-z","--untracked-files=all","--no-renames"],x).split("\0").filter(Boolean).slice(0,M),n=o.map(a=>Et(a)).filter(a=>!!a),s=A(d(e,["diff","--no-ext-diff","--numstat","-z","--no-renames","HEAD","--","."],x));for(const a of n){const c=s.get(a.path);if(c)a.additions=c.additions,a.deletions=c.deletions;else if(a.status==="untracked"){const f=G(e,a.path);a.additions=f.additions,a.deletions=f.deletions}}const i=At(e);return{isRepository:!0,branch:kt(e),files:n,ahead:i.ahead,behind:i.behind,truncated:o.length>=M}}function gt(t){const e=m(String(t.rootPath||"")),r=S(String(t.relativePath||""));if(!r)throw new Error("Diff 文件路径不能为空。");if(!w(e))return{isRepository:!1,path:r,diff:"",additions:0,deletions:0};const o=W(e,r),n=jt(e,r),s=n?d(e,["diff","--no-ext-diff","--unified=80","HEAD","--",r],g):B(e,["diff","--no-ext-diff","--no-index","--unified=80",process.platform==="win32"?"NUL":"/dev/null",o],g),i=n?A(d(e,["diff","--no-ext-diff","--numstat","-z","--no-renames","HEAD","--",r],32*1024)).get(r):G(e,r);return{isRepository:!0,path:r,diff:U(s),additions:i?.additions??0,deletions:i?.deletions??0,truncated:s.length>g}}function St(t){const e=m(String(t.rootPath||""));if(!w(e))return{isRepository:!1,items:[]};if(!vt(e))return{isRepository:!0,items:[]};const r=Math.min(200,Math.max(20,Number(t.limit||100)));return{isRepository:!0,items:d(e,["log",`--max-count=${r}`,"--date=iso-strict","--pretty=format:%H%x1f%h%x1f%an%x1f%ae%x1f%aI%x1f%D%x1f%s%x1f%b%x1e"],x).split("").map(i=>Pt(i)).filter(i=>!!i),limit:r}}function _t(t){const e=m(String(t.rootPath||"")),r=String(t.commit||"").trim();if(!/^[0-9a-f]{7,64}$/i.test(r))throw new Error("Git 提交标识无效。");if(!w(e))return{isRepository:!1,commit:r,diff:"",additions:0,deletions:0};d(e,["cat-file","-e",`${r}^{commit}`],8*1024);const o=d(e,["show","--no-ext-diff","--no-color","--format=","--stat","--patch","--unified=40",r],g),n=[...A(d(e,["show","--no-ext-diff","--numstat","-z","--no-renames","--format=",r],x)).values()];return{isRepository:!0,commit:r,diff:U(o),additions:n.reduce((s,i)=>s+(i.additions||0),0),deletions:n.reduce((s,i)=>s+(i.deletions||0),0),changedFiles:n.length,truncated:o.length>g}}function Pt(t){const e=t.replace(/^\s+|\s+$/g,"");if(!e)return null;const r=e.split(""),o=String(r[0]||"").trim();return o?{hash:o,shortHash:String(r[1]||o.slice(0,8)).trim(),authorName:String(r[2]||"").trim(),authorEmail:String(r[3]||"").trim(),authoredAt:String(r[4]||"").trim(),refs:String(r[5]||"").trim(),subject:String(r[6]||"").trim(),body:r.slice(7).join("").trim()}:null}function Et(t){if(t.length<4)return null;const e=t[0]||" ",r=t[1]||" ",o=S(t.slice(3));return o?{path:o,status:e==="?"&&r==="?"?"untracked":e==="U"||r==="U"?"conflicted":e==="D"||r==="D"?"deleted":e==="A"||r==="A"?"added":e==="R"||r==="R"?"renamed":"modified",indexStatus:e,worktreeStatus:r,staged:e!==" "&&e!=="?",additions:null,deletions:null}:null}function A(t){const e=new Map;for(const r of t.split("\0")){if(!r)continue;const o=H(r);if(!o||!o.path)continue;const n=S(o.path);n&&e.set(n,{additions:o.additions,deletions:o.deletions})}return e}function G(t,e){const r=W(t,e),n=B(t,["diff","--no-ext-diff","--no-index","--numstat","-z","--",process.platform==="win32"?"NUL":"/dev/null",r],32*1024).split("\0",1)[0]||"",s=H(n);return s?{additions:s.additions,deletions:s.deletions}:{additions:null,deletions:null}}function H(t){const e=t.indexOf(" "),r=e<0?-1:t.indexOf(" ",e+1);if(e<0||r<0)return null;const o=t.slice(0,e),n=t.slice(e+1,r);return{additions:/^\d+$/.test(o)?Number(o):null,deletions:/^\d+$/.test(n)?Number(n):null,path:t.slice(r+1)}}function w(t){try{return d(t,["rev-parse","--is-inside-work-tree"],8*1024).trim()==="true"}catch(e){if(String(e.code||"")==="ENOENT")throw new Error("设备未安装 Git。");return!1}}function bt(t,e){try{const r=d(t,["rev-list","--count",`${e}..HEAD`],8192).trim(),o=Number(r);return Number.isSafeInteger(o)&&o>0?o:0}catch{return 0}}function xt(t){try{return l.readdirSync(t).length>0}catch{return!1}}function jt(t,e){try{return d(t,["ls-files","--error-unmatch","--",e],8*1024),!0}catch{return!1}}function vt(t){try{return d(t,["rev-parse","--verify","HEAD"],8*1024),!0}catch{return!1}}function kt(t){try{return d(t,["symbolic-ref","--quiet","--short","HEAD"],8*1024).trim()||null}catch{try{return`HEAD (${d(t,["rev-parse","--short","HEAD"],8*1024).trim()})`}catch{return null}}}function At(t){try{const e=d(t,["rev-list","--left-right","--count","HEAD...@{upstream}"],8192).trim().split(/\s+/).map(Number);return{ahead:Number.isFinite(e[0])?e[0]:0,behind:Number.isFinite(e[1])?e[1]:0}}catch{return{ahead:0,behind:0}}}function W(t,e){const r=u.resolve(t,...e.split("/")),o=u.relative(t,r);if(o.startsWith("..")||u.isAbsolute(o))throw new Error("Diff 路径超出项目范围。");return r}function d(t,e,r){return y("git",["-C",t,...e],{encoding:"utf8",timeout:2e4,maxBuffer:r,stdio:["ignore","pipe","pipe"]})}function B(t,e,r){try{return d(t,e,r)}catch(o){const n=String(o.stdout||"");if(n)return n;if(Number(o.status)===1)return"";throw o}}function U(t){return t.length<=g?t:`${t.slice(0,g)}
|
|
2
2
|
|
|
3
|
-
[Diff 内容过大,已截断]`}function
|
|
4
|
-
`),o=Y(V(r));for(const n of o){const s=String(n.Root||"");if(!s)continue;const i=Number(n.DriveType||0),a=String(n.Name||s.replace(/[\\/]+$/,"")),c=String(n.VolumeName||"").trim();e.set(h(s,"win32"),{id:`drive-${a.replace(/[^a-z0-9]+/gi,"").toLowerCase()}`,label:c?`${c} (${a})`:
|
|
5
|
-
`),n=Y(V(o,{ACA_MACHINE_FS_PATH:r})),s=n.map(i=>{const a=String(i.Attributes||"");return{name:String(i.Name||""),path:String(i.FullName||""),kind:"directory",hidden:/Hidden|System/i.test(a),hasChildren:!0,reparsePoint:/ReparsePoint/i.test(a)}}).filter(i=>i.name&&i.path&&!i.reparsePoint&&(e||!i.hidden)).slice(0,E).sort((i,a)=>i.name.localeCompare(a.name,void 0,{numeric:!0,sensitivity:"base"}));return z(r,s,n.length>E,"win32")}function z(t,e,r,o){const n=o==="win32"?u.win32:u.posix,s=n.parse(t).root,i=h(t,o)===h(s,o)?null:n.dirname(t);return{platform:o,path:t,parentPath:i,breadcrumbs:
|
|
3
|
+
[Diff 内容过大,已截断]`}function Dt(){const t=process.platform,e=D(t);return{platform:t,roots:e}}function Rt(t){if(!t.directoryPath.trim())throw new Error("目录路径不能为空。");return process.platform==="win32"?Tt(t.directoryPath,!!t.includeHidden):$t(t.directoryPath,!!t.includeHidden)}function h(t,e=process.platform){const r=e==="win32"?u.win32:u.posix,o=r.normalize(t),n=r.parse(o).root,s=o.length>n.length?o.replace(/[\\/]+$/,""):o;return e==="win32"?s.toLocaleLowerCase("en-US"):s}function Ct(){const t=l.realpathSync(T.homedir()),e=[{id:"home",label:"用户目录",path:t,kind:"home",available:!0}];return t!==u.parse(t).root&&e.push({id:"root",label:"文件系统",path:"/",kind:"root",available:!0}),e}function D(t){if(b?.platform===t&&b.expiresAtMs>Date.now())return b.roots;const e=t==="win32"?Nt():Ct();return b={platform:t,roots:e,expiresAtMs:Date.now()+st},e}function Nt(){const t=l.realpathSync.native(T.homedir()),e=new Map;e.set(h(t,"win32"),{id:"home",label:"用户目录",path:t,kind:"home",available:!0});try{const r=["$items = Get-CimInstance Win32_LogicalDisk | ForEach-Object {"," [PSCustomObject]@{ Name = $_.DeviceID; Root = ($_.DeviceID + '\\\\'); DriveType = $_.DriveType; VolumeName = $_.VolumeName }","}","@($items) | ConvertTo-Json -Compress"].join(`
|
|
4
|
+
`),o=Y(V(r));for(const n of o){const s=String(n.Root||"");if(!s)continue;const i=Number(n.DriveType||0),a=String(n.Name||s.replace(/[\\/]+$/,"")),c=String(n.VolumeName||"").trim();e.set(h(s,"win32"),{id:`drive-${a.replace(/[^a-z0-9]+/gi,"").toLowerCase()}`,label:c?`${c} (${a})`:Ht(a,i),path:s,kind:Gt(i),available:!0})}}catch{const r=u.win32.parse(t).root;e.set(h(r,"win32"),{id:"drive-system",label:`系统盘 (${r.replace(/\\$/,"")})`,path:r,kind:"fixed",available:!0})}return[...e.values()]}function $t(t,e){const r=R(t),n=l.readdirSync(r,{withFileTypes:!0}).filter(i=>i.isDirectory()&&!i.isSymbolicLink()&&(e||!i.name.startsWith("."))),s=n.slice(0,E).map(i=>({name:i.name,path:u.join(r,i.name),kind:"directory",hidden:i.name.startsWith("."),hasChildren:!0}));return s.sort((i,a)=>i.name.localeCompare(a.name,void 0,{numeric:!0,sensitivity:"base"})),z(r,s,n.length>E,"linux")}function Tt(t,e){const r=R(t),o=["$items = Get-ChildItem -LiteralPath $env:ACA_MACHINE_FS_PATH -Directory -Force -ErrorAction Stop | ForEach-Object {"," [PSCustomObject]@{ Name = $_.Name; FullName = $_.FullName; Attributes = $_.Attributes.ToString() }","}","@($items) | ConvertTo-Json -Compress"].join(`
|
|
5
|
+
`),n=Y(V(o,{ACA_MACHINE_FS_PATH:r})),s=n.map(i=>{const a=String(i.Attributes||"");return{name:String(i.Name||""),path:String(i.FullName||""),kind:"directory",hidden:/Hidden|System/i.test(a),hasChildren:!0,reparsePoint:/ReparsePoint/i.test(a)}}).filter(i=>i.name&&i.path&&!i.reparsePoint&&(e||!i.hidden)).slice(0,E).sort((i,a)=>i.name.localeCompare(a.name,void 0,{numeric:!0,sensitivity:"base"}));return z(r,s,n.length>E,"win32")}function z(t,e,r,o){const n=o==="win32"?u.win32:u.posix,s=n.parse(t).root,i=h(t,o)===h(s,o)?null:n.dirname(t);return{platform:o,path:t,parentPath:i,breadcrumbs:Ft(t,o),readable:X(t,l.constants.R_OK),writable:X(t,l.constants.W_OK),items:e,truncated:r}}function Ft(t,e){const r=e==="win32"?u.win32:u.posix,o=r.parse(t),n=t.slice(o.root.length).split(/[\\/]+/).filter(Boolean),s=[{label:o.root.replace(/[\\/]+$/,"")||o.root,path:o.root}];let i=o.root;for(const a of n)i=r.join(i,a),s.push({label:a,path:i});return s}async function It(t){const e=String(t.workspaceId||"").trim(),r=String(t.name||"").trim(),o=t.projectType==="chat_room"?"chat_room":"project";if(!e||!r)throw new Error("项目名称和工作区不能为空。");const n=R(String(t.path||""));l.accessSync(n,l.constants.R_OK|l.constants.W_OK);const i=(await P("GET",`/api/projects?workspaceId=${encodeURIComponent(e)}`)).items.find(c=>h(c.root_path)===h(n)),a=i||(await P("POST","/api/projects",{projectId:$(),workspaceId:e,name:r,rootPath:n,source:"aica",projectType:o})).item;return tt(Lt(a)),{project:a,duplicate:!!i}}function Lt(t){return{projectId:t.project_id,workspaceId:t.workspace_id,name:t.name,rootPath:t.root_path}}function R(t){if(!t.trim()||!u.isAbsolute(t))throw new Error("必须选择绝对目录路径。");if(Mt(t),!l.statSync(t).isDirectory())throw new Error("选择的路径不是目录。");const r=l.realpathSync.native(t);return Ot(r),r}function m(t){const e=C(t),r=l.realpathSync.native(e);if(!l.statSync(r).isDirectory())throw new Error("项目根目录不存在。");return r}function C(t){const e=rt(t);if(!e||!u.isAbsolute(e))throw new Error("项目根目录无效。");return u.resolve(e)}function S(t){const e=t.replace(/\\/g,"/").replace(/^\/+|\/+$/g,"");if(!e)return"";if(e.includes("\0")||e.split("/").some(r=>!r||r==="."||r===".."))throw new Error("项目相对路径无效。");return e}function j(t,e,r){const o=e?u.join(t,...e.split("/")):t,n=l.realpathSync.native(o),s=u.relative(t,n);if(s.startsWith("..")||u.isAbsolute(s))throw new Error("目录超出项目范围。");const i=l.statSync(n);if(r&&!i.isDirectory())throw new Error("请求路径不是目录。");return n}function K(t,e){return u.relative(t,e).split(u.sep).join("/")}function Mt(t){const e=process.platform==="win32"?u.win32:u.posix;if(!D(process.platform).some(o=>{const n=String(o.path||"");if(!n)return!1;const s=e.relative(n,t);return s===""||!s.startsWith("..")&&!e.isAbsolute(s)}))throw new Error("目录不在允许浏览的位置内。")}function Ot(t){const e=process.platform==="win32"?u.win32:u.posix;if(!D(process.platform).some(o=>{const n=String(o.path||"");if(!n)return!1;const s=e.relative(n,t);return s===""||!s.startsWith("..")&&!e.isAbsolute(s)}))throw new Error("目录不在允许浏览的位置内。")}function X(t,e){try{return l.accessSync(t,e),!0}catch{return!1}}function V(t,e={}){return y("powershell.exe",["-NoLogo","-NoProfile","-NonInteractive","-Command",t],{encoding:"utf8",windowsHide:!0,timeout:8e3,maxBuffer:4*1024*1024,env:{...process.env,...e}}).replace(/^\uFEFF/,"").trim()}function Y(t){if(!t)return[];const e=JSON.parse(t);return Array.isArray(e)?e.filter(r=>!!(r&&typeof r=="object")):e&&typeof e=="object"?[e]:[]}function Gt(t){return t===2?"removable":t===3?"fixed":t===4?"network":t===5?"optical":"drive"}function Ht(t,e){return e===2?`移动磁盘 (${t})`:e===4?`网络驱动器 (${t})`:e===5?`光驱 (${t})`:`本地磁盘 (${t})`}export{lt as cleanupWorktree,ht as commitProjectUploadTempFile,ct as commitWorktree,h as comparableMachinePath,_t as getProjectGitCommit,gt as getProjectGitDiff,St as getProjectGitLog,O as getProjectGitStatus,k as getWorktreeStatus,Rt as listMachineDirectory,Dt as listMachineFilesystemRoots,ut as listProjectDirectory,dt as mutateProjectFiles,te as startMachineFilesystemWorkerLoop,ft as uploadProjectFile};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import e from"node:fs";import r from"node:path";import{createRequire as c}from"node:module";import{getAsset as a,isSea as s}from"node:sea";import{getAcaHome as f}from"../paths.js";async function S(){if(!s())return import("node-pty");const n=JSON.parse(a("terminal-manifest","utf8")),t=r.join(f(),"native",`pty-${n.hash}`);if(!e.existsSync(r.join(t,".ready"))){e.mkdirSync(r.dirname(t),{recursive:!0,mode:448});const o=e.mkdtempSync(`${t}-`);try{for(const i of n.files){const m=r.join(o,i.name);e.mkdirSync(r.dirname(m),{recursive:!0}),e.writeFileSync(m,Buffer.from(a(`terminal/${i.name}`)),{mode:i.executable?448:384})}e.writeFileSync(r.join(o,".ready"),"");try{e.renameSync(o,t)}catch(i){if(!e.existsSync(r.join(t,".ready")))throw i}}finally{e.rmSync(o,{recursive:!0,force:!0})}}return c(r.join(t,"package.json"))(r.join(t,"lib","index.js"))}export{S as loadPty};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{randomUUID as M}from"node:crypto";import p from"node:fs";import u from"node:path";import S from"@xterm/headless";import q from"@xterm/addon-serialize";import{loadPty as D}from"./native.js";import{expandWorkerRuntimePath as E}from"../worker-runtime-paths.js";import{terminalShell as T}from"./shell.js";const y=8,z=1440*6e4;class O{emit;entries=new Map;sweep=setInterval(()=>{for(const[e,t]of this.entries)!t.attachmentId&&Date.now()-t.detachedAt>z&&this.remove(e)},6e4);opening=new Map;epoch=0;disposed=!1;constructor(e){this.emit=e,this.sweep.unref()}async handle(e){const{sessionId:t,attachmentId:i}=e;if(!t||!i||this.disposed)return;const s=this.epoch,n=(this.opening.get(t)||Promise.resolve()).catch(()=>{}).then(async()=>{s!==this.epoch||this.disposed||(e.type==="open"?await this.open(e,s):this.control(e))});this.opening.set(t,n);try{await n}finally{this.opening.get(t)===n&&this.opening.delete(t)}}control(e){const{sessionId:t,attachmentId:i}=e,s=this.entries.get(t);if(!(!s||s.attachmentId!==i))if(e.type==="detach")s.attachmentId=void 0,s.detachedAt=Date.now();else if(e.type==="input"&&s.exitCode===void 0&&typeof e.data=="string"&&e.data.length<=16384)s.pty.write(e.binary?Buffer.from(e.data,"binary"):e.data);else if(e.type==="resize"&&s.exitCode===void 0){const[o,n]=C(e);s.screen.resize(o,n),s.pty.resize(o,n)}else e.type==="end"&&s.exitCode===void 0&&s.pty.kill()}detachAll(){this.epoch++;for(const e of this.entries.values())e.attachmentId&&(e.detachedAt=Date.now()),e.attachmentId=void 0}hasRunningInDirectory(e,t){const i=p.realpathSync(e);return[...this.entries.values()].some(s=>s.exitCode===void 0&&(s.sessionId===t||s.cwd===i||s.cwd.startsWith(i+u.sep)||this.shellIsInside(s,i)))}shellIsInside(e,t){if(process.platform!=="linux")return!1;try{const i=p.realpathSync(`/proc/${e.pty.pid}/cwd`);return i===t||i.startsWith(t+u.sep)}catch{return!1}}dispose(){this.disposed=!0,this.epoch++,clearInterval(this.sweep);for(const e of this.entries.keys())this.remove(e)}async open(e,t){const{sessionId:i,attachmentId:s}=e;let o=this.entries.get(i);if(e.restart&&o?.exitCode!==void 0&&(this.remove(i),o=void 0),!o){if(!e.create){this.emit({type:"missing",sessionId:i,attachmentId:s});return}const d=E(e.cwd||"");if(!d||!u.isAbsolute(d))throw new Error("终端启动目录无效。");const l=p.realpathSync(d);if(!p.statSync(l).isDirectory())throw new Error("终端启动目录不存在。");const A=await D();if(this.disposed||t!==this.epoch)return;if(this.entries.size>=y){const a=[...this.entries.values()].find(v=>v.exitCode!==void 0&&!v.attachmentId);a&&this.remove(a.sessionId)}if(this.entries.size>=y)throw new Error("此设备最多保留 8 个终端,请先结束并收起其他终端。");const[w,m]=C(e),I=process.platform==="win32"?process.env.COMSPEC||"powershell.exe":process.env.SHELL||"/bin/sh",g=T(I),c=A.spawn(I,g.args,{name:"xterm-256color",cwd:l,cols:w,rows:m,env:g.env}),f=new S.Terminal({cols:w,rows:m,scrollback:1e3,allowProposedApi:!0}),x=new q.SerializeAddon;f.loadAddon(x),o={id:M(),sessionId:i,cwd:l,pty:c,screen:f,serializer:x,detachedAt:Date.now(),seq:0,pending:"",queued:0},this.entries.set(i,o);const r=o;f.onData(a=>{if(!r.attachmentId&&r.exitCode===void 0)try{c.write(a)}catch{}}),c.onData(a=>{r.pending+=a,r.pending.length>=32768?this.flush(r):r.timer||(r.timer=setTimeout(()=>this.flush(r),24))}),c.onExit(({exitCode:a})=>{this.entries.get(i)===r&&(this.flush(r),r.screen.write("",()=>{r.exitCode=a,r.attachmentId&&this.emit({type:"exit",sessionId:i,attachmentId:r.attachmentId,exitCode:a})}))})}o.attachmentId=void 0,this.flush(o);const n=o;await new Promise(d=>n.screen.write("",()=>{if(this.disposed||t!==this.epoch){d();return}n.attachmentId=s,this.emit({type:"snapshot",sessionId:i,attachmentId:s,terminalId:n.id,seq:n.seq,cwd:n.cwd,cols:n.screen.cols,rows:n.screen.rows,data:n.serializer.serialize(),exitCode:n.exitCode,retentionHours:24}),d()}))}flush(e){if(clearTimeout(e.timer),e.timer=void 0,!e.pending)return;const t=e.pending;e.pending="",e.queued+=t.length,e.queued>256e3&&e.pty.pause(),e.screen.write(t,()=>{this.entries.get(e.sessionId)===e&&(e.queued-=t.length,e.queued<64e3&&e.exitCode===void 0&&e.pty.resume(),e.seq++,e.attachmentId&&this.emit({type:"output",sessionId:e.sessionId,attachmentId:e.attachmentId,seq:e.seq,data:t}))})}remove(e){const t=this.entries.get(e);t&&(this.entries.delete(e),clearTimeout(t.timer),t.exitCode===void 0&&t.pty.kill(),t.screen.dispose())}}function C(h){return[Math.max(20,Math.min(240,Math.floor(Number(h.cols)||80))),Math.max(5,Math.min(80,Math.floor(Number(h.rows)||24)))]}let _;function U(h){_=h}export{O as TerminalService,_ as activeTerminalService,U as setActiveTerminalService};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import e from"node:fs";import a from"node:path";import{createHash as m,randomUUID as f}from"node:crypto";import{getAcaHome as h}from"../paths.js";const s=`# ACA 交互终端默认配色;用户配置始终优先。
|
|
2
|
+
__aca_terminal_initial_ps1=\${PS1-}
|
|
3
|
+
if [[ -r "$HOME/.bashrc" ]]; then
|
|
4
|
+
. "$HOME/.bashrc"
|
|
5
|
+
fi
|
|
6
|
+
if [[ -z \${NO_COLOR:-} ]]; then
|
|
7
|
+
if [[ \${PS1-} == "$__aca_terminal_initial_ps1" && -z \${PROMPT_COMMAND[*]-} ]]; then
|
|
8
|
+
PS1='\\[\\e[32m\\]\\u@\\h\\[\\e[0m\\]:\\[\\e[34m\\]\\w\\[\\e[0m\\]\\$ '
|
|
9
|
+
fi
|
|
10
|
+
if ! alias ls >/dev/null 2>&1 && ! declare -F ls >/dev/null 2>&1; then
|
|
11
|
+
if command ls --color=auto -d . >/dev/null 2>&1; then
|
|
12
|
+
alias ls='ls --color=auto'
|
|
13
|
+
elif [[ $OSTYPE == darwin* || $OSTYPE == *bsd* ]]; then
|
|
14
|
+
alias ls='ls -G'
|
|
15
|
+
fi
|
|
16
|
+
fi
|
|
17
|
+
fi
|
|
18
|
+
unset __aca_terminal_initial_ps1
|
|
19
|
+
`;function $(o,l=process.env){const t={...l,TERM:"xterm-256color",COLORTERM:"truecolor"};if(process.platform==="win32"||a.basename(o)!=="bash")return{args:[],env:t};const n=a.join(h(),"terminal"),c=m("sha256").update(s).digest("hex").slice(0,16),r=a.join(n,`bashrc-${c}`);if(e.mkdirSync(n,{recursive:!0,mode:448}),!e.existsSync(r)){const i=`${r}.${f()}.tmp`;try{e.writeFileSync(i,s,{mode:384,flag:"wx"}),e.renameSync(i,r)}finally{e.rmSync(i,{force:!0})}}return{args:["--rcfile",r,"-i"],env:t}}export{$ as terminalShell};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import p from"ws";import{loadAcaConfig as w}from"../aca-config.js";import{TerminalService as y,setActiveTerminalService as h}from"./service.js";function v(l){let r,m=!1,s,f,d=Date.now();const i=new y(n=>{if(r?.readyState===p.OPEN){if(r.bufferedAmount>2e6){r.terminate();return}r.send(JSON.stringify(n))}});h(i);function c(){if(m)return;const n=w();if(!n.serverUrl||!n.token){s=setTimeout(c,5e3);return}try{const o=new URL("api/terminal/worker",n.serverUrl.replace(/\/?$/,"/"));o.protocol=o.protocol==="https:"?"wss:":"ws:";const e=new p(o,{headers:{authorization:`Bearer ${n.token}`},rejectUnauthorized:!["1","true","yes"].includes((process.env.ACA_SERVER_INSECURE_TLS||"").toLowerCase()),maxPayload:65536,perMessageDeflate:!1,handshakeTimeout:1e4});r=e,e.on("open",()=>{d=Date.now(),f=setInterval(()=>{Date.now()-d>45e3&&e.terminate()},15e3)}),e.on("ping",()=>{d=Date.now()}),e.on("message",a=>{let t;try{t=JSON.parse(a.toString())}catch{e.close(1008);return}if(!t||typeof t!="object"||typeof t.sessionId!="string"||typeof t.attachmentId!="string"){e.close(1008);return}i.handle(t).catch(u=>{e.readyState===p.OPEN&&e.send(JSON.stringify({type:"error",sessionId:t.sessionId,attachmentId:t.attachmentId,message:u instanceof Error?u.message:"终端操作失败。"}))})}),e.on("error",a=>l.warn(`Terminal connection: ${a.message}`)),e.on("close",a=>{clearInterval(f),i.detachAll(),a===4001?l.warn("Terminal channel replaced by another Worker on this machine; restart this Worker to reconnect."):m||(s=setTimeout(c,3e3+Math.random()*2e3))})}catch(o){l.warn(`Terminal connection: ${String(o)}`),s=setTimeout(c,5e3)}}return c(),{stop(){m=!0,clearTimeout(s),clearInterval(f),r?.terminate(),i.dispose(),h(void 0)}}}export{v as startTerminalWorker};
|
package/dist/version.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
const o="0.1.
|
|
1
|
+
const o="0.1.56";export{o as AICA_VERSION};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yuandc/aica",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.56",
|
|
4
4
|
"description": "AICA remote Agent worker",
|
|
5
5
|
"author": "yuandc",
|
|
6
6
|
"type": "module",
|
|
@@ -27,10 +27,14 @@
|
|
|
27
27
|
],
|
|
28
28
|
"dependencies": {
|
|
29
29
|
"@openai/codex": "0.156.1",
|
|
30
|
+
"@xterm/addon-serialize": "^0.14.0",
|
|
31
|
+
"@xterm/headless": "^6.0.0",
|
|
30
32
|
"chalk": "^5.4.1",
|
|
31
33
|
"commander": "^12.1.0",
|
|
32
34
|
"diff": "^9.0.0",
|
|
35
|
+
"node-pty": "^1.1.0",
|
|
33
36
|
"undici": "^7.28.0",
|
|
37
|
+
"ws": "^8.22.0",
|
|
34
38
|
"zod": "^3.25.76"
|
|
35
39
|
}
|
|
36
40
|
}
|