@yuandc/aica 0.1.84 → 0.1.86
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/acp/providers/codex-acp/adapter.js +5 -3
- package/dist/commands/start.js +1 -1
- package/dist/core/desktop-check.js +1 -0
- package/dist/core/desktop-control.js +1 -0
- package/dist/core/machine-filesystem/powershell.js +2 -0
- package/dist/core/machine-filesystem-worker.js +4 -4
- package/dist/core/terminal/service.js +1 -1
- package/dist/core/terminal/shell.js +2 -2
- package/dist/core/tool-environment.js +2 -0
- package/dist/core/windows-encoding.js +1 -0
- package/dist/version.js +1 -1
- package/dist/worker-cli.js +1 -1
- package/package.json +8 -1
|
@@ -1,7 +1,9 @@
|
|
|
1
|
-
import{UserSteeringLedger as
|
|
1
|
+
import{UserSteeringLedger as $}from"./user-steering.js";import{createToolLifecycleTrace as y}from"../../tool-lifecycle-trace.js";import{toolEnvironmentInstructions as x}from"../../../core/tool-environment.js";import{JsonLineRpcClient as j}from"../../client/json-rpc.js";import{startCodexAppServer as O}from"../codex/codex-process.js";import{CodexConnectionRecoveryTracker as B}from"../codex/connection-events.js";import{codexAcpSessionUpdatesFromNotification as k}from"../codex/events.js";import{CodexPlanDeltaState as v}from"../codex/plan-events.js";import{isCodexApprovalRequest as K,resolveCodexApprovalRequest as W}from"../codex/permissions.js";import{buildCodexContinuityInstructions as z,codexTokenUsageFromRawResponseCompleted as G,codexTokenUsageFromNotification as J,decideCodexContextMaintenance as R,readCodexContextMaintenancePolicy as A,rolloutFileSize as H}from"./context-maintenance.js";import{codexNotificationBelongsToCurrentTurn as V,codexNotificationRouting as P}from"./notification-routing.js";import{completedSubtaskHistoryUpdates as X}from"./subtask-history.js";import{CodexSubtaskRouter as Q}from"./subtask-routing.js";import{decorateSubtaskUpdate as _}from"./subtask-executions.js";import{decorateSubtaskCompletion as Y}from"./subtask-completion.js";import{waitForCancellableTurnStart as Z}from"./cancellation.js";import{codexAcpUpdateIsMeaningfulProgress as M,codexRawItemIsMeaningfulProgress as ee,withCodexProgressMetadata as U}from"./progress-evidence.js";import{acpPromptText as te,acpPromptToCodexInput as se,turnConfigFromConfig as ie}from"./turn-input.js";import{CodexControlToolLoopDetector as oe,codexControlToolLoopAcpUpdate as q,controlToolLoopDetectionThresholds as ne,controlToolLoopReady as re,readCodexControlToolLoopPolicy as ae}from"./control-tool-loop.js";import{CodexControlToolSteeringLimiter as ce,codexControlToolSteeringAcpUpdate as E}from"./control-tool-steering.js";class ue{toolTrace=y();lineBuffer="";started=null;rpc=null;codexStartupPromise=null;initialized=!1;currentSessionId=null;currentCwd=process.cwd();promptActive=!1;currentTurnId=null;userSteering=new $;turnStartPromise=null;promptCancelled=!1;nextParentRequestId=1;pendingParentRequests=new Map;completedTurns=new Map;pendingTurnCompletions=new Map;agentMessagePhases=new Map;planDeltas=new v;subtaskRouter=new Q;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 B;latestTokenUsage=null;currentRolloutPath=null;threadTurnCount=0;currentPromptText="";currentAssistantText="";previousUserText="";previousAssistantText="";previousAffectedFiles=new Set;maintenanceActive=!1;pendingCompaction=null;shutdownPromise=null;managedProvider=null;controlToolLoop;controlToolPolicy=ae();controlToolSteering=new ce(this.controlToolPolicy.steeringMaxAttempts);constructor(){this.controlToolLoop=new oe({hasActiveSubtasks:()=>this.subtaskRouter.hasActiveSubtasks(),shortSleepMaxDurationMs:this.controlToolPolicy.shortSleepMaxDurationMs}),process.stdin.setEncoding("utf8"),process.stdin.on("data",t=>this.handleInput(String(t)));const e=()=>{this.shutdown().finally(()=>process.exit(0))};process.stdin.once("end",e),process.stdin.once("close",e),process.stdin.once("error",e),process.once("SIGTERM",()=>{this.shutdown().finally(()=>process.exit(0))}),process.once("SIGINT",()=>{this.shutdown().finally(()=>process.exit(130))})}handleInput(e){this.lineBuffer+=e;const t=this.lineBuffer.split(`
|
|
2
2
|
`);this.lineBuffer=t.pop()??"";for(const s of t){const i=s.trim();if(!i)continue;let o;try{o=JSON.parse(i)}catch{continue}this.handleMessage(o)}}async handleMessage(e){if("id"in e&&("result"in e||"error"in e)&&typeof e.method!="string"){this.handleParentResponse(e);return}const t=typeof e.method=="string"?e.method:"",s=e.id;if(t){if(s===void 0){await this.handleNotification(t,a(e.params));return}try{const i=await this.handleRequest(t,a(e.params));this.write({jsonrpc:"2.0",id:s,result:i})}catch(i){this.write({jsonrpc:"2.0",id:s,error:{code:-32603,message:i instanceof Error?i.message:String(i)}})}}}async handleNotification(e,t){if(e==="session/cancel")try{await this.cancel(t)}catch(s){process.stderr.write(`Codex cancel failed: ${s instanceof Error?s.message:String(s)}
|
|
3
3
|
`),await this.shutdown(),process.exit(1)}}async handleRequest(e,t){switch(e){case"initialize":return this.initializeResponse();case"aca/runtime_config":return this.applyRuntimeConfig(t);case"session/new":return this.newSession(t);case"session/resume":case"session/load":return this.resumeSession(t);case"session/fork":return this.forkSession(t);case"session/prompt":return this.prompt(t);case"aca/supplement":return this.userSteering.send({id:String(t.deliveryId||""),threadId:String(t.sessionId||""),turnId:String(t.expectedTurnId||""),prompt:Array.isArray(t.prompt)?t.prompt:[]},{threadId:this.currentSessionId,turnId:this.currentTurnId,ready:this.promptActive&&!this.promptCancelled&&!this.maintenanceActive&&!this.shutdownPromise},s=>this.requireRpc().request("turn/steer",s,6e4),s=>{this.currentPromptText+=`
|
|
4
4
|
|
|
5
5
|
[用户补充]
|
|
6
|
-
${s}`});case"aca/supplement_status":return this.userSteering.receipt(String(t.deliveryId||""));case"session/compact":return this.manualCompact(t);case"aca/session_recover":return this.recoverSession(t);case"session/cancel":return await this.cancel(t),{};case"session/close":return await this.closeSession(t),{};case"session/set_config_option":return this.setConfigOption(t);case"session/set_mode":return this.setMode(t);case"fs/read_text_file":case"fs/write_text_file":case"terminal/create":case"terminal/output":case"terminal/wait_for_exit":case"terminal/kill":case"terminal/release":return{};default:throw new Error(`Unsupported ACP method: ${e}`)}}initializeResponse(){return{protocolVersion:1,agentCapabilities:{loadSession:!0,sessionCapabilities:{resume:!0,close:!0}}}}applyRuntimeConfig(e){if(this.started)throw new Error("Codex runtime config must be applied before app-server starts");const t=a(e.codexProvider);if(Object.keys(t).length===0)return this.managedProvider=null,{configured:!1};const s=u(t,"baseUrl"),i=u(t,"apiKey"),o=Number(t.revision||0);if(!s||!i||!Number.isSafeInteger(o)||o<1)throw new Error("Invalid Codex managed provider runtime config");return this.managedProvider={baseUrl:s,apiKey:i,revision:o},{configured:!0,revision:o}}async newSession(e){const t=u(e,"cwd")||process.cwd();this.applyInitialConfig(e),await this.ensureCodex(t);const s=await this.requireRpc().request("thread/start",{cwd:t,approvalsReviewer:"user",...this.codexConfig(),ephemeral:!1,experimentalRawEvents:!0,threadSource:"aca-codex-acp"},6e4),i=T(s);if(!i)throw new Error("Codex thread/start did not return thread id");return this.currentSessionId=i,this.currentCwd=t,this.resetSubtaskState(),this.captureThreadMetadata(s),this.sessionResponse(i)}async resumeSession(e){const t=u(e,"sessionId");if(!t)throw new Error("sessionId is required");const s=u(e,"cwd")||this.currentCwd;this.applyInitialConfig(e),await this.ensureCodex(s);const i=await this.requireRpc().request("thread/resume",{threadId:t,cwd:s,approvalsReviewer:"user",...this.codexConfig()},6e4);return this.currentSessionId=T(i)||t,this.currentCwd=s,this.resetSubtaskState(i),await this.subscribeKnownSubtasks(!1),this.captureThreadMetadata(i),this.sessionResponse(this.currentSessionId)}async forkSession(e){const t=u(e,"sessionId");if(!t)throw new Error("sessionId is required");const s=u(e,"cwd")||this.currentCwd;this.applyInitialConfig(e),await this.ensureCodex(s);const i=await this.requireRpc().request("thread/fork",{threadId:t,cwd:s,approvalsReviewer:"user",ephemeral:!1,threadSource:"aca-codex-acp-fork",...this.codexConfig()},12e4),o=T(i);if(!o)throw new Error("Codex thread/fork did not return thread id");return this.currentSessionId=o,this.currentCwd=s,this.resetSubtaskState(i),this.captureThreadMetadata(i),this.sessionResponse(o)}async prompt(e){this.promptCancelled=!1;const t=u(e,"sessionId")||this.currentSessionId;let s=this.resolveSessionAlias(t);if(!s)throw new Error("sessionId is required");const i=Array.isArray(e.prompt)?e.prompt:[],o=this.requireRpc();this.currentSessionId&&this.currentSessionId!==s&&this.resetSubtaskState(),this.currentSessionId=s;const r=await this.maintainContext("before",{userRequest:this.previousUserText,assistantResponse:this.previousAssistantText,affectedFiles:[...this.previousAffectedFiles]},{allowRollover:!0});if(he(r),this.promptCancelled||this.shutdownPromise)throw new Error("Codex prompt cancelled");s=this.currentSessionId||s,this.toolTrace=x(),this.promptActive=!0,this.currentTurnId=null,this.controlToolLoop.reset(),this.controlToolSteering.reset(),this.currentPromptText=ee(i),this.currentAssistantText="",this.currentAffectedFiles.clear(),this.connectionRecovery.reset(),this.agentMessagePhases.clear(),this.planDeltas.reset(),this.pendingSubtaskNotifications=[],this.flushSubtaskStates();let d;try{this.turnStartPromise=o.request("turn/start",{threadId:s,clientUserMessageId:`aca-acp-${Date.now()}-${Math.random().toString(16).slice(2)}`,input:te(i),cwd:this.currentCwd,...se(this.config)},D()),d=await this.turnStartPromise;const c=String(d?.turn?.id??"");this.currentTurnId=c||null,c&&this.notifySessionUpdate({sessionUpdate:"aca_supplement_target",ready:!this.completedTurns.has(c),threadId:s,turnId:c});const l=c?await this.waitForTurnCompleted(c,D()):d?.turn;this.notifySessionUpdate({sessionUpdate:"aca_supplement_target",ready:!1});const h=await this.subtaskRouter.waitForIdle(Se());if(h.timedOut&&h.activeThreadIds.length>0&&this.notifySessionUpdate({sessionUpdate:"aca_codex_subtask_drain",label:"子任务收尾等待超时",detail:`仍有 ${h.activeThreadIds.length} 个子任务未结束`,_meta:{aca:{subtaskDrain:h}}}),this.promptCancelled)throw new Error("Codex prompt cancelled");this.threadTurnCount+=1,this.previousUserText=this.currentPromptText,this.previousAssistantText=this.currentAssistantText,this.previousAffectedFiles.clear();for(const p of this.currentAffectedFiles)this.previousAffectedFiles.add(p);const f=await this.maintainContext("after",{userRequest:this.previousUserText,assistantResponse:this.previousAssistantText,affectedFiles:[...this.previousAffectedFiles]},{allowRollover:!0});return{stopReason:"end_turn",sessionId:this.currentSessionId,turn:l,_meta:{acaContextUsage:E(f)??le(this.latestTokenUsage),acaContextMaintenance:f,acaSubtaskDrain:h}}}catch(c){const l=this.connectionRecovery.fail(c);throw l&&this.notifySessionUpdate(l),c}finally{this.subtaskRouter.executions.loseObservation(this.promptCancelled?"父任务已中断,子执行状态需重新确认":"本轮观测结束,未确认子执行终态"),this.flushSubtaskStates(),this.controlToolLoop.reset(),this.notifySessionUpdate({sessionUpdate:"aca_supplement_target",ready:!1}),this.promptActive=!1,this.currentTurnId=null,this.turnStartPromise=null,this.pendingSubtaskNotifications=[]}}resetSubtaskState(e){this.subtaskGeneration+=1,this.subtaskRouter.reset(),this.subtaskAgentMessagePhases.clear(),this.subtaskPlanDeltas.clear(),this.subscribedSubtaskThreadIds.clear(),this.pendingSubtaskSubscriptions.clear(),this.hydratedSubtaskThreadIds.clear(),this.seenSubtaskItemIds.clear(),this.pendingSubtaskNotifications=[],e!==void 0&&this.subtaskRouter.restoreFromSessionSnapshot(e,this.currentSessionId)}async subscribeKnownSubtasks(e=!0){const t=this.subtaskRouter.routesForSubscription().map(s=>this.subscribeSubtask(s,e));await Promise.allSettled(t)}subscribeSubtask(e,t){const s=e.sourceThreadId;if(!s||s===this.currentSessionId||this.subscribedSubtaskThreadIds.has(s))return Promise.resolve();const i=this.pendingSubtaskSubscriptions.get(s);if(i)return i;const o=this.subtaskGeneration,r=this.requireRpc().request("thread/resume",{threadId:s,approvalsReviewer:"user",excludeTurns:!1},6e4).then(d=>{if(o!==this.subtaskGeneration)return;this.subscribedSubtaskThreadIds.add(s);const c=this.subtaskRouter.restoreFromSessionSnapshot(d,this.currentSessionId);t&&this.hydrateSubtaskHistory(d,e),c>0&&this.subscribeKnownSubtasks(t)}).catch(()=>{}).finally(()=>{this.pendingSubtaskSubscriptions.get(s)===r&&this.pendingSubtaskSubscriptions.delete(s)});return this.pendingSubtaskSubscriptions.set(s,r),r}async cancel(e){const t=this.resolveSessionAlias(u(e,"sessionId")||this.currentSessionId);if(!t||!this.rpc)return;if(t!==this.currentSessionId)throw new Error("Cannot cancel a different Codex session");this.promptCancelled=!0,this.subtaskRouter.stopWaiting();const s=this.turnStartPromise?await Y(this.turnStartPromise):null,i=this.currentTurnId||u(S(s,"turn"),"id");if(await this.rpc.request("turn/interrupt",{threadId:t,turnId:i},5e3),i){const o=this.pendingTurnCompletions.get(i);o&&(this.pendingTurnCompletions.delete(i),clearTimeout(o.timer),o.reject(new Error("Codex prompt cancelled")))}}async manualCompact(e){const t=u(e,"sessionId")||this.currentSessionId,s=this.resolveSessionAlias(t);if(!s)throw new Error("sessionId is required");if(this.promptActive||this.maintenanceActive)throw new Error("Codex session is busy");this.currentSessionId=s;const i=R(),o=await this.maintainContext("manual",{userRequest:this.previousUserText,assistantResponse:this.previousAssistantText,affectedFiles:[...this.previousAffectedFiles]},{force:!0,allowRollover:e.allowRollover===!0,compactTimeoutMs:de(e,i.compactTimeoutMs)}),r=String(o.action||"");if(r==="compact_failed"||r==="rollover_failed"){const d=String(o.compactError||"Codex 上下文压缩未完成");throw new Error(`Codex 上下文压缩失败:${d}`)}return{sessionId:this.currentSessionId,maintenance:o,contextUsage:E(o)}}async recoverSession(e){const t=u(e,"sessionId")||this.currentSessionId,s=this.resolveSessionAlias(t);if(!s)throw new Error("sessionId is required");const i=u(e,"reason");if(i!=="low_output_loop"&&i!=="control_tool_loop")throw new Error("Unsupported Codex recovery reason");if(this.promptActive||this.maintenanceActive)throw new Error("Codex session is busy");if(s!==this.currentSessionId)throw new Error("Cannot recover a different Codex session");const o=new Set([...this.previousAffectedFiles,...this.currentAffectedFiles]);return await this.rotateThread(s,{userRequest:this.currentPromptText||this.previousUserText,assistantResponse:this.currentAssistantText||this.previousAssistantText,affectedFiles:[...o]}),{...a(this.sessionResponse(this.currentSessionId)),previousSessionId:s,recoveryReason:i}}async closeSession(e){await this.shutdown()}async ensureCodex(e){if(this.rpc&&this.started&&this.started.child.exitCode===null&&this.started.child.signalCode===null)return;if(this.codexStartupPromise){await this.codexStartupPromise;return}const t=(async()=>{if(this.rpc&&this.started&&this.started.child.exitCode===null&&this.started.child.signalCode===null)return;const s=O(e,this.managedProvider),i=new $(s.child,{peerName:"Codex app-server",onNotification:(o,r)=>this.handleCodexNotification(o,r),onRequest:async(o,r)=>this.handleCodexRequest(o,r)});this.started=s,this.rpc=i,s.child.once("exit",()=>{this.started===s&&(this.subtaskRouter.executions.loseObservation("执行连接断开,未确认子代理终态"),this.flushSubtaskStates(),this.subtaskRouter.stopWaiting())}),this.initialized=!1,await i.request("initialize",{clientInfo:{name:"aca-codex-acp",version:"0.1.0"},capabilities:{experimentalApi:!0,requestAttestation:!1}},6e4),this.initialized=!0})();this.codexStartupPromise=t;try{await t}catch(s){throw this.rpc&&this.started&&!this.initialized&&await C(this.started.child,1e3),this.rpc=null,this.started=null,s}finally{this.codexStartupPromise===t&&(this.codexStartupPromise=null)}}handleCodexNotification(e,t){const s=A(t);if(e==="item/started"||e==="item/completed"){const c=a(a(t).item);this.toolTrace("provider_notification",{method:e,threadId:s.threadId,turnId:s.turnId,toolCallId:c.id,itemType:c.type,status:c.status})}if(this.maintenanceActive&&(!s.threadId||s.threadId===this.currentSessionId)){(e==="item/started"||e==="item/completed")&&this.toolTrace("notification_filtered",{method:e,toolCallId:a(a(t).item).id,reason:"context_maintenance"}),this.captureContextNotification(e,t);return}if(!H(t,this.currentSessionId,this.currentTurnId)){if(!this.promptActive||!s.threadId||s.threadId===this.currentSessionId){(e==="item/started"||e==="item/completed")&&this.toolTrace("notification_filtered",{method:e,toolCallId:a(a(t).item).id,reason:"outside_current_turn",threadId:s.threadId,turnId:s.turnId});return}const c=this.subtaskRouter.routeFor(s.threadId);if(!c){(e==="item/started"||e==="item/completed")&&this.toolTrace("notification_buffered",{method:e,toolCallId:a(a(t).item).id,threadId:s.threadId,reason:"subtask_route_pending"}),this.bufferPendingSubtaskNotification(e,t,s.threadId);return}this.handleSubtaskNotification(e,t,c),this.flushPendingSubtaskNotifications();return}const i=this.subtaskRouter.observe(t,this.currentSessionId);if(this.flushSubtaskStates(),i>0&&this.subscribeKnownSubtasks(!0),this.captureContextNotification(e,t),this.maintenanceActive)return;if(!this.promptActive&&e!=="turn/completed"){(e==="item/started"||e==="item/completed")&&this.toolTrace("notification_filtered",{method:e,toolCallId:a(a(t).item).id,reason:"prompt_inactive"});return}this.rpc?.refreshPendingRequestTimeout("turn/start"),this.captureAgentMessagePhase(e,t);const o=this.connectionRecovery.beforeNotification(e,t);e==="turn/completed"&&this.resolveTurnCompleted(t);const r=e==="rawResponseItem/completed"?fe(this.controlToolLoop,t):[],d=e==="rawResponse/completed"?pe(t):e==="rawResponseItem/completed"?r.map(({update:c})=>c):y(e,t,{agentMessagePhases:this.agentMessagePhases,planDeltas:this.planDeltas});for(const c of r)this.maybeSteerControlToolLoop(c.event);for(const c of[...o,...d]){const l=M(c,e,t,this.agentMessagePhases);_(l)&&this.notifyControlToolProgress(),this.captureUpdateEvidence(l),this.notifySessionUpdate(l)}i>0&&this.flushPendingSubtaskNotifications()}handleSubtaskNotification(e,t,s){if(!this.promptActive)return;const i=this.subtaskRouter.acceptNotification(s,e,t);if(this.flushSubtaskStates(),!i){(e==="item/started"||e==="item/completed")&&this.toolTrace("notification_filtered",{method:e,toolCallId:a(a(t).item).id,threadId:s.sourceThreadId,reason:"subtask_not_current"});return}(e==="rawResponseItem/completed"||e==="item/completed")&&Z(a(a(t).item))&&this.notifyControlToolProgress(),this.rememberSubtaskItem(s.sourceThreadId,t),this.rpc?.refreshPendingRequestTimeout("turn/start");const o=this.subtaskRouter.executions.get(s.sourceThreadId)?.runId||s.sourceThreadId,r=this.subtaskAgentMessagePhases.get(o)??new Map;this.subtaskAgentMessagePhases.set(o,r);const d=this.subtaskPlanDeltas.get(o)??new k;this.subtaskPlanDeltas.set(o,d),this.captureAgentMessagePhase(e,t,r);const c=this.subtaskRouter.observe(t,this.currentSessionId);this.flushSubtaskStates(),c>0&&this.subscribeKnownSubtasks(!0);const l=y(e,t,{agentMessagePhases:r,planDeltas:d}),h=A(t).turnId;for(const f of l){const p=M(f,e,t,r);if(!me(p))continue;u(p,"title")&&this.subtaskRouter.executions.action(s.sourceThreadId,u(p,"title")),_(p)&&(this.notifyControlToolProgress(),this.subtaskRouter.executions.progress(s.sourceThreadId,u(p,"title")||"收到执行结果"));const g=P(p,s,h,this.subtaskRouter.executions.get(s.sourceThreadId));I(g,this.currentAffectedFiles),this.notifySessionUpdate(g)}}hydrateSubtaskHistory(e,t){if(!(!this.promptActive||this.hydratedSubtaskThreadIds.has(t.sourceThreadId))){this.hydratedSubtaskThreadIds.add(t.sourceThreadId);for(const s of V(e)){const i=N(t.sourceThreadId,s.itemId);if(this.seenSubtaskItemIds.has(i))continue;this.seenSubtaskItemIds.add(i);const o=P(s.update,t,s.sourceTurnId,{runId:`history.${t.sourceThreadId}.${s.sourceTurnId}`,state:"unknown",name:this.subtaskRouter.executions.nameFor(t.sourceThreadId),outputIncomplete:!0,reason:"历史过程回放,不代表当前执行状态"}),r=a(o._meta);o._meta={...r,aca:{...a(r.aca),historyReplay:!0}},I(o,this.currentAffectedFiles),this.notifySessionUpdate(o)}}}rememberSubtaskItem(e,t){const s=a(t),i=a(s.item),o=u(i,"id")||u(s,"itemId");o&&this.seenSubtaskItemIds.add(N(e,o))}bufferPendingSubtaskNotification(e,t,s){this.pendingSubtaskNotifications.push({method:e,params:t,threadId:s}),this.pendingSubtaskNotifications.length>200&&this.pendingSubtaskNotifications.shift()}flushPendingSubtaskNotifications(){if(this.pendingSubtaskNotifications.length===0)return;let e=!0;for(;e&&this.pendingSubtaskNotifications.length>0;){e=!1;const t=[];for(const s of this.pendingSubtaskNotifications){const i=this.subtaskRouter.routeFor(s.threadId);if(!i){t.push(s);continue}e=!0,this.handleSubtaskNotification(s.method,s.params,i)}this.pendingSubtaskNotifications=t}}notifySessionUpdate(e){e=Q(e,this.subtaskRouter.executions),(e.sessionUpdate==="tool_call"||e.sessionUpdate==="tool_call_update"&&["completed","failed","cancelled"].includes(String(e.status)))&&this.toolTrace("acp_update",{toolCallId:e.toolCallId,status:e.status,threadId:this.currentSessionId,turnId:this.currentTurnId}),this.notify("session/update",{sessionId:this.currentSessionId,update:e})}flushSubtaskStates(){if(this.promptActive)for(const e of this.subtaskRouter.executions.drainUpdates())this.notifySessionUpdate(e)}async handleCodexRequest(e,t){if(B(e))return K(e,t,async s=>{const i=await this.requestParent("session/request_permission",{sessionId:this.currentSessionId,request:{...s,params:t,method:e}}),o=a(i),r=a(o.outcome);return r.outcome==="selected"&&typeof r.optionId=="string"?{outcome:{outcome:"selected",optionId:r.optionId}}:{outcome:{outcome:"cancelled"}}});if(ge(e)){const s=await this.requestParent("elicitation/create",{sessionId:this.currentSessionId,request:{...a(t),method:e}}),i=a(s),o=a(i.outcome);return o.outcome==="submitted"?{outcome:{outcome:"submitted",value:typeof o.value=="string"?o.value:""}}:{outcome:{outcome:"cancelled"}}}return{}}captureAgentMessagePhase(e,t,s=this.agentMessagePhases){if(e!=="item/started"&&e!=="item/completed")return;const i=S(t,"item");if(u(i,"type")!=="agentMessage")return;const o=u(i,"id");o&&s.set(o,u(i,"phase"))}waitForTurnCompleted(e,t){const s=this.completedTurns.get(e);return s?Promise.resolve(s):new Promise((i,o)=>{const r=setTimeout(()=>{this.pendingTurnCompletions.delete(e),o(new Error("Codex app-server request timed out while waiting for turn/completed"))},t);r.unref(),this.pendingTurnCompletions.set(e,{resolve:i,reject:o,timer:r})})}resolveTurnCompleted(e){const t=S(e,"turn"),s=u(t,"id");if(!s)return;this.completedTurns.set(s,t);const i=this.pendingTurnCompletions.get(s);if(i){if(this.pendingTurnCompletions.delete(s),clearTimeout(i.timer),u(t,"status")==="failed"){i.reject(new Error(`Codex turn failed: ${JSON.stringify(t.error??{})}`));return}i.resolve(t)}}async shutdown(){return this.shutdownPromise?this.shutdownPromise:(this.shutdownPromise=this.performShutdown(),this.shutdownPromise)}async performShutdown(){this.subtaskRouter.executions.loseObservation("执行连接关闭,未确认子代理终态"),this.flushSubtaskStates(),this.subtaskRouter.stopWaiting(),this.pendingCompaction&&(clearTimeout(this.pendingCompaction.timer),this.pendingCompaction.reject(new Error("Codex ACP adapter closed")),this.pendingCompaction=null);for(const t of this.pendingParentRequests.values())clearTimeout(t.timer),t.reject(new Error("Codex ACP adapter closed"));this.pendingParentRequests.clear();for(const t of this.pendingTurnCompletions.values())clearTimeout(t.timer),t.reject(new Error("Codex ACP adapter closed"));this.pendingTurnCompletions.clear();const e=this.started?.child;!e||e.exitCode!==null||e.signalCode!==null||(e.stdin.end(),!await C(e,2e3)&&(e.kill("SIGTERM"),!await C(e,1e3)&&(e.kill("SIGKILL"),await C(e,1e3))))}sessionResponse(e){return{sessionId:e,modes:{currentModeId:"workspace-write",availableModes:[{id:"read-only",name:"Read Only"},{id:"workspace-write",name:"Workspace Write"},{id:"danger-full-access",name:"Full Access"}]},configOptions:[{id:"model",category:"model",type:"string"},{id:"approvalPolicy",category:"approval",type:"string"},{id:"sandbox",category:"mode",type:"string"}]}}requireRpc(){if(!this.rpc)throw new Error("Codex app-server is not started");return this.rpc}notify(e,t){this.write({jsonrpc:"2.0",method:e,params:t})}requestParent(e,t,s=600*1e3){const i=this.nextParentRequestId++;return new Promise((o,r)=>{const d=setTimeout(()=>{this.pendingParentRequests.delete(i),r(new Error(`ACP client request timed out: ${e}`))},s);d.unref(),this.pendingParentRequests.set(i,{resolve:o,reject:r,timer:d}),this.write({jsonrpc:"2.0",id:i,method:e,params:t})})}handleParentResponse(e){const t=Number(e.id),s=this.pendingParentRequests.get(t);if(s){if(this.pendingParentRequests.delete(t),clearTimeout(s.timer),"error"in e){const i=a(e.error);s.reject(new Error(u(i,"message")||"ACP client request failed"));return}s.resolve(e.result)}}setConfigOption(e){const t=u(e,"configId"),s=e.value;if(!t)throw new Error("configId is required");if(typeof s!="string"&&typeof s!="boolean")throw new Error("config value must be string or boolean");return this.config[t]=s,this.notify("session/update",{sessionId:this.currentSessionId,update:{sessionUpdate:"config_option_update",configId:t,value:s}}),{}}setMode(e){const t=u(e,"modeId");if(!t)throw new Error("modeId is required");return this.config.sandbox=t,this.notify("session/update",{sessionId:this.currentSessionId,update:{sessionUpdate:"current_mode_update",modeId:t}}),{}}applyInitialConfig(e){const t=S(e,"config");for(const[s,i]of Object.entries(t))(typeof i=="string"||typeof i=="boolean")&&(this.config[s]=i)}codexConfig(){return{...typeof this.config.model=="string"&&this.config.model?{model:this.config.model}:{},approvalPolicy:typeof this.config.approvalPolicy=="string"?this.config.approvalPolicy:"on-request",sandbox:typeof this.config.sandbox=="string"?this.config.sandbox:"workspace-write"}}captureContextNotification(e,t){if(e==="thread/tokenUsage/updated"&&(this.latestTokenUsage=G(t)??this.latestTokenUsage),e==="thread/compacted"&&this.pendingCompaction&&this.resolveCompaction(),e==="item/completed"){const s=S(t,"item"),i=u(a(t),"threadId");u(s,"type")==="contextCompaction"&&(!i||i===this.currentSessionId)&&this.resolveCompaction(),u(s,"type")==="agentMessage"&&u(s,"phase")==="final_answer"&&(this.currentAssistantText=w(s)||this.currentAssistantText)}}resolveCompaction(){if(!this.pendingCompaction)return;const e=this.pendingCompaction;this.pendingCompaction=null,clearTimeout(e.timer),e.resolve()}captureUpdateEvidence(e){String(e.sessionUpdate||"")==="agent_message_chunk"&&(this.currentAssistantText+=w(e.content)),I(e,this.currentAffectedFiles)}captureThreadMetadata(e){const t=S(e,"thread");this.currentRolloutPath=u(t,"path")||null,this.threadTurnCount=Array.isArray(t.turns)?t.turns.length:0;const s=ue(t.turns);s.userRequest&&(this.previousUserText=s.userRequest),s.assistantResponse&&(this.previousAssistantText=s.assistantResponse),this.previousAffectedFiles.clear();for(const i of s.affectedFiles)this.previousAffectedFiles.add(i);this.latestTokenUsage=null}async maintainContext(e,t,s={}){const i=R(),o=this.contextState(),r=v(o,i);if(!r.compact&&!s.force||!this.currentSessionId)return this.maintenanceResult({stage:e,action:"none",reasons:r.reasons,beforeState:o});const d=this.currentSessionId;this.notifyContextStatus("正在整理会话上下文",F(o)),this.maintenanceActive=!0;let c=!1,l="",h=o,f=r,p=!1,g="";try{const m=o.usage?.observedAtMs??0;await this.compactThread(d,s.compactTimeoutMs??i.compactTimeoutMs),await this.waitForTokenUsageAfter(m,2e3)}catch(m){c=!0,l=m instanceof Error?m.message:String(m)}try{if(e!=="manual"&&this.promptCancelled||this.shutdownPromise)throw new Error("Codex prompt cancelled");if(h=this.contextState(),f=v(h,i,{compactAttempted:!0,compactFailed:c}),f.rollover&&s.allowRollover!==!1){this.notifyContextStatus("正在创建轻量会话",f.reasons.join(", "));try{await this.rotateThread(d,t),p=!0}catch(m){g=m instanceof Error?m.message:String(m),this.notifyContextStatus("轻量会话创建失败",g)}}else c||this.notifyContextStatus("会话上下文已压缩",F(h))}finally{this.maintenanceActive=!1}return this.maintenanceResult({action:p?"rollover":g?"rollover_failed":c?"compact_failed":"compact",stage:e,reasons:f.reasons,beforeState:o,afterState:h,compactError:[l,g].filter(Boolean).join("; "),previousThreadId:d,rolloverRecommended:f.rollover&&s.allowRollover===!1})}compactThread(e,t){return this.pendingCompaction?Promise.reject(new Error("Codex context compaction is already running")):new Promise((s,i)=>{const o=setTimeout(()=>{this.pendingCompaction?.timer===o&&(this.pendingCompaction=null),i(new Error("Codex thread compaction timed out"))},t);o.unref(),this.pendingCompaction={resolve:s,reject:i,timer:o},this.requireRpc().request("thread/compact/start",{threadId:e},t).catch(r=>{this.pendingCompaction?.timer===o&&(this.pendingCompaction=null,clearTimeout(o),i(r instanceof Error?r:new Error(String(r))))})})}async rotateThread(e,t){const s=W({previousThreadId:e,cwd:this.currentCwd,userRequest:t.userRequest,assistantResponse:t.assistantResponse,affectedFiles:t.affectedFiles}),i=await this.requireRpc().request("thread/start",{cwd:this.currentCwd,approvalsReviewer:"user",...this.codexConfig(),developerInstructions:s,ephemeral:!1,experimentalRawEvents:!0,threadSource:"aca-codex-acp-rollover"},6e4),o=T(i);if(!o)throw new Error("Codex rollover thread/start did not return thread id");this.sessionAliases.set(e,o),this.currentSessionId=o,this.resetSubtaskState(i),this.captureThreadMetadata(i)}async waitForTokenUsageAfter(e,t){const s=Date.now()+t;for(;Date.now()<s;){if((this.latestTokenUsage?.observedAtMs??0)>e)return;await we(50)}}resolveSessionAlias(e){if(!e)return null;let t=e;const s=new Set;for(;this.sessionAliases.has(t)&&!s.has(t);)s.add(t),t=this.sessionAliases.get(t)??t;return t}contextState(){return{usage:this.latestTokenUsage,rolloutBytes:J(this.currentRolloutPath),turnCount:this.threadTurnCount}}maintenanceResult(e){return{stage:e.stage,action:e.action,reasons:e.reasons,previousThreadId:e.previousThreadId??null,sessionId:this.currentSessionId,before:e.beforeState,after:e.afterState??null,compactError:e.compactError||null,rolloverRecommended:e.rolloverRecommended===!0}}notifyContextStatus(e,t){this.notify("session/update",{sessionId:this.currentSessionId,update:{sessionUpdate:"aca_context_maintenance",label:e,detail:t}})}notifyControlToolProgress(){const e=this.controlToolLoop.recordProgress();e&&this.notifySessionUpdate(U(e))}maybeSteerControlToolLoop(e){if(!ne(e.detection,this.controlToolPolicy))return;const t=oe(e.detection,this.controlToolPolicy),s=this.controlToolSteering.next(e,t.warningCalls);if(!s)return;const{threadId:i,turnId:o}=e.detection;!i||!o||this.requireRpc().request("turn/steer",{threadId:i,expectedTurnId:o,clientUserMessageId:`aca-loop-steer-${Date.now()}-${Math.random().toString(16).slice(2)}`,input:[{type:"text",text:s.text,text_elements:[]}]},6e4).then(()=>{this.notifySessionUpdate(q(e,s,{succeeded:!0}))}).catch(r=>{this.notifySessionUpdate(q(e,s,{succeeded:!1,error:r}))})}write(e){process.stdout.write(`${JSON.stringify(e)}
|
|
7
|
-
|
|
6
|
+
${s}`});case"aca/supplement_status":return this.userSteering.receipt(String(t.deliveryId||""));case"session/compact":return this.manualCompact(t);case"aca/session_recover":return this.recoverSession(t);case"session/cancel":return await this.cancel(t),{};case"session/close":return await this.closeSession(t),{};case"session/set_config_option":return this.setConfigOption(t);case"session/set_mode":return this.setMode(t);case"fs/read_text_file":case"fs/write_text_file":case"terminal/create":case"terminal/output":case"terminal/wait_for_exit":case"terminal/kill":case"terminal/release":return{};default:throw new Error(`Unsupported ACP method: ${e}`)}}initializeResponse(){return{protocolVersion:1,agentCapabilities:{loadSession:!0,sessionCapabilities:{resume:!0,close:!0}}}}applyRuntimeConfig(e){if(this.started)throw new Error("Codex runtime config must be applied before app-server starts");const t=a(e.codexProvider);if(Object.keys(t).length===0)return this.managedProvider=null,{configured:!1};const s=u(t,"baseUrl"),i=u(t,"apiKey"),o=Number(t.revision||0);if(!s||!i||!Number.isSafeInteger(o)||o<1)throw new Error("Invalid Codex managed provider runtime config");return this.managedProvider={baseUrl:s,apiKey:i,revision:o},{configured:!0,revision:o}}async newSession(e){const t=u(e,"cwd")||process.cwd();this.applyInitialConfig(e),await this.ensureCodex(t);const s=await this.requireRpc().request("thread/start",{cwd:t,approvalsReviewer:"user",...this.codexConfig(),ephemeral:!1,experimentalRawEvents:!0,threadSource:"aca-codex-acp"},6e4),i=T(s);if(!i)throw new Error("Codex thread/start did not return thread id");return this.currentSessionId=i,this.currentCwd=t,this.resetSubtaskState(),this.captureThreadMetadata(s),this.sessionResponse(i)}async resumeSession(e){const t=u(e,"sessionId");if(!t)throw new Error("sessionId is required");const s=u(e,"cwd")||this.currentCwd;this.applyInitialConfig(e),await this.ensureCodex(s);const i=await this.requireRpc().request("thread/resume",{threadId:t,cwd:s,approvalsReviewer:"user",...this.codexConfig()},6e4);return this.currentSessionId=T(i)||t,this.currentCwd=s,this.resetSubtaskState(i),await this.subscribeKnownSubtasks(!1),this.captureThreadMetadata(i),this.sessionResponse(this.currentSessionId)}async forkSession(e){const t=u(e,"sessionId");if(!t)throw new Error("sessionId is required");const s=u(e,"cwd")||this.currentCwd;this.applyInitialConfig(e),await this.ensureCodex(s);const i=await this.requireRpc().request("thread/fork",{threadId:t,cwd:s,approvalsReviewer:"user",ephemeral:!1,threadSource:"aca-codex-acp-fork",...this.codexConfig()},12e4),o=T(i);if(!o)throw new Error("Codex thread/fork did not return thread id");return this.currentSessionId=o,this.currentCwd=s,this.resetSubtaskState(i),this.captureThreadMetadata(i),this.sessionResponse(o)}async prompt(e){this.promptCancelled=!1;const t=u(e,"sessionId")||this.currentSessionId;let s=this.resolveSessionAlias(t);if(!s)throw new Error("sessionId is required");const i=Array.isArray(e.prompt)?e.prompt:[],o=this.requireRpc();this.currentSessionId&&this.currentSessionId!==s&&this.resetSubtaskState(),this.currentSessionId=s;const r=await this.maintainContext("before",{userRequest:this.previousUserText,assistantResponse:this.previousAssistantText,affectedFiles:[...this.previousAffectedFiles]},{allowRollover:!0});if(pe(r),this.promptCancelled||this.shutdownPromise)throw new Error("Codex prompt cancelled");s=this.currentSessionId||s,this.toolTrace=y(),this.promptActive=!0,this.currentTurnId=null,this.controlToolLoop.reset(),this.controlToolSteering.reset(),this.currentPromptText=te(i),this.currentAssistantText="",this.currentAffectedFiles.clear(),this.connectionRecovery.reset(),this.agentMessagePhases.clear(),this.planDeltas.reset(),this.pendingSubtaskNotifications=[],this.flushSubtaskStates();let d;try{this.turnStartPromise=o.request("turn/start",{threadId:s,clientUserMessageId:`aca-acp-${Date.now()}-${Math.random().toString(16).slice(2)}`,input:se(i),cwd:this.currentCwd,...ie(this.config)},L()),d=await this.turnStartPromise;const c=String(d?.turn?.id??"");this.currentTurnId=c||null,c&&this.notifySessionUpdate({sessionUpdate:"aca_supplement_target",ready:!this.completedTurns.has(c),threadId:s,turnId:c});const l=c?await this.waitForTurnCompleted(c,L()):d?.turn;this.notifySessionUpdate({sessionUpdate:"aca_supplement_target",ready:!1});const h=await this.subtaskRouter.waitForIdle(we());if(h.timedOut&&h.activeThreadIds.length>0&&this.notifySessionUpdate({sessionUpdate:"aca_codex_subtask_drain",label:"子任务收尾等待超时",detail:`仍有 ${h.activeThreadIds.length} 个子任务未结束`,_meta:{aca:{subtaskDrain:h}}}),this.promptCancelled)throw new Error("Codex prompt cancelled");this.threadTurnCount+=1,this.previousUserText=this.currentPromptText,this.previousAssistantText=this.currentAssistantText,this.previousAffectedFiles.clear();for(const p of this.currentAffectedFiles)this.previousAffectedFiles.add(p);const f=await this.maintainContext("after",{userRequest:this.previousUserText,assistantResponse:this.previousAssistantText,affectedFiles:[...this.previousAffectedFiles]},{allowRollover:!0});return{stopReason:"end_turn",sessionId:this.currentSessionId,turn:l,_meta:{acaContextUsage:F(f)??he(this.latestTokenUsage),acaContextMaintenance:f,acaSubtaskDrain:h}}}catch(c){const l=this.connectionRecovery.fail(c);throw l&&this.notifySessionUpdate(l),c}finally{this.subtaskRouter.executions.loseObservation(this.promptCancelled?"父任务已中断,子执行状态需重新确认":"本轮观测结束,未确认子执行终态"),this.flushSubtaskStates(),this.controlToolLoop.reset(),this.notifySessionUpdate({sessionUpdate:"aca_supplement_target",ready:!1}),this.promptActive=!1,this.currentTurnId=null,this.turnStartPromise=null,this.pendingSubtaskNotifications=[]}}resetSubtaskState(e){this.subtaskGeneration+=1,this.subtaskRouter.reset(),this.subtaskAgentMessagePhases.clear(),this.subtaskPlanDeltas.clear(),this.subscribedSubtaskThreadIds.clear(),this.pendingSubtaskSubscriptions.clear(),this.hydratedSubtaskThreadIds.clear(),this.seenSubtaskItemIds.clear(),this.pendingSubtaskNotifications=[],e!==void 0&&this.subtaskRouter.restoreFromSessionSnapshot(e,this.currentSessionId)}async subscribeKnownSubtasks(e=!0){const t=this.subtaskRouter.routesForSubscription().map(s=>this.subscribeSubtask(s,e));await Promise.allSettled(t)}subscribeSubtask(e,t){const s=e.sourceThreadId;if(!s||s===this.currentSessionId||this.subscribedSubtaskThreadIds.has(s))return Promise.resolve();const i=this.pendingSubtaskSubscriptions.get(s);if(i)return i;const o=this.subtaskGeneration,r=this.requireRpc().request("thread/resume",{threadId:s,approvalsReviewer:"user",excludeTurns:!1},6e4).then(d=>{if(o!==this.subtaskGeneration)return;this.subscribedSubtaskThreadIds.add(s);const c=this.subtaskRouter.restoreFromSessionSnapshot(d,this.currentSessionId);t&&this.hydrateSubtaskHistory(d,e),c>0&&this.subscribeKnownSubtasks(t)}).catch(()=>{}).finally(()=>{this.pendingSubtaskSubscriptions.get(s)===r&&this.pendingSubtaskSubscriptions.delete(s)});return this.pendingSubtaskSubscriptions.set(s,r),r}async cancel(e){const t=this.resolveSessionAlias(u(e,"sessionId")||this.currentSessionId);if(!t||!this.rpc)return;if(t!==this.currentSessionId)throw new Error("Cannot cancel a different Codex session");this.promptCancelled=!0,this.subtaskRouter.stopWaiting();const s=this.turnStartPromise?await Z(this.turnStartPromise):null,i=this.currentTurnId||u(S(s,"turn"),"id");if(await this.rpc.request("turn/interrupt",{threadId:t,turnId:i},5e3),i){const o=this.pendingTurnCompletions.get(i);o&&(this.pendingTurnCompletions.delete(i),clearTimeout(o.timer),o.reject(new Error("Codex prompt cancelled")))}}async manualCompact(e){const t=u(e,"sessionId")||this.currentSessionId,s=this.resolveSessionAlias(t);if(!s)throw new Error("sessionId is required");if(this.promptActive||this.maintenanceActive)throw new Error("Codex session is busy");this.currentSessionId=s;const i=A(),o=await this.maintainContext("manual",{userRequest:this.previousUserText,assistantResponse:this.previousAssistantText,affectedFiles:[...this.previousAffectedFiles]},{force:!0,allowRollover:e.allowRollover===!0,compactTimeoutMs:le(e,i.compactTimeoutMs)}),r=String(o.action||"");if(r==="compact_failed"||r==="rollover_failed"){const d=String(o.compactError||"Codex 上下文压缩未完成");throw new Error(`Codex 上下文压缩失败:${d}`)}return{sessionId:this.currentSessionId,maintenance:o,contextUsage:F(o)}}async recoverSession(e){const t=u(e,"sessionId")||this.currentSessionId,s=this.resolveSessionAlias(t);if(!s)throw new Error("sessionId is required");const i=u(e,"reason");if(i!=="low_output_loop"&&i!=="control_tool_loop")throw new Error("Unsupported Codex recovery reason");if(this.promptActive||this.maintenanceActive)throw new Error("Codex session is busy");if(s!==this.currentSessionId)throw new Error("Cannot recover a different Codex session");const o=new Set([...this.previousAffectedFiles,...this.currentAffectedFiles]);return await this.rotateThread(s,{userRequest:this.currentPromptText||this.previousUserText,assistantResponse:this.currentAssistantText||this.previousAssistantText,affectedFiles:[...o]}),{...a(this.sessionResponse(this.currentSessionId)),previousSessionId:s,recoveryReason:i}}async closeSession(e){await this.shutdown()}async ensureCodex(e){if(this.rpc&&this.started&&this.started.child.exitCode===null&&this.started.child.signalCode===null)return;if(this.codexStartupPromise){await this.codexStartupPromise;return}const t=(async()=>{if(this.rpc&&this.started&&this.started.child.exitCode===null&&this.started.child.signalCode===null)return;const s=O(e,this.managedProvider),i=new j(s.child,{peerName:"Codex app-server",onNotification:(o,r)=>this.handleCodexNotification(o,r),onRequest:async(o,r)=>this.handleCodexRequest(o,r)});this.started=s,this.rpc=i,s.child.once("exit",()=>{this.started===s&&(this.subtaskRouter.executions.loseObservation("执行连接断开,未确认子代理终态"),this.flushSubtaskStates(),this.subtaskRouter.stopWaiting())}),this.initialized=!1,await i.request("initialize",{clientInfo:{name:"aca-codex-acp",version:"0.1.0"},capabilities:{experimentalApi:!0,requestAttestation:!1}},6e4),this.initialized=!0})();this.codexStartupPromise=t;try{await t}catch(s){throw this.rpc&&this.started&&!this.initialized&&await C(this.started.child,1e3),this.rpc=null,this.started=null,s}finally{this.codexStartupPromise===t&&(this.codexStartupPromise=null)}}handleCodexNotification(e,t){const s=P(t);if(e==="item/started"||e==="item/completed"){const c=a(a(t).item);this.toolTrace("provider_notification",{method:e,threadId:s.threadId,turnId:s.turnId,toolCallId:c.id,itemType:c.type,status:c.status})}if(this.maintenanceActive&&(!s.threadId||s.threadId===this.currentSessionId)){(e==="item/started"||e==="item/completed")&&this.toolTrace("notification_filtered",{method:e,toolCallId:a(a(t).item).id,reason:"context_maintenance"}),this.captureContextNotification(e,t);return}if(!V(t,this.currentSessionId,this.currentTurnId)){if(!this.promptActive||!s.threadId||s.threadId===this.currentSessionId){(e==="item/started"||e==="item/completed")&&this.toolTrace("notification_filtered",{method:e,toolCallId:a(a(t).item).id,reason:"outside_current_turn",threadId:s.threadId,turnId:s.turnId});return}const c=this.subtaskRouter.routeFor(s.threadId);if(!c){(e==="item/started"||e==="item/completed")&&this.toolTrace("notification_buffered",{method:e,toolCallId:a(a(t).item).id,threadId:s.threadId,reason:"subtask_route_pending"}),this.bufferPendingSubtaskNotification(e,t,s.threadId);return}this.handleSubtaskNotification(e,t,c),this.flushPendingSubtaskNotifications();return}const i=this.subtaskRouter.observe(t,this.currentSessionId);if(this.flushSubtaskStates(),i>0&&this.subscribeKnownSubtasks(!0),this.captureContextNotification(e,t),this.maintenanceActive)return;if(!this.promptActive&&e!=="turn/completed"){(e==="item/started"||e==="item/completed")&&this.toolTrace("notification_filtered",{method:e,toolCallId:a(a(t).item).id,reason:"prompt_inactive"});return}this.rpc?.refreshPendingRequestTimeout("turn/start"),this.captureAgentMessagePhase(e,t);const o=this.connectionRecovery.beforeNotification(e,t);e==="turn/completed"&&this.resolveTurnCompleted(t);const r=e==="rawResponseItem/completed"?me(this.controlToolLoop,t):[],d=e==="rawResponse/completed"?fe(t):e==="rawResponseItem/completed"?r.map(({update:c})=>c):k(e,t,{agentMessagePhases:this.agentMessagePhases,planDeltas:this.planDeltas});for(const c of r)this.maybeSteerControlToolLoop(c.event);for(const c of[...o,...d]){const l=U(c,e,t,this.agentMessagePhases);M(l)&&this.notifyControlToolProgress(),this.captureUpdateEvidence(l),this.notifySessionUpdate(l)}i>0&&this.flushPendingSubtaskNotifications()}handleSubtaskNotification(e,t,s){if(!this.promptActive)return;const i=this.subtaskRouter.acceptNotification(s,e,t);if(this.flushSubtaskStates(),!i){(e==="item/started"||e==="item/completed")&&this.toolTrace("notification_filtered",{method:e,toolCallId:a(a(t).item).id,threadId:s.sourceThreadId,reason:"subtask_not_current"});return}(e==="rawResponseItem/completed"||e==="item/completed")&&ee(a(a(t).item))&&this.notifyControlToolProgress(),this.rememberSubtaskItem(s.sourceThreadId,t),this.rpc?.refreshPendingRequestTimeout("turn/start");const o=this.subtaskRouter.executions.get(s.sourceThreadId)?.runId||s.sourceThreadId,r=this.subtaskAgentMessagePhases.get(o)??new Map;this.subtaskAgentMessagePhases.set(o,r);const d=this.subtaskPlanDeltas.get(o)??new v;this.subtaskPlanDeltas.set(o,d),this.captureAgentMessagePhase(e,t,r);const c=this.subtaskRouter.observe(t,this.currentSessionId);this.flushSubtaskStates(),c>0&&this.subscribeKnownSubtasks(!0);const l=k(e,t,{agentMessagePhases:r,planDeltas:d}),h=P(t).turnId;for(const f of l){const p=U(f,e,t,r);if(!ge(p))continue;u(p,"title")&&this.subtaskRouter.executions.action(s.sourceThreadId,u(p,"title")),M(p)&&(this.notifyControlToolProgress(),this.subtaskRouter.executions.progress(s.sourceThreadId,u(p,"title")||"收到执行结果"));const g=_(p,s,h,this.subtaskRouter.executions.get(s.sourceThreadId));I(g,this.currentAffectedFiles),this.notifySessionUpdate(g)}}hydrateSubtaskHistory(e,t){if(!(!this.promptActive||this.hydratedSubtaskThreadIds.has(t.sourceThreadId))){this.hydratedSubtaskThreadIds.add(t.sourceThreadId);for(const s of X(e)){const i=D(t.sourceThreadId,s.itemId);if(this.seenSubtaskItemIds.has(i))continue;this.seenSubtaskItemIds.add(i);const o=_(s.update,t,s.sourceTurnId,{runId:`history.${t.sourceThreadId}.${s.sourceTurnId}`,state:"unknown",name:this.subtaskRouter.executions.nameFor(t.sourceThreadId),outputIncomplete:!0,reason:"历史过程回放,不代表当前执行状态"}),r=a(o._meta);o._meta={...r,aca:{...a(r.aca),historyReplay:!0}},I(o,this.currentAffectedFiles),this.notifySessionUpdate(o)}}}rememberSubtaskItem(e,t){const s=a(t),i=a(s.item),o=u(i,"id")||u(s,"itemId");o&&this.seenSubtaskItemIds.add(D(e,o))}bufferPendingSubtaskNotification(e,t,s){this.pendingSubtaskNotifications.push({method:e,params:t,threadId:s}),this.pendingSubtaskNotifications.length>200&&this.pendingSubtaskNotifications.shift()}flushPendingSubtaskNotifications(){if(this.pendingSubtaskNotifications.length===0)return;let e=!0;for(;e&&this.pendingSubtaskNotifications.length>0;){e=!1;const t=[];for(const s of this.pendingSubtaskNotifications){const i=this.subtaskRouter.routeFor(s.threadId);if(!i){t.push(s);continue}e=!0,this.handleSubtaskNotification(s.method,s.params,i)}this.pendingSubtaskNotifications=t}}notifySessionUpdate(e){e=Y(e,this.subtaskRouter.executions),(e.sessionUpdate==="tool_call"||e.sessionUpdate==="tool_call_update"&&["completed","failed","cancelled"].includes(String(e.status)))&&this.toolTrace("acp_update",{toolCallId:e.toolCallId,status:e.status,threadId:this.currentSessionId,turnId:this.currentTurnId}),this.notify("session/update",{sessionId:this.currentSessionId,update:e})}flushSubtaskStates(){if(this.promptActive)for(const e of this.subtaskRouter.executions.drainUpdates())this.notifySessionUpdate(e)}async handleCodexRequest(e,t){if(K(e))return W(e,t,async s=>{const i=await this.requestParent("session/request_permission",{sessionId:this.currentSessionId,request:{...s,params:t,method:e}}),o=a(i),r=a(o.outcome);return r.outcome==="selected"&&typeof r.optionId=="string"?{outcome:{outcome:"selected",optionId:r.optionId}}:{outcome:{outcome:"cancelled"}}});if(Se(e)){const s=await this.requestParent("elicitation/create",{sessionId:this.currentSessionId,request:{...a(t),method:e}}),i=a(s),o=a(i.outcome);return o.outcome==="submitted"?{outcome:{outcome:"submitted",value:typeof o.value=="string"?o.value:""}}:{outcome:{outcome:"cancelled"}}}return{}}captureAgentMessagePhase(e,t,s=this.agentMessagePhases){if(e!=="item/started"&&e!=="item/completed")return;const i=S(t,"item");if(u(i,"type")!=="agentMessage")return;const o=u(i,"id");o&&s.set(o,u(i,"phase"))}waitForTurnCompleted(e,t){const s=this.completedTurns.get(e);return s?Promise.resolve(s):new Promise((i,o)=>{const r=setTimeout(()=>{this.pendingTurnCompletions.delete(e),o(new Error("Codex app-server request timed out while waiting for turn/completed"))},t);r.unref(),this.pendingTurnCompletions.set(e,{resolve:i,reject:o,timer:r})})}resolveTurnCompleted(e){const t=S(e,"turn"),s=u(t,"id");if(!s)return;this.completedTurns.set(s,t);const i=this.pendingTurnCompletions.get(s);if(i){if(this.pendingTurnCompletions.delete(s),clearTimeout(i.timer),u(t,"status")==="failed"){i.reject(new Error(`Codex turn failed: ${JSON.stringify(t.error??{})}`));return}i.resolve(t)}}async shutdown(){return this.shutdownPromise?this.shutdownPromise:(this.shutdownPromise=this.performShutdown(),this.shutdownPromise)}async performShutdown(){this.subtaskRouter.executions.loseObservation("执行连接关闭,未确认子代理终态"),this.flushSubtaskStates(),this.subtaskRouter.stopWaiting(),this.pendingCompaction&&(clearTimeout(this.pendingCompaction.timer),this.pendingCompaction.reject(new Error("Codex ACP adapter closed")),this.pendingCompaction=null);for(const t of this.pendingParentRequests.values())clearTimeout(t.timer),t.reject(new Error("Codex ACP adapter closed"));this.pendingParentRequests.clear();for(const t of this.pendingTurnCompletions.values())clearTimeout(t.timer),t.reject(new Error("Codex ACP adapter closed"));this.pendingTurnCompletions.clear();const e=this.started?.child;!e||e.exitCode!==null||e.signalCode!==null||(e.stdin.end(),!await C(e,2e3)&&(e.kill("SIGTERM"),!await C(e,1e3)&&(e.kill("SIGKILL"),await C(e,1e3))))}sessionResponse(e){return{sessionId:e,modes:{currentModeId:"workspace-write",availableModes:[{id:"read-only",name:"Read Only"},{id:"workspace-write",name:"Workspace Write"},{id:"danger-full-access",name:"Full Access"}]},configOptions:[{id:"model",category:"model",type:"string"},{id:"approvalPolicy",category:"approval",type:"string"},{id:"sandbox",category:"mode",type:"string"}]}}requireRpc(){if(!this.rpc)throw new Error("Codex app-server is not started");return this.rpc}notify(e,t){this.write({jsonrpc:"2.0",method:e,params:t})}requestParent(e,t,s=600*1e3){const i=this.nextParentRequestId++;return new Promise((o,r)=>{const d=setTimeout(()=>{this.pendingParentRequests.delete(i),r(new Error(`ACP client request timed out: ${e}`))},s);d.unref(),this.pendingParentRequests.set(i,{resolve:o,reject:r,timer:d}),this.write({jsonrpc:"2.0",id:i,method:e,params:t})})}handleParentResponse(e){const t=Number(e.id),s=this.pendingParentRequests.get(t);if(s){if(this.pendingParentRequests.delete(t),clearTimeout(s.timer),"error"in e){const i=a(e.error);s.reject(new Error(u(i,"message")||"ACP client request failed"));return}s.resolve(e.result)}}setConfigOption(e){const t=u(e,"configId"),s=e.value;if(!t)throw new Error("configId is required");if(typeof s!="string"&&typeof s!="boolean")throw new Error("config value must be string or boolean");return this.config[t]=s,this.notify("session/update",{sessionId:this.currentSessionId,update:{sessionUpdate:"config_option_update",configId:t,value:s}}),{}}setMode(e){const t=u(e,"modeId");if(!t)throw new Error("modeId is required");return this.config.sandbox=t,this.notify("session/update",{sessionId:this.currentSessionId,update:{sessionUpdate:"current_mode_update",modeId:t}}),{}}applyInitialConfig(e){const t=S(e,"config");for(const[s,i]of Object.entries(t))(typeof i=="string"||typeof i=="boolean")&&(this.config[s]=i)}codexConfig(){return{...x()?{developerInstructions:x()}:{},...typeof this.config.model=="string"&&this.config.model?{model:this.config.model}:{},approvalPolicy:typeof this.config.approvalPolicy=="string"?this.config.approvalPolicy:"on-request",sandbox:typeof this.config.sandbox=="string"?this.config.sandbox:"workspace-write"}}captureContextNotification(e,t){if(e==="thread/tokenUsage/updated"&&(this.latestTokenUsage=J(t)??this.latestTokenUsage),e==="thread/compacted"&&this.pendingCompaction&&this.resolveCompaction(),e==="item/completed"){const s=S(t,"item"),i=u(a(t),"threadId");u(s,"type")==="contextCompaction"&&(!i||i===this.currentSessionId)&&this.resolveCompaction(),u(s,"type")==="agentMessage"&&u(s,"phase")==="final_answer"&&(this.currentAssistantText=w(s)||this.currentAssistantText)}}resolveCompaction(){if(!this.pendingCompaction)return;const e=this.pendingCompaction;this.pendingCompaction=null,clearTimeout(e.timer),e.resolve()}captureUpdateEvidence(e){String(e.sessionUpdate||"")==="agent_message_chunk"&&(this.currentAssistantText+=w(e.content)),I(e,this.currentAffectedFiles)}captureThreadMetadata(e){const t=S(e,"thread");this.currentRolloutPath=u(t,"path")||null,this.threadTurnCount=Array.isArray(t.turns)?t.turns.length:0;const s=de(t.turns);s.userRequest&&(this.previousUserText=s.userRequest),s.assistantResponse&&(this.previousAssistantText=s.assistantResponse),this.previousAffectedFiles.clear();for(const i of s.affectedFiles)this.previousAffectedFiles.add(i);this.latestTokenUsage=null}async maintainContext(e,t,s={}){const i=A(),o=this.contextState(),r=R(o,i);if(!r.compact&&!s.force||!this.currentSessionId)return this.maintenanceResult({stage:e,action:"none",reasons:r.reasons,beforeState:o});const d=this.currentSessionId;this.notifyContextStatus("正在整理会话上下文",N(o)),this.maintenanceActive=!0;let c=!1,l="",h=o,f=r,p=!1,g="";try{const m=o.usage?.observedAtMs??0;await this.compactThread(d,s.compactTimeoutMs??i.compactTimeoutMs),await this.waitForTokenUsageAfter(m,2e3)}catch(m){c=!0,l=m instanceof Error?m.message:String(m)}try{if(e!=="manual"&&this.promptCancelled||this.shutdownPromise)throw new Error("Codex prompt cancelled");if(h=this.contextState(),f=R(h,i,{compactAttempted:!0,compactFailed:c}),f.rollover&&s.allowRollover!==!1){this.notifyContextStatus("正在创建轻量会话",f.reasons.join(", "));try{await this.rotateThread(d,t),p=!0}catch(m){g=m instanceof Error?m.message:String(m),this.notifyContextStatus("轻量会话创建失败",g)}}else c||this.notifyContextStatus("会话上下文已压缩",N(h))}finally{this.maintenanceActive=!1}return this.maintenanceResult({action:p?"rollover":g?"rollover_failed":c?"compact_failed":"compact",stage:e,reasons:f.reasons,beforeState:o,afterState:h,compactError:[l,g].filter(Boolean).join("; "),previousThreadId:d,rolloverRecommended:f.rollover&&s.allowRollover===!1})}compactThread(e,t){return this.pendingCompaction?Promise.reject(new Error("Codex context compaction is already running")):new Promise((s,i)=>{const o=setTimeout(()=>{this.pendingCompaction?.timer===o&&(this.pendingCompaction=null),i(new Error("Codex thread compaction timed out"))},t);o.unref(),this.pendingCompaction={resolve:s,reject:i,timer:o},this.requireRpc().request("thread/compact/start",{threadId:e},t).catch(r=>{this.pendingCompaction?.timer===o&&(this.pendingCompaction=null,clearTimeout(o),i(r instanceof Error?r:new Error(String(r))))})})}async rotateThread(e,t){const s=z({previousThreadId:e,cwd:this.currentCwd,userRequest:t.userRequest,assistantResponse:t.assistantResponse,affectedFiles:t.affectedFiles}),i=await this.requireRpc().request("thread/start",{cwd:this.currentCwd,approvalsReviewer:"user",...this.codexConfig(),developerInstructions:[s,x()].filter(Boolean).join(`
|
|
7
|
+
|
|
8
|
+
`),ephemeral:!1,experimentalRawEvents:!0,threadSource:"aca-codex-acp-rollover"},6e4),o=T(i);if(!o)throw new Error("Codex rollover thread/start did not return thread id");this.sessionAliases.set(e,o),this.currentSessionId=o,this.resetSubtaskState(i),this.captureThreadMetadata(i)}async waitForTokenUsageAfter(e,t){const s=Date.now()+t;for(;Date.now()<s;){if((this.latestTokenUsage?.observedAtMs??0)>e)return;await Ie(50)}}resolveSessionAlias(e){if(!e)return null;let t=e;const s=new Set;for(;this.sessionAliases.has(t)&&!s.has(t);)s.add(t),t=this.sessionAliases.get(t)??t;return t}contextState(){return{usage:this.latestTokenUsage,rolloutBytes:H(this.currentRolloutPath),turnCount:this.threadTurnCount}}maintenanceResult(e){return{stage:e.stage,action:e.action,reasons:e.reasons,previousThreadId:e.previousThreadId??null,sessionId:this.currentSessionId,before:e.beforeState,after:e.afterState??null,compactError:e.compactError||null,rolloverRecommended:e.rolloverRecommended===!0}}notifyContextStatus(e,t){this.notify("session/update",{sessionId:this.currentSessionId,update:{sessionUpdate:"aca_context_maintenance",label:e,detail:t}})}notifyControlToolProgress(){const e=this.controlToolLoop.recordProgress();e&&this.notifySessionUpdate(q(e))}maybeSteerControlToolLoop(e){if(!re(e.detection,this.controlToolPolicy))return;const t=ne(e.detection,this.controlToolPolicy),s=this.controlToolSteering.next(e,t.warningCalls);if(!s)return;const{threadId:i,turnId:o}=e.detection;!i||!o||this.requireRpc().request("turn/steer",{threadId:i,expectedTurnId:o,clientUserMessageId:`aca-loop-steer-${Date.now()}-${Math.random().toString(16).slice(2)}`,input:[{type:"text",text:s.text,text_elements:[]}]},6e4).then(()=>{this.notifySessionUpdate(E(e,s,{succeeded:!0}))}).catch(r=>{this.notifySessionUpdate(E(e,s,{succeeded:!1,error:r}))})}write(e){process.stdout.write(`${JSON.stringify(e)}
|
|
9
|
+
`)}}function T(n){const e=S(n,"thread");return u(e,"id")||null}function S(n,e){const t=a(n);return a(t[e])}function a(n){return n&&typeof n=="object"&&!Array.isArray(n)?n:{}}function w(n){if(typeof n=="string")return n;if(Array.isArray(n))return n.map(w).filter(Boolean).join("");if(!n||typeof n!="object")return"";const e=a(n);for(const t of["text","message","content","output_text"]){const s=w(e[t]);if(s)return s}return""}function I(n,e){if(Array.isArray(n)){for(const o of n)I(o,e);return}const t=a(n),s=u(t,"type").toLowerCase(),i=u(t,"path");if(i&&["diff","resource_link","content"].includes(s)&&e.add(i),Array.isArray(t.locations))for(const o of t.locations){const r=u(a(o),"path");r&&e.add(r)}for(const o of[t.content,t.update,t.rawInput,t.rawOutput])o&&o!==n&&I(o,e)}function de(n){if(!Array.isArray(n)||n.length===0)return{userRequest:"",assistantResponse:"",affectedFiles:[]};const e=n.at(-1),t=new Set;I(e,t);let s="",i="";const o=r=>{if(Array.isArray(r)){for(const l of r)o(l);return}if(!r||typeof r!="object")return;const d=a(r),c=u(d,"type").toLowerCase();c==="usermessage"&&(s=w(d)||s),c==="agentmessage"&&(i=w(d)||i);for(const l of["items","content","input"])d[l]&&o(d[l])};return o(e),{userRequest:s,assistantResponse:i,affectedFiles:[...t]}}function le(n,e){const t=b(n,"timeoutMs");return!Number.isFinite(t)||t<=0?e:Math.max(3e4,Math.min(59e4,t-1e4))}function F(n){const e=String(n.action||"none"),t=a(n.after),s=a(n.before),i=a(t.usage),o=a(s.usage),r=Object.keys(i).length>0?i:o,d=b(r,"contextWindow");if(e==="rollover")return{usedTokens:0,inputTokens:0,contextWindow:d,ratio:0,observedAtMs:Date.now(),state:"new_thread",maintenanceAction:e};if(Object.keys(r).length===0)return null;const c=b(r,"totalTokens"),l=b(r,"inputTokens");return{usedTokens:c||l,inputTokens:l,contextWindow:d,ratio:d>0?(c||l)/d:0,observedAtMs:b(r,"observedAtMs")||Date.now(),state:e==="compact"?"compacted":"active",maintenanceAction:e}}function he(n){return n?{usedTokens:n.totalTokens||n.inputTokens,inputTokens:n.inputTokens,contextWindow:n.contextWindow,ratio:n.contextWindow>0?(n.totalTokens||n.inputTokens)/n.contextWindow:0,observedAtMs:n.observedAtMs,state:"active",maintenanceAction:null}:null}function N(n){const e=n.usage?`${n.usage.inputTokens} tokens`:"token unknown",t=n.rolloutBytes>0?`${Math.round(n.rolloutBytes/1024/1024)}MB rollout`:"rollout unknown";return`${e}, ${t}, ${n.turnCount} turns`}function pe(n){const e=String(n.action||"none");if(e!=="compact_failed"&&e!=="rollover_failed")return;const t=String(n.compactError||"Codex 上下文维护未完成");throw new Error(`Codex 自动上下文维护失败:${t}`)}function b(n,e){const t=a(n)[e];return typeof t=="number"&&Number.isFinite(t)?t:0}function fe(n){return G(n)?[{sessionUpdate:"aca_codex_raw_response_usage",_meta:{codex:{rawResponse:n}}}]:[]}function me(n,e){const t=n.observeRawResponseItem(e);return t?[{event:t,update:q(t)}]:[]}function ge(n){return["agent_message_chunk","agent_progress_chunk","agent_thought_chunk","tool_call","tool_call_update","plan","plan_update","plan_removed"].includes(String(n.sessionUpdate||""))}function D(n,e){return`${n}:${e}`}function u(n,e){const s=a(n)[e];return typeof s=="string"?s:""}function Se(n){const e=n.toLowerCase();return e.includes("elicitation")||e.includes("requestuserinput")||e.includes("request_user_input")||e.includes("input/request")}function L(){const n=Number.parseInt(process.env.ACA_ACP_PROMPT_TIMEOUT_MS??"",10);return Number.isInteger(n)&&n>=3e4?n:720*60*1e3}function we(){const n=Number.parseInt(process.env.ACA_CODEX_SUBTASK_DRAIN_TIMEOUT_MS??"",10);return Number.isInteger(n)&&n>=1e3?Math.min(n,600*1e3):600*1e3}function C(n,e){return n.exitCode!==null||n.signalCode!==null?Promise.resolve(!0):new Promise(t=>{const s=setTimeout(()=>t(!1),e);s.unref(),n.once("close",()=>{clearTimeout(s),t(!0)})})}function Ie(n){return new Promise(e=>setTimeout(e,n))}function be(){new ue}const Te=process.argv[1]||"";/(?:^|[\\/])adapter\.(?:js|ts)$/.test(Te)&&be();export{be as runCodexAcpAdapter};
|
package/dist/commands/start.js
CHANGED
|
@@ -1 +1 @@
|
|
|
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
|
|
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 u}from"../core/management/activity.js";import{acquireWorkerLease as F,assertNoWorkerInstances as H}from"../core/worker-instances.js";import{getAcaHome as K}from"../core/paths.js";import{Command as V}from"commander";import S from"node:fs";import D from"node:os";import m from"node:path";import t from"node:process";import{randomUUID as B}from"node:crypto";import{Logger as G}from"../core/logger.js";import{loadCredentials as J,saveApiKeyCredential as q}from"../core/auth.js";import{getRuntimeStatePath as z}from"../core/paths.js";import{writeJsonFile as Q}from"../core/fs.js";import{HEARTBEAT_INTERVAL_MS as X,startHeartbeatLoop as Y}from"../core/heartbeat.js";import{startJobWorkerLoop as Z}from"../core/job-worker.js";import{startFileTransferWorkerLoop as ee}from"../core/file-transfer-worker.js";import{startTerminalWorker as te}from"../core/terminal/worker.js";import{startMachineFilesystemWorkerLoop as re}from"../core/machine-filesystem-worker.js";import{cleanupOrphanedAcpProcesses as ae}from"../core/acp-orphan-cleanup.js";import{defaultAcpRuntimePool as _}from"../acp/client/acp-runtime-pool.js";import{isProcessAlive as h,readPidFile as d,writePidFile as oe,removePidFile as x}from"../core/process.js";import{applyRuntimePathOptions as $}from"../core/runtime-options.js";import{loadAcaConfig as W,updateAcaConfig as R}from"../core/aca-config.js";import{acaServerRequest as ne}from"../core/aca-server-client.js";import{restoreProjectsAfterLogin as ie}from"../core/project-recovery.js";import{syncCodexProviderRuntimeConfig as se}from"../core/codex-provider-runtime-config.js";import{ACA_WORKER_INSTANCE_ENV as O,ACA_WORKER_PID_ENV as ce,ACA_WORKER_START_TICKS_ENV as me,readProcessStartTicks as de}from"../core/process-identity.js";import{printStartTips as fe,spawnDaemon as le}from"./daemon.js";import{startDesktopControl as pe}from"../core/desktop-control.js";function Ze(){return new V("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 ue(e);return}await we(e)})}async function ue(e){$(e),H();const o=d();if(t.platform!=="linux"&&o&&h(o))throw new Error(`Worker 已在后台运行,PID ${o}。`);o&&x(o);const i=le(he());if(i.unref(),!i.pid)throw new Error("后台 Worker 启动失败:未获得进程 PID。");if(!await ge(i.pid,3e3)&&!h(i.pid))throw new Error(`后台 Worker 启动失败,请检查 ${m.join(t.env.AICA_HOME||t.env.ACA_HOME||m.join(D.homedir(),".aica"),"logs")}。`);console.log(`Worker 已在后台启动,PID ${i.pid}。`),fe()}function he(){const e=t.argv.findIndex((o,i)=>i>=2&&o==="start");return e<0?[]:t.argv.slice(e+1).filter(o=>o!=="--background"&&o!=="-b")}async function ge(e,o){const i=Date.now();for(;Date.now()-i<o;){if(d()===e)return!0;if(!h(e))return!1;await new Promise(a=>setTimeout(a,50))}return d()===e}async function we(e){$(e);const o=await F();try{await ve(e)}finally{o()}}async function ve(e){if(e.managedStart||L(),e.managedStart){if(t.platform!=="linux")throw new Error("当前平台尚未支持受管理启动");if(!t.env.AICA_MANAGER_ID)throw new Error("受管理启动缺少管理实例");t.env.AICA_UPDATE_OPERATION=e.managedStart}const o=t.env[O]||B(),i=de(t.pid)||"";t.env[O]=o,t.env[ce]=String(t.pid),t.env[me]=i;const a=new G("start",e.debug?"debug":"info"),g=W(),I={...e.server?.trim()?{serverUrl:e.server.trim()}:{},...e.machineId?{machineId:e.machineId.trim()}:{},...e.machineName?{machineName:e.machineName.trim()}:{}};let c=Object.keys(I).length>0?R(I):g;const f=e.machineName?.trim()||c.machineName||D.hostname();if(e.token?.trim()){const r=await ne("POST","/api/auth/machine-enroll",{token:e.token.trim(),machineId:c.machineId,machineName:f},{serverUrl:c.serverUrl});c=R({machineId:r.machineId,machineName:f,token:r.token,username:r.username}),a.info(`Machine enrollment completed for user ${r.username}`);try{const n=await ie();n.restored>0&&a.info(`restored ${n.restored} local project(s) after machine enrollment`),n.unavailable>0&&a.warn(`skipped ${n.unavailable} unavailable project path(s) during machine enrollment recovery`),c=W()}catch(n){a.warn(`project recovery after machine enrollment failed: ${n instanceof Error?n.message:String(n)}`)}}const N=e.auth?q(e.auth,f):J(),p=d();if(t.platform!=="linux"&&p&&p!==t.pid&&h(p))throw new Error(`Worker 已在运行,PID ${p}`);oe(t.pid),j({pid:t.pid,workerInstanceId:o,workerStartTicks:i,phase:"running",startupStage:"local-compatible",machineId:c.machineId||N?.machine?.machineId||"local-machine",machineName:f,cliTypes:e.cliTypes??Ae(),startedAt:new Date().toISOString(),heartbeatIntervalMs:X,issues:[{severity:"info",message:"aca is running in aca-server compatibility mode; ACP execution happens on this machine."}]}),a.info(`Starting aca local-compatible service on machine ${f}`),a.info(`PID ${t.pid}`);const w=await ae();w.terminated>0&&a.warn(`terminated ${w.terminated} orphaned ACP process(es): ${w.pids.join(",")}`),se().then(r=>a.info(r?`Codex Provider runtime config loaded revision=${r.revision}`:"Codex Provider runtime config is not configured")).catch(r=>a.warn(`Codex Provider runtime config sync failed: ${r instanceof Error?r.message:String(r)}`));let k=!1,v,y,C,P,E;const l=async r=>{if(k)return;k=!0,M(),a.info("Shutting down"),v&&clearInterval(v),C?.stop(),E?.stop(),P?.stop(),await y?.stop().catch(s=>a.warn(`job worker shutdown failed: ${s instanceof Error?s.message:String(s)}`));const n=_.stats();await _.disposeAll().catch(s=>a.warn(`ACP runtime shutdown failed: ${s instanceof Error?s.message:String(s)}`)),a.info(`ACP runtimes stopped total=${n.total} inUse=${n.inUse} idle=${n.idle}`),d()===t.pid&&(j({pid:t.pid,phase:"stopped",stoppedAt:new Date().toISOString(),issues:[]}),x(t.pid)),t.exit(r)},b=r=>{if(!u.stopping&&d()===t.pid){const n=m.join(K(),"management");S.mkdirSync(n,{recursive:!0,mode:448}),S.writeFileSync(m.join(n,"manual-stop.json"),JSON.stringify({workerInstanceId:o,at:Date.now()}),{mode:384})}l(r)};t.once("SIGINT",()=>b(130)),t.once("SIGTERM",()=>b(0)),t.once("uncaughtException",r=>{a.error(`uncaught exception: ${r.stack||r.message}`),l(1)}),t.once("unhandledRejection",r=>{const n=r instanceof Error?r.stack||r.message:String(r);a.error(`unhandled rejection: ${n}`),l(1)}),e.heartbeatLog&&setInterval(()=>a.info(`heartbeat: ${new Date().toISOString()}`),5e3);let T=!1;const A=()=>{T||(T=!0,y=Z(a),C=ee(a),E=re(a),P=te(a))};try{await U(o,()=>l(0),A)}catch(r){if(e.managedStart||r instanceof Error&&r.message.includes("已有 Worker 管理实例"))throw r;u.paused=!1,a.warn(`可选管理通道不可用: ${r instanceof Error?r.message:String(r)}`)}v=Y(a),t.env.ACA_DESKTOP_CHILD==="1"&&t.env.ACA_DESKTOP_STAGED==="1"&&(u.paused=!0),u.paused||A(),pe(()=>l(0),A),await new Promise(()=>{})}function Ae(){return["codex","claude"].filter(e=>Se(e))}function Se(e){const o=t.env.PATH||"";for(const i of o.split(m.delimiter))try{const a=t.platform==="win32"?["",".exe",".cmd",".bat"]:[""];if(i&&a.some(g=>S.existsSync(m.join(i,e+g))))return!0}catch{}return!1}function j(e){Q(z(),e)}export{Ze as createStartCommand};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{createRequire as p}from"node:module";import{execFile as d}from"node:child_process";import c from"node:path";import{AICA_VERSION as f}from"../version.js";import{resolveCodexPath as w}from"../acp/providers/codex/codex-path.js";import{hideWindowsPtyConsole as u}from"./terminal/windows-console.js";async function C(){const t=p(import.meta.url);for(const o of["@xterm/headless","ws","undici"])t(o);const a=u(t("node-pty"),t.resolve("node-pty"));await new Promise((o,r)=>{const e=a.spawn(process.execPath,["-e","process.stdout.write('ACA_PTY_READY')"],{name:"xterm-256color",cols:80,rows:24,cwd:process.cwd(),env:process.env});let s="";const l=setTimeout(()=>{e.kill(),r(new Error("PTY 自检超时"))},1e4);e.onData(i=>{s=(s+i).slice(-4096)}),e.onExit(({exitCode:i})=>{clearTimeout(l),i===0&&s.includes("ACA_PTY_READY")?o():r(new Error(`PTY 自检失败,退出码 ${i}`))})});const m=t.resolve("@openai/codex/bin/codex.js"),n=w({env:{...process.env,CODEX_PATH:"",PATH:""},bundledCodexLauncher:m});if(!n.startsWith(c.resolve(c.dirname(m),"../..")+c.sep)||/\.(?:js|cmd|bat)$/i.test(n))throw new Error("Worker 缺少当前平台的 Codex 原生程序");await new Promise((o,r)=>{d(n,["--version"],{windowsHide:!0,timeout:3e4,maxBuffer:64*1024},(e,s)=>{e?r(e):s.trim()?o():r(new Error("Codex 自检未返回版本"))})}),console.log(JSON.stringify({protocol:1,configSchema:1,version:f,platform:process.platform,arch:process.arch}))}export{C as checkDesktopWorker};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{managementActivity as e}from"./management/activity.js";import{activeTerminalService as f}from"./terminal/service.js";import{AICA_VERSION as y}from"../version.js";function h(c,l=()=>{}){if(process.env.ACA_DESKTOP_CHILD!=="1"||!process.send)return;let a=!1,o=!1,s=process.env.ACA_DESKTOP_STAGED==="1";s&&(e.paused=!0);const p=()=>[...e.busy.filter(n=>n!=="poll"),...f?.hasActivity?["terminal"]:[]],u=async()=>{if(a||e.paused&&!s||e.stopping)return{busy:["maintenance"],stopping:!1};const n=p();if(n.length)return{busy:n,stopping:!1};a=!0,e.paused=!0;const t=Date.now()+45e3;try{for(;e.busy.length;){const i=p();if(i.length||Date.now()>=t)return{busy:i.length?i:["poll"],stopping:!1};await new Promise(d=>setTimeout(d,50))}const r=p();return r.length?{busy:r,stopping:!1}:(e.stopping=!0,{busy:[],stopping:!0})}finally{a=!1,!e.stopping&&!o&&!s&&(e.paused=!1)}};process.on("message",n=>{const t=n;if(!(!t?.id||!["aca:status","aca:stop","aca:activate"].includes(t.type||""))){if(t.type==="aca:status"){process.send?.({type:"aca:reply",id:t.id,busy:p(),staged:s,stopping:e.stopping});return}if(t.type==="aca:activate"){!o&&!a&&!e.stopping&&s&&(s=!1,e.paused=!1,l()),process.send?.({type:"aca:reply",id:t.id,active:!s&&!e.stopping});return}u().then(r=>{const i=()=>{r.stopping&&c()};process.connected?process.send?.({type:"aca:reply",id:t.id,...r},i):i()})}}),process.once("disconnect",()=>{o=!0,e.paused=!0;const n=setInterval(()=>{a||e.busy.length||p().length||(clearInterval(n),e.stopping=!0,c())},250)}),process.send({type:"aca:ready",pid:process.pid,protocol:1,configSchema:1,version:y,staged:s})}export{h as startDesktopControl};
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{execFileSync as t}from"node:child_process";function c(e,o={}){const n=["[Console]::OutputEncoding = New-Object System.Text.UTF8Encoding($false)","$OutputEncoding = [Console]::OutputEncoding",e].join(`
|
|
2
|
+
`);return t("powershell.exe",["-NoLogo","-NoProfile","-NonInteractive","-Command",n],{encoding:"utf8",windowsHide:!0,timeout:8e3,maxBuffer:4*1024*1024,env:{...process.env,...o}}).replace(/^\uFEFF/,"").trim()}export{c as execPowerShell};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import{executeExternalCodexOperation as q}from"./external-codex-worker.js";import{managementActivity as T}from"./management/activity.js";import{managedClaim as tt}from"./management/claims.js";import{activeTerminalService as et}from"./terminal/service.js";import{execFileSync as y}from"node:child_process";import{randomUUID as I}from"node:crypto";import l from"node:fs";import F from"node:os";import u from"node:path";import{acaServerDownloadToFile as rt,acaServerRequest as E}from"./aca-server-client.js";import{loadAcaConfig as L,upsertLocalProject as ot}from"./aca-config.js";import{startLongPollWorker as nt}from"./long-poll-worker.js";import{expandWorkerRuntimePath as it}from"./worker-runtime-paths.js";import{syncCodexProviderRuntimeConfig as st}from"./codex-provider-runtime-config.js";import{defaultAcpRuntimePool as at}from"../acp/client/acp-runtime-pool.js";const M=25e3,ct=M+1e4,x=300,lt=3e4;let b=null;function ae(t){return nt({run:async()=>{const e=L();if(!e.token)throw new Error("Worker 尚未配置认证令牌");const r=await tt("machine",()=>E("GET",`/api/client/machine-operations/claim?machineId=${encodeURIComponent(e.machineId)}&waitMs=${M}`,void 0,{timeoutMs:ct}));if(!r.item)return;const o=T.track("machine");try{const n=await ut(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 ut(t){if(t.operation.startsWith("external_codex_"))return q(t.operation,t.payload);if(t.operation==="sync_codex_provider_config"){const e=await st(),r=await at.retireProviderRuntimes("codex-acp");return{configured:!!e,revision:e?.revision??null,retiredRuntimes:r}}if(t.operation==="filesystem_roots")return $t();if(t.operation==="list_directory")return Tt({directoryPath:String(t.payload.path||""),includeHidden:t.payload.includeHidden===!0});if(t.operation==="register_project")return Ht(t.payload);if(t.operation==="list_project_directory")return mt(t.payload);if(t.operation==="upload_project_file")return ht(t.payload);if(t.operation==="mutate_project_files")return wt(t.payload);if(t.operation==="get_project_git_status")return W(t.payload);if(t.operation==="get_project_git_diff")return Et(t.payload);if(t.operation==="get_project_git_log")return xt(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 dt(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:kt(r),changedFiles:null,ahead:0};const o=W({rootPath:r}),n=String(t.baseCommit||"").trim(),s=n&&/^[0-9a-f]{7,64}$/i.test(n)?At(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 dt(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"],{windowsHide:!0,stdio:"pipe",timeout:3e4});try{return y("git",["-C",e,"diff","--cached","--quiet"],{windowsHide:!0,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],{windowsHide:!0,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(et?.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],{windowsHide:!0,stdio:"pipe",timeout:6e4}),t.deleteBranch===!0&&n)try{y("git",["-C",r,"branch","-D",n],{windowsHide:!0,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 mt(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:Y(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:pt(u.join(o,a.name))})).filter(a=>a.type!=="other").sort((a,f)=>{if(a.type!==f.type)return a.type==="directory"?-1:1;if(i!=="name"){if(a.modifiedAt==null&&f.modifiedAt!=null)return 1;if(f.modifiedAt==null&&a.modifiedAt!=null)return-1;const P=(a.modifiedAt??0)-(f.modifiedAt??0);if(P)return i==="modified-desc"?-P:P}return a.name.localeCompare(f.name,void 0,{numeric:!0,sensitivity:"base"})}),d=c.slice(n,n+s);return{path:r,rootKind:t.rootKind==="worktree"?"worktree":"project",items:d,total:c.length,nextOffset:n+d.length<c.length?n+d.length:null}}function pt(t){try{const e=l.lstatSync(t);return e.isSymbolicLink()?null:e.mtimeMs}catch{return null}}async function ht(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=O(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 d=u.join(n,`.aca-upload-${I()}.tmp`);try{const a=await rt(`/api/client/project-uploads/${encodeURIComponent(e)}`,d,{maxBytes:i,timeoutMs:3e5});if(a.sizeBytes!==i)throw new Error("项目上传文件大小校验失败。");if(a.sha256.toLowerCase()!==c)throw new Error("项目上传文件完整性校验失败。");const f=St(r,o,s,d);return{name:u.basename(f),path:Y(r,f),directory:o,size:a.sizeBytes,sha256:a.sha256,renamed:u.basename(f)!==s,rootKind:t.rootKind==="worktree"?"worktree":"project"}}finally{l.rmSync(d,{force:!0})}}function wt(t){const e=p(String(t.rootPath||"")),r=t.action&&typeof t.action=="object"?t.action:{};if(r.type==="delete")return yt(e,String(r.path||""),r.recursive===!0,t.rootKind);throw new Error("project_file_operation_unsupported")}function yt(t,e,r,o){const n=S(e);if(!n)throw new Error("project_file_root_protected");gt(n);const s=n.split("/"),i=s.slice(0,-1).join("/"),c=v(t,i,!0),d=u.join(c,s.at(-1)||"");let a;try{a=l.lstatSync(d)}catch(f){throw f.code==="ENOENT"?new Error("project_file_not_found"):f}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(d).length>0)throw new Error("project_directory_not_empty");return a.isDirectory()&&r?l.rmSync(d,{recursive:!0,force:!1,maxRetries:2,retryDelay:150}):a.isDirectory()?l.rmdirSync(d):l.unlinkSync(d),{operation:"delete",path:n,entryType:a.isDirectory()?"directory":"file",parentPath:i,affectedDirectories:[i],rootKind:o==="worktree"?"worktree":"project"}}function gt(t){const e=t.split("/")[0].toLocaleLowerCase("en-US");if([".git",".aca",".aica"].includes(e))throw new Error("project_file_protected")}function St(t,e,r,o){const n=p(t),s=S(e),i=v(n,s,!0),c=O(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 f=1;f<=1e4;f+=1){const P=f===1?c:_t(c,f),_=u.join(i,P);try{return l.linkSync(o,_),l.chmodSync(_,420),l.rmSync(o,{force:!0}),_}catch(A){if(H(A))continue;if(!Pt(A))throw A;try{return l.copyFileSync(o,_,l.constants.COPYFILE_EXCL),l.chmodSync(_,420),l.rmSync(o,{force:!0}),_}catch($){if(H($))continue;throw $}}}throw new Error("同名文件过多,无法生成可用文件名。")}function O(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 _t(t,e){const r=u.extname(t);return`${r?t.slice(0,-r.length):t} (${e})${r}`}function H(t){return!!(t&&typeof t=="object"&&t.code==="EEXIST")}function Pt(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 W(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(c=>vt(c)).filter(c=>!!c),s=D(m(e,["diff","--no-ext-diff","--numstat","-z","--no-renames","HEAD","--","."],j));for(const c of n){const d=s.get(c.path);if(d)c.additions=d.additions,c.deletions=d.deletions;else if(c.status==="untracked"){const a=B(e,c.path);c.additions=a.additions,c.deletions=a.deletions}}const i=Nt(e);return{isRepository:!0,branch:Rt(e),files:n,ahead:i.ahead,behind:i.behind,truncated:o.length>=G}}function Et(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=z(e,r),n=Dt(e,r),s=n?m(e,["diff","--no-ext-diff","--unified=80","HEAD","--",r],g):K(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):B(e,r);return{isRepository:!0,path:r,diff:X(s),additions:i?.additions??0,deletions:i?.deletions??0,truncated:s.length>g}}function xt(t){const e=p(String(t.rootPath||""));if(!w(e))return{isRepository:!1,items:[]};if(!Ct(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=>jt(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:X(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 jt(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 vt(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=U(r);if(!o||!o.path)continue;const n=S(o.path);n&&e.set(n,{additions:o.additions,deletions:o.deletions})}return e}function B(t,e){const r=z(t,e),n=K(t,["diff","--no-ext-diff","--no-index","--numstat","-z","--",process.platform==="win32"?"NUL":"/dev/null",r],32*1024).split("\0",1)[0]||"",s=U(n);return s?{additions:s.additions,deletions:s.deletions}:{additions:null,deletions:null}}function U(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 At(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 kt(t){try{return l.readdirSync(t).length>0}catch{return!1}}function Dt(t,e){try{return m(t,["ls-files","--error-unmatch","--",e],8*1024),!0}catch{return!1}}function Ct(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 Nt(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 z(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],{windowsHide:!0,encoding:"utf8",timeout:2e4,maxBuffer:r,stdio:["ignore","pipe","pipe"]})}function K(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 X(t){return t.length<=g?t:`${t.slice(0,g)}
|
|
1
|
+
import{executeExternalCodexOperation as q}from"./external-codex-worker.js";import{managementActivity as T}from"./management/activity.js";import{managedClaim as tt}from"./management/claims.js";import{activeTerminalService as et}from"./terminal/service.js";import{execFileSync as _}from"node:child_process";import{randomUUID as I}from"node:crypto";import l from"node:fs";import F from"node:os";import u from"node:path";import{acaServerDownloadToFile as rt,acaServerRequest as E}from"./aca-server-client.js";import{loadAcaConfig as L,upsertLocalProject as ot}from"./aca-config.js";import{startLongPollWorker as it}from"./long-poll-worker.js";import{expandWorkerRuntimePath as nt}from"./worker-runtime-paths.js";import{syncCodexProviderRuntimeConfig as st}from"./codex-provider-runtime-config.js";import{defaultAcpRuntimePool as at}from"../acp/client/acp-runtime-pool.js";import{execPowerShell as M}from"./machine-filesystem/powershell.js";const O=25e3,ct=O+1e4,x=300,lt=3e4;let b=null;function ce(t){return it({run:async()=>{const e=L();if(!e.token)throw new Error("Worker 尚未配置认证令牌");const r=await tt("machine",()=>E("GET",`/api/client/machine-operations/claim?machineId=${encodeURIComponent(e.machineId)}&waitMs=${O}`,void 0,{timeoutMs:ct}));if(!r.item)return;const o=T.track("machine");try{const i=await ut(r.item);await E("POST",`/api/client/machine-operations/${encodeURIComponent(r.item.requestId)}/complete`,{result:i})}catch(i){const s=i instanceof Error?i.message:String(i);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 ut(t){if(t.operation.startsWith("external_codex_"))return q(t.operation,t.payload);if(t.operation==="sync_codex_provider_config"){const e=await st(),r=await at.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 Tt({directoryPath:String(t.payload.path||""),includeHidden:t.payload.includeHidden===!0});if(t.operation==="register_project")return Ht(t.payload);if(t.operation==="list_project_directory")return mt(t.payload);if(t.operation==="upload_project_file")return ht(t.payload);if(t.operation==="mutate_project_files")return wt(t.payload);if(t.operation==="get_project_git_status")return B(t.payload);if(t.operation==="get_project_git_diff")return Et(t.payload);if(t.operation==="get_project_git_log")return xt(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 dt(t.payload);if(t.operation==="cleanup_worktree")return ft(t.payload);throw new Error("不支持的 Machine 文件系统操作。 ")}function k(t){const e=$(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:kt(r),changedFiles:null,ahead:0};const o=B({rootPath:r}),i=String(t.baseCommit||"").trim(),s=i&&/^[0-9a-f]{7,64}$/i.test(i)?At(r,i):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 dt(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 仓库,无法提交。");_("git",["-C",e,"add","-A"],{windowsHide:!0,stdio:"pipe",timeout:3e4});try{return _("git",["-C",e,"diff","--cached","--quiet"],{windowsHide:!0,stdio:"pipe",timeout:1e4}),{committed:!1,reason:"no_changes",...k(t)}}catch(o){if(Number(o.status)!==1)throw o}return _("git",["-C",e,"commit","-m",r],{windowsHide:!0,stdio:"pipe",timeout:6e4}),{committed:!0,commit:m(e,["rev-parse","HEAD"],8*1024).trim(),...k(t)}}function ft(t){const e=$(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(et?.hasRunningInDirectory(o,String(t.sessionId||"")))throw new Error("请先结束此目录中的会话终端,再清理 Worktree。");const i=String(t.branchName||"").trim(),s=w(o)&&w(r);if(s){if(_("git",["-C",r,"worktree","remove","--force",o],{windowsHide:!0,stdio:"pipe",timeout:6e4}),t.deleteBranch===!0&&i)try{_("git",["-C",r,"branch","-D",i],{windowsHide:!0,stdio:"pipe",timeout:3e4})}catch(n){const c=String(n.stderr||n);if(!/not found|not exist/i.test(c))throw n}}else l.rmSync(o,{recursive:!0,force:!0,maxRetries:2,retryDelay:200});return{removed:!0,branchDeleted:!!(s&&t.deleteBranch===!0&&i)}}function mt(t){const e=p(String(t.rootPath||"")),r=g(String(t.relativePath||"")),o=v(e,r,!0),i=Math.max(0,Number(t.offset||0)),s=Math.min(300,Math.max(20,Number(t.limit||200))),n=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:J(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:n==="name"?void 0:pt(u.join(o,a.name))})).filter(a=>a.type!=="other").sort((a,f)=>{if(a.type!==f.type)return a.type==="directory"?-1:1;if(n!=="name"){if(a.modifiedAt==null&&f.modifiedAt!=null)return 1;if(f.modifiedAt==null&&a.modifiedAt!=null)return-1;const P=(a.modifiedAt??0)-(f.modifiedAt??0);if(P)return n==="modified-desc"?-P:P}return a.name.localeCompare(f.name,void 0,{numeric:!0,sensitivity:"base"})}),d=c.slice(i,i+s);return{path:r,rootKind:t.rootKind==="worktree"?"worktree":"project",items:d,total:c.length,nextOffset:i+d.length<c.length?i+d.length:null}}function pt(t){try{const e=l.lstatSync(t);return e.isSymbolicLink()?null:e.mtimeMs}catch{return null}}async function ht(t){const e=String(t.uploadId||"").trim();if(!e)throw new Error("项目上传任务缺少 uploadId。");const r=p(String(t.rootPath||"")),o=g(String(t.relativeDirectory||"")),i=v(r,o,!0),s=H(String(t.fileName||"")),n=Number(t.sizeBytes),c=String(t.sha256||"").trim().toLowerCase();if(!Number.isSafeInteger(n)||n<0)throw new Error("项目上传文件大小无效。");if(!/^[0-9a-f]{64}$/.test(c))throw new Error("项目上传文件摘要无效。");const d=u.join(i,`.aca-upload-${I()}.tmp`);try{const a=await rt(`/api/client/project-uploads/${encodeURIComponent(e)}`,d,{maxBytes:n,timeoutMs:3e5});if(a.sizeBytes!==n)throw new Error("项目上传文件大小校验失败。");if(a.sha256.toLowerCase()!==c)throw new Error("项目上传文件完整性校验失败。");const f=St(r,o,s,d);return{name:u.basename(f),path:J(r,f),directory:o,size:a.sizeBytes,sha256:a.sha256,renamed:u.basename(f)!==s,rootKind:t.rootKind==="worktree"?"worktree":"project"}}finally{l.rmSync(d,{force:!0})}}function wt(t){const e=p(String(t.rootPath||"")),r=t.action&&typeof t.action=="object"?t.action:{};if(r.type==="delete")return yt(e,String(r.path||""),r.recursive===!0,t.rootKind);throw new Error("project_file_operation_unsupported")}function yt(t,e,r,o){const i=g(e);if(!i)throw new Error("project_file_root_protected");gt(i);const s=i.split("/"),n=s.slice(0,-1).join("/"),c=v(t,n,!0),d=u.join(c,s.at(-1)||"");let a;try{a=l.lstatSync(d)}catch(f){throw f.code==="ENOENT"?new Error("project_file_not_found"):f}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(d).length>0)throw new Error("project_directory_not_empty");return a.isDirectory()&&r?l.rmSync(d,{recursive:!0,force:!1,maxRetries:2,retryDelay:150}):a.isDirectory()?l.rmdirSync(d):l.unlinkSync(d),{operation:"delete",path:i,entryType:a.isDirectory()?"directory":"file",parentPath:n,affectedDirectories:[n],rootKind:o==="worktree"?"worktree":"project"}}function gt(t){const e=t.split("/")[0].toLocaleLowerCase("en-US");if([".git",".aca",".aica"].includes(e))throw new Error("project_file_protected")}function St(t,e,r,o){const i=p(t),s=g(e),n=v(i,s,!0),c=H(r);if(l.realpathSync.native(u.dirname(o))!==n)throw new Error("项目上传临时文件不在目标目录中。");const a=l.lstatSync(o);if(!a.isFile()||a.isSymbolicLink())throw new Error("项目上传临时文件无效。");for(let f=1;f<=1e4;f+=1){const P=f===1?c:_t(c,f),S=u.join(n,P);try{return l.linkSync(o,S),l.chmodSync(S,420),l.rmSync(o,{force:!0}),S}catch(A){if(G(A))continue;if(!Pt(A))throw A;try{return l.copyFileSync(o,S,l.constants.COPYFILE_EXCL),l.chmodSync(S,420),l.rmSync(o,{force:!0}),S}catch(N){if(G(N))continue;throw N}}}throw new Error("同名文件过多,无法生成可用文件名。")}function H(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 _t(t,e){const r=u.extname(t);return`${r?t.slice(0,-r.length):t} (${e})${r}`}function G(t){return!!(t&&typeof t=="object"&&t.code==="EEXIST")}function Pt(t){const e=t&&typeof t=="object"?t.code:"";return["EPERM","EACCES","EXDEV","ENOTSUP","EOPNOTSUPP"].includes(String(e||""))}const W=500,j=2*1024*1024,y=2*1024*1024;function B(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,W),i=o.map(c=>vt(c)).filter(c=>!!c),s=D(m(e,["diff","--no-ext-diff","--numstat","-z","--no-renames","HEAD","--","."],j));for(const c of i){const d=s.get(c.path);if(d)c.additions=d.additions,c.deletions=d.deletions;else if(c.status==="untracked"){const a=U(e,c.path);c.additions=a.additions,c.deletions=a.deletions}}const n=$t(e);return{isRepository:!0,branch:Ct(e),files:i,ahead:n.ahead,behind:n.behind,truncated:o.length>=W}}function Et(t){const e=p(String(t.rootPath||"")),r=g(String(t.relativePath||""));if(!r)throw new Error("Diff 文件路径不能为空。");if(!w(e))return{isRepository:!1,path:r,diff:"",additions:0,deletions:0};const o=K(e,r),i=Dt(e,r),s=i?m(e,["diff","--no-ext-diff","--unified=80","HEAD","--",r],y):X(e,["diff","--no-ext-diff","--no-index","--unified=80",process.platform==="win32"?"NUL":"/dev/null",o],y),n=i?D(m(e,["diff","--no-ext-diff","--numstat","-z","--no-renames","HEAD","--",r],32*1024)).get(r):U(e,r);return{isRepository:!0,path:r,diff:V(s),additions:n?.additions??0,deletions:n?.deletions??0,truncated:s.length>y}}function xt(t){const e=p(String(t.rootPath||""));if(!w(e))return{isRepository:!1,items:[]};if(!Rt(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(n=>jt(n)).filter(n=>!!n),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],y),i=[...D(m(e,["show","--no-ext-diff","--numstat","-z","--no-renames","--format=",r],j)).values()];return{isRepository:!0,commit:r,diff:V(o),additions:i.reduce((s,n)=>s+(n.additions||0),0),deletions:i.reduce((s,n)=>s+(n.deletions||0),0),changedFiles:i.length,truncated:o.length>y}}function jt(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 vt(t){if(t.length<4)return null;const e=t[0]||" ",r=t[1]||" ",o=g(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=z(r);if(!o||!o.path)continue;const i=g(o.path);i&&e.set(i,{additions:o.additions,deletions:o.deletions})}return e}function U(t,e){const r=K(t,e),i=X(t,["diff","--no-ext-diff","--no-index","--numstat","-z","--",process.platform==="win32"?"NUL":"/dev/null",r],32*1024).split("\0",1)[0]||"",s=z(i);return s?{additions:s.additions,deletions:s.deletions}:{additions:null,deletions:null}}function z(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),i=t.slice(e+1,r);return{additions:/^\d+$/.test(o)?Number(o):null,deletions:/^\d+$/.test(i)?Number(i):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 At(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 kt(t){try{return l.readdirSync(t).length>0}catch{return!1}}function Dt(t,e){try{return m(t,["ls-files","--error-unmatch","--",e],8*1024),!0}catch{return!1}}function Rt(t){try{return m(t,["rev-parse","--verify","HEAD"],8*1024),!0}catch{return!1}}function Ct(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 $t(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 K(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 _("git",["-C",t,...e],{windowsHide:!0,encoding:"utf8",timeout:2e4,maxBuffer:r,stdio:["ignore","pipe","pipe"]})}function X(t,e,r){try{return m(t,e,r)}catch(o){const i=String(o.stdout||"");if(i)return i;if(Number(o.status)===1)return"";throw o}}function V(t){return t.length<=y?t:`${t.slice(0,y)}
|
|
2
2
|
|
|
3
|
-
[Diff 内容过大,已截断]`}function
|
|
4
|
-
`),o=Z(
|
|
5
|
-
`),
|
|
3
|
+
[Diff 内容过大,已截断]`}function Nt(){const t=process.platform,e=R(t);return{platform:t,roots:e}}function Tt(t){if(!t.directoryPath.trim())throw new Error("目录路径不能为空。");return process.platform==="win32"?Mt(t.directoryPath,!!t.includeHidden):Lt(t.directoryPath,!!t.includeHidden)}function h(t,e=process.platform){const r=e==="win32"?u.win32:u.posix,o=r.normalize(t),i=r.parse(o).root,s=o.length>i.length?o.replace(/[\\/]+$/,""):o;return e==="win32"?s.toLocaleLowerCase("en-US"):s}function It(){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(b?.platform===t&&b.expiresAtMs>Date.now())return b.roots;const e=t==="win32"?Ft():It();return b={platform:t,roots:e,expiresAtMs:Date.now()+lt},e}function Ft(){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=Z(M(r));for(const i of o){const s=String(i.Root||"");if(!s)continue;const n=Number(i.DriveType||0),c=String(i.Name||s.replace(/[\\/]+$/,"")),d=String(i.VolumeName||"").trim();e.set(h(s,"win32"),{id:`drive-${c.replace(/[^a-z0-9]+/gi,"").toLowerCase()}`,label:d?`${d} (${c})`:zt(c,n),path:s,kind:Ut(n),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 Lt(t,e){const r=C(t),i=l.readdirSync(r,{withFileTypes:!0}).filter(n=>n.isDirectory()&&!n.isSymbolicLink()&&(e||!n.name.startsWith("."))),s=i.slice(0,x).map(n=>({name:n.name,path:u.join(r,n.name),kind:"directory",hidden:n.name.startsWith("."),hasChildren:!0}));return s.sort((n,c)=>n.name.localeCompare(c.name,void 0,{numeric:!0,sensitivity:"base"})),Y(r,s,i.length>x,"linux")}function Mt(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
|
+
`),i=Z(M(o,{ACA_MACHINE_FS_PATH:r})),s=i.map(n=>{const c=String(n.Attributes||"");return{name:String(n.Name||""),path:String(n.FullName||""),kind:"directory",hidden:/Hidden|System/i.test(c),hasChildren:!0,reparsePoint:/ReparsePoint/i.test(c)}}).filter(n=>n.name&&n.path&&!n.reparsePoint&&(e||!n.hidden)).slice(0,x).sort((n,c)=>n.name.localeCompare(c.name,void 0,{numeric:!0,sensitivity:"base"}));return Y(r,s,i.length>x,"win32")}function Y(t,e,r,o){const i=o==="win32"?u.win32:u.posix,s=i.parse(t).root,n=h(t,o)===h(s,o)?null:i.dirname(t);return{platform:o,path:t,parentPath:n,breadcrumbs:Ot(t,o),readable:Q(t,l.constants.R_OK),writable:Q(t,l.constants.W_OK),items:e,truncated:r}}function Ot(t,e){const r=e==="win32"?u.win32:u.posix,o=r.parse(t),i=t.slice(o.root.length).split(/[\\/]+/).filter(Boolean),s=[{label:o.root.replace(/[\\/]+$/,"")||o.root,path:o.root}];let n=o.root;for(const c of i)n=r.join(n,c),s.push({label:c,path:n});return s}async function Ht(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 i=C(String(t.path||""));l.accessSync(i,l.constants.R_OK|l.constants.W_OK);const s=L().machineId,c=(await E("GET",`/api/projects?workspaceId=${encodeURIComponent(e)}`)).items.find(a=>(!a.machine_id||a.machine_id===s)&&h(a.root_path)===h(i)),d=c||(await E("POST","/api/projects",{projectId:I(),workspaceId:e,name:r,rootPath:i,source:"aica",projectType:o})).item;return ot(Gt(d)),{project:d,duplicate:!!c}}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(Wt(t),!l.statSync(t).isDirectory())throw new Error("选择的路径不是目录。");const r=l.realpathSync.native(t);return Bt(r),r}function p(t){const e=$(t),r=l.realpathSync.native(e);if(!l.statSync(r).isDirectory())throw new Error("项目根目录不存在。");return r}function $(t){const e=nt(t);if(!e||!u.isAbsolute(e))throw new Error("项目根目录无效。");return u.resolve(e)}function g(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,i=l.realpathSync.native(o),s=u.relative(t,i);if(s.startsWith("..")||u.isAbsolute(s))throw new Error("目录超出项目范围。");const n=l.statSync(i);if(r&&!n.isDirectory())throw new Error("请求路径不是目录。");return i}function J(t,e){return u.relative(t,e).split(u.sep).join("/")}function Wt(t){const e=process.platform==="win32"?u.win32:u.posix;if(!R(process.platform).some(o=>{const i=String(o.path||"");if(!i)return!1;const s=e.relative(i,t);return s===""||!s.startsWith("..")&&!e.isAbsolute(s)}))throw new Error("目录不在允许浏览的位置内。")}function Bt(t){const e=process.platform==="win32"?u.win32:u.posix;if(!R(process.platform).some(o=>{const i=String(o.path||"");if(!i)return!1;const s=e.relative(i,t);return s===""||!s.startsWith("..")&&!e.isAbsolute(s)}))throw new Error("目录不在允许浏览的位置内。")}function Q(t,e){try{return l.accessSync(t,e),!0}catch{return!1}}function Z(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 Ut(t){return t===2?"removable":t===3?"fixed":t===4?"network":t===5?"optical":"drive"}function zt(t,e){return e===2?`移动磁盘 (${t})`:e===4?`网络驱动器 (${t})`:e===5?`光驱 (${t})`:`本地磁盘 (${t})`}export{ft as cleanupWorktree,St as commitProjectUploadTempFile,dt as commitWorktree,h as comparableMachinePath,bt as getProjectGitCommit,Et as getProjectGitDiff,xt as getProjectGitLog,B as getProjectGitStatus,k as getWorktreeStatus,Tt as listMachineDirectory,Nt as listMachineFilesystemRoots,mt as listProjectDirectory,wt as mutateProjectFiles,ce as startMachineFilesystemWorkerLoop,ht as uploadProjectFile};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{randomUUID as
|
|
1
|
+
import{randomUUID as S}from"node:crypto";import p from"node:fs";import u from"node:path";import M from"@xterm/headless";import q from"@xterm/addon-serialize";import{loadPty as T}from"./native.js";import{expandWorkerRuntimePath as z}from"../worker-runtime-paths.js";import{terminalShell as D}from"./shell.js";const y=8,E=1440*6e4;class B{emit;ptyLoader;entries=new Map;sweep=setInterval(()=>{for(const[e,t]of this.entries)!t.attachmentId&&Date.now()-t.detachedAt>E&&this.remove(e)},6e4);opening=new Map;epoch=0;disposed=!1;constructor(e,t=T){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]=A(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=z(e.cwd||"");if(!d||!u.isAbsolute(d))throw new Error("终端启动目录无效。");const l=p.realpathSync(d);if(!p.statSync(l).isDirectory())throw new Error("终端启动目录不存在。");const C=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]=A(e),I=process.platform==="win32"?process.env.ACA_BASH_PATH||process.env.COMSPEC||"powershell.exe":process.env.SHELL||"/bin/sh",g=D(I),c=C.spawn(I,g.args,{name:"xterm-256color",cwd:l,cols:w,rows:m,env:g.env}),f=new M.Terminal({cols:w,rows:m,scrollback:1e3,allowProposedApi:!0}),x=new q.SerializeAddon;f.loadAddon(x),o={id:S(),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 A(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 O(h){_=h}export{B as TerminalService,_ as activeTerminalService,O as setActiveTerminalService};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import n from"node:fs";import i from"node:path";import{createHash as m,randomUUID as f}from"node:crypto";import{getAcaHome as p}from"../paths.js";import{windowsUtf8Environment as u}from"../windows-encoding.js";const c=`# ACA 交互终端默认配色;用户配置始终优先。
|
|
2
2
|
__aca_terminal_initial_ps1=\${PS1-}
|
|
3
3
|
if [[ -r "$HOME/.bashrc" ]]; then
|
|
4
4
|
. "$HOME/.bashrc"
|
|
@@ -16,4 +16,4 @@ if [[ -z \${NO_COLOR:-} ]]; then
|
|
|
16
16
|
fi
|
|
17
17
|
fi
|
|
18
18
|
unset __aca_terminal_initial_ps1
|
|
19
|
-
`;function $(
|
|
19
|
+
`;function $(t,s=process.env){const r={...process.platform==="win32"?u(s):s,TERM:"xterm-256color",COLORTERM:"truecolor"};if(process.platform==="win32"&&t===s.ACA_BASH_PATH)return{args:["--noprofile","--norc","-i"],env:r};if(process.platform==="win32"){const e=i.win32.basename(t).toLowerCase();return e==="cmd.exe"||e==="cmd"?{args:["/K","chcp 65001>nul"],env:r}:["powershell.exe","powershell","pwsh.exe","pwsh"].includes(e)?{args:["-NoExit","-Command","[Console]::InputEncoding = [Console]::OutputEncoding = New-Object System.Text.UTF8Encoding($false); $OutputEncoding = [Console]::OutputEncoding"],env:r}:{args:[],env:r}}if(i.basename(t)!=="bash")return{args:[],env:r};const a=i.join(p(),"terminal"),l=m("sha256").update(c).digest("hex").slice(0,16),o=i.join(a,`bashrc-${l}`);if(n.mkdirSync(a,{recursive:!0,mode:448}),!n.existsSync(o)){const e=`${o}.${f()}.tmp`;try{n.writeFileSync(e,c,{mode:384,flag:"wx"}),n.renameSync(e,o)}finally{n.rmSync(e,{force:!0})}}return{args:["--rcfile",o,"-i"],env:r}}export{$ as terminalShell};
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import A from"node:fs";import r from"node:path";import{windowsUtf8Environment as _}from"./windows-encoding.js";function a(){if(process.platform!=="win32"||!process.env.ACA_TOOL_ENV_ROOT)return;const s=r.resolve(process.env.ACA_TOOL_ENV_ROOT),e=JSON.parse(A.readFileSync(r.join(s,"runtime.json"),"utf8"));if(e.schema!==1||!Array.isArray(e.path))throw new Error("ACA 工具环境清单无效");const o=n=>{const i=r.resolve(s,n);if(i===s||!i.startsWith(s+r.sep)||!A.existsSync(i))throw new Error(`ACA 工具环境缺少组件:${n}`);return i},c=e.path.map(o);if(process.env.ACA_DESKTOP_CHILD==="1"&&process.env.ACA_WORKER_ROOT){const n=r.join(process.env.ACA_WORKER_ROOT,".desktop-bin");if(!A.existsSync(n))throw new Error("当前 Worker 的桌面工具入口不存在");c.unshift(n)}const t=Object.keys(process.env).find(n=>n.toLowerCase()==="path"),p=t&&process.env[t]||"";t&&t!=="PATH"&&delete process.env[t],process.env.PATH=[...c,p].filter(Boolean).join(r.delimiter),process.env.ACA_BASH_PATH=o(e.bash),process.env.SHELL=process.env.ACA_BASH_PATH,process.env.ACA_PYTHON_PATH=o(e.python),process.env.ACA_NODE_PATH=o(e.node),process.env.MSYSTEM="UCRT64",process.env.CHERE_INVOKING="1",process.env.MSYS2_PATH_TYPE="inherit",Object.assign(process.env,_(process.env)),e.nodePath&&(process.env.NODE_PATH=o(e.nodePath)),e.office&&(process.env.ACA_OFFICE_CONVERTER=o(e.office)),e.tessdata&&(process.env.TESSDATA_PREFIX=o(e.tessdata)),delete process.env.PYTHONHOME,delete process.env.PYTHONPATH,process.env.PYTHONNOUSERSITE="1",e.browserCache&&(process.env.PLAYWRIGHT_BROWSERS_PATH=r.join(s,e.browserCache))}function v(s=process.env){return!s.ACA_TOOL_ENV_ROOT||!s.ACA_BASH_PATH?"":["ACA 已准备 Windows 本机工具环境,优先使用内置工具,不要求用户安装系统 Node.js 或 Python。",`Bash 路径:${JSON.stringify(s.ACA_BASH_PATH)}。执行 Bash 脚本、管道及 Unix 命令时,请在命令工具中显式选择该 shell;若工具不支持 shell 参数,通过该程序执行 --noprofile --norc -c。`,`Node.js:${JSON.stringify(s.ACA_NODE_PATH)};Python:${JSON.stringify(s.ACA_PYTHON_PATH)}。安装 Python 包使用对应解释器的 -m pip。`,"当前操作系统仍是 Windows。原生工具使用 Windows 路径,Bash 路径转换使用 cygpath;不要假设存在 Linux 内核接口。"].join(`
|
|
2
|
+
`)}export{a as applyToolEnvironment,v as toolEnvironmentInstructions};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
function N(L){const C=[L.LC_ALL,L.LC_CTYPE,L.LANG].find(t=>/\.utf-?8(?:@.*)?$/i.test(t||""))||"C.UTF-8";return{...L,LANG:C,LC_ALL:C,LC_CTYPE:C,PYTHONUTF8:"1",PYTHONIOENCODING:"utf-8"}}export{N as windowsUtf8Environment};
|
package/dist/version.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
const o="0.1.
|
|
1
|
+
const o="0.1.86";export{o as AICA_VERSION};
|
package/dist/worker-cli.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import{Command as m}from"commander";import a from"node:path";import{createStartCommand as n}from"./commands/start.js";import{createDaemonCommand as t}from"./commands/daemon.js";import{createWorkerAuthCommand as
|
|
2
|
+
import{Command as m}from"commander";import a from"node:path";import{createStartCommand as n}from"./commands/start.js";import{createDaemonCommand as t}from"./commands/daemon.js";import{createWorkerAuthCommand as c}from"./commands/worker-auth.js";import{createWorkerCodexCommand as i}from"./commands/worker-codex.js";import{createWorkerProjectCommand as s}from"./commands/worker-project.js";import{DEFAULT_ACA_SERVER_URL as d}from"./core/aca-config.js";import{AICA_VERSION as p}from"./version.js";import{applyToolEnvironment as C}from"./core/tool-environment.js";import{checkDesktopWorker as f}from"./core/desktop-check.js";process.env.ACA_DEFAULT_SERVER_URL||=d;async function A(r=process.argv){C();const o=new m;o.name("aica").description("AICA remote Agent worker").version(p).enablePositionalOptions(),o.addCommand(n()),o.addCommand(t()),o.addCommand(c()),o.addCommand(s()),o.addCommand(i()),o.addCommand(new m("desktop-check").description("检查桌面运行环境,不连接 Server").action(f)),await o.parseAsync(r)}const k=process.argv[1]||"",e=a.basename(k).replace(/\.(?:js|ts|cjs|mjs)$/i,"");(e==="worker-cli"||e==="aica"||e==="aca")&&A().catch(r=>{const o=r instanceof Error?r.message:String(r);console.error(o),process.exitCode=1});export{A as runWorkerCli};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yuandc/aica",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.86",
|
|
4
4
|
"description": "AICA remote Agent worker",
|
|
5
5
|
"author": "yuandc",
|
|
6
6
|
"type": "module",
|
|
@@ -15,6 +15,13 @@
|
|
|
15
15
|
"engines": {
|
|
16
16
|
"node": ">=22.0.0"
|
|
17
17
|
},
|
|
18
|
+
"acaDesktop": {
|
|
19
|
+
"protocol": 1,
|
|
20
|
+
"configSchema": 1,
|
|
21
|
+
"capabilities": [
|
|
22
|
+
"staged-start"
|
|
23
|
+
]
|
|
24
|
+
},
|
|
18
25
|
"publishConfig": {
|
|
19
26
|
"access": "public"
|
|
20
27
|
},
|