@yuandc/aica 0.1.68 → 0.1.70

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,4 +1,4 @@
1
- import{createToolLifecycleTrace as x}from"../../tool-lifecycle-trace.js";import{JsonLineRpcClient as L}from"../../client/json-rpc.js";import{startCodexAppServer as $}from"../codex/codex-process.js";import{CodexConnectionRecoveryTracker as O}from"../codex/connection-events.js";import{codexAcpSessionUpdatesFromNotification as y}from"../codex/events.js";import{CodexPlanDeltaState as k}from"../codex/plan-events.js";import{isCodexApprovalRequest as j,resolveCodexApprovalRequest as B}from"../codex/permissions.js";import{buildCodexContinuityInstructions as K,codexTokenUsageFromRawResponseCompleted as W,codexTokenUsageFromNotification as z,decideCodexContextMaintenance as v,readCodexContextMaintenancePolicy as R,rolloutFileSize as G}from"./context-maintenance.js";import{codexNotificationBelongsToCurrentTurn as J,codexNotificationRouting as A}from"./notification-routing.js";import{completedSubtaskHistoryUpdates as H}from"./subtask-history.js";import{CodexSubtaskRouter as V}from"./subtask-routing.js";import{decorateSubtaskUpdate as P}from"./subtask-executions.js";import{waitForCancellableTurnStart as X}from"./cancellation.js";import{codexAcpUpdateIsMeaningfulProgress as _,codexRawItemIsMeaningfulProgress as Q,withCodexProgressMetadata as M}from"./progress-evidence.js";import{acpPromptText as Y,acpPromptToCodexInput as Z,turnConfigFromConfig as tt}from"./turn-input.js";import{CodexControlToolLoopDetector as et,codexControlToolLoopAcpUpdate as q,controlToolLoopDetectionThresholds as st,controlToolLoopReady as ot,readCodexControlToolLoopPolicy as it}from"./control-tool-loop.js";import{CodexControlToolSteeringLimiter as nt,codexControlToolSteeringAcpUpdate as U}from"./control-tool-steering.js";class rt{toolTrace=x();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 k;subtaskRouter=new V;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 O;latestTokenUsage=null;currentRolloutPath=null;threadTurnCount=0;currentPromptText="";currentAssistantText="";previousUserText="";previousAssistantText="";previousAffectedFiles=new Set;maintenanceActive=!1;pendingCompaction=null;shutdownPromise=null;managedProvider=null;controlToolLoop;controlToolPolicy=it();controlToolSteering=new nt(this.controlToolPolicy.steeringMaxAttempts);constructor(){this.controlToolLoop=new et({hasActiveSubtasks:()=>this.subtaskRouter.hasActiveSubtasks(),shortSleepMaxDurationMs:this.controlToolPolicy.shortSleepMaxDurationMs}),process.stdin.setEncoding("utf8"),process.stdin.on("data",e=>this.handleInput(String(e)));const t=()=>{this.shutdown().finally(()=>process.exit(0))};process.stdin.once("end",t),process.stdin.once("close",t),process.stdin.once("error",t),process.once("SIGTERM",()=>{this.shutdown().finally(()=>process.exit(0))}),process.once("SIGINT",()=>{this.shutdown().finally(()=>process.exit(130))})}handleInput(t){this.lineBuffer+=t;const e=this.lineBuffer.split(`
1
+ import{createToolLifecycleTrace as x}from"../../tool-lifecycle-trace.js";import{JsonLineRpcClient as L}from"../../client/json-rpc.js";import{startCodexAppServer as $}from"../codex/codex-process.js";import{CodexConnectionRecoveryTracker as O}from"../codex/connection-events.js";import{codexAcpSessionUpdatesFromNotification as y}from"../codex/events.js";import{CodexPlanDeltaState as k}from"../codex/plan-events.js";import{isCodexApprovalRequest as j,resolveCodexApprovalRequest as B}from"../codex/permissions.js";import{buildCodexContinuityInstructions as K,codexTokenUsageFromRawResponseCompleted as W,codexTokenUsageFromNotification as z,decideCodexContextMaintenance as v,readCodexContextMaintenancePolicy as R,rolloutFileSize as G}from"./context-maintenance.js";import{codexNotificationBelongsToCurrentTurn as J,codexNotificationRouting as A}from"./notification-routing.js";import{completedSubtaskHistoryUpdates as H}from"./subtask-history.js";import{CodexSubtaskRouter as V}from"./subtask-routing.js";import{decorateSubtaskUpdate as P}from"./subtask-executions.js";import{decorateSubtaskCompletion as X}from"./subtask-completion.js";import{waitForCancellableTurnStart as Q}from"./cancellation.js";import{codexAcpUpdateIsMeaningfulProgress as _,codexRawItemIsMeaningfulProgress as Y,withCodexProgressMetadata as M}from"./progress-evidence.js";import{acpPromptText as Z,acpPromptToCodexInput as tt,turnConfigFromConfig as et}from"./turn-input.js";import{CodexControlToolLoopDetector as st,codexControlToolLoopAcpUpdate as q,controlToolLoopDetectionThresholds as ot,controlToolLoopReady as it,readCodexControlToolLoopPolicy as nt}from"./control-tool-loop.js";import{CodexControlToolSteeringLimiter as rt,codexControlToolSteeringAcpUpdate as U}from"./control-tool-steering.js";class at{toolTrace=x();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 k;subtaskRouter=new V;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 O;latestTokenUsage=null;currentRolloutPath=null;threadTurnCount=0;currentPromptText="";currentAssistantText="";previousUserText="";previousAssistantText="";previousAffectedFiles=new Set;maintenanceActive=!1;pendingCompaction=null;shutdownPromise=null;managedProvider=null;controlToolLoop;controlToolPolicy=nt();controlToolSteering=new rt(this.controlToolPolicy.steeringMaxAttempts);constructor(){this.controlToolLoop=new st({hasActiveSubtasks:()=>this.subtaskRouter.hasActiveSubtasks(),shortSleepMaxDurationMs:this.controlToolPolicy.shortSleepMaxDurationMs}),process.stdin.setEncoding("utf8"),process.stdin.on("data",e=>this.handleInput(String(e)));const t=()=>{this.shutdown().finally(()=>process.exit(0))};process.stdin.once("end",t),process.stdin.once("close",t),process.stdin.once("error",t),process.once("SIGTERM",()=>{this.shutdown().finally(()=>process.exit(0))}),process.once("SIGINT",()=>{this.shutdown().finally(()=>process.exit(130))})}handleInput(t){this.lineBuffer+=t;const e=this.lineBuffer.split(`
2
2
  `);this.lineBuffer=e.pop()??"";for(const s of e){const o=s.trim();if(!o)continue;let i;try{i=JSON.parse(o)}catch{continue}this.handleMessage(i)}}async handleMessage(t){if("id"in t&&("result"in t||"error"in t)&&typeof t.method!="string"){this.handleParentResponse(t);return}const e=typeof t.method=="string"?t.method:"",s=t.id;if(e){if(s===void 0){await this.handleNotification(e,a(t.params));return}try{const o=await this.handleRequest(e,a(t.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(t,e){if(t==="session/cancel")try{await this.cancel(e)}catch(s){process.stderr.write(`Codex cancel failed: ${s instanceof Error?s.message:String(s)}
3
- `),await this.shutdown(),process.exit(1)}}async handleRequest(t,e){switch(t){case"initialize":return this.initializeResponse();case"aca/runtime_config":return this.applyRuntimeConfig(e);case"session/new":return this.newSession(e);case"session/resume":case"session/load":return this.resumeSession(e);case"session/fork":return this.forkSession(e);case"session/prompt":return this.prompt(e);case"session/compact":return this.manualCompact(e);case"aca/session_recover":return this.recoverSession(e);case"session/cancel":return await this.cancel(e),{};case"session/close":return await this.closeSession(e),{};case"session/set_config_option":return this.setConfigOption(e);case"session/set_mode":return this.setMode(e);case"fs/read_text_file":case"fs/write_text_file":case"terminal/create":case"terminal/output":case"terminal/wait_for_exit":case"terminal/kill":case"terminal/release":return{};default:throw new Error(`Unsupported ACP method: ${t}`)}}initializeResponse(){return{protocolVersion:1,agentCapabilities:{loadSession:!0,sessionCapabilities:{resume:!0,close:!0}}}}applyRuntimeConfig(t){if(this.started)throw new Error("Codex runtime config must be applied before app-server starts");const e=a(t.codexProvider);if(Object.keys(e).length===0)return this.managedProvider=null,{configured:!1};const s=c(e,"baseUrl"),o=c(e,"apiKey"),i=Number(e.revision||0);if(!s||!o||!Number.isSafeInteger(i)||i<1)throw new Error("Invalid Codex managed provider runtime config");return this.managedProvider={baseUrl:s,apiKey:o,revision:i},{configured:!0,revision:i}}async newSession(t){const e=c(t,"cwd")||process.cwd();this.applyInitialConfig(t),await this.ensureCodex(e);const s=await this.requireRpc().request("thread/start",{cwd:e,approvalsReviewer:"user",...this.codexConfig(),ephemeral:!1,experimentalRawEvents:!0,threadSource:"aca-codex-acp"},6e4),o=T(s);if(!o)throw new Error("Codex thread/start did not return thread id");return this.currentSessionId=o,this.currentCwd=e,this.resetSubtaskState(),this.captureThreadMetadata(s),this.sessionResponse(o)}async resumeSession(t){const e=c(t,"sessionId");if(!e)throw new Error("sessionId is required");const s=c(t,"cwd")||this.currentCwd;this.applyInitialConfig(t),await this.ensureCodex(s);const o=await this.requireRpc().request("thread/resume",{threadId:e,cwd:s,approvalsReviewer:"user",...this.codexConfig()},6e4);return this.currentSessionId=T(o)||e,this.currentCwd=s,this.resetSubtaskState(o),await this.subscribeKnownSubtasks(!1),this.captureThreadMetadata(o),this.sessionResponse(this.currentSessionId)}async forkSession(t){const e=c(t,"sessionId");if(!e)throw new Error("sessionId is required");const s=c(t,"cwd")||this.currentCwd;this.applyInitialConfig(t),await this.ensureCodex(s);const o=await this.requireRpc().request("thread/fork",{threadId:e,cwd:s,approvalsReviewer:"user",ephemeral:!1,threadSource:"aca-codex-acp-fork",...this.codexConfig()},12e4),i=T(o);if(!i)throw new Error("Codex thread/fork did not return thread id");return this.currentSessionId=i,this.currentCwd=s,this.resetSubtaskState(o),this.captureThreadMetadata(o),this.sessionResponse(i)}async prompt(t){this.promptCancelled=!1;const e=c(t,"sessionId")||this.currentSessionId;let s=this.resolveSessionAlias(e);if(!s)throw new Error("sessionId is required");const o=Array.isArray(t.prompt)?t.prompt:[],i=this.requireRpc();this.currentSessionId&&this.currentSessionId!==s&&this.resetSubtaskState(),this.currentSessionId=s;const r=await this.maintainContext("before",{userRequest:this.previousUserText,assistantResponse:this.previousAssistantText,affectedFiles:[...this.previousAffectedFiles]},{allowRollover:!0});if(dt(r),this.promptCancelled||this.shutdownPromise)throw new Error("Codex prompt cancelled");s=this.currentSessionId||s,this.toolTrace=x(),this.promptActive=!0,this.currentTurnId=null,this.controlToolLoop.reset(),this.controlToolSteering.reset(),this.currentPromptText=Y(o),this.currentAssistantText="",this.currentAffectedFiles.clear(),this.connectionRecovery.reset(),this.agentMessagePhases.clear(),this.planDeltas.reset(),this.pendingSubtaskNotifications=[],this.flushSubtaskStates();let d;try{this.turnStartPromise=i.request("turn/start",{threadId:s,clientUserMessageId:`aca-acp-${Date.now()}-${Math.random().toString(16).slice(2)}`,input:Z(o),cwd:this.currentCwd,...tt(this.config)},D()),d=await this.turnStartPromise;const u=String(d?.turn?.id??"");this.currentTurnId=u||null;const l=u?await this.waitForTurnCompleted(u,D()):d?.turn,h=await this.subtaskRouter.waitForIdle(mt());if(h.timedOut&&h.activeThreadIds.length>0&&this.notifySessionUpdate({sessionUpdate:"aca_codex_subtask_drain",label:"子任务收尾等待超时",detail:`仍有 ${h.activeThreadIds.length} 个子任务未结束`,_meta:{aca:{subtaskDrain:h}}}),this.promptCancelled)throw new Error("Codex prompt cancelled");this.threadTurnCount+=1,this.previousUserText=this.currentPromptText,this.previousAssistantText=this.currentAssistantText,this.previousAffectedFiles.clear();for(const p of this.currentAffectedFiles)this.previousAffectedFiles.add(p);const f=await this.maintainContext("after",{userRequest:this.previousUserText,assistantResponse:this.previousAssistantText,affectedFiles:[...this.previousAffectedFiles]},{allowRollover:!0});return{stopReason:"end_turn",sessionId:this.currentSessionId,turn:l,_meta:{acaContextUsage:E(f)??ut(this.latestTokenUsage),acaContextMaintenance:f,acaSubtaskDrain:h}}}catch(u){const l=this.connectionRecovery.fail(u);throw l&&this.notifySessionUpdate(l),u}finally{this.subtaskRouter.executions.loseObservation(this.promptCancelled?"父任务已中断,子执行状态需重新确认":"本轮观测结束,未确认子执行终态"),this.flushSubtaskStates(),this.controlToolLoop.reset(),this.promptActive=!1,this.currentTurnId=null,this.turnStartPromise=null,this.pendingSubtaskNotifications=[]}}resetSubtaskState(t){this.subtaskGeneration+=1,this.subtaskRouter.reset(),this.subtaskAgentMessagePhases.clear(),this.subtaskPlanDeltas.clear(),this.subscribedSubtaskThreadIds.clear(),this.pendingSubtaskSubscriptions.clear(),this.hydratedSubtaskThreadIds.clear(),this.seenSubtaskItemIds.clear(),this.pendingSubtaskNotifications=[],t!==void 0&&this.subtaskRouter.restoreFromSessionSnapshot(t,this.currentSessionId)}async subscribeKnownSubtasks(t=!0){const e=this.subtaskRouter.routesForSubscription().map(s=>this.subscribeSubtask(s,t));await Promise.allSettled(e)}subscribeSubtask(t,e){const s=t.sourceThreadId;if(!s||s===this.currentSessionId||this.subscribedSubtaskThreadIds.has(s))return Promise.resolve();const o=this.pendingSubtaskSubscriptions.get(s);if(o)return o;const i=this.subtaskGeneration,r=this.requireRpc().request("thread/resume",{threadId:s,approvalsReviewer:"user",excludeTurns:!1},6e4).then(d=>{if(i!==this.subtaskGeneration)return;this.subscribedSubtaskThreadIds.add(s);const u=this.subtaskRouter.restoreFromSessionSnapshot(d,this.currentSessionId);e&&this.hydrateSubtaskHistory(d,t),u>0&&this.subscribeKnownSubtasks(e)}).catch(()=>{}).finally(()=>{this.pendingSubtaskSubscriptions.get(s)===r&&this.pendingSubtaskSubscriptions.delete(s)});return this.pendingSubtaskSubscriptions.set(s,r),r}async cancel(t){const e=this.resolveSessionAlias(c(t,"sessionId")||this.currentSessionId);if(!e||!this.rpc)return;if(e!==this.currentSessionId)throw new Error("Cannot cancel a different Codex session");this.promptCancelled=!0,this.subtaskRouter.stopWaiting();const s=this.turnStartPromise?await X(this.turnStartPromise):null,o=this.currentTurnId||c(w(s,"turn"),"id");if(await this.rpc.request("turn/interrupt",{threadId:e,turnId:o},5e3),o){const i=this.pendingTurnCompletions.get(o);i&&(this.pendingTurnCompletions.delete(o),clearTimeout(i.timer),i.reject(new Error("Codex prompt cancelled")))}}async manualCompact(t){const e=c(t,"sessionId")||this.currentSessionId,s=this.resolveSessionAlias(e);if(!s)throw new Error("sessionId is required");if(this.promptActive||this.maintenanceActive)throw new Error("Codex session is busy");this.currentSessionId=s;const o=R(),i=await this.maintainContext("manual",{userRequest:this.previousUserText,assistantResponse:this.previousAssistantText,affectedFiles:[...this.previousAffectedFiles]},{force:!0,allowRollover:t.allowRollover===!0,compactTimeoutMs:ct(t,o.compactTimeoutMs)}),r=String(i.action||"");if(r==="compact_failed"||r==="rollover_failed"){const d=String(i.compactError||"Codex 上下文压缩未完成");throw new Error(`Codex 上下文压缩失败:${d}`)}return{sessionId:this.currentSessionId,maintenance:i,contextUsage:E(i)}}async recoverSession(t){const e=c(t,"sessionId")||this.currentSessionId,s=this.resolveSessionAlias(e);if(!s)throw new Error("sessionId is required");const o=c(t,"reason");if(o!=="low_output_loop"&&o!=="control_tool_loop")throw new Error("Unsupported Codex recovery reason");if(this.promptActive||this.maintenanceActive)throw new Error("Codex session is busy");if(s!==this.currentSessionId)throw new Error("Cannot recover a different Codex session");const i=new Set([...this.previousAffectedFiles,...this.currentAffectedFiles]);return await this.rotateThread(s,{userRequest:this.currentPromptText||this.previousUserText,assistantResponse:this.currentAssistantText||this.previousAssistantText,affectedFiles:[...i]}),{...a(this.sessionResponse(this.currentSessionId)),previousSessionId:s,recoveryReason:o}}async closeSession(t){await this.shutdown()}async ensureCodex(t){if(this.rpc&&this.started&&this.started.child.exitCode===null&&this.started.child.signalCode===null)return;if(this.codexStartupPromise){await this.codexStartupPromise;return}const e=(async()=>{if(this.rpc&&this.started&&this.started.child.exitCode===null&&this.started.child.signalCode===null)return;const s=$(t,this.managedProvider),o=new L(s.child,{peerName:"Codex app-server",onNotification:(i,r)=>this.handleCodexNotification(i,r),onRequest:async(i,r)=>this.handleCodexRequest(i,r)});this.started=s,this.rpc=o,s.child.once("exit",()=>{this.started===s&&(this.subtaskRouter.executions.loseObservation("执行连接断开,未确认子代理终态"),this.flushSubtaskStates(),this.subtaskRouter.stopWaiting())}),this.initialized=!1,await o.request("initialize",{clientInfo:{name:"aca-codex-acp",version:"0.1.0"},capabilities:{experimentalApi:!0,requestAttestation:!1}},6e4),this.initialized=!0})();this.codexStartupPromise=e;try{await e}catch(s){throw this.rpc&&this.started&&!this.initialized&&await C(this.started.child,1e3),this.rpc=null,this.started=null,s}finally{this.codexStartupPromise===e&&(this.codexStartupPromise=null)}}handleCodexNotification(t,e){const s=A(e);if(t==="item/started"||t==="item/completed"){const u=a(a(e).item);this.toolTrace("provider_notification",{method:t,threadId:s.threadId,turnId:s.turnId,toolCallId:u.id,itemType:u.type,status:u.status})}if(this.maintenanceActive&&(!s.threadId||s.threadId===this.currentSessionId)){(t==="item/started"||t==="item/completed")&&this.toolTrace("notification_filtered",{method:t,toolCallId:a(a(e).item).id,reason:"context_maintenance"}),this.captureContextNotification(t,e);return}if(!J(e,this.currentSessionId,this.currentTurnId)){if(!this.promptActive||!s.threadId||s.threadId===this.currentSessionId){(t==="item/started"||t==="item/completed")&&this.toolTrace("notification_filtered",{method:t,toolCallId:a(a(e).item).id,reason:"outside_current_turn",threadId:s.threadId,turnId:s.turnId});return}const u=this.subtaskRouter.routeFor(s.threadId);if(!u){(t==="item/started"||t==="item/completed")&&this.toolTrace("notification_buffered",{method:t,toolCallId:a(a(e).item).id,threadId:s.threadId,reason:"subtask_route_pending"}),this.bufferPendingSubtaskNotification(t,e,s.threadId);return}this.handleSubtaskNotification(t,e,u),this.flushPendingSubtaskNotifications();return}const o=this.subtaskRouter.observe(e,this.currentSessionId);if(this.flushSubtaskStates(),o>0&&this.subscribeKnownSubtasks(!0),this.captureContextNotification(t,e),this.maintenanceActive)return;if(!this.promptActive&&t!=="turn/completed"){(t==="item/started"||t==="item/completed")&&this.toolTrace("notification_filtered",{method:t,toolCallId:a(a(e).item).id,reason:"prompt_inactive"});return}this.rpc?.refreshPendingRequestTimeout("turn/start"),this.captureAgentMessagePhase(t,e);const i=this.connectionRecovery.beforeNotification(t,e);t==="turn/completed"&&this.resolveTurnCompleted(e);const r=t==="rawResponseItem/completed"?ht(this.controlToolLoop,e):[],d=t==="rawResponse/completed"?lt(e):t==="rawResponseItem/completed"?r.map(({update:u})=>u):y(t,e,{agentMessagePhases:this.agentMessagePhases,planDeltas:this.planDeltas});for(const u of r)this.maybeSteerControlToolLoop(u.event);for(const u of[...i,...d]){const l=M(u,t,e,this.agentMessagePhases);_(l)&&this.notifyControlToolProgress(),this.captureUpdateEvidence(l),this.notifySessionUpdate(l)}o>0&&this.flushPendingSubtaskNotifications()}handleSubtaskNotification(t,e,s){if(!this.promptActive)return;const o=this.subtaskRouter.acceptNotification(s,t,e);if(this.flushSubtaskStates(),!o){(t==="item/started"||t==="item/completed")&&this.toolTrace("notification_filtered",{method:t,toolCallId:a(a(e).item).id,threadId:s.sourceThreadId,reason:"subtask_not_current"});return}(t==="rawResponseItem/completed"||t==="item/completed")&&Q(a(a(e).item))&&this.notifyControlToolProgress(),this.rememberSubtaskItem(s.sourceThreadId,e),this.rpc?.refreshPendingRequestTimeout("turn/start");const i=this.subtaskRouter.executions.get(s.sourceThreadId)?.runId||s.sourceThreadId,r=this.subtaskAgentMessagePhases.get(i)??new Map;this.subtaskAgentMessagePhases.set(i,r);const d=this.subtaskPlanDeltas.get(i)??new k;this.subtaskPlanDeltas.set(i,d),this.captureAgentMessagePhase(t,e,r);const u=this.subtaskRouter.observe(e,this.currentSessionId);this.flushSubtaskStates(),u>0&&this.subscribeKnownSubtasks(!0);const l=y(t,e,{agentMessagePhases:r,planDeltas:d}),h=A(e).turnId;for(const f of l){const p=M(f,t,e,r);if(!pt(p))continue;c(p,"title")&&this.subtaskRouter.executions.action(s.sourceThreadId,c(p,"title")),_(p)&&(this.notifyControlToolProgress(),this.subtaskRouter.executions.progress(s.sourceThreadId,c(p,"title")||"收到执行结果"));const g=P(p,s,h,this.subtaskRouter.executions.get(s.sourceThreadId));b(g,this.currentAffectedFiles),this.notifySessionUpdate(g)}}hydrateSubtaskHistory(t,e){if(!(!this.promptActive||this.hydratedSubtaskThreadIds.has(e.sourceThreadId))){this.hydratedSubtaskThreadIds.add(e.sourceThreadId);for(const s of H(t)){const o=N(e.sourceThreadId,s.itemId);if(this.seenSubtaskItemIds.has(o))continue;this.seenSubtaskItemIds.add(o);const i=P(s.update,e,s.sourceTurnId,{runId:`history.${e.sourceThreadId}.${s.sourceTurnId}`,state:"unknown",outputIncomplete:!0,reason:"历史过程回放,不代表当前执行状态"}),r=a(i._meta);i._meta={...r,aca:{...a(r.aca),historyReplay:!0}},b(i,this.currentAffectedFiles),this.notifySessionUpdate(i)}}}rememberSubtaskItem(t,e){const s=a(e),o=a(s.item),i=c(o,"id")||c(s,"itemId");i&&this.seenSubtaskItemIds.add(N(t,i))}bufferPendingSubtaskNotification(t,e,s){this.pendingSubtaskNotifications.push({method:t,params:e,threadId:s}),this.pendingSubtaskNotifications.length>200&&this.pendingSubtaskNotifications.shift()}flushPendingSubtaskNotifications(){if(this.pendingSubtaskNotifications.length===0)return;let t=!0;for(;t&&this.pendingSubtaskNotifications.length>0;){t=!1;const e=[];for(const s of this.pendingSubtaskNotifications){const o=this.subtaskRouter.routeFor(s.threadId);if(!o){e.push(s);continue}t=!0,this.handleSubtaskNotification(s.method,s.params,o)}this.pendingSubtaskNotifications=e}}notifySessionUpdate(t){(t.sessionUpdate==="tool_call"||t.sessionUpdate==="tool_call_update"&&["completed","failed","cancelled"].includes(String(t.status)))&&this.toolTrace("acp_update",{toolCallId:t.toolCallId,status:t.status,threadId:this.currentSessionId,turnId:this.currentTurnId}),this.notify("session/update",{sessionId:this.currentSessionId,update:t})}flushSubtaskStates(){if(this.promptActive)for(const t of this.subtaskRouter.executions.drainUpdates())this.notifySessionUpdate(t)}async handleCodexRequest(t,e){if(j(t))return B(t,e,async s=>{const o=await this.requestParent("session/request_permission",{sessionId:this.currentSessionId,request:{...s,params:e,method:t}}),i=a(o),r=a(i.outcome);return r.outcome==="selected"&&typeof r.optionId=="string"?{outcome:{outcome:"selected",optionId:r.optionId}}:{outcome:{outcome:"cancelled"}}});if(ft(t)){const s=await this.requestParent("elicitation/create",{sessionId:this.currentSessionId,request:{...a(e),method:t}}),o=a(s),i=a(o.outcome);return i.outcome==="submitted"?{outcome:{outcome:"submitted",value:typeof i.value=="string"?i.value:""}}:{outcome:{outcome:"cancelled"}}}return{}}captureAgentMessagePhase(t,e,s=this.agentMessagePhases){if(t!=="item/started"&&t!=="item/completed")return;const o=w(e,"item");if(c(o,"type")!=="agentMessage")return;const i=c(o,"id");i&&s.set(i,c(o,"phase"))}waitForTurnCompleted(t,e){const s=this.completedTurns.get(t);return s?Promise.resolve(s):new Promise((o,i)=>{const r=setTimeout(()=>{this.pendingTurnCompletions.delete(t),i(new Error("Codex app-server request timed out while waiting for turn/completed"))},e);r.unref(),this.pendingTurnCompletions.set(t,{resolve:o,reject:i,timer:r})})}resolveTurnCompleted(t){const e=w(t,"turn"),s=c(e,"id");if(!s)return;this.completedTurns.set(s,e);const o=this.pendingTurnCompletions.get(s);if(o){if(this.pendingTurnCompletions.delete(s),clearTimeout(o.timer),c(e,"status")==="failed"){o.reject(new Error(`Codex turn failed: ${JSON.stringify(e.error??{})}`));return}o.resolve(e)}}async shutdown(){return this.shutdownPromise?this.shutdownPromise:(this.shutdownPromise=this.performShutdown(),this.shutdownPromise)}async performShutdown(){this.subtaskRouter.executions.loseObservation("执行连接关闭,未确认子代理终态"),this.flushSubtaskStates(),this.subtaskRouter.stopWaiting(),this.pendingCompaction&&(clearTimeout(this.pendingCompaction.timer),this.pendingCompaction.reject(new Error("Codex ACP adapter closed")),this.pendingCompaction=null);for(const e of this.pendingParentRequests.values())clearTimeout(e.timer),e.reject(new Error("Codex ACP adapter closed"));this.pendingParentRequests.clear();for(const e of this.pendingTurnCompletions.values())clearTimeout(e.timer),e.reject(new Error("Codex ACP adapter closed"));this.pendingTurnCompletions.clear();const t=this.started?.child;!t||t.exitCode!==null||t.signalCode!==null||(t.stdin.end(),!await C(t,2e3)&&(t.kill("SIGTERM"),!await C(t,1e3)&&(t.kill("SIGKILL"),await C(t,1e3))))}sessionResponse(t){return{sessionId:t,modes:{currentModeId:"workspace-write",availableModes:[{id:"read-only",name:"Read Only"},{id:"workspace-write",name:"Workspace Write"},{id:"danger-full-access",name:"Full Access"}]},configOptions:[{id:"model",category:"model",type:"string"},{id:"approvalPolicy",category:"approval",type:"string"},{id:"sandbox",category:"mode",type:"string"}]}}requireRpc(){if(!this.rpc)throw new Error("Codex app-server is not started");return this.rpc}notify(t,e){this.write({jsonrpc:"2.0",method:t,params:e})}requestParent(t,e,s=600*1e3){const o=this.nextParentRequestId++;return new Promise((i,r)=>{const d=setTimeout(()=>{this.pendingParentRequests.delete(o),r(new Error(`ACP client request timed out: ${t}`))},s);d.unref(),this.pendingParentRequests.set(o,{resolve:i,reject:r,timer:d}),this.write({jsonrpc:"2.0",id:o,method:t,params:e})})}handleParentResponse(t){const e=Number(t.id),s=this.pendingParentRequests.get(e);if(s){if(this.pendingParentRequests.delete(e),clearTimeout(s.timer),"error"in t){const o=a(t.error);s.reject(new Error(c(o,"message")||"ACP client request failed"));return}s.resolve(t.result)}}setConfigOption(t){const e=c(t,"configId"),s=t.value;if(!e)throw new Error("configId is required");if(typeof s!="string"&&typeof s!="boolean")throw new Error("config value must be string or boolean");return this.config[e]=s,this.notify("session/update",{sessionId:this.currentSessionId,update:{sessionUpdate:"config_option_update",configId:e,value:s}}),{}}setMode(t){const e=c(t,"modeId");if(!e)throw new Error("modeId is required");return this.config.sandbox=e,this.notify("session/update",{sessionId:this.currentSessionId,update:{sessionUpdate:"current_mode_update",modeId:e}}),{}}applyInitialConfig(t){const e=w(t,"config");for(const[s,o]of Object.entries(e))(typeof o=="string"||typeof o=="boolean")&&(this.config[s]=o)}codexConfig(){return{...typeof this.config.model=="string"&&this.config.model?{model:this.config.model}:{},approvalPolicy:typeof this.config.approvalPolicy=="string"?this.config.approvalPolicy:"on-request",sandbox:typeof this.config.sandbox=="string"?this.config.sandbox:"workspace-write"}}captureContextNotification(t,e){if(t==="thread/tokenUsage/updated"&&(this.latestTokenUsage=z(e)??this.latestTokenUsage),t==="thread/compacted"&&this.pendingCompaction&&this.resolveCompaction(),t==="item/completed"){const s=w(e,"item"),o=c(a(e),"threadId");c(s,"type")==="contextCompaction"&&(!o||o===this.currentSessionId)&&this.resolveCompaction(),c(s,"type")==="agentMessage"&&c(s,"phase")==="final_answer"&&(this.currentAssistantText=S(s)||this.currentAssistantText)}}resolveCompaction(){if(!this.pendingCompaction)return;const t=this.pendingCompaction;this.pendingCompaction=null,clearTimeout(t.timer),t.resolve()}captureUpdateEvidence(t){String(t.sessionUpdate||"")==="agent_message_chunk"&&(this.currentAssistantText+=S(t.content)),b(t,this.currentAffectedFiles)}captureThreadMetadata(t){const e=w(t,"thread");this.currentRolloutPath=c(e,"path")||null,this.threadTurnCount=Array.isArray(e.turns)?e.turns.length:0;const s=at(e.turns);s.userRequest&&(this.previousUserText=s.userRequest),s.assistantResponse&&(this.previousAssistantText=s.assistantResponse),this.previousAffectedFiles.clear();for(const o of s.affectedFiles)this.previousAffectedFiles.add(o);this.latestTokenUsage=null}async maintainContext(t,e,s={}){const o=R(),i=this.contextState(),r=v(i,o);if(!r.compact&&!s.force||!this.currentSessionId)return this.maintenanceResult({stage:t,action:"none",reasons:r.reasons,beforeState:i});const d=this.currentSessionId;this.notifyContextStatus("正在整理会话上下文",F(i)),this.maintenanceActive=!0;let u=!1,l="",h=i,f=r,p=!1,g="";try{const m=i.usage?.observedAtMs??0;await this.compactThread(d,s.compactTimeoutMs??o.compactTimeoutMs),await this.waitForTokenUsageAfter(m,2e3)}catch(m){u=!0,l=m instanceof Error?m.message:String(m)}try{if(t!=="manual"&&this.promptCancelled||this.shutdownPromise)throw new Error("Codex prompt cancelled");if(h=this.contextState(),f=v(h,o,{compactAttempted:!0,compactFailed:u}),f.rollover&&s.allowRollover!==!1){this.notifyContextStatus("正在创建轻量会话",f.reasons.join(", "));try{await this.rotateThread(d,e),p=!0}catch(m){g=m instanceof Error?m.message:String(m),this.notifyContextStatus("轻量会话创建失败",g)}}else u||this.notifyContextStatus("会话上下文已压缩",F(h))}finally{this.maintenanceActive=!1}return this.maintenanceResult({action:p?"rollover":g?"rollover_failed":u?"compact_failed":"compact",stage:t,reasons:f.reasons,beforeState:i,afterState:h,compactError:[l,g].filter(Boolean).join("; "),previousThreadId:d,rolloverRecommended:f.rollover&&s.allowRollover===!1})}compactThread(t,e){return this.pendingCompaction?Promise.reject(new Error("Codex context compaction is already running")):new Promise((s,o)=>{const i=setTimeout(()=>{this.pendingCompaction?.timer===i&&(this.pendingCompaction=null),o(new Error("Codex thread compaction timed out"))},e);i.unref(),this.pendingCompaction={resolve:s,reject:o,timer:i},this.requireRpc().request("thread/compact/start",{threadId:t},e).catch(r=>{this.pendingCompaction?.timer===i&&(this.pendingCompaction=null,clearTimeout(i),o(r instanceof Error?r:new Error(String(r))))})})}async rotateThread(t,e){const s=K({previousThreadId:t,cwd:this.currentCwd,userRequest:e.userRequest,assistantResponse:e.assistantResponse,affectedFiles:e.affectedFiles}),o=await this.requireRpc().request("thread/start",{cwd:this.currentCwd,approvalsReviewer:"user",...this.codexConfig(),developerInstructions:s,ephemeral:!1,experimentalRawEvents:!0,threadSource:"aca-codex-acp-rollover"},6e4),i=T(o);if(!i)throw new Error("Codex rollover thread/start did not return thread id");this.sessionAliases.set(t,i),this.currentSessionId=i,this.resetSubtaskState(o),this.captureThreadMetadata(o)}async waitForTokenUsageAfter(t,e){const s=Date.now()+e;for(;Date.now()<s;){if((this.latestTokenUsage?.observedAtMs??0)>t)return;await gt(50)}}resolveSessionAlias(t){if(!t)return null;let e=t;const s=new Set;for(;this.sessionAliases.has(e)&&!s.has(e);)s.add(e),e=this.sessionAliases.get(e)??e;return e}contextState(){return{usage:this.latestTokenUsage,rolloutBytes:G(this.currentRolloutPath),turnCount:this.threadTurnCount}}maintenanceResult(t){return{stage:t.stage,action:t.action,reasons:t.reasons,previousThreadId:t.previousThreadId??null,sessionId:this.currentSessionId,before:t.beforeState,after:t.afterState??null,compactError:t.compactError||null,rolloverRecommended:t.rolloverRecommended===!0}}notifyContextStatus(t,e){this.notify("session/update",{sessionId:this.currentSessionId,update:{sessionUpdate:"aca_context_maintenance",label:t,detail:e}})}notifyControlToolProgress(){const t=this.controlToolLoop.recordProgress();t&&this.notifySessionUpdate(q(t))}maybeSteerControlToolLoop(t){if(!ot(t.detection,this.controlToolPolicy))return;const e=st(t.detection,this.controlToolPolicy),s=this.controlToolSteering.next(t,e.warningCalls);if(!s)return;const{threadId:o,turnId:i}=t.detection;!o||!i||this.requireRpc().request("turn/steer",{threadId:o,expectedTurnId:i,clientUserMessageId:`aca-loop-steer-${Date.now()}-${Math.random().toString(16).slice(2)}`,input:[{type:"text",text:s.text,text_elements:[]}]},6e4).then(()=>{this.notifySessionUpdate(U(t,s,{succeeded:!0}))}).catch(r=>{this.notifySessionUpdate(U(t,s,{succeeded:!1,error:r}))})}write(t){process.stdout.write(`${JSON.stringify(t)}
4
- `)}}function T(n){const t=w(n,"thread");return c(t,"id")||null}function w(n,t){const e=a(n);return a(e[t])}function a(n){return n&&typeof n=="object"&&!Array.isArray(n)?n:{}}function S(n){if(typeof n=="string")return n;if(Array.isArray(n))return n.map(S).filter(Boolean).join("");if(!n||typeof n!="object")return"";const t=a(n);for(const e of["text","message","content","output_text"]){const s=S(t[e]);if(s)return s}return""}function b(n,t){if(Array.isArray(n)){for(const i of n)b(i,t);return}const e=a(n),s=c(e,"type").toLowerCase(),o=c(e,"path");if(o&&["diff","resource_link","content"].includes(s)&&t.add(o),Array.isArray(e.locations))for(const i of e.locations){const r=c(a(i),"path");r&&t.add(r)}for(const i of[e.content,e.update,e.rawInput,e.rawOutput])i&&i!==n&&b(i,t)}function at(n){if(!Array.isArray(n)||n.length===0)return{userRequest:"",assistantResponse:"",affectedFiles:[]};const t=n.at(-1),e=new Set;b(t,e);let s="",o="";const i=r=>{if(Array.isArray(r)){for(const l of r)i(l);return}if(!r||typeof r!="object")return;const d=a(r),u=c(d,"type").toLowerCase();u==="usermessage"&&(s=S(d)||s),u==="agentmessage"&&(o=S(d)||o);for(const l of["items","content","input"])d[l]&&i(d[l])};return i(t),{userRequest:s,assistantResponse:o,affectedFiles:[...e]}}function ct(n,t){const e=I(n,"timeoutMs");return!Number.isFinite(e)||e<=0?t:Math.max(3e4,Math.min(59e4,e-1e4))}function E(n){const t=String(n.action||"none"),e=a(n.after),s=a(n.before),o=a(e.usage),i=a(s.usage),r=Object.keys(o).length>0?o:i,d=I(r,"contextWindow");if(t==="rollover")return{usedTokens:0,inputTokens:0,contextWindow:d,ratio:0,observedAtMs:Date.now(),state:"new_thread",maintenanceAction:t};if(Object.keys(r).length===0)return null;const u=I(r,"totalTokens"),l=I(r,"inputTokens");return{usedTokens:u||l,inputTokens:l,contextWindow:d,ratio:d>0?(u||l)/d:0,observedAtMs:I(r,"observedAtMs")||Date.now(),state:t==="compact"?"compacted":"active",maintenanceAction:t}}function ut(n){return n?{usedTokens:n.totalTokens||n.inputTokens,inputTokens:n.inputTokens,contextWindow:n.contextWindow,ratio:n.contextWindow>0?(n.totalTokens||n.inputTokens)/n.contextWindow:0,observedAtMs:n.observedAtMs,state:"active",maintenanceAction:null}:null}function F(n){const t=n.usage?`${n.usage.inputTokens} tokens`:"token unknown",e=n.rolloutBytes>0?`${Math.round(n.rolloutBytes/1024/1024)}MB rollout`:"rollout unknown";return`${t}, ${e}, ${n.turnCount} turns`}function dt(n){const t=String(n.action||"none");if(t!=="compact_failed"&&t!=="rollover_failed")return;const e=String(n.compactError||"Codex 上下文维护未完成");throw new Error(`Codex 自动上下文维护失败:${e}`)}function I(n,t){const e=a(n)[t];return typeof e=="number"&&Number.isFinite(e)?e:0}function lt(n){return W(n)?[{sessionUpdate:"aca_codex_raw_response_usage",_meta:{codex:{rawResponse:n}}}]:[]}function ht(n,t){const e=n.observeRawResponseItem(t);return e?[{event:e,update:q(e)}]:[]}function pt(n){return["agent_message_chunk","agent_progress_chunk","agent_thought_chunk","tool_call","tool_call_update","plan","plan_update","plan_removed"].includes(String(n.sessionUpdate||""))}function N(n,t){return`${n}:${t}`}function c(n,t){const s=a(n)[t];return typeof s=="string"?s:""}function ft(n){const t=n.toLowerCase();return t.includes("elicitation")||t.includes("requestuserinput")||t.includes("request_user_input")||t.includes("input/request")}function D(){const n=Number.parseInt(process.env.ACA_ACP_PROMPT_TIMEOUT_MS??"",10);return Number.isInteger(n)&&n>=3e4?n:720*60*1e3}function mt(){const n=Number.parseInt(process.env.ACA_CODEX_SUBTASK_DRAIN_TIMEOUT_MS??"",10);return Number.isInteger(n)&&n>=1e3?Math.min(n,600*1e3):600*1e3}function C(n,t){return n.exitCode!==null||n.signalCode!==null?Promise.resolve(!0):new Promise(e=>{const s=setTimeout(()=>e(!1),t);s.unref(),n.once("close",()=>{clearTimeout(s),e(!0)})})}function gt(n){return new Promise(t=>setTimeout(t,n))}function wt(){new rt}const St=process.argv[1]||"";/(?:^|[\\/])adapter\.(?:js|ts)$/.test(St)&&wt();export{wt as runCodexAcpAdapter};
3
+ `),await this.shutdown(),process.exit(1)}}async handleRequest(t,e){switch(t){case"initialize":return this.initializeResponse();case"aca/runtime_config":return this.applyRuntimeConfig(e);case"session/new":return this.newSession(e);case"session/resume":case"session/load":return this.resumeSession(e);case"session/fork":return this.forkSession(e);case"session/prompt":return this.prompt(e);case"session/compact":return this.manualCompact(e);case"aca/session_recover":return this.recoverSession(e);case"session/cancel":return await this.cancel(e),{};case"session/close":return await this.closeSession(e),{};case"session/set_config_option":return this.setConfigOption(e);case"session/set_mode":return this.setMode(e);case"fs/read_text_file":case"fs/write_text_file":case"terminal/create":case"terminal/output":case"terminal/wait_for_exit":case"terminal/kill":case"terminal/release":return{};default:throw new Error(`Unsupported ACP method: ${t}`)}}initializeResponse(){return{protocolVersion:1,agentCapabilities:{loadSession:!0,sessionCapabilities:{resume:!0,close:!0}}}}applyRuntimeConfig(t){if(this.started)throw new Error("Codex runtime config must be applied before app-server starts");const e=a(t.codexProvider);if(Object.keys(e).length===0)return this.managedProvider=null,{configured:!1};const s=c(e,"baseUrl"),o=c(e,"apiKey"),i=Number(e.revision||0);if(!s||!o||!Number.isSafeInteger(i)||i<1)throw new Error("Invalid Codex managed provider runtime config");return this.managedProvider={baseUrl:s,apiKey:o,revision:i},{configured:!0,revision:i}}async newSession(t){const e=c(t,"cwd")||process.cwd();this.applyInitialConfig(t),await this.ensureCodex(e);const s=await this.requireRpc().request("thread/start",{cwd:e,approvalsReviewer:"user",...this.codexConfig(),ephemeral:!1,experimentalRawEvents:!0,threadSource:"aca-codex-acp"},6e4),o=T(s);if(!o)throw new Error("Codex thread/start did not return thread id");return this.currentSessionId=o,this.currentCwd=e,this.resetSubtaskState(),this.captureThreadMetadata(s),this.sessionResponse(o)}async resumeSession(t){const e=c(t,"sessionId");if(!e)throw new Error("sessionId is required");const s=c(t,"cwd")||this.currentCwd;this.applyInitialConfig(t),await this.ensureCodex(s);const o=await this.requireRpc().request("thread/resume",{threadId:e,cwd:s,approvalsReviewer:"user",...this.codexConfig()},6e4);return this.currentSessionId=T(o)||e,this.currentCwd=s,this.resetSubtaskState(o),await this.subscribeKnownSubtasks(!1),this.captureThreadMetadata(o),this.sessionResponse(this.currentSessionId)}async forkSession(t){const e=c(t,"sessionId");if(!e)throw new Error("sessionId is required");const s=c(t,"cwd")||this.currentCwd;this.applyInitialConfig(t),await this.ensureCodex(s);const o=await this.requireRpc().request("thread/fork",{threadId:e,cwd:s,approvalsReviewer:"user",ephemeral:!1,threadSource:"aca-codex-acp-fork",...this.codexConfig()},12e4),i=T(o);if(!i)throw new Error("Codex thread/fork did not return thread id");return this.currentSessionId=i,this.currentCwd=s,this.resetSubtaskState(o),this.captureThreadMetadata(o),this.sessionResponse(i)}async prompt(t){this.promptCancelled=!1;const e=c(t,"sessionId")||this.currentSessionId;let s=this.resolveSessionAlias(e);if(!s)throw new Error("sessionId is required");const o=Array.isArray(t.prompt)?t.prompt:[],i=this.requireRpc();this.currentSessionId&&this.currentSessionId!==s&&this.resetSubtaskState(),this.currentSessionId=s;const r=await this.maintainContext("before",{userRequest:this.previousUserText,assistantResponse:this.previousAssistantText,affectedFiles:[...this.previousAffectedFiles]},{allowRollover:!0});if(lt(r),this.promptCancelled||this.shutdownPromise)throw new Error("Codex prompt cancelled");s=this.currentSessionId||s,this.toolTrace=x(),this.promptActive=!0,this.currentTurnId=null,this.controlToolLoop.reset(),this.controlToolSteering.reset(),this.currentPromptText=Z(o),this.currentAssistantText="",this.currentAffectedFiles.clear(),this.connectionRecovery.reset(),this.agentMessagePhases.clear(),this.planDeltas.reset(),this.pendingSubtaskNotifications=[],this.flushSubtaskStates();let d;try{this.turnStartPromise=i.request("turn/start",{threadId:s,clientUserMessageId:`aca-acp-${Date.now()}-${Math.random().toString(16).slice(2)}`,input:tt(o),cwd:this.currentCwd,...et(this.config)},D()),d=await this.turnStartPromise;const u=String(d?.turn?.id??"");this.currentTurnId=u||null;const l=u?await this.waitForTurnCompleted(u,D()):d?.turn,h=await this.subtaskRouter.waitForIdle(gt());if(h.timedOut&&h.activeThreadIds.length>0&&this.notifySessionUpdate({sessionUpdate:"aca_codex_subtask_drain",label:"子任务收尾等待超时",detail:`仍有 ${h.activeThreadIds.length} 个子任务未结束`,_meta:{aca:{subtaskDrain:h}}}),this.promptCancelled)throw new Error("Codex prompt cancelled");this.threadTurnCount+=1,this.previousUserText=this.currentPromptText,this.previousAssistantText=this.currentAssistantText,this.previousAffectedFiles.clear();for(const p of this.currentAffectedFiles)this.previousAffectedFiles.add(p);const f=await this.maintainContext("after",{userRequest:this.previousUserText,assistantResponse:this.previousAssistantText,affectedFiles:[...this.previousAffectedFiles]},{allowRollover:!0});return{stopReason:"end_turn",sessionId:this.currentSessionId,turn:l,_meta:{acaContextUsage:E(f)??dt(this.latestTokenUsage),acaContextMaintenance:f,acaSubtaskDrain:h}}}catch(u){const l=this.connectionRecovery.fail(u);throw l&&this.notifySessionUpdate(l),u}finally{this.subtaskRouter.executions.loseObservation(this.promptCancelled?"父任务已中断,子执行状态需重新确认":"本轮观测结束,未确认子执行终态"),this.flushSubtaskStates(),this.controlToolLoop.reset(),this.promptActive=!1,this.currentTurnId=null,this.turnStartPromise=null,this.pendingSubtaskNotifications=[]}}resetSubtaskState(t){this.subtaskGeneration+=1,this.subtaskRouter.reset(),this.subtaskAgentMessagePhases.clear(),this.subtaskPlanDeltas.clear(),this.subscribedSubtaskThreadIds.clear(),this.pendingSubtaskSubscriptions.clear(),this.hydratedSubtaskThreadIds.clear(),this.seenSubtaskItemIds.clear(),this.pendingSubtaskNotifications=[],t!==void 0&&this.subtaskRouter.restoreFromSessionSnapshot(t,this.currentSessionId)}async subscribeKnownSubtasks(t=!0){const e=this.subtaskRouter.routesForSubscription().map(s=>this.subscribeSubtask(s,t));await Promise.allSettled(e)}subscribeSubtask(t,e){const s=t.sourceThreadId;if(!s||s===this.currentSessionId||this.subscribedSubtaskThreadIds.has(s))return Promise.resolve();const o=this.pendingSubtaskSubscriptions.get(s);if(o)return o;const i=this.subtaskGeneration,r=this.requireRpc().request("thread/resume",{threadId:s,approvalsReviewer:"user",excludeTurns:!1},6e4).then(d=>{if(i!==this.subtaskGeneration)return;this.subscribedSubtaskThreadIds.add(s);const u=this.subtaskRouter.restoreFromSessionSnapshot(d,this.currentSessionId);e&&this.hydrateSubtaskHistory(d,t),u>0&&this.subscribeKnownSubtasks(e)}).catch(()=>{}).finally(()=>{this.pendingSubtaskSubscriptions.get(s)===r&&this.pendingSubtaskSubscriptions.delete(s)});return this.pendingSubtaskSubscriptions.set(s,r),r}async cancel(t){const e=this.resolveSessionAlias(c(t,"sessionId")||this.currentSessionId);if(!e||!this.rpc)return;if(e!==this.currentSessionId)throw new Error("Cannot cancel a different Codex session");this.promptCancelled=!0,this.subtaskRouter.stopWaiting();const s=this.turnStartPromise?await Q(this.turnStartPromise):null,o=this.currentTurnId||c(w(s,"turn"),"id");if(await this.rpc.request("turn/interrupt",{threadId:e,turnId:o},5e3),o){const i=this.pendingTurnCompletions.get(o);i&&(this.pendingTurnCompletions.delete(o),clearTimeout(i.timer),i.reject(new Error("Codex prompt cancelled")))}}async manualCompact(t){const e=c(t,"sessionId")||this.currentSessionId,s=this.resolveSessionAlias(e);if(!s)throw new Error("sessionId is required");if(this.promptActive||this.maintenanceActive)throw new Error("Codex session is busy");this.currentSessionId=s;const o=R(),i=await this.maintainContext("manual",{userRequest:this.previousUserText,assistantResponse:this.previousAssistantText,affectedFiles:[...this.previousAffectedFiles]},{force:!0,allowRollover:t.allowRollover===!0,compactTimeoutMs:ut(t,o.compactTimeoutMs)}),r=String(i.action||"");if(r==="compact_failed"||r==="rollover_failed"){const d=String(i.compactError||"Codex 上下文压缩未完成");throw new Error(`Codex 上下文压缩失败:${d}`)}return{sessionId:this.currentSessionId,maintenance:i,contextUsage:E(i)}}async recoverSession(t){const e=c(t,"sessionId")||this.currentSessionId,s=this.resolveSessionAlias(e);if(!s)throw new Error("sessionId is required");const o=c(t,"reason");if(o!=="low_output_loop"&&o!=="control_tool_loop")throw new Error("Unsupported Codex recovery reason");if(this.promptActive||this.maintenanceActive)throw new Error("Codex session is busy");if(s!==this.currentSessionId)throw new Error("Cannot recover a different Codex session");const i=new Set([...this.previousAffectedFiles,...this.currentAffectedFiles]);return await this.rotateThread(s,{userRequest:this.currentPromptText||this.previousUserText,assistantResponse:this.currentAssistantText||this.previousAssistantText,affectedFiles:[...i]}),{...a(this.sessionResponse(this.currentSessionId)),previousSessionId:s,recoveryReason:o}}async closeSession(t){await this.shutdown()}async ensureCodex(t){if(this.rpc&&this.started&&this.started.child.exitCode===null&&this.started.child.signalCode===null)return;if(this.codexStartupPromise){await this.codexStartupPromise;return}const e=(async()=>{if(this.rpc&&this.started&&this.started.child.exitCode===null&&this.started.child.signalCode===null)return;const s=$(t,this.managedProvider),o=new L(s.child,{peerName:"Codex app-server",onNotification:(i,r)=>this.handleCodexNotification(i,r),onRequest:async(i,r)=>this.handleCodexRequest(i,r)});this.started=s,this.rpc=o,s.child.once("exit",()=>{this.started===s&&(this.subtaskRouter.executions.loseObservation("执行连接断开,未确认子代理终态"),this.flushSubtaskStates(),this.subtaskRouter.stopWaiting())}),this.initialized=!1,await o.request("initialize",{clientInfo:{name:"aca-codex-acp",version:"0.1.0"},capabilities:{experimentalApi:!0,requestAttestation:!1}},6e4),this.initialized=!0})();this.codexStartupPromise=e;try{await e}catch(s){throw this.rpc&&this.started&&!this.initialized&&await C(this.started.child,1e3),this.rpc=null,this.started=null,s}finally{this.codexStartupPromise===e&&(this.codexStartupPromise=null)}}handleCodexNotification(t,e){const s=A(e);if(t==="item/started"||t==="item/completed"){const u=a(a(e).item);this.toolTrace("provider_notification",{method:t,threadId:s.threadId,turnId:s.turnId,toolCallId:u.id,itemType:u.type,status:u.status})}if(this.maintenanceActive&&(!s.threadId||s.threadId===this.currentSessionId)){(t==="item/started"||t==="item/completed")&&this.toolTrace("notification_filtered",{method:t,toolCallId:a(a(e).item).id,reason:"context_maintenance"}),this.captureContextNotification(t,e);return}if(!J(e,this.currentSessionId,this.currentTurnId)){if(!this.promptActive||!s.threadId||s.threadId===this.currentSessionId){(t==="item/started"||t==="item/completed")&&this.toolTrace("notification_filtered",{method:t,toolCallId:a(a(e).item).id,reason:"outside_current_turn",threadId:s.threadId,turnId:s.turnId});return}const u=this.subtaskRouter.routeFor(s.threadId);if(!u){(t==="item/started"||t==="item/completed")&&this.toolTrace("notification_buffered",{method:t,toolCallId:a(a(e).item).id,threadId:s.threadId,reason:"subtask_route_pending"}),this.bufferPendingSubtaskNotification(t,e,s.threadId);return}this.handleSubtaskNotification(t,e,u),this.flushPendingSubtaskNotifications();return}const o=this.subtaskRouter.observe(e,this.currentSessionId);if(this.flushSubtaskStates(),o>0&&this.subscribeKnownSubtasks(!0),this.captureContextNotification(t,e),this.maintenanceActive)return;if(!this.promptActive&&t!=="turn/completed"){(t==="item/started"||t==="item/completed")&&this.toolTrace("notification_filtered",{method:t,toolCallId:a(a(e).item).id,reason:"prompt_inactive"});return}this.rpc?.refreshPendingRequestTimeout("turn/start"),this.captureAgentMessagePhase(t,e);const i=this.connectionRecovery.beforeNotification(t,e);t==="turn/completed"&&this.resolveTurnCompleted(e);const r=t==="rawResponseItem/completed"?pt(this.controlToolLoop,e):[],d=t==="rawResponse/completed"?ht(e):t==="rawResponseItem/completed"?r.map(({update:u})=>u):y(t,e,{agentMessagePhases:this.agentMessagePhases,planDeltas:this.planDeltas});for(const u of r)this.maybeSteerControlToolLoop(u.event);for(const u of[...i,...d]){const l=M(u,t,e,this.agentMessagePhases);_(l)&&this.notifyControlToolProgress(),this.captureUpdateEvidence(l),this.notifySessionUpdate(l)}o>0&&this.flushPendingSubtaskNotifications()}handleSubtaskNotification(t,e,s){if(!this.promptActive)return;const o=this.subtaskRouter.acceptNotification(s,t,e);if(this.flushSubtaskStates(),!o){(t==="item/started"||t==="item/completed")&&this.toolTrace("notification_filtered",{method:t,toolCallId:a(a(e).item).id,threadId:s.sourceThreadId,reason:"subtask_not_current"});return}(t==="rawResponseItem/completed"||t==="item/completed")&&Y(a(a(e).item))&&this.notifyControlToolProgress(),this.rememberSubtaskItem(s.sourceThreadId,e),this.rpc?.refreshPendingRequestTimeout("turn/start");const i=this.subtaskRouter.executions.get(s.sourceThreadId)?.runId||s.sourceThreadId,r=this.subtaskAgentMessagePhases.get(i)??new Map;this.subtaskAgentMessagePhases.set(i,r);const d=this.subtaskPlanDeltas.get(i)??new k;this.subtaskPlanDeltas.set(i,d),this.captureAgentMessagePhase(t,e,r);const u=this.subtaskRouter.observe(e,this.currentSessionId);this.flushSubtaskStates(),u>0&&this.subscribeKnownSubtasks(!0);const l=y(t,e,{agentMessagePhases:r,planDeltas:d}),h=A(e).turnId;for(const f of l){const p=M(f,t,e,r);if(!ft(p))continue;c(p,"title")&&this.subtaskRouter.executions.action(s.sourceThreadId,c(p,"title")),_(p)&&(this.notifyControlToolProgress(),this.subtaskRouter.executions.progress(s.sourceThreadId,c(p,"title")||"收到执行结果"));const g=P(p,s,h,this.subtaskRouter.executions.get(s.sourceThreadId));b(g,this.currentAffectedFiles),this.notifySessionUpdate(g)}}hydrateSubtaskHistory(t,e){if(!(!this.promptActive||this.hydratedSubtaskThreadIds.has(e.sourceThreadId))){this.hydratedSubtaskThreadIds.add(e.sourceThreadId);for(const s of H(t)){const o=N(e.sourceThreadId,s.itemId);if(this.seenSubtaskItemIds.has(o))continue;this.seenSubtaskItemIds.add(o);const i=P(s.update,e,s.sourceTurnId,{runId:`history.${e.sourceThreadId}.${s.sourceTurnId}`,state:"unknown",name:this.subtaskRouter.executions.nameFor(e.sourceThreadId),outputIncomplete:!0,reason:"历史过程回放,不代表当前执行状态"}),r=a(i._meta);i._meta={...r,aca:{...a(r.aca),historyReplay:!0}},b(i,this.currentAffectedFiles),this.notifySessionUpdate(i)}}}rememberSubtaskItem(t,e){const s=a(e),o=a(s.item),i=c(o,"id")||c(s,"itemId");i&&this.seenSubtaskItemIds.add(N(t,i))}bufferPendingSubtaskNotification(t,e,s){this.pendingSubtaskNotifications.push({method:t,params:e,threadId:s}),this.pendingSubtaskNotifications.length>200&&this.pendingSubtaskNotifications.shift()}flushPendingSubtaskNotifications(){if(this.pendingSubtaskNotifications.length===0)return;let t=!0;for(;t&&this.pendingSubtaskNotifications.length>0;){t=!1;const e=[];for(const s of this.pendingSubtaskNotifications){const o=this.subtaskRouter.routeFor(s.threadId);if(!o){e.push(s);continue}t=!0,this.handleSubtaskNotification(s.method,s.params,o)}this.pendingSubtaskNotifications=e}}notifySessionUpdate(t){t=X(t,this.subtaskRouter.executions),(t.sessionUpdate==="tool_call"||t.sessionUpdate==="tool_call_update"&&["completed","failed","cancelled"].includes(String(t.status)))&&this.toolTrace("acp_update",{toolCallId:t.toolCallId,status:t.status,threadId:this.currentSessionId,turnId:this.currentTurnId}),this.notify("session/update",{sessionId:this.currentSessionId,update:t})}flushSubtaskStates(){if(this.promptActive)for(const t of this.subtaskRouter.executions.drainUpdates())this.notifySessionUpdate(t)}async handleCodexRequest(t,e){if(j(t))return B(t,e,async s=>{const o=await this.requestParent("session/request_permission",{sessionId:this.currentSessionId,request:{...s,params:e,method:t}}),i=a(o),r=a(i.outcome);return r.outcome==="selected"&&typeof r.optionId=="string"?{outcome:{outcome:"selected",optionId:r.optionId}}:{outcome:{outcome:"cancelled"}}});if(mt(t)){const s=await this.requestParent("elicitation/create",{sessionId:this.currentSessionId,request:{...a(e),method:t}}),o=a(s),i=a(o.outcome);return i.outcome==="submitted"?{outcome:{outcome:"submitted",value:typeof i.value=="string"?i.value:""}}:{outcome:{outcome:"cancelled"}}}return{}}captureAgentMessagePhase(t,e,s=this.agentMessagePhases){if(t!=="item/started"&&t!=="item/completed")return;const o=w(e,"item");if(c(o,"type")!=="agentMessage")return;const i=c(o,"id");i&&s.set(i,c(o,"phase"))}waitForTurnCompleted(t,e){const s=this.completedTurns.get(t);return s?Promise.resolve(s):new Promise((o,i)=>{const r=setTimeout(()=>{this.pendingTurnCompletions.delete(t),i(new Error("Codex app-server request timed out while waiting for turn/completed"))},e);r.unref(),this.pendingTurnCompletions.set(t,{resolve:o,reject:i,timer:r})})}resolveTurnCompleted(t){const e=w(t,"turn"),s=c(e,"id");if(!s)return;this.completedTurns.set(s,e);const o=this.pendingTurnCompletions.get(s);if(o){if(this.pendingTurnCompletions.delete(s),clearTimeout(o.timer),c(e,"status")==="failed"){o.reject(new Error(`Codex turn failed: ${JSON.stringify(e.error??{})}`));return}o.resolve(e)}}async shutdown(){return this.shutdownPromise?this.shutdownPromise:(this.shutdownPromise=this.performShutdown(),this.shutdownPromise)}async performShutdown(){this.subtaskRouter.executions.loseObservation("执行连接关闭,未确认子代理终态"),this.flushSubtaskStates(),this.subtaskRouter.stopWaiting(),this.pendingCompaction&&(clearTimeout(this.pendingCompaction.timer),this.pendingCompaction.reject(new Error("Codex ACP adapter closed")),this.pendingCompaction=null);for(const e of this.pendingParentRequests.values())clearTimeout(e.timer),e.reject(new Error("Codex ACP adapter closed"));this.pendingParentRequests.clear();for(const e of this.pendingTurnCompletions.values())clearTimeout(e.timer),e.reject(new Error("Codex ACP adapter closed"));this.pendingTurnCompletions.clear();const t=this.started?.child;!t||t.exitCode!==null||t.signalCode!==null||(t.stdin.end(),!await C(t,2e3)&&(t.kill("SIGTERM"),!await C(t,1e3)&&(t.kill("SIGKILL"),await C(t,1e3))))}sessionResponse(t){return{sessionId:t,modes:{currentModeId:"workspace-write",availableModes:[{id:"read-only",name:"Read Only"},{id:"workspace-write",name:"Workspace Write"},{id:"danger-full-access",name:"Full Access"}]},configOptions:[{id:"model",category:"model",type:"string"},{id:"approvalPolicy",category:"approval",type:"string"},{id:"sandbox",category:"mode",type:"string"}]}}requireRpc(){if(!this.rpc)throw new Error("Codex app-server is not started");return this.rpc}notify(t,e){this.write({jsonrpc:"2.0",method:t,params:e})}requestParent(t,e,s=600*1e3){const o=this.nextParentRequestId++;return new Promise((i,r)=>{const d=setTimeout(()=>{this.pendingParentRequests.delete(o),r(new Error(`ACP client request timed out: ${t}`))},s);d.unref(),this.pendingParentRequests.set(o,{resolve:i,reject:r,timer:d}),this.write({jsonrpc:"2.0",id:o,method:t,params:e})})}handleParentResponse(t){const e=Number(t.id),s=this.pendingParentRequests.get(e);if(s){if(this.pendingParentRequests.delete(e),clearTimeout(s.timer),"error"in t){const o=a(t.error);s.reject(new Error(c(o,"message")||"ACP client request failed"));return}s.resolve(t.result)}}setConfigOption(t){const e=c(t,"configId"),s=t.value;if(!e)throw new Error("configId is required");if(typeof s!="string"&&typeof s!="boolean")throw new Error("config value must be string or boolean");return this.config[e]=s,this.notify("session/update",{sessionId:this.currentSessionId,update:{sessionUpdate:"config_option_update",configId:e,value:s}}),{}}setMode(t){const e=c(t,"modeId");if(!e)throw new Error("modeId is required");return this.config.sandbox=e,this.notify("session/update",{sessionId:this.currentSessionId,update:{sessionUpdate:"current_mode_update",modeId:e}}),{}}applyInitialConfig(t){const e=w(t,"config");for(const[s,o]of Object.entries(e))(typeof o=="string"||typeof o=="boolean")&&(this.config[s]=o)}codexConfig(){return{...typeof this.config.model=="string"&&this.config.model?{model:this.config.model}:{},approvalPolicy:typeof this.config.approvalPolicy=="string"?this.config.approvalPolicy:"on-request",sandbox:typeof this.config.sandbox=="string"?this.config.sandbox:"workspace-write"}}captureContextNotification(t,e){if(t==="thread/tokenUsage/updated"&&(this.latestTokenUsage=z(e)??this.latestTokenUsage),t==="thread/compacted"&&this.pendingCompaction&&this.resolveCompaction(),t==="item/completed"){const s=w(e,"item"),o=c(a(e),"threadId");c(s,"type")==="contextCompaction"&&(!o||o===this.currentSessionId)&&this.resolveCompaction(),c(s,"type")==="agentMessage"&&c(s,"phase")==="final_answer"&&(this.currentAssistantText=S(s)||this.currentAssistantText)}}resolveCompaction(){if(!this.pendingCompaction)return;const t=this.pendingCompaction;this.pendingCompaction=null,clearTimeout(t.timer),t.resolve()}captureUpdateEvidence(t){String(t.sessionUpdate||"")==="agent_message_chunk"&&(this.currentAssistantText+=S(t.content)),b(t,this.currentAffectedFiles)}captureThreadMetadata(t){const e=w(t,"thread");this.currentRolloutPath=c(e,"path")||null,this.threadTurnCount=Array.isArray(e.turns)?e.turns.length:0;const s=ct(e.turns);s.userRequest&&(this.previousUserText=s.userRequest),s.assistantResponse&&(this.previousAssistantText=s.assistantResponse),this.previousAffectedFiles.clear();for(const o of s.affectedFiles)this.previousAffectedFiles.add(o);this.latestTokenUsage=null}async maintainContext(t,e,s={}){const o=R(),i=this.contextState(),r=v(i,o);if(!r.compact&&!s.force||!this.currentSessionId)return this.maintenanceResult({stage:t,action:"none",reasons:r.reasons,beforeState:i});const d=this.currentSessionId;this.notifyContextStatus("正在整理会话上下文",F(i)),this.maintenanceActive=!0;let u=!1,l="",h=i,f=r,p=!1,g="";try{const m=i.usage?.observedAtMs??0;await this.compactThread(d,s.compactTimeoutMs??o.compactTimeoutMs),await this.waitForTokenUsageAfter(m,2e3)}catch(m){u=!0,l=m instanceof Error?m.message:String(m)}try{if(t!=="manual"&&this.promptCancelled||this.shutdownPromise)throw new Error("Codex prompt cancelled");if(h=this.contextState(),f=v(h,o,{compactAttempted:!0,compactFailed:u}),f.rollover&&s.allowRollover!==!1){this.notifyContextStatus("正在创建轻量会话",f.reasons.join(", "));try{await this.rotateThread(d,e),p=!0}catch(m){g=m instanceof Error?m.message:String(m),this.notifyContextStatus("轻量会话创建失败",g)}}else u||this.notifyContextStatus("会话上下文已压缩",F(h))}finally{this.maintenanceActive=!1}return this.maintenanceResult({action:p?"rollover":g?"rollover_failed":u?"compact_failed":"compact",stage:t,reasons:f.reasons,beforeState:i,afterState:h,compactError:[l,g].filter(Boolean).join("; "),previousThreadId:d,rolloverRecommended:f.rollover&&s.allowRollover===!1})}compactThread(t,e){return this.pendingCompaction?Promise.reject(new Error("Codex context compaction is already running")):new Promise((s,o)=>{const i=setTimeout(()=>{this.pendingCompaction?.timer===i&&(this.pendingCompaction=null),o(new Error("Codex thread compaction timed out"))},e);i.unref(),this.pendingCompaction={resolve:s,reject:o,timer:i},this.requireRpc().request("thread/compact/start",{threadId:t},e).catch(r=>{this.pendingCompaction?.timer===i&&(this.pendingCompaction=null,clearTimeout(i),o(r instanceof Error?r:new Error(String(r))))})})}async rotateThread(t,e){const s=K({previousThreadId:t,cwd:this.currentCwd,userRequest:e.userRequest,assistantResponse:e.assistantResponse,affectedFiles:e.affectedFiles}),o=await this.requireRpc().request("thread/start",{cwd:this.currentCwd,approvalsReviewer:"user",...this.codexConfig(),developerInstructions:s,ephemeral:!1,experimentalRawEvents:!0,threadSource:"aca-codex-acp-rollover"},6e4),i=T(o);if(!i)throw new Error("Codex rollover thread/start did not return thread id");this.sessionAliases.set(t,i),this.currentSessionId=i,this.resetSubtaskState(o),this.captureThreadMetadata(o)}async waitForTokenUsageAfter(t,e){const s=Date.now()+e;for(;Date.now()<s;){if((this.latestTokenUsage?.observedAtMs??0)>t)return;await wt(50)}}resolveSessionAlias(t){if(!t)return null;let e=t;const s=new Set;for(;this.sessionAliases.has(e)&&!s.has(e);)s.add(e),e=this.sessionAliases.get(e)??e;return e}contextState(){return{usage:this.latestTokenUsage,rolloutBytes:G(this.currentRolloutPath),turnCount:this.threadTurnCount}}maintenanceResult(t){return{stage:t.stage,action:t.action,reasons:t.reasons,previousThreadId:t.previousThreadId??null,sessionId:this.currentSessionId,before:t.beforeState,after:t.afterState??null,compactError:t.compactError||null,rolloverRecommended:t.rolloverRecommended===!0}}notifyContextStatus(t,e){this.notify("session/update",{sessionId:this.currentSessionId,update:{sessionUpdate:"aca_context_maintenance",label:t,detail:e}})}notifyControlToolProgress(){const t=this.controlToolLoop.recordProgress();t&&this.notifySessionUpdate(q(t))}maybeSteerControlToolLoop(t){if(!it(t.detection,this.controlToolPolicy))return;const e=ot(t.detection,this.controlToolPolicy),s=this.controlToolSteering.next(t,e.warningCalls);if(!s)return;const{threadId:o,turnId:i}=t.detection;!o||!i||this.requireRpc().request("turn/steer",{threadId:o,expectedTurnId:i,clientUserMessageId:`aca-loop-steer-${Date.now()}-${Math.random().toString(16).slice(2)}`,input:[{type:"text",text:s.text,text_elements:[]}]},6e4).then(()=>{this.notifySessionUpdate(U(t,s,{succeeded:!0}))}).catch(r=>{this.notifySessionUpdate(U(t,s,{succeeded:!1,error:r}))})}write(t){process.stdout.write(`${JSON.stringify(t)}
4
+ `)}}function T(n){const t=w(n,"thread");return c(t,"id")||null}function w(n,t){const e=a(n);return a(e[t])}function a(n){return n&&typeof n=="object"&&!Array.isArray(n)?n:{}}function S(n){if(typeof n=="string")return n;if(Array.isArray(n))return n.map(S).filter(Boolean).join("");if(!n||typeof n!="object")return"";const t=a(n);for(const e of["text","message","content","output_text"]){const s=S(t[e]);if(s)return s}return""}function b(n,t){if(Array.isArray(n)){for(const i of n)b(i,t);return}const e=a(n),s=c(e,"type").toLowerCase(),o=c(e,"path");if(o&&["diff","resource_link","content"].includes(s)&&t.add(o),Array.isArray(e.locations))for(const i of e.locations){const r=c(a(i),"path");r&&t.add(r)}for(const i of[e.content,e.update,e.rawInput,e.rawOutput])i&&i!==n&&b(i,t)}function ct(n){if(!Array.isArray(n)||n.length===0)return{userRequest:"",assistantResponse:"",affectedFiles:[]};const t=n.at(-1),e=new Set;b(t,e);let s="",o="";const i=r=>{if(Array.isArray(r)){for(const l of r)i(l);return}if(!r||typeof r!="object")return;const d=a(r),u=c(d,"type").toLowerCase();u==="usermessage"&&(s=S(d)||s),u==="agentmessage"&&(o=S(d)||o);for(const l of["items","content","input"])d[l]&&i(d[l])};return i(t),{userRequest:s,assistantResponse:o,affectedFiles:[...e]}}function ut(n,t){const e=I(n,"timeoutMs");return!Number.isFinite(e)||e<=0?t:Math.max(3e4,Math.min(59e4,e-1e4))}function E(n){const t=String(n.action||"none"),e=a(n.after),s=a(n.before),o=a(e.usage),i=a(s.usage),r=Object.keys(o).length>0?o:i,d=I(r,"contextWindow");if(t==="rollover")return{usedTokens:0,inputTokens:0,contextWindow:d,ratio:0,observedAtMs:Date.now(),state:"new_thread",maintenanceAction:t};if(Object.keys(r).length===0)return null;const u=I(r,"totalTokens"),l=I(r,"inputTokens");return{usedTokens:u||l,inputTokens:l,contextWindow:d,ratio:d>0?(u||l)/d:0,observedAtMs:I(r,"observedAtMs")||Date.now(),state:t==="compact"?"compacted":"active",maintenanceAction:t}}function dt(n){return n?{usedTokens:n.totalTokens||n.inputTokens,inputTokens:n.inputTokens,contextWindow:n.contextWindow,ratio:n.contextWindow>0?(n.totalTokens||n.inputTokens)/n.contextWindow:0,observedAtMs:n.observedAtMs,state:"active",maintenanceAction:null}:null}function F(n){const t=n.usage?`${n.usage.inputTokens} tokens`:"token unknown",e=n.rolloutBytes>0?`${Math.round(n.rolloutBytes/1024/1024)}MB rollout`:"rollout unknown";return`${t}, ${e}, ${n.turnCount} turns`}function lt(n){const t=String(n.action||"none");if(t!=="compact_failed"&&t!=="rollover_failed")return;const e=String(n.compactError||"Codex 上下文维护未完成");throw new Error(`Codex 自动上下文维护失败:${e}`)}function I(n,t){const e=a(n)[t];return typeof e=="number"&&Number.isFinite(e)?e:0}function ht(n){return W(n)?[{sessionUpdate:"aca_codex_raw_response_usage",_meta:{codex:{rawResponse:n}}}]:[]}function pt(n,t){const e=n.observeRawResponseItem(t);return e?[{event:e,update:q(e)}]:[]}function ft(n){return["agent_message_chunk","agent_progress_chunk","agent_thought_chunk","tool_call","tool_call_update","plan","plan_update","plan_removed"].includes(String(n.sessionUpdate||""))}function N(n,t){return`${n}:${t}`}function c(n,t){const s=a(n)[t];return typeof s=="string"?s:""}function mt(n){const t=n.toLowerCase();return t.includes("elicitation")||t.includes("requestuserinput")||t.includes("request_user_input")||t.includes("input/request")}function D(){const n=Number.parseInt(process.env.ACA_ACP_PROMPT_TIMEOUT_MS??"",10);return Number.isInteger(n)&&n>=3e4?n:720*60*1e3}function gt(){const n=Number.parseInt(process.env.ACA_CODEX_SUBTASK_DRAIN_TIMEOUT_MS??"",10);return Number.isInteger(n)&&n>=1e3?Math.min(n,600*1e3):600*1e3}function C(n,t){return n.exitCode!==null||n.signalCode!==null?Promise.resolve(!0):new Promise(e=>{const s=setTimeout(()=>e(!1),t);s.unref(),n.once("close",()=>{clearTimeout(s),e(!0)})})}function wt(n){return new Promise(t=>setTimeout(t,n))}function St(){new at}const bt=process.argv[1]||"";/(?:^|[\\/])adapter\.(?:js|ts)$/.test(bt)&&St();export{St as runCodexAcpAdapter};
@@ -0,0 +1 @@
1
+ const r=t=>t&&typeof t=="object"?t:{};function n(t,a){const c=r(t._meta),e=r(c.aca),o=r(e.task);if(e.subtask||e.action!=="subAgentActivity"||o.status!=="completed"||typeof o.sourceThreadId!="string")return t;const s=a.completionRunId(o.sourceThreadId);return s?{...t,_meta:{...c,aca:{...e,subtaskCompletion:{threadId:o.sourceThreadId,runId:s}}}}:t}export{n as decorateSubtaskCompletion};
@@ -1 +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
+ import{randomUUID as f}from"node:crypto";import{codexNotificationIsSubtaskMetadata as g}from"./notification-routing.js";class T{runs=new Map;starts=new Set;closedTurns=new Map;pending=new Map;names=new Map;reset(){this.runs.clear(),this.starts.clear(),this.closedTurns.clear(),this.pending.clear(),this.names.clear()}get(s){return this.runs.get(s)?.snapshot}completionRunId(s){const n=this.runs.get(s);return n?.generation===1&&n.snapshot.state==="completed"?n.snapshot.runId:void 0}nameFor(s){return this.names.get(s)}rememberName(s,n){const t=n.trim();if(!t)return;this.names.set(s,t);const r=this.runs.get(s);r&&!r.snapshot.name&&this.change(r,{name:t})}hasStart(s,n){return this.starts.has(`${s}:${n}`)}activeThreadIds(){return[...this.runs].filter(([,s])=>h(s.snapshot.state)).map(([s])=>s)}start(s,n,t,r={}){r.name&&this.rememberName(s.sourceThreadId,r.name);const o={...r};delete o.name;const i=this.nameFor(s.sourceThreadId);i&&(o.name=i);const e=`${s.sourceThreadId}:${n}`;if(this.starts.has(e))return;this.starts.add(e);const u=this.runs.get(s.sourceThreadId);if(u&&h(u.snapshot.state)){this.change(u,{...o,state:u.snapshot.state==="running"?"running":t});return}const d={route:{...s},generation:(u?.generation??0)+1,snapshot:{...o,runId:f(),state:t,startedAtMs:Date.now()}};this.runs.set(s.sourceThreadId,d),this.pending.set(d.snapshot.runId,d)}observeState(s,n){let t=this.runs.get(s.sourceThreadId);if(t){if(c(t.snapshot.state))return}else{const r=this.nameFor(s.sourceThreadId);t={route:{...s},generation:1,snapshot:{runId:f(),state:"unknown",...r?{name:r}:{}}},this.runs.set(s.sourceThreadId,t),this.pending.set(t.snapshot.runId,t)}if(!(t.snapshot.state==="running"&&n==="pending")){if(t.generation>1&&c(n)){this.change(t,{state:"unknown",reason:t.outputRiskReason??"收到无法确认执行归属的结束通知"});return}this.change(t,{state:n,...c(n)?{endedAtMs:Date.now()}:{}})}}accept(s,n,t){if(g(n))return!1;const r=s.sourceThreadId,o=l(t.turn),i=p(t.turnId)||p(o.id);if(i&&this.closedTurns.get(r)?.has(i))return!1;let e=this.runs.get(r);if(n==="turn/started"&&i&&(e?.snapshot.sourceTurnId&&e.snapshot.sourceTurnId!==i&&h(e.snapshot.state)&&(this.closeTurn(r,e.snapshot.sourceTurnId),this.markOutputRisk(e,"新执行开始前未收到旧执行结束事件","unknown")),this.start(s,`turn:${i}`,"running"),e=this.runs.get(r)),e||(this.observeState(s,"unknown"),e=this.runs.get(r)),c(e.snapshot.state)||i&&e.snapshot.sourceTurnId&&i!==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(!i&&e.generation>1&&n!=="turn/started")return this.markOutputRisk(e,`部分事件缺少新执行归属,已隔离(${n.slice(0,120)})`),!1;if(i&&!e.snapshot.sourceTurnId&&this.change(e,{sourceTurnId:i}),n==="turn/completed"){const u=p(o.status).toLowerCase(),d=u==="failed"?"failed":["interrupted","cancelled","canceled"].includes(u)?"interrupted":u==="completed"?"completed":"unknown";d==="unknown"?this.markOutputRisk(e,"结束通知未提供可识别的执行结果",d):this.change(e,{state:d,endedAtMs:Date.now(),reason:e.outputRiskReason}),i&&this.closeTurn(r,i)}return!0}progress(s,n){const t=this.runs.get(s);!t||c(t.snapshot.state)||(t.snapshot={...t.snapshot,lastProgressAtMs:Date.now(),...n?{lastAction:n}:{}})}action(s,n){const t=this.runs.get(s);t&&!c(t.snapshot.state)&&(t.snapshot={...t.snapshot,lastAction:n})}loseObservation(s){for(const n of this.runs.values())(h(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)&&(c(t.state)&&t.sourceTurnId&&this.closeTurn(s.route.sourceThreadId,t.sourceTurnId),s.snapshot=t,this.pending.set(t.runId,s))}}function I(a,s,n,t){const r={...a},o=t?.runId||s.sourceThreadId;["tool_call","tool_call_update"].includes(String(a.sessionUpdate))&&a.toolCallId&&(r.toolCallId=`subtask.${o}.${a.toolCallId}`),["plan","plan_update","plan_removed"].includes(String(a.sessionUpdate))&&(r.planId=`subtask.${o}.${a.planId||a.plan_id||a.id||"current"}`,delete r.plan_id);const i=l(a._meta);return r._meta={...i,aca:{...l(i.aca),subtask:{...s,sourceTurnId:n,...t?{runId:t.runId,run:{...t}}:{}}}},r}function c(a){return["completed","failed","interrupted"].includes(a)}function h(a){return a==="pending"||a==="running"}function l(a){return a&&typeof a=="object"?a:{}}function p(a){return typeof a=="string"?a:""}export{T as CodexSubtaskExecutions,I as decorateSubtaskUpdate};
@@ -1 +1 @@
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,c(s));return this.hasActiveSubtasks()||this.resolveIdleWaiters(!1),i}restoreFromSessionSnapshot(t,e){const s=c(t),i=c(s.thread),n=a(i,"id")||e,o=c(s.initialTurnsPage),h=[...I(i,"turns"),...I(o,"data")];let u=0;for(const f of h){const l=c(f);for(const p of I(l,"items"))u+=this.observe({threadId:n,item:p},e,!0)}return u}observe(t,e,s=!1){const i=c(t),n=c(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 h=a(n,"senderThreadId")||a(i,"threadId"),u=this.routeFor(h||null),f=u?.parentToolCallId||o,l=Math.min(8,(u?.depth??0)+1),p=x(n),b=w(n.agentsStates);let T=0;for(const d of p){if(!d||d===e||d===h)continue;this.routes.has(d)||(this.routes.set(d,{parentToolCallId:f,sourceThreadId:d,depth:l}),T+=1);const m=b.get(d);if(!s){if(a(n,"tool")==="spawnAgent"){const S=this.executions.get(d);!this.executions.hasStart(d,o)&&(!S||!["pending","running"].includes(S.state))&&this.routes.set(d,{parentToolCallId:f,sourceThreadId:d,depth:l}),this.executions.start(this.routes.get(d),o,"pending",{description:a(n,"prompt")||void 0})}m?this.updateStatus(d,m):a(n,"tool")==="spawnAgent"&&this.updateStatus(d,"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 h=this.routeFor(e||null),u=h?.parentToolCallId||a(t,"id"),f=Math.min(8,(h?.depth??0)+1);if(!u)return 0;const l=this.routes.has(o)?0:1;if(l&&this.routes.set(o,{parentToolCallId:u,sourceThreadId:o,depth:f}),i)return l;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 l}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=c(r);for(const[s,i]of Object.entries(e)){const n=y(a(c(i),"status"));s.trim()&&n!=="unknown"&&t.set(s.trim(),n)}return t}function y(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 x(r){const t=c(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=c(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(c(r),"type");return t==="collabAgentToolCall"||t==="collabToolCall"}function v(r,t){const e=c(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 c(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,x as collaborationChildThreadIds,k as isCollaborationItem};
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,c(s));return this.hasActiveSubtasks()||this.resolveIdleWaiters(!1),i}restoreFromSessionSnapshot(t,e){const s=c(t),i=c(s.thread),n=a(i,"id")||e,o=c(s.initialTurnsPage),u=[...m(i,"turns"),...m(o,"data")];let l=0;for(const h of u){const f=c(h);for(const p of m(f,"items"))l+=this.observe({threadId:n,item:p},e,!0)}return l}observe(t,e,s=!1){const i=c(t),n=c(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 u=a(n,"senderThreadId")||a(i,"threadId"),l=this.routeFor(u||null),h=l?.parentToolCallId||o,f=Math.min(8,(l?.depth??0)+1),p=x(n),g=w(n.agentsStates);let T=0;for(const d of p){if(!d||d===e||d===u)continue;this.routes.has(d)||(this.routes.set(d,{parentToolCallId:h,sourceThreadId:d,depth:f}),T+=1);const S=g.get(d);if(!s){if(a(n,"tool")==="spawnAgent"){const v=this.executions.get(d);!this.executions.hasStart(d,o)&&(!v||!["pending","running"].includes(v.state))&&this.routes.set(d,{parentToolCallId:h,sourceThreadId:d,depth:f}),this.executions.start(this.routes.get(d),o,"pending",{description:a(n,"prompt")||void 0})}S?this.updateStatus(d,S):a(n,"tool")==="spawnAgent"&&this.updateStatus(d,"pending")}}return T}observeSubAgentActivity(t,e,s,i){const n=a(t,"kind"),o=a(t,"agentThreadId");if(!o||o===s)return 0;const u=a(t,"agentPath").split("/").filter(Boolean).at(-1);if(u&&this.executions.rememberName(o,u),!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),h=l?.parentToolCallId||a(t,"id"),f=Math.min(8,(l?.depth??0)+1);if(!h)return 0;const p=this.routes.has(o)?0:1;if(p&&this.routes.set(o,{parentToolCallId:h,sourceThreadId:o,depth:f}),i)return p;if(n==="started"){const g=this.executions.get(o);(!g||!["pending","running"].includes(g.state))&&this.routes.set(o,{parentToolCallId:h,sourceThreadId:o,depth:f}),this.executions.start(this.routes.get(o),a(t,"id"),"running",{...u?{name:u}:{}}),this.updateStatus(o,"running")}return p}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=c(r);for(const[s,i]of Object.entries(e)){const n=y(a(c(i),"status"));s.trim()&&n!=="unknown"&&t.set(s.trim(),n)}return t}function y(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 x(r){const t=c(r),e=new Set;I(e,t.newThreadId),I(e,t.receiverThreadId),C(e,t.receiverThreadIds);const s=t.agentsStates;if(Array.isArray(s))for(const i of s)b(e,i);else{const i=c(s);for(const[n,o]of Object.entries(i))b(e,o),W(n)&&e.add(n)}return[...e]}function k(r){const t=a(c(r),"type");return t==="collabAgentToolCall"||t==="collabToolCall"}function b(r,t){const e=c(t);for(const s of["threadId","thread_id","receiverThreadId","newThreadId"])I(r,e[s])}function I(r,t){typeof t=="string"&&t.trim()&&r.add(t.trim())}function C(r,t){if(Array.isArray(t))for(const e of t)I(r,e)}function m(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 c(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,x as collaborationChildThreadIds,k as isCollaborationItem};
@@ -1,3 +1,3 @@
1
- import{Command as a}from"commander";import s from"chalk";import m from"node:fs";import p from"node:path";import{spawn as S}from"node:child_process";import{randomUUID as $}from"node:crypto";import{getLogDir as c,getRuntimeStatePath as y}from"../core/paths.js";import{ensureDir as g,readJsonFile as C}from"../core/fs.js";import{isProcessAlive as r,readPidFile as u,removePidFile as f}from"../core/process.js";import{ACA_WORKER_INSTANCE_ENV as x}from"../core/process-identity.js";import{applyRuntimePathOptions as l}from"../core/runtime-options.js";const w=3e4;function G(){return new a("daemon").description("Run aca as a background daemon service").addCommand(d(new a("start").description("Start aca daemon in the background")).allowUnknownOption(!0).action(e=>{l(e);const o=u();if(o&&r(o)){console.log(`Daemon is already running (PID ${o}).`),process.exitCode=1;return}const n=D(h("start"));n.unref(),console.log(`Daemon started (PID ${n.pid})`),I()})).addCommand(d(new a("stop").description("Stop the running aca daemon")).action(async e=>{l(e);const o=u();if(!o){console.log("No daemon PID file found. Daemon is not running.");return}if(!r(o)){console.log(`Daemon process (PID ${o}) is not running. Cleaning up PID file.`),f();return}await P(o)?(f(),console.log(`Daemon stopped successfully (PID ${o}).`)):(console.log(`Daemon (PID ${o}) is still running. You may need to kill it manually: ${s.yellow(`kill -9 ${o}`)}`),process.exitCode=1)})).addCommand(d(new a("status").description("Show daemon status")).action(e=>{l(e);const o=C(y()),n=u();if(n&&r(n)){console.log(s.green("● Daemon is running")),console.log(` PID: ${n}`),o?.phase&&console.log(` Phase: ${o.phase}`),o?.startupStage&&console.log(` Stage: ${o.startupStage}`);return}n?(console.log(s.red("● Daemon is not running (stale PID file)")),f()):console.log(s.red("● Daemon is not running")),process.exitCode=1})).addCommand(d(new a("logs").description("Show daemon logs")).option("-n, --lines <count>","number of lines to show","50").action(e=>{l(e);const o=Number.parseInt(e.lines,10)||50,n=E();if(!n){console.log(`No log files found in ${c()}`),process.exitCode=1;return}const t=m.readFileSync(n,"utf8").trimEnd().split(`
2
- `);console.log(s.dim(`--- ${n} (last ${o} lines) ---`)),console.log(t.slice(-o).join(`
3
- `))})).addCommand(d(new a("restart").description("Restart the aca daemon")).allowUnknownOption(!0).action(async e=>{l(e);const o=u();if(o&&r(o)&&!await P(o)){console.error(`Daemon restart aborted because PID ${o} did not stop within ${w}ms.`),process.exitCode=1;return}f();const n=D(h("restart"));n.unref(),console.log(`Daemon started (PID ${n.pid})`),I()}))}function d(e){return e.option("--aica-home <path>","Runtime home directory (defaults to ~/.aica)").option("--aica-config <path>","Config file path").option("--aca-home <path>","Deprecated alias for --aica-home").option("--aca-config <path>","Deprecated alias for --aica-config")}function h(e){const o=process.argv.findIndex((t,i)=>i>=2&&t==="daemon"),n=process.argv.findIndex((t,i)=>i>o&&t===e);return n>=0?process.argv.slice(n+1):[]}function D(e){const o=v();g(c());const n=new Date().toISOString().slice(0,10),t=p.join(c(),`daemon-stderr-${n}.log`),i=m.openSync(t,"a");try{return S(o.command,[...o.prefixArgs,"start",...e],{detached:!0,stdio:["ignore","ignore",i],windowsHide:!0,env:{...process.env,[x]:$()}})}finally{m.closeSync(i)}}function v(){if(process.env.ACA_SINGLE_FILE_WORKER==="1")return{command:process.execPath,prefixArgs:[]};const e=process.argv[1];if(!e)throw new Error("Unable to resolve the current ACA CLI entrypoint");return{command:process.execPath,prefixArgs:[p.resolve(e)]}}function I(){const e=A();console.log(`Use ${s.yellow(`${e} daemon status`)} to check status`),console.log(`Use ${s.yellow(`${e} daemon stop`)} to stop`),console.log(`Use ${s.yellow(`${e} daemon logs`)} to view logs`)}function A(){if(process.env.ACA_SINGLE_FILE_WORKER==="1")return process.execPath;const e=p.basename(process.argv[1]||"");return/worker-cli|aica/i.test(e)?"aica":"aca"}function E(){return g(c()),m.readdirSync(c()).filter(o=>/^\d{4}-\d{2}-\d{2}\.log$/.test(o)).map(o=>p.join(c(),o)).sort().at(-1)??null}async function R(e,o){const n=Date.now();for(;Date.now()-n<o;){if(!r(e))return!0;await new Promise(t=>setTimeout(t,100))}return!r(e)}async function P(e,o=w){return r(e)?(process.kill(e,"SIGTERM"),R(e,o)):!0}export{G as createDaemonCommand,I as printStartTips,D as spawnDaemon,P as stopDaemonProcess};
1
+ import{assertManualStartAllowed as $,prepareManualWorkerAction as g}from"../core/management/manual.js";import{Command as i}from"commander";import r from"chalk";import m from"node:fs";import p from"node:path";import{spawn as y}from"node:child_process";import{randomUUID as C}from"node:crypto";import{getLogDir as c,getRuntimeStatePath as x}from"../core/paths.js";import{ensureDir as w,readJsonFile as v}from"../core/fs.js";import{isProcessAlive as s,readPidFile as u,removePidFile as f}from"../core/process.js";import{ACA_WORKER_INSTANCE_ENV as A}from"../core/process-identity.js";import{applyRuntimePathOptions as l}from"../core/runtime-options.js";const h=3e4;function H(){return new i("daemon").description("Run aca as a background daemon service").addCommand(d(new i("start").description("Start aca daemon in the background")).allowUnknownOption(!0).action(e=>{l(e),$();const o=u();if(o&&s(o)){console.log(`Daemon is already running (PID ${o}).`),process.exitCode=1;return}const n=I(D("start"));n.unref(),console.log(`Daemon started (PID ${n.pid})`),S()})).addCommand(d(new i("stop").description("Stop the running aca daemon")).action(async e=>{l(e),await g();const o=u();if(!o){console.log("No daemon PID file found. Daemon is not running.");return}if(!s(o)){console.log(`Daemon process (PID ${o}) is not running. Cleaning up PID file.`),f();return}await P(o)?(f(),console.log(`Daemon stopped successfully (PID ${o}).`)):(console.log(`Daemon (PID ${o}) is still running. You may need to kill it manually: ${r.yellow(`kill -9 ${o}`)}`),process.exitCode=1)})).addCommand(d(new i("status").description("Show daemon status")).action(e=>{l(e);const o=v(x()),n=u();if(n&&s(n)){console.log(r.green("● Daemon is running")),console.log(` PID: ${n}`),o?.phase&&console.log(` Phase: ${o.phase}`),o?.startupStage&&console.log(` Stage: ${o.startupStage}`);return}n?(console.log(r.red("● Daemon is not running (stale PID file)")),f()):console.log(r.red("● Daemon is not running")),process.exitCode=1})).addCommand(d(new i("logs").description("Show daemon logs")).option("-n, --lines <count>","number of lines to show","50").action(e=>{l(e);const o=Number.parseInt(e.lines,10)||50,n=_();if(!n){console.log(`No log files found in ${c()}`),process.exitCode=1;return}const t=m.readFileSync(n,"utf8").trimEnd().split(`
2
+ `);console.log(r.dim(`--- ${n} (last ${o} lines) ---`)),console.log(t.slice(-o).join(`
3
+ `))})).addCommand(d(new i("restart").description("Restart the aca daemon")).allowUnknownOption(!0).action(async e=>{l(e),await g();const o=u();if(o&&s(o)&&!await P(o)){console.error(`Daemon restart aborted because PID ${o} did not stop within ${h}ms.`),process.exitCode=1;return}f();const n=I(D("restart"));n.unref(),console.log(`Daemon started (PID ${n.pid})`),S()}))}function d(e){return e.option("--aica-home <path>","Runtime home directory (defaults to ~/.aica)").option("--aica-config <path>","Config file path").option("--aca-home <path>","Deprecated alias for --aica-home").option("--aca-config <path>","Deprecated alias for --aica-config")}function D(e){const o=process.argv.findIndex((t,a)=>a>=2&&t==="daemon"),n=process.argv.findIndex((t,a)=>a>o&&t===e);return n>=0?process.argv.slice(n+1):[]}function I(e){const o=E();w(c());const n=new Date().toISOString().slice(0,10),t=p.join(c(),`daemon-stderr-${n}.log`),a=m.openSync(t,"a");try{return y(o.command,[...o.prefixArgs,"start",...e],{detached:!0,stdio:["ignore","ignore",a],windowsHide:!0,env:{...process.env,[A]:C()}})}finally{m.closeSync(a)}}function E(){if(process.env.ACA_SINGLE_FILE_WORKER==="1")return{command:process.execPath,prefixArgs:[]};const e=process.argv[1];if(!e)throw new Error("Unable to resolve the current ACA CLI entrypoint");return{command:process.execPath,prefixArgs:[p.resolve(e)]}}function S(){const e=R();console.log(`Use ${r.yellow(`${e} daemon status`)} to check status`),console.log(`Use ${r.yellow(`${e} daemon stop`)} to stop`),console.log(`Use ${r.yellow(`${e} daemon logs`)} to view logs`)}function R(){if(process.env.ACA_SINGLE_FILE_WORKER==="1")return process.execPath;const e=p.basename(process.argv[1]||"");return/worker-cli|aica/i.test(e)?"aica":"aca"}function _(){return w(c()),m.readdirSync(c()).filter(o=>/^\d{4}-\d{2}-\d{2}\.log$/.test(o)).map(o=>p.join(c(),o)).sort().at(-1)??null}async function k(e,o){const n=Date.now();for(;Date.now()-n<o;){if(!s(e))return!0;await new Promise(t=>setTimeout(t,100))}return!s(e)}async function P(e,o=h){return s(e)?(process.kill(e,"SIGTERM"),k(e,o)):!0}export{H as createDaemonCommand,S as printStartTips,I as spawnDaemon,P as stopDaemonProcess};
@@ -1 +1 @@
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
+ import{stopManagedClaims as M}from"../core/management/claims.js";import{assertManualStartAllowed as L}from"../core/management/manual.js";import{startManagementControl as U}from"../core/management/control-server.js";import{managementActivity as w}from"../core/management/activity.js";import{getAcaHome as F}from"../core/paths.js";import{Command as H}from"commander";import v from"node:fs";import T from"node:os";import d from"node:path";import a from"node:process";import{randomUUID as K}from"node:crypto";import{Logger as V}from"../core/logger.js";import{loadCredentials as B,saveApiKeyCredential as G}from"../core/auth.js";import{getRuntimeStatePath as J}from"../core/paths.js";import{writeJsonFile as q}from"../core/fs.js";import{HEARTBEAT_INTERVAL_MS as z,startHeartbeatLoop as Q}from"../core/heartbeat.js";import{startJobWorkerLoop as X}from"../core/job-worker.js";import{startFileTransferWorkerLoop as Y}from"../core/file-transfer-worker.js";import{startTerminalWorker as Z}from"../core/terminal/worker.js";import{startMachineFilesystemWorkerLoop as ee}from"../core/machine-filesystem-worker.js";import{cleanupOrphanedAcpProcesses as te}from"../core/acp-orphan-cleanup.js";import{defaultAcpRuntimePool as $}from"../acp/client/acp-runtime-pool.js";import{isProcessAlive as p,readPidFile as u,writePidFile as re,removePidFile as D}from"../core/process.js";import{applyRuntimePathOptions as R}from"../core/runtime-options.js";import{loadAcaConfig as _,updateAcaConfig as W}from"../core/aca-config.js";import{acaServerRequest as ae}from"../core/aca-server-client.js";import{restoreProjectsAfterLogin as oe}from"../core/project-recovery.js";import{syncCodexProviderRuntimeConfig as ne}from"../core/codex-provider-runtime-config.js";import{ACA_WORKER_INSTANCE_ENV as x,ACA_WORKER_PID_ENV as ie,ACA_WORKER_START_TICKS_ENV as se,readProcessStartTicks as ce}from"../core/process-identity.js";import{printStartTips as me,spawnDaemon as de}from"./daemon.js";function Je(){return new H("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("--managed-start <operationId>","受管理启动:保持业务入口关闭,等待 Proxy 激活").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 fe(e);return}await ue(e)})}async function fe(e){R(e);const o=u();if(o&&p(o))throw new Error(`Worker 已在后台运行,PID ${o}。`);o&&D();const i=de(le());if(i.unref(),!i.pid)throw new Error("后台 Worker 启动失败:未获得进程 PID。");if(!await pe(i.pid,3e3)&&!p(i.pid))throw new Error(`后台 Worker 启动失败,请检查 ${d.join(a.env.AICA_HOME||a.env.ACA_HOME||d.join(T.homedir(),".aica"),"logs")}。`);console.log(`Worker 已在后台启动,PID ${i.pid}。`),me()}function le(){const e=a.argv.findIndex((o,i)=>i>=2&&o==="start");return e<0?[]:a.argv.slice(e+1).filter(o=>o!=="--background"&&o!=="-b")}async function pe(e,o){const i=Date.now();for(;Date.now()-i<o;){if(u()===e)return!0;if(!p(e))return!1;await new Promise(r=>setTimeout(r,50))}return u()===e}async function ue(e){if(R(e),e.managedStart||L(),e.managedStart){if(a.platform!=="linux")throw new Error("当前平台尚未支持受管理启动");if(!a.env.AICA_MANAGER_ID)throw new Error("受管理启动缺少管理实例");a.env.AICA_UPDATE_OPERATION=e.managedStart}const o=a.env[x]||K(),i=ce(a.pid)||"";a.env[x]=o,a.env[ie]=String(a.pid),a.env[se]=i;const r=new V("start",e.debug?"debug":"info"),N=_(),A={...e.server?.trim()?{serverUrl:e.server.trim()}:{},...e.machineId?{machineId:e.machineId.trim()}:{},...e.machineName?{machineName:e.machineName.trim()}:{}};let c=Object.keys(A).length>0?W(A):N;const m=e.machineName?.trim()||c.machineName||T.hostname();if(e.token?.trim()){const t=await ae("POST","/api/auth/machine-enroll",{token:e.token.trim(),machineId:c.machineId,machineName:m},{serverUrl:c.serverUrl});c=W({machineId:t.machineId,machineName:m,token:t.token,username:t.username}),r.info(`Machine enrollment completed for user ${t.username}`);try{const n=await oe();n.restored>0&&r.info(`restored ${n.restored} local project(s) after machine enrollment`),n.unavailable>0&&r.warn(`skipped ${n.unavailable} unavailable project path(s) during machine enrollment recovery`),c=_()}catch(n){r.warn(`project recovery after machine enrollment failed: ${n instanceof Error?n.message:String(n)}`)}}const O=e.auth?G(e.auth,m):B(),f=u();if(f&&f!==a.pid&&p(f))throw new Error(`Worker 已在运行,PID ${f}`);re(a.pid),j({pid:a.pid,workerInstanceId:o,workerStartTicks:i,phase:"running",startupStage:"local-compatible",machineId:c.machineId||O?.machine?.machineId||"local-machine",machineName:m,cliTypes:e.cliTypes??he(),startedAt:new Date().toISOString(),heartbeatIntervalMs:z,issues:[{severity:"info",message:"aca is running in aca-server compatibility mode; ACP execution happens on this machine."}]}),r.info(`Starting aca local-compatible service on machine ${m}`),r.info(`PID ${a.pid}`);const h=await te();h.terminated>0&&r.warn(`terminated ${h.terminated} orphaned ACP process(es): ${h.pids.join(",")}`),ne().then(t=>r.info(t?`Codex Provider runtime config loaded revision=${t.revision}`:"Codex Provider runtime config is not configured")).catch(t=>r.warn(`Codex Provider runtime config sync failed: ${t instanceof Error?t.message:String(t)}`));let I=!1,g,S,k,y,P;const l=async t=>{if(I)return;I=!0,M(),r.info("Shutting down"),g&&clearInterval(g),k?.stop(),P?.stop(),y?.stop(),await S?.stop().catch(s=>r.warn(`job worker shutdown failed: ${s instanceof Error?s.message:String(s)}`));const n=$.stats();await $.disposeAll().catch(s=>r.warn(`ACP runtime shutdown failed: ${s instanceof Error?s.message:String(s)}`)),r.info(`ACP runtimes stopped total=${n.total} inUse=${n.inUse} idle=${n.idle}`),j({pid:a.pid,phase:"stopped",stoppedAt:new Date().toISOString(),issues:[]}),D(),a.exit(t)},C=t=>{if(!w.stopping){const n=d.join(F(),"management");v.mkdirSync(n,{recursive:!0,mode:448}),v.writeFileSync(d.join(n,"manual-stop.json"),JSON.stringify({workerInstanceId:o,at:Date.now()}),{mode:384})}l(t)};a.once("SIGINT",()=>C(130)),a.once("SIGTERM",()=>C(0)),a.once("uncaughtException",t=>{r.error(`uncaught exception: ${t.stack||t.message}`),l(1)}),a.once("unhandledRejection",t=>{const n=t instanceof Error?t.stack||t.message:String(t);r.error(`unhandled rejection: ${n}`),l(1)}),e.heartbeatLog&&setInterval(()=>r.info(`heartbeat: ${new Date().toISOString()}`),5e3);let E=!1;const b=()=>{E||(E=!0,S=X(r),k=Y(r),P=ee(r),y=Z(r))};try{await U(o,()=>l(0),b)}catch(t){if(e.managedStart||t instanceof Error&&t.message.includes("已有 Worker 管理实例"))throw t;w.paused=!1,r.warn(`可选管理通道不可用: ${t instanceof Error?t.message:String(t)}`)}g=Q(r),w.paused||b(),await new Promise(()=>{})}function he(){return["codex","claude"].filter(e=>ge(e))}function ge(e){const o=a.env.PATH||"";for(const i of o.split(":"))try{if(i&&v.existsSync(d.join(i,e)))return!0}catch{}return!1}function j(e){q(J(),e)}export{Je as createStartCommand};
@@ -1 +1 @@
1
- import i from"node:fs";import d from"node:os";import s from"node:path";import{Readable as w}from"node:stream";import{acaServerRequest as p,acaServerStreamRequest as h}from"./aca-server-client.js";import{loadAcaConfig as x}from"./aca-config.js";import{expandWorkerRuntimePath as y}from"./worker-runtime-paths.js";import{startLongPollWorker as b}from"./long-poll-worker.js";const f=25e3,S=f+1e4;function C(e){return b({run:async()=>{const t=x();if(!t.token)throw new Error("Worker 尚未配置认证令牌");const a=await p("GET",`/api/client/file-requests/claim?machineId=${encodeURIComponent(t.machineId)}&waitMs=${f}`,void 0,{timeoutMs:S});if(a.item)try{await v(a.item,t,e)}catch(n){const o=n instanceof Error?n.message:String(n);e.warn(`file request failed request=${a.item.requestId}: ${o}`);try{await j(a.item.requestId,o)}catch(r){e.warn(`file request fail callback failed request=${a.item.requestId}: ${r instanceof Error?r.message:String(r)}`)}}},onError:(t,a)=>e.warn(`file transfer worker failed, retry in ${a}ms: ${t instanceof Error?t.message:String(t)}`)})}async function j(e,t){await p("POST",`/api/client/file-requests/${encodeURIComponent(e)}/fail`,{message:t})}async function v(e,t,a){const n=t.projects.find(g=>g.projectId===e.projectId),o=u(e.projectRoot||n?.rootPath||"");if(!o)throw new Error(`file request ${e.requestId} project root is unknown`);const r=e.purpose==="temp-image"?"temp-image":e.purpose==="project-preview"?"project-preview":"project-file",c=r==="temp-image"?E(e.path):I(o,e.path),m=i.statSync(c);if(!m.isFile())throw new Error(`file request ${e.requestId} target is not a file`);if(m.size>e.maxBytes)throw new Error(`file request ${e.requestId} target is too large (${m.size} bytes)`);const l=r==="temp-image"?R(c):F(c);a.info(`streaming file request=${e.requestId} path=${c}`),await h("POST",`/api/client/file-requests/${encodeURIComponent(e.requestId)}/stream`,w.toWeb(i.createReadStream(c)),{"content-type":"application/octet-stream","x-aca-file-name":encodeURIComponent(s.basename(c)),"x-aca-file-size":String(m.size),"x-aca-file-mime":l})}function I(e,t){const a=i.realpathSync(u(e)),n=s.isAbsolute(t)?t:s.join(a,t),o=i.realpathSync(n),r=s.relative(a,o);if(r===""||r.startsWith("..")||s.isAbsolute(r))throw new Error("requested file is outside project root");return o}function E(e){const t=$(e);if(!s.isAbsolute(t))throw new Error("temporary image path must be absolute");const a=i.lstatSync(t);if(a.isSymbolicLink())throw new Error("temporary image cannot be a symbolic link");if(!a.isFile())throw new Error("temporary image target is not a regular file");const n=i.realpathSync(d.tmpdir()),o=i.realpathSync(t),r=s.relative(n,o);if(r===""||r.startsWith("..")||s.isAbsolute(r))throw new Error("temporary image is outside the system temp directory");return o}function R(e){const t=s.extname(e).toLowerCase(),a=Buffer.alloc(16),n=i.openSync(e,"r");let o=0;try{o=i.readSync(n,a,0,a.length,0)}finally{i.closeSync(n)}const r=a.subarray(0,o);if(t===".png"&&r.length>=8&&r.subarray(0,8).equals(Buffer.from([137,80,78,71,13,10,26,10])))return"image/png";if([".jpg",".jpeg"].includes(t)&&r.length>=3&&r[0]===255&&r[1]===216&&r[2]===255)return"image/jpeg";if(t===".gif"&&r.length>=6&&["GIF87a","GIF89a"].includes(r.subarray(0,6).toString("ascii")))return"image/gif";if(t===".webp"&&r.length>=12&&r.subarray(0,4).toString("ascii")==="RIFF"&&r.subarray(8,12).toString("ascii")==="WEBP")return"image/webp";throw new Error("temporary file is not a supported raster image")}function $(e){const t=String(e||"").trim();if(!t.startsWith("file://"))return t;try{return decodeURIComponent(new URL(t).pathname)}catch{throw new Error("temporary image path is invalid")}}function u(e){return y(e)}function F(e){switch(s.extname(e).toLowerCase()){case".pdf":return"application/pdf";case".md":case".markdown":return"text/markdown; charset=utf-8";case".txt":case".log":return"text/plain; charset=utf-8";case".html":case".htm":return"text/html; charset=utf-8";case".css":return"text/css; charset=utf-8";case".js":case".mjs":return"text/javascript; charset=utf-8";case".json":case".map":return"application/json; charset=utf-8";case".xml":return"application/xml; charset=utf-8";case".svg":return"image/svg+xml; charset=utf-8";case".png":return"image/png";case".jpg":case".jpeg":return"image/jpeg";case".gif":return"image/gif";case".webp":return"image/webp";case".avif":return"image/avif";case".ico":return"image/x-icon";case".bmp":return"image/bmp";case".woff":return"font/woff";case".woff2":return"font/woff2";case".ttf":return"font/ttf";case".otf":return"font/otf";case".eot":return"application/vnd.ms-fontobject";case".mp4":return"video/mp4";case".webm":return"video/webm";case".mp3":return"audio/mpeg";case".wav":return"audio/wav";case".ogg":return"audio/ogg";case".webmanifest":return"application/manifest+json; charset=utf-8";case".zip":return"application/zip";case".docx":return"application/vnd.openxmlformats-officedocument.wordprocessingml.document";case".xlsx":return"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";default:return"application/octet-stream"}}export{R as detectSafeTempImageMime,F as mimeForPath,I as resolveProjectFile,E as resolveTempImageFile,C as startFileTransferWorkerLoop};
1
+ import{managementActivity as f}from"./management/activity.js";import{managedClaim as y}from"./management/claims.js";import c from"node:fs";import x from"node:os";import i from"node:path";import{Readable as l}from"node:stream";import{acaServerRequest as u,acaServerStreamRequest as d}from"./aca-server-client.js";import{loadAcaConfig as b}from"./aca-config.js";import{expandWorkerRuntimePath as S}from"./worker-runtime-paths.js";import{startLongPollWorker as I}from"./long-poll-worker.js";import{openImageFile as j}from"./image-file-access.js";const g=25e3,v=g+1e4;function G(e){return I({run:async()=>{const t=b();if(!t.token)throw new Error("Worker 尚未配置认证令牌");const a=await y("file",()=>u("GET",`/api/client/file-requests/claim?machineId=${encodeURIComponent(t.machineId)}&waitMs=${g}`,void 0,{timeoutMs:v}));if(!a.item)return;const n=f.track("file");try{await E(a.item,t,e)}catch(o){const r=o instanceof Error?o.message:String(o);e.warn(`file request failed request=${a.item.requestId}: ${r}`);try{await R(a.item.requestId,r)}catch(s){f.uncertain.add("file-finalize"),e.warn(`file request fail callback failed request=${a.item.requestId}: ${s instanceof Error?s.message:String(s)}`)}}finally{n()}},onError:(t,a)=>e.warn(`file transfer worker failed, retry in ${a}ms: ${t instanceof Error?t.message:String(t)}`)})}async function R(e,t){await u("POST",`/api/client/file-requests/${encodeURIComponent(e)}/fail`,{message:t})}async function E(e,t,a){const n=t.projects.find(m=>m.projectId===e.projectId),o=h(e.projectRoot||n?.rootPath||"");if(e.purpose==="image-file"){const m=j(o,e.path,e.maxBytes);try{a.info(`streaming image request=${e.requestId} path=${m.path}`),await d("POST",`/api/client/file-requests/${encodeURIComponent(e.requestId)}/stream`,l.toWeb(m.stream),{"content-type":"application/octet-stream","x-aca-file-name":encodeURIComponent(i.basename(m.path)),"x-aca-file-size":String(m.sizeBytes),"x-aca-file-mime":m.mimeType})}finally{m.stream.destroy()}return}if(!o)throw new Error(`file request ${e.requestId} project root is unknown`);const r=e.purpose==="temp-image"?"temp-image":e.purpose==="project-preview"?"project-preview":"project-file",s=r==="temp-image"?F(e.path):$(o,e.path),p=c.statSync(s);if(!p.isFile())throw new Error(`file request ${e.requestId} target is not a file`);if(p.size>e.maxBytes)throw new Error(`file request ${e.requestId} target is too large (${p.size} bytes)`);const w=r==="temp-image"?k(s):L(s);a.info(`streaming file request=${e.requestId} path=${s}`),await d("POST",`/api/client/file-requests/${encodeURIComponent(e.requestId)}/stream`,l.toWeb(c.createReadStream(s)),{"content-type":"application/octet-stream","x-aca-file-name":encodeURIComponent(i.basename(s)),"x-aca-file-size":String(p.size),"x-aca-file-mime":w})}function $(e,t){const a=c.realpathSync(h(e)),n=i.isAbsolute(t)?t:i.join(a,t),o=c.realpathSync(n),r=i.relative(a,o);if(r===""||r.startsWith("..")||i.isAbsolute(r))throw new Error("requested file is outside project root");return o}function F(e){const t=T(e);if(!i.isAbsolute(t))throw new Error("temporary image path must be absolute");const a=c.lstatSync(t);if(a.isSymbolicLink())throw new Error("temporary image cannot be a symbolic link");if(!a.isFile())throw new Error("temporary image target is not a regular file");const n=c.realpathSync(x.tmpdir()),o=c.realpathSync(t),r=i.relative(n,o);if(r===""||r.startsWith("..")||i.isAbsolute(r))throw new Error("temporary image is outside the system temp directory");return o}function k(e){const t=i.extname(e).toLowerCase(),a=Buffer.alloc(16),n=c.openSync(e,"r");let o=0;try{o=c.readSync(n,a,0,a.length,0)}finally{c.closeSync(n)}const r=a.subarray(0,o);if(t===".png"&&r.length>=8&&r.subarray(0,8).equals(Buffer.from([137,80,78,71,13,10,26,10])))return"image/png";if([".jpg",".jpeg"].includes(t)&&r.length>=3&&r[0]===255&&r[1]===216&&r[2]===255)return"image/jpeg";if(t===".gif"&&r.length>=6&&["GIF87a","GIF89a"].includes(r.subarray(0,6).toString("ascii")))return"image/gif";if(t===".webp"&&r.length>=12&&r.subarray(0,4).toString("ascii")==="RIFF"&&r.subarray(8,12).toString("ascii")==="WEBP")return"image/webp";throw new Error("temporary file is not a supported raster image")}function T(e){const t=String(e||"").trim();if(!t.startsWith("file://"))return t;try{return decodeURIComponent(new URL(t).pathname)}catch{throw new Error("temporary image path is invalid")}}function h(e){return S(e)}function L(e){switch(i.extname(e).toLowerCase()){case".pdf":return"application/pdf";case".md":case".markdown":return"text/markdown; charset=utf-8";case".txt":case".log":return"text/plain; charset=utf-8";case".html":case".htm":return"text/html; charset=utf-8";case".css":return"text/css; charset=utf-8";case".js":case".mjs":return"text/javascript; charset=utf-8";case".json":case".map":return"application/json; charset=utf-8";case".xml":return"application/xml; charset=utf-8";case".svg":return"image/svg+xml; charset=utf-8";case".png":return"image/png";case".jpg":case".jpeg":return"image/jpeg";case".gif":return"image/gif";case".webp":return"image/webp";case".avif":return"image/avif";case".ico":return"image/x-icon";case".bmp":return"image/bmp";case".woff":return"font/woff";case".woff2":return"font/woff2";case".ttf":return"font/ttf";case".otf":return"font/otf";case".eot":return"application/vnd.ms-fontobject";case".mp4":return"video/mp4";case".webm":return"video/webm";case".mp3":return"audio/mpeg";case".wav":return"audio/wav";case".ogg":return"audio/ogg";case".webmanifest":return"application/manifest+json; charset=utf-8";case".zip":return"application/zip";case".docx":return"application/vnd.openxmlformats-officedocument.wordprocessingml.document";case".xlsx":return"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";default:return"application/octet-stream"}}export{k as detectSafeTempImageMime,L as mimeForPath,$ as resolveProjectFile,F as resolveTempImageFile,G as startFileTransferWorkerLoop};
@@ -0,0 +1 @@
1
+ import t from"node:fs";import s from"node:path";import{fileURLToPath as u}from"node:url";function I(o,i,n){let r=i.trim();if(/^file:/i.test(r)){const e=new URL(r);if(e.hostname&&e.hostname!=="localhost")throw new Error("image URL must reference a local file");r=u(e)}if(!r||r.includes("\0"))throw new Error("image path is invalid");if(!s.isAbsolute(r)&&!o)throw new Error("relative image path requires a project root");const a=s.resolve(o,r),c=t.lstatSync(a);if(c.isSymbolicLink())throw new Error("image cannot be a symbolic link");if(!c.isFile())throw new Error("image target is not a regular file");const l=t.realpathSync(a),f=t.openSync(l,t.constants.O_RDONLY|(t.constants.O_NOFOLLOW||0)|(t.constants.O_NONBLOCK||0));try{const e=t.fstatSync(f);if(!e.isFile()||c.dev!==e.dev||c.ino!==e.ino)throw new Error("image file changed while opening");if(!Number.isFinite(n)||n<=0||e.size>n)throw new Error("image file is too large");const g=Buffer.alloc(512),m=t.readSync(f,g,0,g.length,0),d=p(l,g.subarray(0,m)),h=t.createReadStream(l,{fd:f,autoClose:!0,start:0,end:e.size-1});return{path:l,sizeBytes:e.size,mimeType:d,stream:h}}catch(e){throw t.closeSync(f),e}}function p(o,i){const n=s.extname(o).toLowerCase();if(n===".png"&&i.length>=24&&i.subarray(0,8).equals(Buffer.from([137,80,78,71,13,10,26,10]))&&i.toString("ascii",12,16)==="IHDR"&&i.readUInt32BE(16)>0&&i.readUInt32BE(20)>0)return"image/png";if([".jpg",".jpeg"].includes(n)&&i.length>=4&&i[0]===255&&i[1]===216&&i[2]===255)return"image/jpeg";if(n===".gif"&&i.length>=10&&["GIF87a","GIF89a"].includes(i.toString("ascii",0,6))&&i.readUInt16LE(6)>0&&i.readUInt16LE(8)>0)return"image/gif";if(n===".webp"&&i.length>=16&&i.toString("ascii",0,4)==="RIFF"&&i.toString("ascii",8,12)==="WEBP"&&["VP8 ","VP8L","VP8X"].includes(i.toString("ascii",12,16)))return"image/webp";if(n===".bmp"&&i.length>=26&&i.toString("ascii",0,2)==="BM"&&i.readUInt32LE(10)>=26&&[12,40,52,56,64,108,124].includes(i.readUInt32LE(14)))return"image/bmp";if(n===".ico"&&i.length>=22&&i.readUInt16LE(0)===0&&i.readUInt16LE(2)===1&&i.readUInt16LE(4)>0&&i.readUInt32LE(14)>0&&i.readUInt32LE(18)>=22)return"image/x-icon";if(n===".avif"&&i.length>=16&&i.toString("ascii",4,8)==="ftyp"){const r=i.readUInt32BE(0);if(r>=16&&r<=i.length&&r%4===0){for(let a=8;a<r;a+=4)if(a!==12&&["avif","avis"].includes(i.toString("ascii",a,a+4)))return"image/avif"}}throw new Error("file is not a supported raster image")}export{I as openImageFile};
@@ -1,6 +1,6 @@
1
- import ee from"node:crypto";import{startJobArtifactMonitor as te,mergeArtifactFiles as W}from"./artifacts/job-artifact-monitor.js";import{appendSessionReferenceContext as ne}from"./session-reference-prompt.js";import F from"node:fs";import N from"node:path";import{compactAgentContext as oe,forkAgentSession as ie,sendAgentPrompt as ae}from"../acp/agent.js";import{acaServerDownload as re,acaServerRequest as S}from"./aca-server-client.js";import{loadAcaConfig as se}from"./aca-config.js";import{prepareLocalWorktree as ce}from"./worktree.js";import{expandWorkerRuntimePath as le}from"./worker-runtime-paths.js";import{collectAffectedFiles as D,extractAffectedFilePathsFromAcpRaw as de}from"./affected-files.js";import{resolveJobPermissionPolicy as ue}from"./job-permission-policy.js";import{createAcpEventBatcher as pe}from"./acp-event-batcher.js";import{createToolLifecycleTrace as me}from"../acp/tool-lifecycle-trace.js";import{shouldBufferAcpEvent as fe}from"./acp-event-upload-filter.js";import{userFacingAcpError as he}from"../acp/error-display.js";import{ensureCodexProviderRuntimeRevision as we}from"./codex-provider-runtime-config.js";import{acpSessionInvalidationFromError as be}from"../acp/session-errors.js";const ye=1500,V=5e3,q=1e3,G=900,Ie=1e4,Se=0,ve=12e3;function ge(e){return le(e)}function _e(e){const t=e.project_metadata;return t&&typeof t=="object"&&!Array.isArray(t)&&t.kind==="chat"?!0:typeof e.project_root_path=="string"&&(e.project_root_path.startsWith("~/.aica/chats/")||e.project_root_path.startsWith("~/.aca/chats/")||/(^|\/)\.aica\/chats\//.test(e.project_root_path)||/(^|\/)\.aca\/chats\//.test(e.project_root_path))}function ht(e,t={}){let n=!1,i=null,o=null,s=0,l=0;const u=new Set,r=new Map,I=new Map,_=async()=>{if(n||Date.now()<l)return;const f=Ce();if(K(u.size,f))try{const p=t.loadConfig?.()??se();if(!p.token)return;for(;!n&&K(u.size,f);){const v=t.claimNextJob?await t.claimNextJob(p,[...r.keys()]):await S("POST","/api/client/jobs/claim",{machineId:p.machineId,workspaceId:p.defaultWorkspaceId,activeSessionIds:[...r.keys()]});if(s=0,l=0,!v.item)break;const h=v.item;if(n){await(t.cancelClaimedJob?t.cancelClaimedJob(h,p):M(h.job_id,p.machineId,"cancelled")).catch(()=>{});break}const w=h.session_id,b=r.get(w);u.add(h.job_id);const P=new AbortController;let x;x=(async()=>{b&&(e.warn(`claimed job=${h.job_id} for already active session=${w}; waiting for the previous run to release its ACP runtime`),await Te(b,h,p,P)),!P.signal.aborted&&await(t.runJob??Pe)(h,p,e,P)})().catch(R=>{e.warn(`job ${h.job_id} failed outside handler: ${R instanceof Error?R.message:String(R)}`)}).finally(()=>{u.delete(h.job_id),r.get(w)===x&&r.delete(w),I.delete(h.job_id)}),r.set(w,x),I.set(h.job_id,{controller:P,promise:x})}}catch(p){if(!n){s+=1;const v=Ke(s);l=Date.now()+v,e.warn(`job worker failed, retry in ${v}ms: ${p instanceof Error?p.message:String(p)}`)}}},C=()=>{if(n||i)return;const f=_();i=f,f.finally(()=>{i===f&&(i=null)})};C();const d=setInterval(()=>{C()},t.claimIntervalMs??ye);return{timer:d,stop:()=>o||(o=(async()=>{if(n)return;n=!0,clearInterval(d);for(const p of I.values())p.controller.abort();await i?.catch(()=>{});for(const p of I.values())p.controller.abort();const f=[...I.values()].map(p=>p.promise);f.length!==0&&await Promise.race([Promise.allSettled(f).then(()=>{}),z(ve)])})(),o)}}async function Te(e,t,n,i){let o=!1;const l=setInterval(()=>{o||i.signal.aborted||(o=!0,Promise.allSettled([S("POST",`/api/client/jobs/${encodeURIComponent(t.job_id)}/heartbeat`,{machineId:n.machineId}),Z(t.job_id,i)]).finally(()=>{o=!1}))},V);try{await Promise.race([e,Ae(i.signal)])}finally{clearInterval(l)}}function Ae(e){return e.aborted?Promise.resolve():new Promise(t=>e.addEventListener("abort",()=>t(),{once:!0}))}function Ce(){const e=process.env.ACA_MAX_CONCURRENT_JOBS||process.env.ACA_MAX_CONCURRENT_PROJECT_JOBS||"",t=Number.parseInt(e,10);return!e.trim()||!Number.isFinite(t)||t<0?Se:t===0?0:Math.max(1,Math.min(t,8))}function K(e,t){return t===0||e<t}async function Pe(e,t,n,i){const o=e.job_id,s=e.session_id,l=e.workspace_id,u=e.project_id??null,r=e.input??{},I=String(r.control?.type||"").toLowerCase();if(!I&&!r.message?.trim()&&(r.attachments??[]).length===0&&!r.sessionReferences?.length){await M(o,t.machineId,"job input missing message");return}const _=t.projects.find(c=>c.projectId===u),C=ge(_?.rootPath||e.project_root_path||"");if(!C){await M(o,t.machineId,"job project root is unknown");return}_e(e)&&F.mkdirSync(C,{recursive:!0}),n.info(`claimed job=${o} session=${s} project=${_?.name??u??"unknown"}`);let d=!1,f=!1;const p=setInterval(()=>{d||(d=!0,S("POST",`/api/client/jobs/${encodeURIComponent(o)}/heartbeat`,{machineId:t.machineId}).catch(()=>{}).finally(()=>{d=!1})),f||(f=!0,Z(o,i).catch(()=>{}).finally(()=>{f=!1}))},V);let v=0;const h=me(c=>n.info(c)),w=pe(o,t.machineId,{warn:c=>n.warn(c),trace:h}),b=Me(o,t.machineId),P=new Set;let x=null,R;const J=async()=>R?.finish().catch(c=>(n.warn(`artifact collection failed job=${o}: ${String(c)}`),[]))??[];try{await b.sendNow({phase:"initializing",label:"准备工作区",detail:e.worktree_id?"worktree":"project root"}).catch(()=>{});const c=ce({projectId:u,sessionId:s,projectRoot:C,worktree:e});x=c,await We(o,t.machineId,c);const g=Re(r.acpRun);if(ke(g.agentType)&&await we(e.codex_provider_revision),I==="compact"){if(!e.acp_session_id)throw new Error("Current session does not have an ACP context to compact");const a=await oe({agentType:$(g.agentType,"codex"),cliType:$(g.cliType,"builtin"),acpSessionId:e.acp_session_id,cwd:c.cwd,model:r.model,mode:r.mode,configOptionValues:r.configOptionValues,timeoutMs:r.timeoutMs,allowRollover:r.control?.allowRollover===!0,signal:i.signal,onStatus:y=>{b.report({phase:y.phase,label:y.label,detail:y.detail,updateType:y.updateType,raw:y})}});await b.flush(),await S("POST",`/api/client/jobs/${encodeURIComponent(o)}/complete`,{machineId:t.machineId,acpSessionId:a.acpSessionId,suppressTurn:!0,result:{ok:!0,control:{type:"compact",allowRollover:r.control?.allowRollover===!0},acpSessionId:a.acpSessionId,durationMs:a.durationMs,usage:a.contextUsage,maintenance:a.maintenance,worktree:E(c)}}),n.info(`completed context compact job=${o}`);return}if(I==="fork"){const a=String(r.control?.sourceAcpSessionId||"").trim();if(!a)throw new Error("Source session does not have a Codex ACP context to fork");await b.sendNow({phase:"forking",label:"正在分叉 Codex 会话",detail:r.control?.sourceSessionId?`源会话 ${r.control.sourceSessionId}`:void 0}).catch(()=>{});const y=await ie({agentType:$(g.agentType,"codex"),cliType:$(g.cliType,"builtin"),sourceProviderSessionId:a,cwd:c.cwd,model:r.model,mode:r.mode,configOptionValues:r.configOptionValues,timeoutMs:r.timeoutMs,signal:i.signal,onStatus:k=>{b.report({phase:k.phase,label:k.label,detail:k.detail,updateType:k.updateType,raw:k})}});await b.flush(),await S("POST",`/api/client/jobs/${encodeURIComponent(o)}/complete`,{machineId:t.machineId,acpSessionId:y.acpSessionId,suppressTurn:!0,result:{ok:!0,control:{type:"fork",sourceSessionId:r.control?.sourceSessionId||null},acpSessionId:y.acpSessionId,worktree:E(c)}}),n.info(`completed Codex session fork job=${o}`);return}const T=await ze({cwd:c.cwd,sessionId:s,jobId:o,attachments:r.attachments??[]});T.length>0&&(n.info(`materialized ${T.length} attachment(s) for job=${o}`),await b.sendNow({phase:"initializing",label:"附件已写入本机",detail:T.map(a=>a.relativePath).join(", "),raw:{attachments:X(T)}}).catch(()=>{}));const j=Ne(r,T),A=Fe(j,T);R=await te(c.cwd,a=>n.warn(a)).catch(a=>{n.warn(`artifact monitor unavailable job=${o}: ${String(a)}`)});const m=await ae({agentType:$(g.agentType,"codex"),cliType:$(g.cliType,"builtin"),acpSessionId:e.acp_session_id??null,requireSessionResume:xe(e.session_raw),prompt:j,promptBlocks:A,cwd:c.cwd,model:r.model,mode:r.mode,configOptionValues:r.configOptionValues,canExecuteTools:r.canExecuteTools,timeoutMs:r.timeoutMs,contextWindow:$e(g.contextWindow),signal:i.signal,onStatus:a=>{b.report({phase:a.phase,label:a.label,detail:a.detail,updateType:a.updateType,raw:a})},onUpdate:a=>{v+=1,(a.type==="tool_call"||a.type==="tool_call_update"&&["completed","failed","cancelled"].includes(a.status||""))&&h("worker_event",{jobId:o,uploadRunId:w.uploadRunId,toolCallId:a.toolCallId,status:a.status,sequence:v});for(const k of de(a.raw))P.add(k);const y={turnId:e.user_turn_id??null,eventType:a.type,label:a.label,text:a.text,status:a.status,toolCallId:a.toolCallId,raw:Oe(a.raw,v)};fe(y)&&w.push(y)},onPermissionRequest:a=>{const y=ue(r,a);return y?Promise.resolve(y):De(o,t.machineId,a,i.signal)},onUserInputRequest:a=>Ve(o,t.machineId,a,i.signal),onProviderSessionMigration:async a=>{const y=await S("POST",`/api/client/jobs/${encodeURIComponent(o)}/acp-session`,{machineId:t.machineId,previousAcpSessionId:a.previousProviderSessionId,acpSessionId:a.providerSessionId,reason:a.reason,migratedAtMs:a.migratedAtMs});e.acp_session_id=a.providerSessionId,n.info(`migrated ACP session for job=${o} ${a.previousProviderSessionId} -> ${a.providerSessionId}`),await w.flush().catch(()=>{}),await b.sendNow({phase:"resuming",label:"Codex 会话已迁移",detail:a.providerSessionId,updateType:"aca.performance.provider_session_migrated",raw:{migration:a,server:{migrated:y.migrated===!0}}}).catch(()=>{})}}),Y=await J(),B=await w.finish();await b.flush();const L=W(D({cwd:c.cwd,evidencePaths:P}),Y),O=je(m.promptResponse);if(!(Ee(O)||!!m.content)){await M(o,t.machineId,`${m.agentType} stopped: ${O??"unknown"}`,{acpSessionId:m.acpSessionId,stopReason:O,eventUpload:B,files:L,exitCode:m.exitCode,stderr:He(m.stderr,4e3),worktree:E(c)});return}await S("POST",`/api/client/jobs/${encodeURIComponent(o)}/complete`,{machineId:t.machineId,acpSessionId:m.acpSessionId,assistantText:m.content,result:{ok:!0,eventUpload:B,acpSessionId:m.acpSessionId,stopReason:O,durationMs:m.durationMs,acpRun:r.acpRun??null,effectiveConfig:m.effectiveConfig??null,performance:m.performance??null,usage:m.contextUsage??null,providerDiagnostics:m.providerDiagnostics??null,updateCount:m.updateCount,statusTimeline:m.statusTimeline,worktree:E(c),attachments:X(T),files:L}}),n.info(`completed job=${o}`)}catch(c){if(Ge(i.signal)){w.discard(),b.discard(),n.warn(`stopped local job=${o} because ACA Server already finalized it`);return}const g=await J(),T=await w.finish();await b.flush();const j=i.signal.aborted?"cancelled":c instanceof Error?c.message:String(c),A=be(c),m=W(x?D({cwd:x.cwd,evidencePaths:P}):[],g);await M(o,t.machineId,he(j),{rawError:j,eventUpload:T,files:m,providerDiagnostics:U(c,"providerDiagnostics"),statusTimeline:U(c,"statusTimeline"),performance:U(c,"performance"),...A?{acpSessionInvalidated:!0,invalidatedAcpSessionId:A.providerSessionId,failure:{category:A.category,actions:A.actions,details:A.details}}:{}},A?{invalidateAcpSession:!0,invalidatedAcpSessionId:A.providerSessionId}:void 0)}finally{R?.dispose(),w.discard(),clearInterval(p),await b.flush()}}function xe(e){if(!e||typeof e!="object"||Array.isArray(e))return!1;const t=e.importedProviderSession;return!!(t&&typeof t=="object"&&!Array.isArray(t)&&t.strictResume===!0)}function Re(e){return!e||typeof e!="object"||Array.isArray(e)?{}:e}function ke(e){const t=String(e||"codex").toLowerCase().replace(/[^a-z0-9]+/g,"");return t==="codex"||t==="codexacp"||t==="codexstandard"}function $e(e){return typeof e=="number"&&Number.isFinite(e)&&e>0?e:void 0}function $(...e){for(const t of e)if(typeof t=="string"&&t.trim())return t.trim();return""}function je(e){if(!e||typeof e!="object"||Array.isArray(e))return;const t=e;if(typeof t.stopReason=="string")return t.stopReason;if(typeof t.reason=="string")return t.reason;const n=t.part;if(n&&typeof n=="object"&&!Array.isArray(n)&&typeof n.reason=="string")return n.reason}function Ee(e){return e?["end_turn","stop","completed","complete"].includes(e):!1}function Me(e,t){let n=null,i=0,o="",s=null,l=Promise.resolve(),u=!1;const r=d=>JSON.stringify({phase:d.phase??"",label:d.label??"",detail:d.detail??"",updateType:d.updateType??""}),I=async d=>{await S("POST",`/api/client/jobs/${encodeURIComponent(e)}/presence`,{machineId:t,phase:d.phase,label:d.label,detail:d.detail,updateType:d.updateType,raw:d.raw}),i=Date.now(),o=r(d)},_=()=>{if(s&&(clearTimeout(s),s=null),u)return n=null,l;if(!n)return l;const d=n;return n=null,l=l.then(()=>I(d)).catch(()=>{}),l},C=d=>{s||(s=setTimeout(()=>{_()},d),s.unref())};return{report(d){if(u)return;const f=Date.now(),v=r(d)!==o,h=f-i>=Ie;if(!v&&!h)return;n=d;const w=f-i;if(w>=G||i===0){_();return}C(G-w)},sendNow(d){return u?l:(n=d,_())},flush:_,discard(){u=!0,n=null,s&&clearTimeout(s),s=null}}}function Oe(e,t){return e&&typeof e=="object"&&!Array.isArray(e)?{...e,acaSequence:t}:{value:e,acaSequence:t}}function Ne(e,t=[]){const n=ne(e?.message?.trim()??"",e?.sessionReferences);if(t.length===0)return n;const o=["用户随消息上传了以下附件,aca 已将附件写入本机工作区。需要读取附件时请直接使用下面的 localPath;图片附件也已作为 ACP image block 随本轮请求传入。",...t.map((s,l)=>`${l+1}. ${s.name} (${s.kind}, ${s.mimeType}, ${Le(s.size)})
1
+ import{managementActivity as T}from"./management/activity.js";import{managedClaim as te}from"./management/claims.js";import ne from"node:crypto";import{startJobArtifactMonitor as oe,mergeArtifactFiles as D}from"./artifacts/job-artifact-monitor.js";import{appendSessionReferenceContext as ae}from"./session-reference-prompt.js";import J from"node:fs";import z from"node:path";import{compactAgentContext as ie,forkAgentSession as re,sendAgentPrompt as se}from"../acp/agent.js";import{acaServerDownload as ce,acaServerRequest as I}from"./aca-server-client.js";import{loadAcaConfig as le}from"./aca-config.js";import{prepareLocalWorktree as de}from"./worktree.js";import{expandWorkerRuntimePath as ue}from"./worker-runtime-paths.js";import{collectAffectedFiles as V,extractAffectedFilePathsFromAcpRaw as pe}from"./affected-files.js";import{resolveJobPermissionPolicy as me}from"./job-permission-policy.js";import{createAcpEventBatcher as fe}from"./acp-event-batcher.js";import{createToolLifecycleTrace as he}from"../acp/tool-lifecycle-trace.js";import{shouldBufferAcpEvent as we}from"./acp-event-upload-filter.js";import{userFacingAcpError as be}from"../acp/error-display.js";import{ensureCodexProviderRuntimeRevision as ye}from"./codex-provider-runtime-config.js";import{acpSessionInvalidationFromError as Ie}from"../acp/session-errors.js";const Se=1500,q=5e3,G=1e3,K=900,ve=1e4,ge=0,_e=12e3;function Te(e){return ue(e)}function Ae(e){const t=e.project_metadata;return t&&typeof t=="object"&&!Array.isArray(t)&&t.kind==="chat"?!0:typeof e.project_root_path=="string"&&(e.project_root_path.startsWith("~/.aica/chats/")||e.project_root_path.startsWith("~/.aca/chats/")||/(^|\/)\.aica\/chats\//.test(e.project_root_path)||/(^|\/)\.aca\/chats\//.test(e.project_root_path))}function It(e,t={}){let n=!1,a=null,o=null,s=0,l=0;const p=new Set,r=new Map,y=new Map,A=async()=>{if(n||T.paused||T.stopping||Date.now()<l)return;const w=ke();if(!H(p.size,w))return;const S=T.enter("poll");if(S)try{const m=t.loadConfig?.()??le();if(!m.token)return;for(;!n&&!T.paused&&!T.stopping&&H(p.size,w);){const v=t.claimNextJob?await t.claimNextJob(m,[...r.keys()]):await te("job",()=>I("POST","/api/client/jobs/claim",{machineId:m.machineId,workspaceId:m.defaultWorkspaceId,activeSessionIds:[...r.keys()]}),{workspaceId:m.defaultWorkspaceId,activeSessionIds:[...r.keys()]});if(s=0,l=0,!v.item)break;const u=v.item;if(n){await(t.cancelClaimedJob?t.cancelClaimedJob(u,m):N(u.job_id,m.machineId,"cancelled")).catch(()=>{});break}const f=u.session_id,j=r.get(f),E=T.track("job");p.add(u.job_id);const k=new AbortController;let x;x=(async()=>{j&&(e.warn(`claimed job=${u.job_id} for already active session=${f}; waiting for the previous run to release its ACP runtime`),await Ce(j,u,m,k)),!k.signal.aborted&&await(t.runJob??xe)(u,m,e,k)})().catch(c=>{T.uncertain.add("job-finalize"),e.warn(`job ${u.job_id} failed outside handler: ${c instanceof Error?c.message:String(c)}`)}).finally(()=>{E(),p.delete(u.job_id),r.get(f)===x&&r.delete(f),y.delete(u.job_id)}),r.set(f,x),y.set(u.job_id,{controller:k,promise:x})}}catch(m){if(!n){s+=1;const v=Xe(s);l=Date.now()+v,e.warn(`job worker failed, retry in ${v}ms: ${m instanceof Error?m.message:String(m)}`)}}finally{S()}},P=()=>{if(n||a)return;const w=A();a=w,w.finally(()=>{a===w&&(a=null)})};P();const d=setInterval(()=>{P()},t.claimIntervalMs??Se);return{timer:d,stop:()=>o||(o=(async()=>{if(n)return;n=!0,clearInterval(d);for(const S of y.values())S.controller.abort();await a?.catch(()=>{});for(const S of y.values())S.controller.abort();const w=[...y.values()].map(S=>S.promise);w.length!==0&&await Promise.race([Promise.allSettled(w).then(()=>{}),B(_e)])})(),o)}}async function Ce(e,t,n,a){let o=!1;const l=setInterval(()=>{o||a.signal.aborted||(o=!0,Promise.allSettled([I("POST",`/api/client/jobs/${encodeURIComponent(t.job_id)}/heartbeat`,{machineId:n.machineId}),Q(t.job_id,a)]).finally(()=>{o=!1}))},q);try{await Promise.race([e,Pe(a.signal)])}finally{clearInterval(l)}}function Pe(e){return e.aborted?Promise.resolve():new Promise(t=>e.addEventListener("abort",()=>t(),{once:!0}))}function ke(){const e=process.env.ACA_MAX_CONCURRENT_JOBS||process.env.ACA_MAX_CONCURRENT_PROJECT_JOBS||"",t=Number.parseInt(e,10);return!e.trim()||!Number.isFinite(t)||t<0?ge:t===0?0:Math.max(1,Math.min(t,8))}function H(e,t){return t===0||e<t}async function xe(e,t,n,a){const o=e.job_id,s=e.session_id,l=e.workspace_id,p=e.project_id??null,r=e.input??{},y=String(r.control?.type||"").toLowerCase();if(!y&&!r.message?.trim()&&(r.attachments??[]).length===0&&!r.sessionReferences?.length){await N(o,t.machineId,"job input missing message");return}const A=t.projects.find(c=>c.projectId===p),P=Te(A?.rootPath||e.project_root_path||"");if(!P){await N(o,t.machineId,"job project root is unknown");return}Ae(e)&&J.mkdirSync(P,{recursive:!0}),n.info(`claimed job=${o} session=${s} project=${A?.name??p??"unknown"}`);let d=!1,w=!1;const S=setInterval(()=>{d||(d=!0,I("POST",`/api/client/jobs/${encodeURIComponent(o)}/heartbeat`,{machineId:t.machineId}).catch(()=>{}).finally(()=>{d=!1})),w||(w=!0,Q(o,a).catch(()=>{}).finally(()=>{w=!1}))},q);let m=0;const v=he(c=>n.info(c)),u=fe(o,t.machineId,{warn:c=>n.warn(c),trace:v}),f=Ne(o,t.machineId),j=new Set;let E=null,k;const x=async()=>k?.finish().catch(c=>(T.uncertain.add("job-artifacts"),n.warn(`artifact collection failed job=${o}: ${String(c)}`),[]))??[];try{await f.sendNow({phase:"initializing",label:"准备工作区",detail:e.worktree_id?"worktree":"project root"}).catch(()=>{});const c=de({projectId:p,sessionId:s,projectRoot:P,worktree:e});E=c,await Ve(o,t.machineId,c);const g=je(r.acpRun);if($e(g.agentType)&&await ye(e.codex_provider_revision),y==="compact"){if(!e.acp_session_id)throw new Error("Current session does not have an ACP context to compact");const i=await ie({agentType:$(g.agentType,"codex"),cliType:$(g.cliType,"builtin"),acpSessionId:e.acp_session_id,cwd:c.cwd,model:r.model,mode:r.mode,configOptionValues:r.configOptionValues,timeoutMs:r.timeoutMs,allowRollover:r.control?.allowRollover===!0,signal:a.signal,onStatus:b=>{f.report({phase:b.phase,label:b.label,detail:b.detail,updateType:b.updateType,raw:b})}});await f.flush(),await I("POST",`/api/client/jobs/${encodeURIComponent(o)}/complete`,{machineId:t.machineId,acpSessionId:i.acpSessionId,suppressTurn:!0,result:{ok:!0,control:{type:"compact",allowRollover:r.control?.allowRollover===!0},acpSessionId:i.acpSessionId,durationMs:i.durationMs,usage:i.contextUsage,maintenance:i.maintenance,worktree:O(c)}}),n.info(`completed context compact job=${o}`);return}if(y==="fork"){const i=String(r.control?.sourceAcpSessionId||"").trim();if(!i)throw new Error("Source session does not have a Codex ACP context to fork");await f.sendNow({phase:"forking",label:"正在分叉 Codex 会话",detail:r.control?.sourceSessionId?`源会话 ${r.control.sourceSessionId}`:void 0}).catch(()=>{});const b=await re({agentType:$(g.agentType,"codex"),cliType:$(g.cliType,"builtin"),sourceProviderSessionId:i,cwd:c.cwd,model:r.model,mode:r.mode,configOptionValues:r.configOptionValues,timeoutMs:r.timeoutMs,signal:a.signal,onStatus:R=>{f.report({phase:R.phase,label:R.label,detail:R.detail,updateType:R.updateType,raw:R})}});await f.flush(),await I("POST",`/api/client/jobs/${encodeURIComponent(o)}/complete`,{machineId:t.machineId,acpSessionId:b.acpSessionId,suppressTurn:!0,result:{ok:!0,control:{type:"fork",sourceSessionId:r.control?.sourceSessionId||null},acpSessionId:b.acpSessionId,worktree:O(c)}}),n.info(`completed Codex session fork job=${o}`);return}const _=await Je({cwd:c.cwd,sessionId:s,jobId:o,attachments:r.attachments??[]});_.length>0&&(n.info(`materialized ${_.length} attachment(s) for job=${o}`),await f.sendNow({phase:"initializing",label:"附件已写入本机",detail:_.map(i=>i.relativePath).join(", "),raw:{attachments:Z(_)}}).catch(()=>{}));const M=ze(r,_),C=Ue(M,_);k=await oe(c.cwd,i=>n.warn(i)).catch(i=>{n.warn(`artifact monitor unavailable job=${o}: ${String(i)}`)});const h=await se({agentType:$(g.agentType,"codex"),cliType:$(g.cliType,"builtin"),acpSessionId:e.acp_session_id??null,requireSessionResume:Re(e.session_raw),prompt:M,promptBlocks:C,cwd:c.cwd,model:r.model,mode:r.mode,configOptionValues:r.configOptionValues,canExecuteTools:r.canExecuteTools,timeoutMs:r.timeoutMs,contextWindow:Ee(g.contextWindow),signal:a.signal,onStatus:i=>{f.report({phase:i.phase,label:i.label,detail:i.detail,updateType:i.updateType,raw:i})},onUpdate:i=>{m+=1,(i.type==="tool_call"||i.type==="tool_call_update"&&["completed","failed","cancelled"].includes(i.status||""))&&v("worker_event",{jobId:o,uploadRunId:u.uploadRunId,toolCallId:i.toolCallId,status:i.status,sequence:m});for(const R of pe(i.raw))j.add(R);const b={turnId:e.user_turn_id??null,eventType:i.type,label:i.label,text:i.text,status:i.status,toolCallId:i.toolCallId,raw:Fe(i.raw,m)};we(b)&&u.push(b)},onPermissionRequest:i=>{const b=me(r,i);return b?Promise.resolve(b):qe(o,t.machineId,i,a.signal)},onUserInputRequest:i=>Ge(o,t.machineId,i,a.signal),onProviderSessionMigration:async i=>{const b=await I("POST",`/api/client/jobs/${encodeURIComponent(o)}/acp-session`,{machineId:t.machineId,previousAcpSessionId:i.previousProviderSessionId,acpSessionId:i.providerSessionId,reason:i.reason,migratedAtMs:i.migratedAtMs});e.acp_session_id=i.providerSessionId,n.info(`migrated ACP session for job=${o} ${i.previousProviderSessionId} -> ${i.providerSessionId}`),await u.flush().catch(()=>{}),await f.sendNow({phase:"resuming",label:"Codex 会话已迁移",detail:i.providerSessionId,updateType:"aca.performance.provider_session_migrated",raw:{migration:i,server:{migrated:b.migrated===!0}}}).catch(()=>{})}}),ee=await x(),U=await u.finish();U.complete||T.uncertain.add("job-event-upload"),await f.flush();const W=D(V({cwd:c.cwd,evidencePaths:j}),ee),F=Me(h.promptResponse);if(!(Oe(F)||!!h.content)){await N(o,t.machineId,`${h.agentType} stopped: ${F??"unknown"}`,{acpSessionId:h.acpSessionId,stopReason:F,eventUpload:U,files:W,exitCode:h.exitCode,stderr:Ze(h.stderr,4e3),worktree:O(c)});return}await I("POST",`/api/client/jobs/${encodeURIComponent(o)}/complete`,{machineId:t.machineId,acpSessionId:h.acpSessionId,assistantText:h.content,result:{ok:!0,eventUpload:U,acpSessionId:h.acpSessionId,stopReason:F,durationMs:h.durationMs,acpRun:r.acpRun??null,effectiveConfig:h.effectiveConfig??null,performance:h.performance??null,usage:h.contextUsage??null,providerDiagnostics:h.providerDiagnostics??null,updateCount:h.updateCount,statusTimeline:h.statusTimeline,worktree:O(c),attachments:Z(_),files:W}}),n.info(`completed job=${o}`)}catch(c){if(He(a.signal)){u.discard(),f.discard(),n.warn(`stopped local job=${o} because ACA Server already finalized it`);return}const g=await x(),_=await u.finish();_.complete||T.uncertain.add("job-event-upload"),await f.flush();const M=a.signal.aborted?"cancelled":c instanceof Error?c.message:String(c),C=Ie(c),h=D(E?V({cwd:E.cwd,evidencePaths:j}):[],g);await N(o,t.machineId,be(M),{rawError:M,eventUpload:_,files:h,providerDiagnostics:L(c,"providerDiagnostics"),statusTimeline:L(c,"statusTimeline"),performance:L(c,"performance"),...C?{acpSessionInvalidated:!0,invalidatedAcpSessionId:C.providerSessionId,failure:{category:C.category,actions:C.actions,details:C.details}}:{}},C?{invalidateAcpSession:!0,invalidatedAcpSessionId:C.providerSessionId}:void 0)}finally{k?.dispose(),u.discard(),clearInterval(S),await f.flush()}}function Re(e){if(!e||typeof e!="object"||Array.isArray(e))return!1;const t=e.importedProviderSession;return!!(t&&typeof t=="object"&&!Array.isArray(t)&&t.strictResume===!0)}function je(e){return!e||typeof e!="object"||Array.isArray(e)?{}:e}function $e(e){const t=String(e||"codex").toLowerCase().replace(/[^a-z0-9]+/g,"");return t==="codex"||t==="codexacp"||t==="codexstandard"}function Ee(e){return typeof e=="number"&&Number.isFinite(e)&&e>0?e:void 0}function $(...e){for(const t of e)if(typeof t=="string"&&t.trim())return t.trim();return""}function Me(e){if(!e||typeof e!="object"||Array.isArray(e))return;const t=e;if(typeof t.stopReason=="string")return t.stopReason;if(typeof t.reason=="string")return t.reason;const n=t.part;if(n&&typeof n=="object"&&!Array.isArray(n)&&typeof n.reason=="string")return n.reason}function Oe(e){return e?["end_turn","stop","completed","complete"].includes(e):!1}function Ne(e,t){let n=null,a=0,o="",s=null,l=Promise.resolve(),p=!1;const r=d=>JSON.stringify({phase:d.phase??"",label:d.label??"",detail:d.detail??"",updateType:d.updateType??""}),y=async d=>{await I("POST",`/api/client/jobs/${encodeURIComponent(e)}/presence`,{machineId:t,phase:d.phase,label:d.label,detail:d.detail,updateType:d.updateType,raw:d.raw}),a=Date.now(),o=r(d)},A=()=>{if(s&&(clearTimeout(s),s=null),p)return n=null,l;if(!n)return l;const d=n;return n=null,l=l.then(()=>y(d)).catch(()=>{}),l},P=d=>{s||(s=setTimeout(()=>{A()},d),s.unref())};return{report(d){if(p)return;const w=Date.now(),m=r(d)!==o,v=w-a>=ve;if(!m&&!v)return;n=d;const u=w-a;if(u>=K||a===0){A();return}P(K-u)},sendNow(d){return p?l:(n=d,A())},flush:A,discard(){p=!0,n=null,s&&clearTimeout(s),s=null}}}function Fe(e,t){return e&&typeof e=="object"&&!Array.isArray(e)?{...e,acaSequence:t}:{value:e,acaSequence:t}}function ze(e,t=[]){const n=ae(e?.message?.trim()??"",e?.sessionReferences);if(t.length===0)return n;const o=["用户随消息上传了以下附件,aca 已将附件写入本机工作区。需要读取附件时请直接使用下面的 localPath;图片附件也已作为 ACP image block 随本轮请求传入。",...t.map((s,l)=>`${l+1}. ${s.name} (${s.kind}, ${s.mimeType}, ${De(s.size)})
2
2
  localPath: ${s.path}
3
3
  workspacePath: ${s.relativePath}`)].join(`
4
4
  `);return n?`${n}
5
5
 
6
- ${o}`:o}function Fe(e,t){const n=[{type:"text",text:e}];for(const i of t)i.kind!=="image"||!i.mimeType.startsWith("image/")||n.push({type:"image",data:i.base64,mimeType:i.mimeType,uri:i.path});return n}async function ze(e){if(e.attachments.length===0)return[];const t=N.join(e.cwd,".aca","attachments",H(e.sessionId),H(e.jobId));F.mkdirSync(t,{recursive:!0});const n=[];for(const[i,o]of e.attachments.entries()){const s=o.attachmentId?{mimeType:o.mimeType||"application/octet-stream",bytes:await re(`/api/client/attachments/${encodeURIComponent(o.attachmentId)}`)}:Ue(o.dataUrl??""),l=o.mimeType||s.mimeType||"application/octet-stream",u=o.kind==="image"||l.startsWith("image/")?"image":"file",r=Je(o.name||`attachment-${i+1}`,l),I=N.join(t,`${String(i+1).padStart(2,"0")}-${r}`);F.writeFileSync(I,s.bytes),n.push({name:o.name||r,mimeType:l,size:s.bytes.byteLength,kind:u,path:I,relativePath:N.relative(e.cwd,I),base64:s.bytes.toString("base64"),sha256:ee.createHash("sha256").update(s.bytes).digest("hex")})}return n}function Ue(e){const t=e.indexOf(",");if(!e.startsWith("data:")||t<0)throw new Error("attachment dataUrl is not a data URL");const n=e.slice(5,t),i=e.slice(t+1),o=n.split(";").filter(Boolean),s=o[0]?.includes("/")?o[0]:"application/octet-stream";if(!o.includes("base64"))throw new Error("attachment dataUrl is not base64 encoded");return{mimeType:s,bytes:Buffer.from(i,"base64")}}function Je(e,t){const n=N.basename(e).replace(/[<>:"/\\|?*\x00-\x1F]/g,"_").trim(),i=`attachment${Be(t)}`;return(!n||n==="."||n===".."?i:n).slice(0,180)}function Be(e){switch(e.toLowerCase()){case"image/png":return".png";case"image/jpeg":return".jpg";case"image/webp":return".webp";case"image/gif":return".gif";case"text/plain":return".txt";case"application/json":return".json";case"text/markdown":case"text/x-markdown":return".md";case"application/pdf":return".pdf";case"application/msword":return".doc";case"application/vnd.openxmlformats-officedocument.wordprocessingml.document":return".docx";case"application/vnd.ms-excel":return".xls";case"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet":return".xlsx";case"application/vnd.ms-powerpoint":return".ppt";case"application/vnd.openxmlformats-officedocument.presentationml.presentation":return".pptx";case"application/zip":return".zip";case"application/x-7z-compressed":return".7z";case"application/x-rar-compressed":case"application/vnd.rar":return".rar";case"application/gzip":return".gz";case"application/x-tar":return".tar";default:return""}}function H(e){return e.replace(/[^a-zA-Z0-9._-]+/g,"-").replace(/^-+|-+$/g,"").slice(0,80)||"item"}function X(e){return e.map(t=>({name:t.name,kind:t.kind,mimeType:t.mimeType,size:t.size,path:t.path,relativePath:t.relativePath,sha256:t.sha256}))}function Le(e){if(!Number.isFinite(e)||e<=0)return"0 B";const t=["B","KB","MB","GB"];let n=e,i=0;for(;n>=1024&&i<t.length-1;)n/=1024,i+=1;return`${n>=10||i===0?n.toFixed(0):n.toFixed(1)} ${t[i]}`}async function We(e,t,n){!n.usedWorktree||!n.worktreeId||await S("POST",`/api/client/jobs/${encodeURIComponent(e)}/worktree`,{machineId:t,baseCommit:n.baseCommit??null,worktreePath:n.cwd,status:n.status??"active",raw:n.raw??E(n)})}function E(e){return{usedWorktree:e.usedWorktree,worktreeId:e.worktreeId??null,cwd:e.cwd,status:e.status??null,baseCommit:e.baseCommit??null,branchName:e.branchName??null,baseBranch:e.baseBranch??null}}async function De(e,t,n,i){const s=(await S("POST",`/api/client/jobs/${encodeURIComponent(e)}/permissions`,{machineId:t,request:n})).item.permission_id;for(;!i.aborted;){await z(q);const l=await S("GET",`/api/client/permissions/${encodeURIComponent(s)}`);if(l.item.status==="resolved"&&l.item.response){const u=l.item.response.outcome;return u.outcome==="selected"?{outcome:u}:{outcome:{outcome:"cancelled"}}}}return{outcome:{outcome:"cancelled"}}}async function Ve(e,t,n,i){const s=(await S("POST",`/api/client/jobs/${encodeURIComponent(e)}/permissions`,{machineId:t,request:{kind:"elicitation",...n}})).item.permission_id;for(;!i.aborted;){await z(q);const l=await S("GET",`/api/client/permissions/${encodeURIComponent(s)}`);if(l.item.status==="resolved"&&l.item.response){const u=l.item.response.outcome;return u.outcome==="submitted"?{outcome:u}:{outcome:{outcome:"cancelled"}}}}return{outcome:{outcome:"cancelled"}}}async function Z(e,t){if(t.signal.aborted)return;const n=await S("GET",`/api/client/jobs/${encodeURIComponent(e)}`);if(Number(n.item.cancel_requested??0)===1){t.abort();return}const i=qe(n.item);i&&t.abort(new Q(e,i))}function qe(e){const t=String(e.status||"").toLowerCase();return t&&!["claimed","running"].includes(t)?t:null}class Q extends Error{constructor(t,n){super(`ACA Server already finalized job=${t} status=${n}`),this.name="ServerFinalizedJobError"}}function Ge(e){return e.aborted&&e.reason instanceof Q}async function M(e,t,n,i,o){await S("POST",`/api/client/jobs/${encodeURIComponent(e)}/fail`,{machineId:t,error:n,result:i,...o?.invalidateAcpSession?{invalidateAcpSession:!0,invalidatedAcpSessionId:o.invalidatedAcpSessionId}:{}})}function z(e){return new Promise(t=>setTimeout(t,e))}function Ke(e){return Math.min(1e4,500*2**Math.min(5,Math.max(0,e-1)))+Math.floor(Math.random()*250)}function He(e,t){return e.length<=t?e:`${e.slice(0,t)}...`}function U(e,t){return!e||typeof e!="object"||Array.isArray(e)?null:e[t]??null}export{qe as serverFinalizedJobStatus,ht as startJobWorkerLoop};
6
+ ${o}`:o}function Ue(e,t){const n=[{type:"text",text:e}];for(const a of t)a.kind!=="image"||!a.mimeType.startsWith("image/")||n.push({type:"image",data:a.base64,mimeType:a.mimeType,uri:a.path});return n}async function Je(e){if(e.attachments.length===0)return[];const t=z.join(e.cwd,".aca","attachments",X(e.sessionId),X(e.jobId));J.mkdirSync(t,{recursive:!0});const n=[];for(const[a,o]of e.attachments.entries()){const s=o.attachmentId?{mimeType:o.mimeType||"application/octet-stream",bytes:await ce(`/api/client/attachments/${encodeURIComponent(o.attachmentId)}`)}:Be(o.dataUrl??""),l=o.mimeType||s.mimeType||"application/octet-stream",p=o.kind==="image"||l.startsWith("image/")?"image":"file",r=Le(o.name||`attachment-${a+1}`,l),y=z.join(t,`${String(a+1).padStart(2,"0")}-${r}`);J.writeFileSync(y,s.bytes),n.push({name:o.name||r,mimeType:l,size:s.bytes.byteLength,kind:p,path:y,relativePath:z.relative(e.cwd,y),base64:s.bytes.toString("base64"),sha256:ne.createHash("sha256").update(s.bytes).digest("hex")})}return n}function Be(e){const t=e.indexOf(",");if(!e.startsWith("data:")||t<0)throw new Error("attachment dataUrl is not a data URL");const n=e.slice(5,t),a=e.slice(t+1),o=n.split(";").filter(Boolean),s=o[0]?.includes("/")?o[0]:"application/octet-stream";if(!o.includes("base64"))throw new Error("attachment dataUrl is not base64 encoded");return{mimeType:s,bytes:Buffer.from(a,"base64")}}function Le(e,t){const n=z.basename(e).replace(/[<>:"/\\|?*\x00-\x1F]/g,"_").trim(),a=`attachment${We(t)}`;return(!n||n==="."||n===".."?a:n).slice(0,180)}function We(e){switch(e.toLowerCase()){case"image/png":return".png";case"image/jpeg":return".jpg";case"image/webp":return".webp";case"image/gif":return".gif";case"text/plain":return".txt";case"application/json":return".json";case"text/markdown":case"text/x-markdown":return".md";case"application/pdf":return".pdf";case"application/msword":return".doc";case"application/vnd.openxmlformats-officedocument.wordprocessingml.document":return".docx";case"application/vnd.ms-excel":return".xls";case"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet":return".xlsx";case"application/vnd.ms-powerpoint":return".ppt";case"application/vnd.openxmlformats-officedocument.presentationml.presentation":return".pptx";case"application/zip":return".zip";case"application/x-7z-compressed":return".7z";case"application/x-rar-compressed":case"application/vnd.rar":return".rar";case"application/gzip":return".gz";case"application/x-tar":return".tar";default:return""}}function X(e){return e.replace(/[^a-zA-Z0-9._-]+/g,"-").replace(/^-+|-+$/g,"").slice(0,80)||"item"}function Z(e){return e.map(t=>({name:t.name,kind:t.kind,mimeType:t.mimeType,size:t.size,path:t.path,relativePath:t.relativePath,sha256:t.sha256}))}function De(e){if(!Number.isFinite(e)||e<=0)return"0 B";const t=["B","KB","MB","GB"];let n=e,a=0;for(;n>=1024&&a<t.length-1;)n/=1024,a+=1;return`${n>=10||a===0?n.toFixed(0):n.toFixed(1)} ${t[a]}`}async function Ve(e,t,n){!n.usedWorktree||!n.worktreeId||await I("POST",`/api/client/jobs/${encodeURIComponent(e)}/worktree`,{machineId:t,baseCommit:n.baseCommit??null,worktreePath:n.cwd,status:n.status??"active",raw:n.raw??O(n)})}function O(e){return{usedWorktree:e.usedWorktree,worktreeId:e.worktreeId??null,cwd:e.cwd,status:e.status??null,baseCommit:e.baseCommit??null,branchName:e.branchName??null,baseBranch:e.baseBranch??null}}async function qe(e,t,n,a){const s=(await I("POST",`/api/client/jobs/${encodeURIComponent(e)}/permissions`,{machineId:t,request:n})).item.permission_id;for(;!a.aborted;){await B(G);const l=await I("GET",`/api/client/permissions/${encodeURIComponent(s)}`);if(l.item.status==="resolved"&&l.item.response){const p=l.item.response.outcome;return p.outcome==="selected"?{outcome:p}:{outcome:{outcome:"cancelled"}}}}return{outcome:{outcome:"cancelled"}}}async function Ge(e,t,n,a){const s=(await I("POST",`/api/client/jobs/${encodeURIComponent(e)}/permissions`,{machineId:t,request:{kind:"elicitation",...n}})).item.permission_id;for(;!a.aborted;){await B(G);const l=await I("GET",`/api/client/permissions/${encodeURIComponent(s)}`);if(l.item.status==="resolved"&&l.item.response){const p=l.item.response.outcome;return p.outcome==="submitted"?{outcome:p}:{outcome:{outcome:"cancelled"}}}}return{outcome:{outcome:"cancelled"}}}async function Q(e,t){if(t.signal.aborted)return;const n=await I("GET",`/api/client/jobs/${encodeURIComponent(e)}`);if(Number(n.item.cancel_requested??0)===1){t.abort();return}const a=Ke(n.item);a&&t.abort(new Y(e,a))}function Ke(e){const t=String(e.status||"").toLowerCase();return t&&!["claimed","running"].includes(t)?t:null}class Y extends Error{constructor(t,n){super(`ACA Server already finalized job=${t} status=${n}`),this.name="ServerFinalizedJobError"}}function He(e){return e.aborted&&e.reason instanceof Y}async function N(e,t,n,a,o){await I("POST",`/api/client/jobs/${encodeURIComponent(e)}/fail`,{machineId:t,error:n,result:a,...o?.invalidateAcpSession?{invalidateAcpSession:!0,invalidatedAcpSessionId:o.invalidatedAcpSessionId}:{}})}function B(e){return new Promise(t=>setTimeout(t,e))}function Xe(e){return Math.min(1e4,500*2**Math.min(5,Math.max(0,e-1)))+Math.floor(Math.random()*250)}function Ze(e,t){return e.length<=t?e:`${e.slice(0,t)}...`}function L(e,t){return!e||typeof e!="object"||Array.isArray(e)?null:e[t]??null}export{Ke as serverFinalizedJobStatus,It as startJobWorkerLoop};
@@ -1 +1 @@
1
- function l(t){let r=!1,o=0;return(async()=>{for(;!r;)try{await t.run(),o=0}catch(a){if(r)break;o+=1;const e=Math.min(1e4,500*2**Math.min(5,o-1))+Math.floor(Math.random()*250);t.onError(a,e),await n(e)}})(),{stop:()=>{r=!0}}}function n(t){return new Promise(r=>setTimeout(r,t))}export{l as startLongPollWorker};
1
+ import{managementActivity as l}from"./management/activity.js";function f(o){let t=!1,e=0;return(async()=>{for(;!t;){const r=l.enter("poll");if(!r){await n(100);continue}try{await o.run(),e=0}catch(i){if(t)break;e+=1;const a=Math.min(1e4,500*2**Math.min(5,e-1))+Math.floor(Math.random()*250);o.onError(i,a),await n(a)}finally{r()}}})(),{stop:()=>{t=!0}}}function n(o){return new Promise(t=>setTimeout(t,o))}export{f as startLongPollWorker};
@@ -1,5 +1,5 @@
1
- import{activeTerminalService as Q}from"./terminal/service.js";import{execFileSync as y}from"node:child_process";import{randomUUID as T}from"node:crypto";import l from"node:fs";import F from"node:os";import u from"node:path";import{acaServerDownloadToFile as Z,acaServerRequest as E}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 I=25e3,it=I+1e4,b=300,st=3e4;let x=null;function ee(t){return et({run:async()=>{const e=q();if(!e.token)throw new Error("Worker 尚未配置认证令牌");const r=await E("GET",`/api/client/machine-operations/claim?machineId=${encodeURIComponent(e.machineId)}&waitMs=${I}`,void 0,{timeoutMs:it});if(r.item)try{const o=await ct(r.item);await E("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 E("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 ct(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 Rt();if(t.operation==="list_directory")return Ct({directoryPath:String(t.payload.path||""),includeHidden:t.payload.includeHidden===!0});if(t.operation==="register_project")return Lt(t.payload);if(t.operation==="list_project_directory")return ut(t.payload);if(t.operation==="upload_project_file")return dt(t.payload);if(t.operation==="mutate_project_files")return mt(t.payload);if(t.operation==="get_project_git_status")return G(t.payload);if(t.operation==="get_project_git_diff")return St(t.payload);if(t.operation==="get_project_git_log")return _t(t.payload);if(t.operation==="get_project_git_commit")return Pt(t.payload);if(t.operation==="get_worktree_status")return k(t.payload);if(t.operation==="commit_worktree")return at(t.payload);if(t.operation==="cleanup_worktree")return lt(t.payload);throw new Error("不支持的 Machine 文件系统操作。 ")}function k(t){const e=N(String(t.worktreePath||""));if(!l.existsSync(e))return{exists:!1,isRepository:!1,dirty:!1,changedFiles:0,ahead:0};const r=p(e);if(!w(r))return{exists:!0,isRepository:!1,dirty:jt(r),changedFiles:null,ahead:0};const o=G({rootPath:r}),n=String(t.baseCommit||"").trim(),s=n&&/^[0-9a-f]{7,64}$/i.test(n)?xt(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=p(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:m(e,["rev-parse","HEAD"],8*1024).trim(),...k(t)}}function lt(t){const e=N(String(t.worktreePath||"")),r=p(String(t.projectRoot||""));if(h(e)===h(r))throw new Error("不能清理项目主目录。");if(!l.existsSync(e))return{removed:!1,reason:"not_found"};const o=p(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 c=String(i.stderr||i);if(!/not found|not exist/i.test(c))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=p(String(t.rootPath||"")),r=S(String(t.relativePath||"")),o=v(e,r,!0),n=Math.max(0,Number(t.offset||0)),s=Math.min(300,Math.max(20,Number(t.limit||200))),i=t.sort==="modified-desc"||t.sort==="modified-asc"?t.sort:"name",c=l.readdirSync(o,{withFileTypes:!0}).filter(a=>!a.isSymbolicLink()).map(a=>({name:a.name,path:X(e,u.join(o,a.name)),type:a.isDirectory()?"directory":a.isFile()?"file":"other",hidden:a.name.startsWith("."),extension:a.isFile()?u.extname(a.name).slice(1).toLowerCase():"",modifiedAt:i==="name"?void 0:ft(u.join(o,a.name))})).filter(a=>a.type!=="other").sort((a,d)=>{if(a.type!==d.type)return a.type==="directory"?-1:1;if(i!=="name"){if(a.modifiedAt==null&&d.modifiedAt!=null)return 1;if(d.modifiedAt==null&&a.modifiedAt!=null)return-1;const P=(a.modifiedAt??0)-(d.modifiedAt??0);if(P)return i==="modified-desc"?-P:P}return a.name.localeCompare(d.name,void 0,{numeric:!0,sensitivity:"base"})}),f=c.slice(n,n+s);return{path:r,rootKind:t.rootKind==="worktree"?"worktree":"project",items:f,total:c.length,nextOffset:n+f.length<c.length?n+f.length:null}}function ft(t){try{const e=l.lstatSync(t);return e.isSymbolicLink()?null:e.mtimeMs}catch{return null}}async function dt(t){const e=String(t.uploadId||"").trim();if(!e)throw new Error("项目上传任务缺少 uploadId。");const r=p(String(t.rootPath||"")),o=S(String(t.relativeDirectory||"")),n=v(r,o,!0),s=L(String(t.fileName||"")),i=Number(t.sizeBytes),c=String(t.sha256||"").trim().toLowerCase();if(!Number.isSafeInteger(i)||i<0)throw new Error("项目上传文件大小无效。");if(!/^[0-9a-f]{64}$/.test(c))throw new Error("项目上传文件摘要无效。");const f=u.join(n,`.aca-upload-${T()}.tmp`);try{const a=await Z(`/api/client/project-uploads/${encodeURIComponent(e)}`,f,{maxBytes:i,timeoutMs:3e5});if(a.sizeBytes!==i)throw new Error("项目上传文件大小校验失败。");if(a.sha256.toLowerCase()!==c)throw new Error("项目上传文件完整性校验失败。");const d=wt(r,o,s,f);return{name:u.basename(d),path:X(r,d),directory:o,size:a.sizeBytes,sha256:a.sha256,renamed:u.basename(d)!==s,rootKind:t.rootKind==="worktree"?"worktree":"project"}}finally{l.rmSync(f,{force:!0})}}function mt(t){const e=p(String(t.rootPath||"")),r=t.action&&typeof t.action=="object"?t.action:{};if(r.type==="delete")return pt(e,String(r.path||""),r.recursive===!0,t.rootKind);throw new Error("project_file_operation_unsupported")}function pt(t,e,r,o){const n=S(e);if(!n)throw new Error("project_file_root_protected");ht(n);const s=n.split("/"),i=s.slice(0,-1).join("/"),c=v(t,i,!0),f=u.join(c,s.at(-1)||"");let a;try{a=l.lstatSync(f)}catch(d){throw d.code==="ENOENT"?new Error("project_file_not_found"):d}if(a.isSymbolicLink())throw new Error("project_file_symbolic_link");if(!a.isFile()&&!a.isDirectory())throw new Error("project_file_type_unsupported");if(a.isDirectory()&&!r&&l.readdirSync(f).length>0)throw new Error("project_directory_not_empty");return a.isDirectory()&&r?l.rmSync(f,{recursive:!0,force:!1,maxRetries:2,retryDelay:150}):a.isDirectory()?l.rmdirSync(f):l.unlinkSync(f),{operation:"delete",path:n,entryType:a.isDirectory()?"directory":"file",parentPath:i,affectedDirectories:[i],rootKind:o==="worktree"?"worktree":"project"}}function ht(t){const e=t.split("/")[0].toLocaleLowerCase("en-US");if([".git",".aca",".aica"].includes(e))throw new Error("project_file_protected")}function wt(t,e,r,o){const n=p(t),s=S(e),i=v(n,s,!0),c=L(r);if(l.realpathSync.native(u.dirname(o))!==i)throw new Error("项目上传临时文件不在目标目录中。");const a=l.lstatSync(o);if(!a.isFile()||a.isSymbolicLink())throw new Error("项目上传临时文件无效。");for(let d=1;d<=1e4;d+=1){const P=d===1?c:yt(c,d),_=u.join(i,P);try{return l.linkSync(o,_),l.chmodSync(_,420),l.rmSync(o,{force:!0}),_}catch(A){if(M(A))continue;if(!gt(A))throw A;try{return l.copyFileSync(o,_,l.constants.COPYFILE_EXCL),l.chmodSync(_,420),l.rmSync(o,{force:!0}),_}catch($){if(M($))continue;throw $}}}throw new Error("同名文件过多,无法生成可用文件名。")}function L(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 yt(t,e){const r=u.extname(t);return`${r?t.slice(0,-r.length):t} (${e})${r}`}function M(t){return!!(t&&typeof t=="object"&&t.code==="EEXIST")}function gt(t){const e=t&&typeof t=="object"?t.code:"";return["EPERM","EACCES","EXDEV","ENOTSUP","EOPNOTSUPP"].includes(String(e||""))}const O=500,j=2*1024*1024,g=2*1024*1024;function G(t){const e=p(String(t.rootPath||""));if(!w(e))return{isRepository:!1,branch:null,files:[],ahead:0,behind:0};const o=m(e,["status","--porcelain=v1","-z","--untracked-files=all","--no-renames"],j).split("\0").filter(Boolean).slice(0,O),n=o.map(c=>bt(c)).filter(c=>!!c),s=D(m(e,["diff","--no-ext-diff","--numstat","-z","--no-renames","HEAD","--","."],j));for(const c of n){const f=s.get(c.path);if(f)c.additions=f.additions,c.deletions=f.deletions;else if(c.status==="untracked"){const a=H(e,c.path);c.additions=a.additions,c.deletions=a.deletions}}const i=Dt(e);return{isRepository:!0,branch:kt(e),files:n,ahead:i.ahead,behind:i.behind,truncated:o.length>=O}}function St(t){const e=p(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=vt(e,r),s=n?m(e,["diff","--no-ext-diff","--unified=80","HEAD","--",r],g):U(e,["diff","--no-ext-diff","--no-index","--unified=80",process.platform==="win32"?"NUL":"/dev/null",o],g),i=n?D(m(e,["diff","--no-ext-diff","--numstat","-z","--no-renames","HEAD","--",r],32*1024)).get(r):H(e,r);return{isRepository:!0,path:r,diff:z(s),additions:i?.additions??0,deletions:i?.deletions??0,truncated:s.length>g}}function _t(t){const e=p(String(t.rootPath||""));if(!w(e))return{isRepository:!1,items:[]};if(!At(e))return{isRepository:!0,items:[]};const r=Math.min(200,Math.max(20,Number(t.limit||100)));return{isRepository:!0,items:m(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"],j).split("").map(i=>Et(i)).filter(i=>!!i),limit:r}}function Pt(t){const e=p(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};m(e,["cat-file","-e",`${r}^{commit}`],8*1024);const o=m(e,["show","--no-ext-diff","--no-color","--format=","--stat","--patch","--unified=40",r],g),n=[...D(m(e,["show","--no-ext-diff","--numstat","-z","--no-renames","--format=",r],j)).values()];return{isRepository:!0,commit:r,diff:z(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 Et(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 bt(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 D(t){const e=new Map;for(const r of t.split("\0")){if(!r)continue;const o=W(r);if(!o||!o.path)continue;const n=S(o.path);n&&e.set(n,{additions:o.additions,deletions:o.deletions})}return e}function H(t,e){const r=B(t,e),n=U(t,["diff","--no-ext-diff","--no-index","--numstat","-z","--",process.platform==="win32"?"NUL":"/dev/null",r],32*1024).split("\0",1)[0]||"",s=W(n);return s?{additions:s.additions,deletions:s.deletions}:{additions:null,deletions:null}}function W(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 m(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 xt(t,e){try{const r=m(t,["rev-list","--count",`${e}..HEAD`],8192).trim(),o=Number(r);return Number.isSafeInteger(o)&&o>0?o:0}catch{return 0}}function jt(t){try{return l.readdirSync(t).length>0}catch{return!1}}function vt(t,e){try{return m(t,["ls-files","--error-unmatch","--",e],8*1024),!0}catch{return!1}}function At(t){try{return m(t,["rev-parse","--verify","HEAD"],8*1024),!0}catch{return!1}}function kt(t){try{return m(t,["symbolic-ref","--quiet","--short","HEAD"],8*1024).trim()||null}catch{try{return`HEAD (${m(t,["rev-parse","--short","HEAD"],8*1024).trim()})`}catch{return null}}}function Dt(t){try{const e=m(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 m(t,e,r){return y("git",["-C",t,...e],{encoding:"utf8",timeout:2e4,maxBuffer:r,stdio:["ignore","pipe","pipe"]})}function U(t,e,r){try{return m(t,e,r)}catch(o){const n=String(o.stdout||"");if(n)return n;if(Number(o.status)===1)return"";throw o}}function z(t){return t.length<=g?t:`${t.slice(0,g)}
1
+ import{managementActivity as T}from"./management/activity.js";import{managedClaim as Z}from"./management/claims.js";import{activeTerminalService as q}from"./terminal/service.js";import{execFileSync as y}from"node:child_process";import{randomUUID as F}from"node:crypto";import l from"node:fs";import I from"node:os";import u from"node:path";import{acaServerDownloadToFile as tt,acaServerRequest as E}from"./aca-server-client.js";import{loadAcaConfig as et,upsertLocalProject as rt}from"./aca-config.js";import{startLongPollWorker as ot}from"./long-poll-worker.js";import{expandWorkerRuntimePath as nt}from"./worker-runtime-paths.js";import{syncCodexProviderRuntimeConfig as it}from"./codex-provider-runtime-config.js";import{defaultAcpRuntimePool as st}from"../acp/client/acp-runtime-pool.js";const L=25e3,at=L+1e4,b=300,ct=3e4;let x=null;function ie(t){return ot({run:async()=>{const e=et();if(!e.token)throw new Error("Worker 尚未配置认证令牌");const r=await Z("machine",()=>E("GET",`/api/client/machine-operations/claim?machineId=${encodeURIComponent(e.machineId)}&waitMs=${L}`,void 0,{timeoutMs:at}));if(!r.item)return;const o=T.track("machine");try{const n=await lt(r.item);await E("POST",`/api/client/machine-operations/${encodeURIComponent(r.item.requestId)}/complete`,{result:n})}catch(n){const s=n instanceof Error?n.message:String(n);t.warn(`machine filesystem request failed request=${r.item.requestId}: ${s}`),await E("POST",`/api/client/machine-operations/${encodeURIComponent(r.item.requestId)}/fail`,{message:s}).catch(()=>{T.uncertain.add("machine-finalize")})}finally{o()}},onError:(e,r)=>t.warn(`machine filesystem worker failed, retry in ${r}ms: ${e instanceof Error?e.message:String(e)}`)})}async function lt(t){if(t.operation==="sync_codex_provider_config"){const e=await it(),r=await st.retireProviderRuntimes("codex-acp");return{configured:!!e,revision:e?.revision??null,retiredRuntimes:r}}if(t.operation==="filesystem_roots")return Nt();if(t.operation==="list_directory")return $t({directoryPath:String(t.payload.path||""),includeHidden:t.payload.includeHidden===!0});if(t.operation==="register_project")return Ot(t.payload);if(t.operation==="list_project_directory")return dt(t.payload);if(t.operation==="upload_project_file")return pt(t.payload);if(t.operation==="mutate_project_files")return ht(t.payload);if(t.operation==="get_project_git_status")return H(t.payload);if(t.operation==="get_project_git_diff")return Pt(t.payload);if(t.operation==="get_project_git_log")return Et(t.payload);if(t.operation==="get_project_git_commit")return bt(t.payload);if(t.operation==="get_worktree_status")return k(t.payload);if(t.operation==="commit_worktree")return ut(t.payload);if(t.operation==="cleanup_worktree")return ft(t.payload);throw new Error("不支持的 Machine 文件系统操作。 ")}function k(t){const e=N(String(t.worktreePath||""));if(!l.existsSync(e))return{exists:!1,isRepository:!1,dirty:!1,changedFiles:0,ahead:0};const r=p(e);if(!w(r))return{exists:!0,isRepository:!1,dirty:At(r),changedFiles:null,ahead:0};const o=H({rootPath:r}),n=String(t.baseCommit||"").trim(),s=n&&/^[0-9a-f]{7,64}$/i.test(n)?vt(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 ut(t){const e=p(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:m(e,["rev-parse","HEAD"],8*1024).trim(),...k(t)}}function ft(t){const e=N(String(t.worktreePath||"")),r=p(String(t.projectRoot||""));if(h(e)===h(r))throw new Error("不能清理项目主目录。");if(!l.existsSync(e))return{removed:!1,reason:"not_found"};const o=p(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 dt(t){const e=p(String(t.rootPath||"")),r=S(String(t.relativePath||"")),o=v(e,r,!0),n=Math.max(0,Number(t.offset||0)),s=Math.min(300,Math.max(20,Number(t.limit||200))),i=t.sort==="modified-desc"||t.sort==="modified-asc"?t.sort:"name",a=l.readdirSync(o,{withFileTypes:!0}).filter(c=>!c.isSymbolicLink()).map(c=>({name:c.name,path:V(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():"",modifiedAt:i==="name"?void 0:mt(u.join(o,c.name))})).filter(c=>c.type!=="other").sort((c,d)=>{if(c.type!==d.type)return c.type==="directory"?-1:1;if(i!=="name"){if(c.modifiedAt==null&&d.modifiedAt!=null)return 1;if(d.modifiedAt==null&&c.modifiedAt!=null)return-1;const P=(c.modifiedAt??0)-(d.modifiedAt??0);if(P)return i==="modified-desc"?-P:P}return c.name.localeCompare(d.name,void 0,{numeric:!0,sensitivity:"base"})}),f=a.slice(n,n+s);return{path:r,rootKind:t.rootKind==="worktree"?"worktree":"project",items:f,total:a.length,nextOffset:n+f.length<a.length?n+f.length:null}}function mt(t){try{const e=l.lstatSync(t);return e.isSymbolicLink()?null:e.mtimeMs}catch{return null}}async function pt(t){const e=String(t.uploadId||"").trim();if(!e)throw new Error("项目上传任务缺少 uploadId。");const r=p(String(t.rootPath||"")),o=S(String(t.relativeDirectory||"")),n=v(r,o,!0),s=M(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 f=u.join(n,`.aca-upload-${F()}.tmp`);try{const c=await tt(`/api/client/project-uploads/${encodeURIComponent(e)}`,f,{maxBytes:i,timeoutMs:3e5});if(c.sizeBytes!==i)throw new Error("项目上传文件大小校验失败。");if(c.sha256.toLowerCase()!==a)throw new Error("项目上传文件完整性校验失败。");const d=gt(r,o,s,f);return{name:u.basename(d),path:V(r,d),directory:o,size:c.sizeBytes,sha256:c.sha256,renamed:u.basename(d)!==s,rootKind:t.rootKind==="worktree"?"worktree":"project"}}finally{l.rmSync(f,{force:!0})}}function ht(t){const e=p(String(t.rootPath||"")),r=t.action&&typeof t.action=="object"?t.action:{};if(r.type==="delete")return wt(e,String(r.path||""),r.recursive===!0,t.rootKind);throw new Error("project_file_operation_unsupported")}function wt(t,e,r,o){const n=S(e);if(!n)throw new Error("project_file_root_protected");yt(n);const s=n.split("/"),i=s.slice(0,-1).join("/"),a=v(t,i,!0),f=u.join(a,s.at(-1)||"");let c;try{c=l.lstatSync(f)}catch(d){throw d.code==="ENOENT"?new Error("project_file_not_found"):d}if(c.isSymbolicLink())throw new Error("project_file_symbolic_link");if(!c.isFile()&&!c.isDirectory())throw new Error("project_file_type_unsupported");if(c.isDirectory()&&!r&&l.readdirSync(f).length>0)throw new Error("project_directory_not_empty");return c.isDirectory()&&r?l.rmSync(f,{recursive:!0,force:!1,maxRetries:2,retryDelay:150}):c.isDirectory()?l.rmdirSync(f):l.unlinkSync(f),{operation:"delete",path:n,entryType:c.isDirectory()?"directory":"file",parentPath:i,affectedDirectories:[i],rootKind:o==="worktree"?"worktree":"project"}}function yt(t){const e=t.split("/")[0].toLocaleLowerCase("en-US");if([".git",".aca",".aica"].includes(e))throw new Error("project_file_protected")}function gt(t,e,r,o){const n=p(t),s=S(e),i=v(n,s,!0),a=M(r);if(l.realpathSync.native(u.dirname(o))!==i)throw new Error("项目上传临时文件不在目标目录中。");const c=l.lstatSync(o);if(!c.isFile()||c.isSymbolicLink())throw new Error("项目上传临时文件无效。");for(let d=1;d<=1e4;d+=1){const P=d===1?a:St(a,d),_=u.join(i,P);try{return l.linkSync(o,_),l.chmodSync(_,420),l.rmSync(o,{force:!0}),_}catch(A){if(O(A))continue;if(!_t(A))throw A;try{return l.copyFileSync(o,_,l.constants.COPYFILE_EXCL),l.chmodSync(_,420),l.rmSync(o,{force:!0}),_}catch($){if(O($))continue;throw $}}}throw new Error("同名文件过多,无法生成可用文件名。")}function M(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 St(t,e){const r=u.extname(t);return`${r?t.slice(0,-r.length):t} (${e})${r}`}function O(t){return!!(t&&typeof t=="object"&&t.code==="EEXIST")}function _t(t){const e=t&&typeof t=="object"?t.code:"";return["EPERM","EACCES","EXDEV","ENOTSUP","EOPNOTSUPP"].includes(String(e||""))}const G=500,j=2*1024*1024,g=2*1024*1024;function H(t){const e=p(String(t.rootPath||""));if(!w(e))return{isRepository:!1,branch:null,files:[],ahead:0,behind:0};const o=m(e,["status","--porcelain=v1","-z","--untracked-files=all","--no-renames"],j).split("\0").filter(Boolean).slice(0,G),n=o.map(a=>jt(a)).filter(a=>!!a),s=D(m(e,["diff","--no-ext-diff","--numstat","-z","--no-renames","HEAD","--","."],j));for(const a of n){const f=s.get(a.path);if(f)a.additions=f.additions,a.deletions=f.deletions;else if(a.status==="untracked"){const c=W(e,a.path);a.additions=c.additions,a.deletions=c.deletions}}const i=Ct(e);return{isRepository:!0,branch:Rt(e),files:n,ahead:i.ahead,behind:i.behind,truncated:o.length>=G}}function Pt(t){const e=p(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=U(e,r),n=kt(e,r),s=n?m(e,["diff","--no-ext-diff","--unified=80","HEAD","--",r],g):z(e,["diff","--no-ext-diff","--no-index","--unified=80",process.platform==="win32"?"NUL":"/dev/null",o],g),i=n?D(m(e,["diff","--no-ext-diff","--numstat","-z","--no-renames","HEAD","--",r],32*1024)).get(r):W(e,r);return{isRepository:!0,path:r,diff:K(s),additions:i?.additions??0,deletions:i?.deletions??0,truncated:s.length>g}}function Et(t){const e=p(String(t.rootPath||""));if(!w(e))return{isRepository:!1,items:[]};if(!Dt(e))return{isRepository:!0,items:[]};const r=Math.min(200,Math.max(20,Number(t.limit||100)));return{isRepository:!0,items:m(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"],j).split("").map(i=>xt(i)).filter(i=>!!i),limit:r}}function bt(t){const e=p(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};m(e,["cat-file","-e",`${r}^{commit}`],8*1024);const o=m(e,["show","--no-ext-diff","--no-color","--format=","--stat","--patch","--unified=40",r],g),n=[...D(m(e,["show","--no-ext-diff","--numstat","-z","--no-renames","--format=",r],j)).values()];return{isRepository:!0,commit:r,diff:K(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 xt(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 jt(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 D(t){const e=new Map;for(const r of t.split("\0")){if(!r)continue;const o=B(r);if(!o||!o.path)continue;const n=S(o.path);n&&e.set(n,{additions:o.additions,deletions:o.deletions})}return e}function W(t,e){const r=U(t,e),n=z(t,["diff","--no-ext-diff","--no-index","--numstat","-z","--",process.platform==="win32"?"NUL":"/dev/null",r],32*1024).split("\0",1)[0]||"",s=B(n);return s?{additions:s.additions,deletions:s.deletions}:{additions:null,deletions:null}}function B(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 m(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 vt(t,e){try{const r=m(t,["rev-list","--count",`${e}..HEAD`],8192).trim(),o=Number(r);return Number.isSafeInteger(o)&&o>0?o:0}catch{return 0}}function At(t){try{return l.readdirSync(t).length>0}catch{return!1}}function kt(t,e){try{return m(t,["ls-files","--error-unmatch","--",e],8*1024),!0}catch{return!1}}function Dt(t){try{return m(t,["rev-parse","--verify","HEAD"],8*1024),!0}catch{return!1}}function Rt(t){try{return m(t,["symbolic-ref","--quiet","--short","HEAD"],8*1024).trim()||null}catch{try{return`HEAD (${m(t,["rev-parse","--short","HEAD"],8*1024).trim()})`}catch{return null}}}function Ct(t){try{const e=m(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 U(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 m(t,e,r){return y("git",["-C",t,...e],{encoding:"utf8",timeout:2e4,maxBuffer:r,stdio:["ignore","pipe","pipe"]})}function z(t,e,r){try{return m(t,e,r)}catch(o){const n=String(o.stdout||"");if(n)return n;if(Number(o.status)===1)return"";throw o}}function K(t){return t.length<=g?t:`${t.slice(0,g)}
2
2
 
3
- [Diff 内容过大,已截断]`}function Rt(){const t=process.platform,e=R(t);return{platform:t,roots:e}}function Ct(t){if(!t.directoryPath.trim())throw new Error("目录路径不能为空。");return process.platform==="win32"?Ft(t.directoryPath,!!t.includeHidden):Tt(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 Nt(){const t=l.realpathSync(F.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 R(t){if(x?.platform===t&&x.expiresAtMs>Date.now())return x.roots;const e=t==="win32"?$t():Nt();return x={platform:t,roots:e,expiresAtMs:Date.now()+st},e}function $t(){const t=l.realpathSync.native(F.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=J(Y(r));for(const n of o){const s=String(n.Root||"");if(!s)continue;const i=Number(n.DriveType||0),c=String(n.Name||s.replace(/[\\/]+$/,"")),f=String(n.VolumeName||"").trim();e.set(h(s,"win32"),{id:`drive-${c.replace(/[^a-z0-9]+/gi,"").toLowerCase()}`,label:f?`${f} (${c})`:Wt(c,i),path:s,kind:Ht(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 Tt(t,e){const r=C(t),n=l.readdirSync(r,{withFileTypes:!0}).filter(i=>i.isDirectory()&&!i.isSymbolicLink()&&(e||!i.name.startsWith("."))),s=n.slice(0,b).map(i=>({name:i.name,path:u.join(r,i.name),kind:"directory",hidden:i.name.startsWith("."),hasChildren:!0}));return s.sort((i,c)=>i.name.localeCompare(c.name,void 0,{numeric:!0,sensitivity:"base"})),K(r,s,n.length>b,"linux")}function Ft(t,e){const r=C(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=J(Y(o,{ACA_MACHINE_FS_PATH:r})),s=n.map(i=>{const c=String(i.Attributes||"");return{name:String(i.Name||""),path:String(i.FullName||""),kind:"directory",hidden:/Hidden|System/i.test(c),hasChildren:!0,reparsePoint:/ReparsePoint/i.test(c)}}).filter(i=>i.name&&i.path&&!i.reparsePoint&&(e||!i.hidden)).slice(0,b).sort((i,c)=>i.name.localeCompare(c.name,void 0,{numeric:!0,sensitivity:"base"}));return K(r,s,n.length>b,"win32")}function K(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:It(t,o),readable:V(t,l.constants.R_OK),writable:V(t,l.constants.W_OK),items:e,truncated:r}}function It(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 c of n)i=r.join(i,c),s.push({label:c,path:i});return s}async function Lt(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=C(String(t.path||""));l.accessSync(n,l.constants.R_OK|l.constants.W_OK);const i=(await E("GET",`/api/projects?workspaceId=${encodeURIComponent(e)}`)).items.find(f=>h(f.root_path)===h(n)),c=i||(await E("POST","/api/projects",{projectId:T(),workspaceId:e,name:r,rootPath:n,source:"aica",projectType:o})).item;return tt(Mt(c)),{project:c,duplicate:!!i}}function Mt(t){return{projectId:t.project_id,workspaceId:t.workspace_id,name:t.name,rootPath:t.root_path}}function C(t){if(!t.trim()||!u.isAbsolute(t))throw new Error("必须选择绝对目录路径。");if(Ot(t),!l.statSync(t).isDirectory())throw new Error("选择的路径不是目录。");const r=l.realpathSync.native(t);return Gt(r),r}function p(t){const e=N(t),r=l.realpathSync.native(e);if(!l.statSync(r).isDirectory())throw new Error("项目根目录不存在。");return r}function N(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 v(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 X(t,e){return u.relative(t,e).split(u.sep).join("/")}function Ot(t){const e=process.platform==="win32"?u.win32:u.posix;if(!R(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 Gt(t){const e=process.platform==="win32"?u.win32:u.posix;if(!R(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 V(t,e){try{return l.accessSync(t,e),!0}catch{return!1}}function Y(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 J(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 Ht(t){return t===2?"removable":t===3?"fixed":t===4?"network":t===5?"optical":"drive"}function Wt(t,e){return e===2?`移动磁盘 (${t})`:e===4?`网络驱动器 (${t})`:e===5?`光驱 (${t})`:`本地磁盘 (${t})`}export{lt as cleanupWorktree,wt as commitProjectUploadTempFile,at as commitWorktree,h as comparableMachinePath,Pt as getProjectGitCommit,St as getProjectGitDiff,_t as getProjectGitLog,G as getProjectGitStatus,k as getWorktreeStatus,Ct as listMachineDirectory,Rt as listMachineFilesystemRoots,ut as listProjectDirectory,mt as mutateProjectFiles,ee as startMachineFilesystemWorkerLoop,dt as uploadProjectFile};
3
+ [Diff 内容过大,已截断]`}function Nt(){const t=process.platform,e=R(t);return{platform:t,roots:e}}function $t(t){if(!t.directoryPath.trim())throw new Error("目录路径不能为空。");return process.platform==="win32"?Lt(t.directoryPath,!!t.includeHidden):It(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 Tt(){const t=l.realpathSync(I.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 R(t){if(x?.platform===t&&x.expiresAtMs>Date.now())return x.roots;const e=t==="win32"?Ft():Tt();return x={platform:t,roots:e,expiresAtMs:Date.now()+ct},e}function Ft(){const t=l.realpathSync.native(I.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=Q(J(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(/[\\/]+$/,"")),f=String(n.VolumeName||"").trim();e.set(h(s,"win32"),{id:`drive-${a.replace(/[^a-z0-9]+/gi,"").toLowerCase()}`,label:f?`${f} (${a})`:Ut(a,i),path:s,kind:Bt(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 It(t,e){const r=C(t),n=l.readdirSync(r,{withFileTypes:!0}).filter(i=>i.isDirectory()&&!i.isSymbolicLink()&&(e||!i.name.startsWith("."))),s=n.slice(0,b).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"})),X(r,s,n.length>b,"linux")}function Lt(t,e){const r=C(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=Q(J(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,b).sort((i,a)=>i.name.localeCompare(a.name,void 0,{numeric:!0,sensitivity:"base"}));return X(r,s,n.length>b,"win32")}function X(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:Mt(t,o),readable:Y(t,l.constants.R_OK),writable:Y(t,l.constants.W_OK),items:e,truncated:r}}function Mt(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 Ot(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=C(String(t.path||""));l.accessSync(n,l.constants.R_OK|l.constants.W_OK);const i=(await E("GET",`/api/projects?workspaceId=${encodeURIComponent(e)}`)).items.find(f=>h(f.root_path)===h(n)),a=i||(await E("POST","/api/projects",{projectId:F(),workspaceId:e,name:r,rootPath:n,source:"aica",projectType:o})).item;return rt(Gt(a)),{project:a,duplicate:!!i}}function Gt(t){return{projectId:t.project_id,workspaceId:t.workspace_id,name:t.name,rootPath:t.root_path}}function C(t){if(!t.trim()||!u.isAbsolute(t))throw new Error("必须选择绝对目录路径。");if(Ht(t),!l.statSync(t).isDirectory())throw new Error("选择的路径不是目录。");const r=l.realpathSync.native(t);return Wt(r),r}function p(t){const e=N(t),r=l.realpathSync.native(e);if(!l.statSync(r).isDirectory())throw new Error("项目根目录不存在。");return r}function N(t){const e=nt(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 v(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 V(t,e){return u.relative(t,e).split(u.sep).join("/")}function Ht(t){const e=process.platform==="win32"?u.win32:u.posix;if(!R(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 Wt(t){const e=process.platform==="win32"?u.win32:u.posix;if(!R(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 Y(t,e){try{return l.accessSync(t,e),!0}catch{return!1}}function J(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 Q(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 Bt(t){return t===2?"removable":t===3?"fixed":t===4?"network":t===5?"optical":"drive"}function Ut(t,e){return e===2?`移动磁盘 (${t})`:e===4?`网络驱动器 (${t})`:e===5?`光驱 (${t})`:`本地磁盘 (${t})`}export{ft as cleanupWorktree,gt as commitProjectUploadTempFile,ut as commitWorktree,h as comparableMachinePath,bt as getProjectGitCommit,Pt as getProjectGitDiff,Et as getProjectGitLog,H as getProjectGitStatus,k as getWorktreeStatus,$t as listMachineDirectory,Nt as listMachineFilesystemRoots,dt as listProjectDirectory,ht as mutateProjectFiles,ie as startMachineFilesystemWorkerLoop,pt as uploadProjectFile};
@@ -0,0 +1 @@
1
+ class s{paused=!1;stopping=!1;managed=!1;uncertain=new Set;activities=new Map;enter(t){if(!(this.paused||this.stopping))return this.track(t)}track(t){const e=Symbol(t);return this.activities.set(e,t),()=>{this.activities.delete(e)}}get busy(){return[...this.activities.values()]}get hasBusiness(){return this.busy.some(t=>t!=="poll")}}const a=new s;export{s as ActivityGate,a as managementActivity};
@@ -0,0 +1 @@
1
+ import{acaServerRequest as s,AcaServerHttpError as g}from"../aca-server-client.js";import{managementActivity as n}from"./activity.js";let a;const r=new AbortController;function f(){r.abort()}let c;const m=new Map,i=new Map;async function y(){a||(c??=(async()=>{a=await s("POST","/api/client/worker-management/open",{})})().finally(()=>{c=void 0}),await c)}async function h(){await Promise.all([...i.values()].map(e=>s("POST","/api/client/worker-management/cancel",e,{timeoutMs:1500})))}async function M(e,l,d={}){if(!n.managed||!a)try{return await l()}catch(t){throw n.uncertain.add(`legacy-${e}`),t}const o={...d,...a,lane:e,seq:(m.get(e)??0)+1};for(m.set(e,o.seq),i.set(e,o);;){if(r.signal.aborted)throw new Error("Worker 正在停止");try{const t=await s("POST","/api/client/worker-management/claim",o,{timeoutMs:35e3,signal:r.signal});return i.delete(e),t}catch(t){if(r.signal.aborted)throw t;if(t instanceof g&&t.status===409)throw n.uncertain.add(`receipt-${e}`),i.delete(e),n.managed=!1,a=void 0,m.clear(),t;await new Promise(u=>setTimeout(u,1e3))}}}export{h as cancelManagedClaims,y as enableManagedClaims,M as managedClaim,f as stopManagedClaims};
@@ -0,0 +1 @@
1
+ import N from"node:http";import a from"node:fs";import f from"node:path";import{randomBytes as O}from"node:crypto";import{getAcaHome as j}from"../paths.js";import{managementActivity as e}from"./activity.js";import{enableManagedClaims as b,cancelManagedClaims as D}from"./claims.js";import{activeTerminalService as v}from"../terminal/service.js";import{readProcessStartTicks as k}from"../process-identity.js";import{AICA_VERSION as P}from"../../version.js";function E(s,y){const g=`${s}.${process.pid}.tmp`,i=a.openSync(g,"w",384);try{a.writeFileSync(i,JSON.stringify(y)),a.fsyncSync(i)}finally{a.closeSync(i)}a.renameSync(g,s);const c=a.openSync(f.dirname(s),"r");try{a.fsyncSync(c)}finally{a.closeSync(c)}}async function R(s,y,g){if(process.platform!=="linux")return;const i=f.join(j(),"management");a.mkdirSync(i,{recursive:!0,mode:448});const c=f.join(i,"worker.sock"),S=f.join(i,"worker.json");try{const r=JSON.parse(a.readFileSync(S,"utf8"));if(k(r.pid)===r.startTicks&&r.startTicks)throw new Error("已有 Worker 管理实例正在运行")}catch(r){if(r instanceof Error&&r.message==="已有 Worker 管理实例正在运行")throw r}a.rmSync(c,{force:!0});const I=O(32).toString("hex");let p=process.env.AICA_MANAGER_ID||"";const h=process.env.AICA_UPDATE_OPERATION||"";e.paused=!!h;let _=!1;const m=new Map,o=()=>({protocol:1,instanceId:s,pid:process.pid,startTicks:k(process.pid),version:P,entry:f.resolve(process.argv[1]),node:process.execPath,managerId:p,state:e.stopping?"stopping":e.paused?"paused":"running",operationId:h,busy:[...e.busy,...v?.hasActivity?["terminal"]:[]],uncertain:[...e.uncertain]}),u=N.createServer(async(r,d)=>{try{if(r.headers.authorization!==`Bearer ${I}`){d.writeHead(403).end();return}let l="";for await(const w of r)if(l+=w,l.length>16384)throw new Error("request_too_large");const t=JSON.parse(l||"{}");let n;if(t.command==="hello"||t.command==="status")n=o();else if(t.command==="manual"){if(t.instanceId!==s||e.paused||e.stopping)throw new Error("maintenance_in_progress");e.uncertain.add("manual-operation"),e.managed=!1,n={ok:!0}}else{if(t.instanceId!==s)throw new Error("worker_instance_changed");if(typeof t.managerId!="string"||!t.managerId)throw new Error("missing_manager");if(p&&p!==t.managerId)throw new Error("manager_conflict");if(t.command==="attach")await b(),p=t.managerId,e.managed=!0,n=o();else{if(!p)throw new Error("worker_not_attached");if(t.command==="detach"){if(e.paused||e.stopping)throw new Error("maintenance_in_progress");p="",e.managed=!1,n=o()}else if(t.command==="activate"){if(t.operationId!==h||!h||e.stopping)throw new Error("activation_mismatch");e.paused&&(e.paused=!1,g()),n=o()}else if(t.command==="canUpdate"){if(!t.operationId||!t.attemptId)throw new Error("missing_operation");const w=`${t.operationId}/${t.attemptId}`;if(m.has(w))n=m.get(w);else{if(!Number.isFinite(t.validUntil)||t.validUntil<Date.now()||t.validUntil>Date.now()+1e4)throw new Error("expired_update_attempt");if(_||e.paused||e.stopping||e.hasBusiness||v?.hasActivity||e.uncertain.size||!e.managed)n={allowed:!1,...o()};else{_=!0,e.paused=!0;try{const A=Date.now()+1800;for(await D();e.busy.length&&Date.now()<A&&!e.hasBusiness;)await new Promise(T=>setTimeout(T,10));e.busy.length||v?.hasActivity||e.uncertain.size||Date.now()>t.validUntil?n={allowed:!1,...o()}:(n={allowed:!0,...o(),state:"stopping",operationId:t.operationId,attemptId:t.attemptId},E(f.join(i,"shutdown.json"),n),e.stopping=!0,setTimeout(()=>{y()},50))}catch{n={allowed:!1,...o()}}finally{_=!1,e.stopping||(e.paused=!1)}}}m.size>1e3&&m.delete(m.keys().next().value),m.set(w,n)}else throw new Error("unsupported_command")}}d.setHeader("content-type","application/json"),d.end(JSON.stringify(n))}catch(l){d.writeHead(409,{"content-type":"application/json"}).end(JSON.stringify({error:l instanceof Error?l.message:"control_failed"}))}});u.requestTimeout=5e3,await new Promise((r,d)=>{u.once("error",d),u.listen(c,r)});try{a.chmodSync(c,384),E(S,{socketPath:c,secret:I,...o()})}catch(r){throw u.close(),r}return u}export{R as startManagementControl};
@@ -0,0 +1 @@
1
+ import u from"node:fs";import n from"node:path";import d from"node:http";import{getAcaHome as s}from"../paths.js";import{readProcessStartTicks as c}from"../process-identity.js";function i(r){try{return JSON.parse(u.readFileSync(r,"utf8"))}catch{return}}function p(){const r=n.join(s(),"proxy"),t=i(n.join(r,"daemon.json"));if(!t?.startTicks||c(Number(t.pid))!==t.startTicks)return;const e=i(n.join(r,"state.json"));if(e&&!["watching","preparing","waiting"].includes(String(e.phase)))throw new Error("Proxy 正在升级或恢复 Worker,请等待维护结束后再操作。")}async function g(){p();const r=i(n.join(s(),"management/worker.json"));!r?.startTicks||c(Number(r.pid))!==r.startTicks||await new Promise((t,e)=>{const o=d.request({socketPath:String(r.socketPath),path:"/",method:"POST",headers:{authorization:`Bearer ${r.secret}`}},a=>{a.resume(),a.on("end",()=>a.statusCode===200?t():e(new Error("Worker 已进入维护,请稍后执行人工操作。")))});o.on("error",e);const m=setTimeout(()=>o.destroy(new Error("Worker 管理通道未响应,请检查 Proxy 状态。")),5e3);o.on("close",()=>clearTimeout(m)),o.end(JSON.stringify({command:"manual",instanceId:r.instanceId}))})}export{p as assertManualStartAllowed,g as prepareManualWorkerAction};
@@ -1 +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;ptyLoader;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,t=D){this.emit=e,this.ptyLoader=t,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 this.ptyLoader();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};
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 z}from"./native.js";import{expandWorkerRuntimePath as D}from"../worker-runtime-paths.js";import{terminalShell as E}from"./shell.js";const y=8,T=1440*6e4;class O{emit;ptyLoader;entries=new Map;sweep=setInterval(()=>{for(const[e,t]of this.entries)!t.attachmentId&&Date.now()-t.detachedAt>T&&this.remove(e)},6e4);opening=new Map;epoch=0;disposed=!1;constructor(e,t=z){this.emit=e,this.ptyLoader=t,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}get hasActivity(){return this.opening.size>0||[...this.entries.values()].some(e=>e.exitCode===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=D(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 this.ptyLoader();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=E(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};
@@ -1 +1 @@
1
- import u from"ws";import{loadAcaConfig as w}from"../aca-config.js";import{TerminalService as S,setActiveTerminalService as h}from"./service.js";import{loadPty as T,TerminalDependencyError as g}from"./native.js";function E(m,y=T){let n,d=!1,i,f,p=Date.now();const c=new S(r=>{if(n?.readyState===u.OPEN){if(n.bufferedAmount>2e6){n.terminate();return}n.send(JSON.stringify(r))}},y);h(c);function l(){if(d)return;const r=w();if(!r.serverUrl||!r.token){i=setTimeout(l,5e3);return}try{const o=new URL("api/terminal/worker",r.serverUrl.replace(/\/?$/,"/"));o.protocol=o.protocol==="https:"?"wss:":"ws:";const e=new u(o,{headers:{authorization:`Bearer ${r.token}`},rejectUnauthorized:!["1","true","yes"].includes((process.env.ACA_SERVER_INSECURE_TLS||"").toLowerCase()),maxPayload:65536,perMessageDeflate:!1,handshakeTimeout:1e4});n=e,e.on("open",()=>{p=Date.now(),f=setInterval(()=>{Date.now()-p>45e3&&e.terminate()},15e3)}),e.on("ping",()=>{p=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}c.handle(t).catch(s=>{e.readyState===u.OPEN&&e.send(JSON.stringify({type:"error",sessionId:t.sessionId,attachmentId:t.attachmentId,message:s instanceof Error?s.message:"终端操作失败。",...s instanceof g?{code:s.code,installation:s.installation}:{}}))})}),e.on("error",a=>m.warn(`Terminal connection: ${a.message}`)),e.on("close",a=>{clearInterval(f),c.detachAll(),a===4001?m.warn("Terminal channel replaced by another Worker on this machine; restart this Worker to reconnect."):d||(i=setTimeout(l,3e3+Math.random()*2e3))})}catch(o){m.warn(`Terminal connection: ${String(o)}`),i=setTimeout(l,5e3)}}return l(),{stop(){d=!0,clearTimeout(i),clearInterval(f),n?.terminate(),c.dispose(),h(void 0)}}}export{E as startTerminalWorker};
1
+ import{managementActivity as h}from"../management/activity.js";import u from"ws";import{loadAcaConfig as w}from"../aca-config.js";import{TerminalService as I,setActiveTerminalService as y}from"./service.js";import{loadPty as S,TerminalDependencyError as T}from"./native.js";function N(m,g=S){let n,d=!1,i,f,p=Date.now();const c=new I(r=>{if(n?.readyState===u.OPEN){if(n.bufferedAmount>2e6){n.terminate();return}n.send(JSON.stringify(r))}},g);y(c);function l(){if(d)return;const r=w();if(!r.serverUrl||!r.token){i=setTimeout(l,5e3);return}try{const o=new URL("api/terminal/worker",r.serverUrl.replace(/\/?$/,"/"));o.protocol=o.protocol==="https:"?"wss:":"ws:";const e=new u(o,{headers:{authorization:`Bearer ${r.token}`},rejectUnauthorized:!["1","true","yes"].includes((process.env.ACA_SERVER_INSECURE_TLS||"").toLowerCase()),maxPayload:65536,perMessageDeflate:!1,handshakeTimeout:1e4});n=e,e.on("open",()=>{p=Date.now(),f=setInterval(()=>{Date.now()-p>45e3&&e.terminate()},15e3)}),e.on("ping",()=>{p=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}if(h.paused||h.stopping){e.send(JSON.stringify({type:"error",sessionId:t.sessionId,attachmentId:t.attachmentId,message:"Worker 正在维护,请稍后重试。"}));return}c.handle(t).catch(s=>{e.readyState===u.OPEN&&e.send(JSON.stringify({type:"error",sessionId:t.sessionId,attachmentId:t.attachmentId,message:s instanceof Error?s.message:"终端操作失败。",...s instanceof T?{code:s.code,installation:s.installation}:{}}))})}),e.on("error",a=>m.warn(`Terminal connection: ${a.message}`)),e.on("close",a=>{clearInterval(f),c.detachAll(),a===4001?m.warn("Terminal channel replaced by another Worker on this machine; restart this Worker to reconnect."):d||(i=setTimeout(l,3e3+Math.random()*2e3))})}catch(o){m.warn(`Terminal connection: ${String(o)}`),i=setTimeout(l,5e3)}}return l(),{stop(){d=!0,clearTimeout(i),clearInterval(f),n?.terminate(),c.dispose(),y(void 0)}}}export{N as startTerminalWorker};
package/dist/version.js CHANGED
@@ -1 +1 @@
1
- const o="0.1.68";export{o as AICA_VERSION};
1
+ const o="0.1.70";export{o as AICA_VERSION};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yuandc/aica",
3
- "version": "0.1.68",
3
+ "version": "0.1.70",
4
4
  "description": "AICA remote Agent worker",
5
5
  "author": "yuandc",
6
6
  "type": "module",