@yuandc/aica 0.1.6 → 0.1.8

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/agent.js CHANGED
@@ -1 +1 @@
1
- import s from"node:fs";import{createAgentProvider as r}from"./providers/registry.js";async function a(e){if(!s.existsSync(e.cwd)||!s.statSync(e.cwd).isDirectory())throw new Error(`Project root does not exist or is not a directory: ${e.cwd}`);const o=await r({agentType:e.agentType||"codex",cliType:e.cliType||"builtin"}).createSession({cwd:e.cwd,providerSessionId:e.acpSessionId??null,timeoutMs:e.timeoutMs});try{const t=await o.sendPrompt({...e,providerSessionId:e.acpSessionId??null});return{...t,acpSessionId:t.providerSessionId}}finally{await o.close().catch(()=>{})}}async function d(e){const o=await r({agentType:e.agentType||"codex",cliType:e.cliType||"builtin"}).createSession({cwd:e.cwd,providerSessionId:e.acpSessionId,timeoutMs:e.timeoutMs});if(!o.compactContext)throw await o.close().catch(()=>{}),new Error(`Provider does not support context compaction: ${e.agentType||"codex"}`);try{const t=await o.compactContext(e);return{...t,acpSessionId:t.providerSessionId}}finally{await o.close().catch(()=>{})}}export{d as compactAgentContext,a as sendAgentPrompt};
1
+ import r from"node:fs";import{createAgentProvider as t}from"./providers/registry.js";async function a(e){if(!r.existsSync(e.cwd)||!r.statSync(e.cwd).isDirectory())throw new Error(`Project root does not exist or is not a directory: ${e.cwd}`);if(e.requireSessionResume&&!e.acpSessionId)throw new Error("导入的历史 Session 缺少 Provider Session ID,禁止创建新的会话。");const o=await t({agentType:e.agentType||"codex",cliType:e.cliType||"builtin"}).createSession({cwd:e.cwd,providerSessionId:e.acpSessionId??null,timeoutMs:e.timeoutMs});try{const s=await o.sendPrompt({...e,providerSessionId:e.acpSessionId??null});return{...s,acpSessionId:s.providerSessionId}}finally{await o.close().catch(()=>{})}}async function d(e){const o=await t({agentType:e.agentType||"codex",cliType:e.cliType||"builtin"}).createSession({cwd:e.cwd,providerSessionId:e.acpSessionId,timeoutMs:e.timeoutMs});if(!o.compactContext)throw await o.close().catch(()=>{}),new Error(`Provider does not support context compaction: ${e.agentType||"codex"}`);try{const s=await o.compactContext(e);return{...s,acpSessionId:s.providerSessionId}}finally{await o.close().catch(()=>{})}}export{d as compactAgentContext,a as sendAgentPrompt};
@@ -1 +1 @@
1
- import c from"node:fs";import{JsonLineRpcClient as u}from"../../client/json-rpc.js";import{startCodexAppServer as h}from"./codex-process.js";import{extractAssistantTextFromTurn as p,mapCodexNotification as m}from"./events.js";import{isCodexApprovalRequest as f,resolveCodexApprovalRequest as g}from"./permissions.js";const C=1500,w={sessionResume:!0,imageInput:!0,fileAttachment:!0,filesystem:!0,terminal:!0,permissionRequest:!0,configOptions:!0,usage:!0,contextUsage:!0,contextCompaction:!0,plan:!0,diff:!0};class _{id="native-codex";name="Native Codex";capabilities=w;async createSession(e){return new v(e.cwd,e.providerSessionId??null,e.timeoutMs)}}class v{cwd;providerSessionId;defaultTimeoutMs;started;rpc;initialized=!1;content="";promptActive=!1;updates=[];statusTimeline=[];statusCallback=null;updateCallback=null;permissionCallback=null;abortHandler=null;completedTurns=new Map;pendingTurnCompletions=new Map;agentMessagePhases=new Map;constructor(e,s,t=I()){this.cwd=e,this.providerSessionId=s,this.defaultTimeoutMs=t,this.started=h(e),this.rpc=new u(this.started.child,{onNotification:(r,n)=>this.handleNotification(r,n),onRequest:(r,n)=>this.handleReverseRequest(r,n)})}async sendPrompt(e){const s=Date.now(),t=d(e.mode);this.statusCallback=e.onStatus??null,this.updateCallback=e.onUpdate??null,this.permissionCallback=e.onPermissionRequest??null,this.attachAbortSignal(e.signal),this.emitStatus("initializing","Codex 初始化中","initialize"),await this.initialize();const r=await this.establishThread(e);this.emitStatus("thinking","思考中"),this.content="",this.updates.length=0,this.agentMessagePhases.clear(),this.promptActive=!0;let n,a;try{n=await this.rpc.request("turn/start",{threadId:r,clientUserMessageId:`aca-${Date.now()}-${Math.random().toString(16).slice(2)}`,input:T(e.prompt,e.promptBlocks),cwd:e.cwd,...e.model?{model:e.model}:{},...x(e.configOptionValues)},e.timeoutMs??this.defaultTimeoutMs);const o=String(n?.turn?.id??"");a=o?await this.waitForTurnCompleted(o,e.timeoutMs??this.defaultTimeoutMs):n?.turn}finally{this.promptActive=!1}const l=this.content.trim()||p(a);return this.emitStatus("completed","已完成"),{providerSessionId:r,cliType:"builtin",agentType:"codex",effectiveConfig:t,command:this.started.command,args:this.started.args,cwd:this.cwd,exitCode:this.rpc.exitCode,signal:this.rpc.signal,durationMs:Date.now()-s,stdout:this.rpc.stdout,stderr:this.rpc.stderr,content:l,updates:this.updates,statusTimeline:this.statusTimeline,promptResponse:n}}async cancel(){try{this.providerSessionId&&this.started.child.exitCode===null&&this.started.child.signalCode===null&&await this.rpc.request("turn/interrupt",{threadId:this.providerSessionId},C).catch(()=>{})}finally{await this.close()}}async close(){this.statusCallback=null,this.updateCallback=null,this.permissionCallback=null;for(const e of this.pendingTurnCompletions.values())clearTimeout(e.timer),e.reject(new Error("Codex app-server session closed"));this.pendingTurnCompletions.clear(),this.abortHandler&&(this.abortHandler=null),await this.closeCodexProcess()}async closeCodexProcess(){this.started.child.exitCode!==null||this.started.child.signalCode!==null||(this.started.child.stdin.end(),!await this.waitForProcessClose(2e3)&&(this.started.child.kill("SIGTERM"),!await this.waitForProcessClose(1e3)&&(this.started.child.kill("SIGKILL"),await this.waitForProcessClose(1e3))))}waitForProcessClose(e){return this.started.child.exitCode!==null||this.started.child.signalCode!==null?Promise.resolve(!0):new Promise(s=>{const t=setTimeout(()=>s(!1),e);t.unref(),this.started.child.once("close",()=>{clearTimeout(t),s(!0)})})}async initialize(){this.initialized||(await this.rpc.request("initialize",{clientInfo:{name:"aca",version:"0.1.0"},capabilities:{experimentalApi:!0,requestAttestation:!1}}),this.initialized=!0)}async establishThread(e){const s=d(e.mode);if(this.providerSessionId)try{this.emitStatus("resuming","Codex 会话恢复中","thread/resume");const n=await this.rpc.request("thread/resume",{threadId:this.providerSessionId,cwd:e.cwd,...e.model?{model:e.model}:{},approvalsReviewer:"user",...s},6e4);return this.providerSessionId=String(n?.thread?.id??this.providerSessionId),this.providerSessionId}catch{this.providerSessionId=null}this.emitStatus("acp","Codex 启动中","thread/start");const r=(await this.rpc.request("thread/start",{cwd:e.cwd,...e.model?{model:e.model}:{},approvalsReviewer:"user",...s,ephemeral:!1,threadSource:"aca"},6e4))?.thread?.id;if(!r)throw new Error("Codex app-server thread/start did not return thread id");return this.providerSessionId=r,r}attachAbortSignal(e){if(e){if(this.abortHandler=()=>{this.cancel()},e.aborted)throw new Error("ACP prompt cancelled");e.addEventListener("abort",this.abortHandler,{once:!0})}}handleNotification(e,s){!this.promptActive&&!P(e)||(this.rpc.refreshPendingRequestTimeout("turn/start"),this.updates.push({method:e,params:s}),this.captureAgentMessagePhase(e,s),this.appendFinalAnswerDelta(e,s),e==="turn/completed"&&this.resolveTurnCompleted(s),m(e,s,{pushUpdate:t=>this.updateCallback?.(t),pushStatus:(t,r,n,a)=>this.emitStatus(t,r,n,a)},{agentMessagePhases:this.agentMessagePhases}))}captureAgentMessagePhase(e,s){if(e!=="item/started"&&e!=="item/completed")return;const t=s?.item;t?.type!=="agentMessage"||!t.id||this.agentMessagePhases.set(t.id,t.phase??"")}appendFinalAnswerDelta(e,s){if(e!=="item/agentMessage/delta")return;const t=s;if(!t.itemId||typeof t.delta!="string")return;const r=this.agentMessagePhases.get(t.itemId);(r==="final_answer"||!r)&&(this.content+=t.delta)}waitForTurnCompleted(e,s){const t=this.completedTurns.get(e);return t?Promise.resolve(t):new Promise((r,n)=>{const a=setTimeout(()=>{this.pendingTurnCompletions.delete(e),n(new Error("Codex app-server request timed out while waiting for turn/completed"))},s);a.unref(),this.pendingTurnCompletions.set(e,{resolve:r,reject:n,timer:a})})}resolveTurnCompleted(e){const s=e?.turn,t=s?.id;if(!t)return;this.completedTurns.set(t,s);const r=this.pendingTurnCompletions.get(t);if(r){if(this.pendingTurnCompletions.delete(t),clearTimeout(r.timer),s.status==="failed"){r.reject(new Error(`Codex turn failed: ${JSON.stringify(s.error??{})}`));return}r.resolve(s)}}async handleReverseRequest(e,s){return f(e)?(this.emitStatus("requestPermission","等待授权",y(s),e),g(e,s,this.permissionCallback)):{}}emitStatus(e,s,t,r){const n={phase:e,label:s,...t?{detail:t}:{},...r?{updateType:r}:{},atMs:Date.now()},a=this.statusTimeline[this.statusTimeline.length-1];a&&a.phase===n.phase&&a.label===n.label&&a.detail===n.detail&&a.updateType===n.updateType||(this.statusTimeline.push(n),this.statusCallback?.(n))}}function T(i,e){return(e?.length?e:[{type:"text",text:i}]).map(t=>t.type==="image"?t.uri&&c.existsSync(t.uri)?{type:"localImage",path:t.uri}:{type:"image",url:`data:${t.mimeType};base64,${t.data}`}:{type:"text",text:t.text,text_elements:[]})}function d(i){const e=String(i||"").trim();return e==="read-only"?{approvalPolicy:"on-request",sandbox:"read-only"}:e==="acceptEdits"||e==="accept-edits"?{approvalPolicy:"on-failure",sandbox:"workspace-write"}:["agent-full-access","full-access","bypassPermissions","danger-full-access"].includes(e)?{approvalPolicy:"never",sandbox:"danger-full-access"}:{approvalPolicy:"on-request",sandbox:"workspace-write"}}function x(i){if(!i)return{};const e={},s=i.reasoning_effort??i.reasoningEffort??i.model_reasoning_effort??i.modelReasoningEffort??i.effort;return typeof s=="string"&&s&&(e.effort=s),e}function y(i){if(!i||typeof i!="object"||Array.isArray(i))return;const e=i;for(const s of["command","cwd","reason","grantRoot"]){const t=e[s];if(typeof t=="string"&&t.trim())return S(t)}}function S(i){const e=i.replace(/\s+/g," ").trim();return e.length>120?`${e.slice(0,117)}...`:e}function P(i){return i.startsWith("thread/")?!0:["account/rateLimits/updated","account/updated","configWarning","warning","model/rerouted","thread/compacted"].includes(i)}function I(){const i=Number.parseInt(process.env.ACA_ACP_PROMPT_TIMEOUT_MS??"",10);return Number.isInteger(i)&&i>=3e4?i:360*60*1e3}export{_ as NativeCodexProvider,w as nativeCodexCapabilities};
1
+ import c from"node:fs";import{JsonLineRpcClient as u}from"../../client/json-rpc.js";import{startCodexAppServer as h}from"./codex-process.js";import{extractAssistantTextFromTurn as p,mapCodexNotification as m}from"./events.js";import{isCodexApprovalRequest as f,resolveCodexApprovalRequest as g}from"./permissions.js";const C=1500,w={sessionResume:!0,imageInput:!0,fileAttachment:!0,filesystem:!0,terminal:!0,permissionRequest:!0,configOptions:!0,usage:!0,contextUsage:!0,contextCompaction:!0,plan:!0,diff:!0};class _{id="native-codex";name="Native Codex";capabilities=w;async createSession(e){return new v(e.cwd,e.providerSessionId??null,e.timeoutMs)}}class v{cwd;providerSessionId;defaultTimeoutMs;started;rpc;initialized=!1;content="";promptActive=!1;updates=[];statusTimeline=[];statusCallback=null;updateCallback=null;permissionCallback=null;abortHandler=null;completedTurns=new Map;pendingTurnCompletions=new Map;agentMessagePhases=new Map;constructor(e,s,t=I()){this.cwd=e,this.providerSessionId=s,this.defaultTimeoutMs=t,this.started=h(e),this.rpc=new u(this.started.child,{onNotification:(n,r)=>this.handleNotification(n,r),onRequest:(n,r)=>this.handleReverseRequest(n,r)})}async sendPrompt(e){const s=Date.now(),t=d(e.mode);this.statusCallback=e.onStatus??null,this.updateCallback=e.onUpdate??null,this.permissionCallback=e.onPermissionRequest??null,this.attachAbortSignal(e.signal),this.emitStatus("initializing","Codex 初始化中","initialize"),await this.initialize();const n=await this.establishThread(e);this.emitStatus("thinking","思考中"),this.content="",this.updates.length=0,this.agentMessagePhases.clear(),this.promptActive=!0;let r,a;try{r=await this.rpc.request("turn/start",{threadId:n,clientUserMessageId:`aca-${Date.now()}-${Math.random().toString(16).slice(2)}`,input:T(e.prompt,e.promptBlocks),cwd:e.cwd,...e.model?{model:e.model}:{},...x(e.configOptionValues)},e.timeoutMs??this.defaultTimeoutMs);const o=String(r?.turn?.id??"");a=o?await this.waitForTurnCompleted(o,e.timeoutMs??this.defaultTimeoutMs):r?.turn}finally{this.promptActive=!1}const l=this.content.trim()||p(a);return this.emitStatus("completed","已完成"),{providerSessionId:n,cliType:"builtin",agentType:"codex",effectiveConfig:t,command:this.started.command,args:this.started.args,cwd:this.cwd,exitCode:this.rpc.exitCode,signal:this.rpc.signal,durationMs:Date.now()-s,stdout:this.rpc.stdout,stderr:this.rpc.stderr,content:l,updates:this.updates,statusTimeline:this.statusTimeline,promptResponse:r}}async cancel(){try{this.providerSessionId&&this.started.child.exitCode===null&&this.started.child.signalCode===null&&await this.rpc.request("turn/interrupt",{threadId:this.providerSessionId},C).catch(()=>{})}finally{await this.close()}}async close(){this.statusCallback=null,this.updateCallback=null,this.permissionCallback=null;for(const e of this.pendingTurnCompletions.values())clearTimeout(e.timer),e.reject(new Error("Codex app-server session closed"));this.pendingTurnCompletions.clear(),this.abortHandler&&(this.abortHandler=null),await this.closeCodexProcess()}async closeCodexProcess(){this.started.child.exitCode!==null||this.started.child.signalCode!==null||(this.started.child.stdin.end(),!await this.waitForProcessClose(2e3)&&(this.started.child.kill("SIGTERM"),!await this.waitForProcessClose(1e3)&&(this.started.child.kill("SIGKILL"),await this.waitForProcessClose(1e3))))}waitForProcessClose(e){return this.started.child.exitCode!==null||this.started.child.signalCode!==null?Promise.resolve(!0):new Promise(s=>{const t=setTimeout(()=>s(!1),e);t.unref(),this.started.child.once("close",()=>{clearTimeout(t),s(!0)})})}async initialize(){this.initialized||(await this.rpc.request("initialize",{clientInfo:{name:"aca",version:"0.1.0"},capabilities:{experimentalApi:!0,requestAttestation:!1}}),this.initialized=!0)}async establishThread(e){const s=d(e.mode);if(this.providerSessionId)try{this.emitStatus("resuming","Codex 会话恢复中","thread/resume");const r=await this.rpc.request("thread/resume",{threadId:this.providerSessionId,cwd:e.cwd,...e.model?{model:e.model}:{},approvalsReviewer:"user",...s},6e4);return this.providerSessionId=String(r?.thread?.id??this.providerSessionId),this.providerSessionId}catch(r){if(e.requireSessionResume){const a=r instanceof Error?r.message:String(r);throw new Error(`无法恢复导入的 Codex Session ${this.providerSessionId}:${a}`)}this.providerSessionId=null}this.emitStatus("acp","Codex 启动中","thread/start");const n=(await this.rpc.request("thread/start",{cwd:e.cwd,...e.model?{model:e.model}:{},approvalsReviewer:"user",...s,ephemeral:!1,threadSource:"aca"},6e4))?.thread?.id;if(!n)throw new Error("Codex app-server thread/start did not return thread id");return this.providerSessionId=n,n}attachAbortSignal(e){if(e){if(this.abortHandler=()=>{this.cancel()},e.aborted)throw new Error("ACP prompt cancelled");e.addEventListener("abort",this.abortHandler,{once:!0})}}handleNotification(e,s){!this.promptActive&&!P(e)||(this.rpc.refreshPendingRequestTimeout("turn/start"),this.updates.push({method:e,params:s}),this.captureAgentMessagePhase(e,s),this.appendFinalAnswerDelta(e,s),e==="turn/completed"&&this.resolveTurnCompleted(s),m(e,s,{pushUpdate:t=>this.updateCallback?.(t),pushStatus:(t,n,r,a)=>this.emitStatus(t,n,r,a)},{agentMessagePhases:this.agentMessagePhases}))}captureAgentMessagePhase(e,s){if(e!=="item/started"&&e!=="item/completed")return;const t=s?.item;t?.type!=="agentMessage"||!t.id||this.agentMessagePhases.set(t.id,t.phase??"")}appendFinalAnswerDelta(e,s){if(e!=="item/agentMessage/delta")return;const t=s;if(!t.itemId||typeof t.delta!="string")return;const n=this.agentMessagePhases.get(t.itemId);(n==="final_answer"||!n)&&(this.content+=t.delta)}waitForTurnCompleted(e,s){const t=this.completedTurns.get(e);return t?Promise.resolve(t):new Promise((n,r)=>{const a=setTimeout(()=>{this.pendingTurnCompletions.delete(e),r(new Error("Codex app-server request timed out while waiting for turn/completed"))},s);a.unref(),this.pendingTurnCompletions.set(e,{resolve:n,reject:r,timer:a})})}resolveTurnCompleted(e){const s=e?.turn,t=s?.id;if(!t)return;this.completedTurns.set(t,s);const n=this.pendingTurnCompletions.get(t);if(n){if(this.pendingTurnCompletions.delete(t),clearTimeout(n.timer),s.status==="failed"){n.reject(new Error(`Codex turn failed: ${JSON.stringify(s.error??{})}`));return}n.resolve(s)}}async handleReverseRequest(e,s){return f(e)?(this.emitStatus("requestPermission","等待授权",S(s),e),g(e,s,this.permissionCallback)):{}}emitStatus(e,s,t,n){const r={phase:e,label:s,...t?{detail:t}:{},...n?{updateType:n}:{},atMs:Date.now()},a=this.statusTimeline[this.statusTimeline.length-1];a&&a.phase===r.phase&&a.label===r.label&&a.detail===r.detail&&a.updateType===r.updateType||(this.statusTimeline.push(r),this.statusCallback?.(r))}}function T(i,e){return(e?.length?e:[{type:"text",text:i}]).map(t=>t.type==="image"?t.uri&&c.existsSync(t.uri)?{type:"localImage",path:t.uri}:{type:"image",url:`data:${t.mimeType};base64,${t.data}`}:{type:"text",text:t.text,text_elements:[]})}function d(i){const e=String(i||"").trim();return e==="read-only"?{approvalPolicy:"on-request",sandbox:"read-only"}:e==="acceptEdits"||e==="accept-edits"?{approvalPolicy:"on-failure",sandbox:"workspace-write"}:["agent-full-access","full-access","bypassPermissions","danger-full-access"].includes(e)?{approvalPolicy:"never",sandbox:"danger-full-access"}:{approvalPolicy:"on-request",sandbox:"workspace-write"}}function x(i){if(!i)return{};const e={},s=i.reasoning_effort??i.reasoningEffort??i.model_reasoning_effort??i.modelReasoningEffort??i.effort;return typeof s=="string"&&s&&(e.effort=s),e}function S(i){if(!i||typeof i!="object"||Array.isArray(i))return;const e=i;for(const s of["command","cwd","reason","grantRoot"]){const t=e[s];if(typeof t=="string"&&t.trim())return y(t)}}function y(i){const e=i.replace(/\s+/g," ").trim();return e.length>120?`${e.slice(0,117)}...`:e}function P(i){return i.startsWith("thread/")?!0:["account/rateLimits/updated","account/updated","configWarning","warning","model/rerouted","thread/compacted"].includes(i)}function I(){const i=Number.parseInt(process.env.ACA_ACP_PROMPT_TIMEOUT_MS??"",10);return Number.isInteger(i)&&i>=3e4?i:360*60*1e3}export{_ as NativeCodexProvider,w as nativeCodexCapabilities};
@@ -0,0 +1,7 @@
1
+ import h from"node:fs";import{closeAcpProcess as C}from"../../client/acp-process.js";import{JsonLineRpcClient as k}from"../../client/json-rpc.js";import{createPlanEvent as _,normalizePlanSessionUpdate as b}from"../../plan-events.js";import{startCodexAppServer as E}from"./codex-process.js";import{codexAcpSessionUpdatesFromNotification as $}from"./events.js";const A=6e4,D=128*1024,L=2*1024*1024,j=5*1024*1024;async function X(t){const e=t.sessionId.trim();if(!e)throw new Error("Codex Session ID 不能为空。");const n=Math.max(1,Math.min(20,Math.trunc(t.turnLimit))),r=E(process.cwd()),o=new k(r.child,{peerName:"Codex app-server"});try{await o.request("initialize",{clientInfo:{name:"aica-session-import",version:"0.1.0"},capabilities:{experimentalApi:!0,requestAttestation:!1}},A);const s=p(await o.request("thread/read",{threadId:e,includeTurns:!1},A))||{},a=p(s.thread);if(!a||i(a.id)!==e)throw new Error(`找不到 Codex Session:${e}`);const d=i(a.cwd);if(!d||!h.existsSync(d)||!h.statSync(d).isDirectory())throw new Error(`Codex Session 的工作目录不存在:${d||"unknown"}`);const u=t.detailMode==="diagnostic"?"full":"summary",f=p(await o.request("thread/turns/list",{threadId:e,limit:n,sortDirection:"desc",itemsView:u},A))||{};return R({thread:a,turns:Array.isArray(f.data)?f.data:[]},{...t,sessionId:e,turnLimit:n})}finally{await C(r.child).catch(()=>{})}}function R(t,e){const n=p(t.thread)||{},r=e.sessionId.trim(),o=Math.max(1,Math.min(20,Math.trunc(e.turnLimit))),s=i(n.cwd);if(!s||!h.existsSync(s)||!h.statSync(s).isDirectory())throw new Error(`Codex Session 的工作目录不存在:${s||"unknown"}`);const a=t.turns.map(p).filter(g).slice(0,o).reverse(),d=V(e.detailMode==="diagnostic"?j:L),u=a.map(c=>({sourceTurn:c,importedTurn:B(c,d)})).filter(c=>!!c.importedTurn);if(e.detailMode==="diagnostic")for(const{sourceTurn:c,importedTurn:w}of u){const M=(Array.isArray(c.items)?c.items:[]).map(p).filter(g);w.events=M.flatMap((S,I)=>N(S,w.startedAtMs+I,e.diffEnabled,d))}const f=u.map(c=>c.importedTurn),m=i(n.preview);return{provider:"codex",providerSessionId:r,cwd:h.realpathSync(s),title:l(i(n.name)||m||"Codex 历史会话",120),preview:l(m,240),sourceCreatedAtMs:y(n.createdAt),sourceUpdatedAtMs:y(n.updatedAt),sourceCliVersion:l(i(n.cliVersion),80),detailMode:e.detailMode,turnLimit:o,turns:f,truncated:d.truncated}}function B(t,e){const n=i(t.id);if(!n)return null;const r=(Array.isArray(t.items)?t.items:[]).map(p).filter(g),o=e.takeText(r.filter(u=>u.type==="userMessage").map(U).filter(Boolean).join(`
2
+
3
+ `)),s=e.takeText(r.filter(u=>u.type==="agentMessage"&&(!u.phase||u.phase==="final_answer")).map(u=>i(u.text)).filter(Boolean).join(`
4
+
5
+ `)),a=y(t.startedAt)||Date.now(),d=y(t.completedAt)||a;return!o&&!s?null:{sourceTurnId:n,status:l(i(t.status)||"completed",40),startedAtMs:a,completedAtMs:Math.max(a,d),userText:o,assistantText:s,events:[]}}function N(t,e,n,r){const o=i(t.type),s=i(t.id);if(o==="reasoning"){const f=r.takeText(P(t.summary).join(`
6
+ `));return f?r.takeEvent({eventType:"agent_thought_chunk",label:"thought",text:f,raw:{method:"history/item",params:{itemId:s},update:{sessionUpdate:"agent_thought_chunk",content:{type:"text",text:f}}},atMs:e}):[]}if(o==="plan"){const f=r.takeText(i(t.text)),m=b({sessionUpdate:"plan",planId:s,markdown:f});if(!m)return[];const c=_({update:m,method:"history/item",params:{itemId:s},atMs:e});return r.takeEvent({eventType:c.type,label:c.label,...c.text?{text:c.text}:{},...c.status?{status:c.status}:{},...c.toolCallId?{toolCallId:c.toolCallId}:{},raw:c.raw,atMs:c.atMs})}if(["userMessage","agentMessage","hookPrompt"].includes(o))return[];const a=q(t,n),d=$("item/completed",{item:a})[0];if(!d)return[];const u=i(d.status)||O(i(t.status));return r.takeEvent({eventType:"tool_call_update",label:z(o,a),...u?{status:u}:{},...s?{toolCallId:s}:{},raw:{method:"history/item",params:{item:a},update:d},atMs:e})}function U(t){return(Array.isArray(t.content)?t.content:[]).map(n=>{const r=p(n);return r?r.type==="text"?i(r.text):r.type==="localImage"?`[历史图片:${i(r.path)||"本地文件未导入"}]`:r.type==="image"?"[历史图片:远程内容未导入]":r.type==="skill"?`[Skill:${i(r.name)||i(r.path)}]`:r.type==="mention"?`[引用:${i(r.name)||i(r.path)}]`:"[历史内容未导入]":""}).filter(Boolean).join(`
7
+ `)}function q(t,e){const n=x(t,65536);return!e&&n.type==="fileChange"&&Array.isArray(n.changes)&&(n.changes=n.changes.map(r=>{const o=p(r);return o?{path:o.path,kind:o.kind,diff:""}:r})),n}function z(t,e){return t==="commandExecution"?l(i(e.command)||"执行命令",160):t==="fileChange"?"文件修改":t==="webSearch"?l(i(e.query)?`Web Search: ${i(e.query)}`:"Web Search",160):t==="mcpToolCall"?l(`MCP: ${i(e.server)}.${i(e.tool)}`,160):t==="dynamicToolCall"?l(i(e.tool)||"工具调用",160):t==="imageView"?l(`查看图片:${i(e.path)}`,160):t==="imageGeneration"?"生成图片":l(t||"过程记录",160)}function O(t){const e=t.toLowerCase();return["completed","success","succeeded"].includes(e)?"completed":["failed","error","declined"].includes(e)?"failed":["cancelled","canceled"].includes(e)?"cancelled":e||"completed"}function V(t){let e=t,n=0;const r={truncated:!1,takeText(o){const s=l(o,D);if(s.length<o.length&&(r.truncated=!0),e<=0)return s&&(r.truncated=!0),"";const a=s.slice(0,e);return a.length<s.length&&(r.truncated=!0),e-=a.length,a},takeEvent(o){if(n>=300)return r.truncated=!0,[];const s=JSON.stringify(o).length;return s>e?(r.truncated=!0,[]):(e-=s,n+=1,[o])}};return r}function x(t,e,n=0){if(!t||typeof t!="object"||Array.isArray(t))return{};const r={};for(const[o,s]of Object.entries(t))typeof s=="string"?r[o]=l(s,e):Array.isArray(s)?r[o]=s.slice(0,100).map(a=>T(a,e,n+1)):r[o]=T(s,e,n+1);return r}function T(t,e,n){return n>8?"[内容层级过深,已截断]":typeof t=="string"?l(t,e):Array.isArray(t)?t.slice(0,100).map(r=>T(r,e,n+1)):t&&typeof t=="object"?x(t,e,n):t}function p(t){return t&&typeof t=="object"&&!Array.isArray(t)?t:null}function g(t){return!!t}function i(t){return typeof t=="string"?t.trim():""}function P(t){return Array.isArray(t)?t.filter(e=>typeof e=="string"&&!!e.trim()):[]}function y(t){const e=Number(t);return Number.isFinite(e)&&e>0?Math.trunc(e*1e3):0}function l(t,e){return t.length<=e?t:t.slice(0,e)}export{R as buildCodexSessionImport,X as extractCodexSession};
@@ -1 +1 @@
1
- import m from"node:fs";import{promptBlocksToAcpContent as S}from"../../client/acp-content.js";import{eventFromAcpSessionUpdate as y,statusFromAcpSessionUpdate as C}from"../../client/acp-events.js";import{defaultAcpRuntimePool as I}from"../../client/acp-runtime-pool.js";import{resolveCodexAcpLaunch as v}from"./launch.js";import{CodexPromptPerformanceTracker as A,readCodexContextMaintenancePolicy as x}from"./context-maintenance.js";const P={sessionResume:!0,imageInput:!0,fileAttachment:!0,filesystem:!0,terminal:!0,permissionRequest:!0,configOptions:!0,usage:!0,contextUsage:!0,contextCompaction:!0,plan:!0,diff:!0};class F{id="codex-acp";name="Codex ACP";capabilities=P;async createSession(e){return new k(e.cwd,e.providerSessionId??null,e.timeoutMs)}}class k{cwd;providerSessionId;defaultTimeoutMs;lease=null;sessionResponse=null;statusCallback=null;updateCallback=null;permissionCallback=null;userInputCallback=null;abortHandler=null;statusTimeline=[];updates=[];content="";performanceTracker=null;constructor(e,s,t=q()){this.cwd=e,this.providerSessionId=s,this.defaultTimeoutMs=t}async sendPrompt(e){if(!m.existsSync(e.cwd)||!m.statSync(e.cwd).isDirectory())throw new Error(`Project root does not exist or is not a directory: ${e.cwd}`);const s=Date.now();this.statusCallback=e.onStatus??null,this.updateCallback=e.onUpdate??null,this.permissionCallback=e.onPermissionRequest??null,this.userInputCallback=e.onUserInputRequest??null,this.attachAbortSignal(e.signal),this.content="",this.updates.length=0,this.statusTimeline.length=0,this.performanceTracker=new A(s);let t=null,n=null,r=this.providerSessionId,i,c=null;try{this.emitStatus("initializing","Codex ACP 启动中","codex-acp");const a=await this.acquireRuntime(e.cwd);this.lease=a,t=a.client,n=a.started,await this.initialize(a),r=await this.establishSession(a,e),await this.applyConfig(t,r,e),this.emitStatus("thinking","思考中"),c=this.startSlowStatusTimer(),i=await this.promptWithAbort(t,{sessionId:r,prompt:S(e.prompt,e.promptBlocks),timeoutMs:e.timeoutMs??this.defaultTimeoutMs,signal:e.signal});const d=b(i);d&&d!==r&&(r=d,this.providerSessionId=d,a.setProviderSession({providerSessionId:d,sessionResponse:this.sessionResponse}))}finally{c&&clearInterval(c),this.abortHandler&&e.signal&&(e.signal.removeEventListener("abort",this.abortHandler),this.abortHandler=null),this.releaseLease()}return this.emitStatus("completed","已完成"),{providerSessionId:r,cliType:"builtin",agentType:"codex",effectiveConfig:{model:e.model??null,mode:e.mode??null,configOptionValues:e.configOptionValues??null,command:n.command,processPid:n.child.pid??null,provider:"codex-acp",protocol:"acp"},command:n.command,args:n.args,cwd:e.cwd,exitCode:n.child.exitCode,signal:n.child.signalCode,durationMs:Date.now()-s,stdout:t.rpc.stdout,stderr:t.rpc.stderr,content:this.content.trim(),updates:[...this.updates],statusTimeline:[...this.statusTimeline],performance:this.performanceTracker?.snapshot(),contextUsage:O(i),promptResponse:i}}async compactContext(e){if(!m.existsSync(e.cwd)||!m.statSync(e.cwd).isDirectory())throw new Error(`Project root does not exist or is not a directory: ${e.cwd}`);const s=Date.now();this.statusCallback=e.onStatus??null,this.updateCallback=e.onUpdate??null,this.attachAbortSignal(e.signal);let t=this.providerSessionId,n;try{this.emitStatus("initializing","Codex ACP 启动中","codex-acp");const i=await this.acquireRuntime(e.cwd);this.lease=i,await this.initialize(i),t=await this.establishSession(i,e),await this.applyConfig(i.client,t,e);const c=E(e.timeoutMs);n=await this.compactWithAbort(i.client,{sessionId:t,allowRollover:e.allowRollover===!0,timeoutMs:c,signal:e.signal});const a=b(n);a&&a!==t&&(t=a,this.providerSessionId=a,i.setProviderSession({providerSessionId:a,sessionResponse:this.sessionResponse}))}catch(i){if(this.lease&&T(i,e.signal)){const c=this.lease;this.lease=null,await c.dispose().catch(()=>{})}throw this.emitStatus("failed","上下文压缩失败",i instanceof Error?i.message:String(i)),i}finally{this.abortHandler&&e.signal&&(e.signal.removeEventListener("abort",this.abortHandler),this.abortHandler=null),this.releaseLease()}const r=l(n)??{};return{providerSessionId:t||this.providerSessionId||"",contextUsage:w(r.contextUsage),maintenance:l(r.maintenance)??{},durationMs:Date.now()-s}}async cancel(){this.providerSessionId&&this.lease?.client.cancel(this.providerSessionId)}async close(){this.statusCallback=null,this.updateCallback=null,this.permissionCallback=null,this.userInputCallback=null,this.abortHandler&&(this.abortHandler=null),this.releaseLease()}async acquireRuntime(e){const s=v();return I.acquire({providerId:"codex-acp",cwd:e,providerSessionId:this.providerSessionId,command:s.command,args:s.args,onSessionUpdate:t=>this.handleSessionUpdate(t),onClientRequest:async(t,n)=>{if(t==="session/request_permission")return this.requestPermission(n);if(t==="elicitation/create"||t==="session/request_user_input")return this.requestUserInput(n);throw new Error(`Unsupported Codex ACP client request: ${t}`)}})}async initialize(e){e.initialized||(await e.client.initialize(6e4),e.markInitialized())}async establishSession(e,s){const t=e.client,n=M(s);if(e.providerSessionId&&e.sessionEstablished)return this.providerSessionId=e.providerSessionId,this.sessionResponse=e.sessionResponse,e.providerSessionId;if(this.providerSessionId)try{this.emitStatus("resuming","Codex ACP 会话恢复中","session/load");const c=await t.loadSession({sessionId:this.providerSessionId,cwd:s.cwd,config:n},6e4);this.sessionResponse=l(c);const a=f(c)||this.providerSessionId;return this.providerSessionId=a,e.setProviderSession({providerSessionId:a,sessionResponse:this.sessionResponse}),a}catch{this.providerSessionId=null}this.emitStatus("acp","Codex ACP 会话创建中","session/new");const r=await t.newSession({cwd:s.cwd,config:n},12e4);this.sessionResponse=l(r);const i=f(r);if(!i)throw new Error("Codex ACP session/new did not return sessionId");return this.providerSessionId=i,e.setProviderSession({providerSessionId:i,sessionResponse:this.sessionResponse}),i}async applyConfig(e,s,t){t.model&&await e.setSessionConfigOption({sessionId:s,configId:"model",value:t.model},6e4).catch(()=>{});const n=h(t.mode);n&&await e.setSessionConfigOption({sessionId:s,configId:"approvalPolicy",value:n},6e4).catch(()=>{});const r=p(t.mode);r&&await e.setSessionConfigOption({sessionId:s,configId:"sandbox",value:r},6e4).catch(()=>{});for(const[i,c]of Object.entries(t.configOptionValues??{}))["model","mode","sandbox","approvalPolicy"].includes(i)||await e.setSessionConfigOption({sessionId:s,configId:i,value:c},6e4).catch(()=>{})}handleSessionUpdate(e){this.performanceTracker?.recordEvent(),this.updates.push({method:"session/update",params:e});const s=y(e);s&&(s.type==="agent_message_chunk"&&(this.content+=s.text??""),this.updateCallback?.(s));const t=C(e);t&&this.emitStatus(t.phase,t.label,t.detail,t.updateType)}startSlowStatusTimer(){const e=x().slowEventThresholdMs,s=setInterval(()=>{const t=this.performanceTracker,n=Date.now();if(!t?.shouldReportSlow(n,e))return;const r=t.snapshot(n),i=Math.max(1,Math.round(r.silentForMs/6e4));this.emitStatus("thinking","模型长时间处理中",`已 ${i} 分钟没有收到新的 ACP 事件`,"aca.performance.slow")},Math.min(3e4,Math.max(1e3,Math.floor(e/4))));return s.unref(),s}attachAbortSignal(e){if(e){if(this.abortHandler=()=>{this.cancel()},e.aborted)throw new Error("Codex ACP prompt cancelled");e.addEventListener("abort",this.abortHandler,{once:!0})}}async promptWithAbort(e,s){const t=e.prompt({sessionId:s.sessionId,prompt:s.prompt},s.timeoutMs);if(!s.signal)return t;if(s.signal.aborted)throw await this.lease?.dispose().catch(()=>{}),new Error("Codex ACP prompt cancelled");let n=null;const r=new Promise((i,c)=>{n=()=>{e.cancel(s.sessionId),this.lease?.dispose().catch(()=>{}),c(new Error("Codex ACP prompt cancelled"))},s.signal?.addEventListener("abort",n,{once:!0})});try{return await Promise.race([t,r])}finally{n&&s.signal.removeEventListener("abort",n)}}async compactWithAbort(e,s){const t=e.compactSession({sessionId:s.sessionId,allowRollover:s.allowRollover,timeoutMs:s.timeoutMs},s.timeoutMs);if(!s.signal)return t;if(s.signal.aborted)throw await this.lease?.dispose().catch(()=>{}),new Error("Codex ACP 上下文压缩已取消");let n=null;const r=new Promise((i,c)=>{n=()=>{e.cancel(s.sessionId),this.lease?.dispose().catch(()=>{}),c(new Error("Codex ACP 上下文压缩已取消"))},s.signal?.addEventListener("abort",n,{once:!0})});try{return await Promise.race([t,r])}finally{n&&s.signal.removeEventListener("abort",n)}}async requestPermission(e){if(!this.permissionCallback)return{outcome:{outcome:"cancelled"}};const s=e&&typeof e=="object"&&!Array.isArray(e)?e:{},t=s.request&&typeof s.request=="object"&&!Array.isArray(s.request)?s.request:s,r=(Array.isArray(t.options)?t.options:[]).map((i,c)=>{const a=i&&typeof i=="object"&&!Array.isArray(i)?i:{};return{optionId:String(a.optionId??a.id??c),kind:String(a.kind??"choice"),...typeof a.label=="string"?{label:a.label}:{}}});return this.emitStatus("requestPermission","等待授权",R(t),"session/request_permission"),this.permissionCallback({requestId:String(t.requestId??t.id??`codex-acp-permission-${Date.now()}`),params:e,options:r})}async requestUserInput(e){if(!this.userInputCallback)return{outcome:{outcome:"cancelled"}};const s=e&&typeof e=="object"&&!Array.isArray(e)?e:{},t=s.request&&typeof s.request=="object"&&!Array.isArray(s.request)?s.request:s;return this.emitStatus("requestPermission","等待输入",_(t),"elicitation/create"),this.userInputCallback({requestId:String(t.requestId??t.id??`codex-acp-input-${Date.now()}`),prompt:String(t.prompt??t.message??t.question??""),params:e,...typeof t.defaultValue=="string"?{defaultValue:t.defaultValue}:{}})}emitStatus(e,s,t,n){const r={phase:e,label:s,...t?{detail:t}:{},...n?{updateType:n}:{},atMs:Date.now()},i=this.statusTimeline[this.statusTimeline.length-1];i&&i.phase===r.phase&&i.label===r.label&&i.detail===r.detail&&i.updateType===r.updateType||(this.statusTimeline.push(r),this.statusCallback?.(r))}releaseLease(){this.lease?.release(),this.lease=null}}function T(o,e){if(e?.aborted)return!0;const s=o instanceof Error?o.message:String(o||"");return/compact.*timed out|request timed out.*compact|adapter closed|aborted|cancelled/i.test(s)}function f(o){if(!o||typeof o!="object"||Array.isArray(o))return null;const e=o.sessionId;return typeof e=="string"&&e.trim()?e.trim():null}function l(o){return o&&typeof o=="object"&&!Array.isArray(o)?o:null}function p(o){const e=String(o||"").trim();return e?e==="read-only"?"read-only":["agent-full-access","full-access","bypassPermissions","danger-full-access"].includes(e)?"danger-full-access":"workspace-write":null}function h(o){const e=String(o||"").trim();return e?["agent-full-access","full-access","bypassPermissions","danger-full-access"].includes(e)?"never":e==="acceptEdits"||e==="accept-edits"?"on-failure":"on-request":null}function M(o){return{...o.model?{model:o.model}:{},...h(o.mode)?{approvalPolicy:h(o.mode)}:{},...p(o.mode)?{sandbox:p(o.mode)}:{},...o.configOptionValues??{}}}function R(o){const e=o.params&&typeof o.params=="object"&&!Array.isArray(o.params)?o.params:o;for(const s of["command","cwd","reason","grantRoot","method"]){const t=e[s];if(typeof t=="string"&&t.trim())return g(t)}}function _(o){for(const e of["prompt","message","question","title"]){const s=o[e];if(typeof s=="string"&&s.trim())return g(s)}}function g(o){const e=o.replace(/\s+/g," ").trim();return e.length>120?`${e.slice(0,117)}...`:e}function q(){const o=Number.parseInt(process.env.ACA_CODEX_ACP_PROMPT_TIMEOUT_MS??process.env.ACA_ACP_PROMPT_TIMEOUT_MS??"",10);return Number.isInteger(o)&&o>=3e4?o:360*60*1e3}function E(o){const e=Number.isInteger(o)?Number(o):Number.parseInt(process.env.ACA_CODEX_ACP_COMPACT_TIMEOUT_MS??"",10);return Number.isFinite(e)&&e>=3e4?Math.min(e,6e5):12e4}function b(o){if(!o||typeof o!="object"||Array.isArray(o))return null;const e=o;return typeof e.sessionId=="string"&&e.sessionId.trim()?e.sessionId:null}function O(o){const e=l(o),s=l(e?._meta);return w(s?.acaContextUsage)}function w(o){const e=l(o);if(!e)return null;const s=u(e.contextWindow),t=u(e.usedTokens),n=u(e.inputTokens),r=String(e.state||"unknown"),i=["active","compacted","new_thread"].includes(r)?r:"unknown";return{usedTokens:t,inputTokens:n,contextWindow:s,ratio:s>0?Math.max(0,u(e.ratio)||t/s):0,observedAtMs:u(e.observedAtMs)||Date.now(),state:i,maintenanceAction:typeof e.maintenanceAction=="string"?e.maintenanceAction:null}}function u(o){return typeof o=="number"&&Number.isFinite(o)&&o>=0?o:0}export{F as CodexAcpProvider,P as codexAcpCapabilities};
1
+ import m from"node:fs";import{promptBlocksToAcpContent as b}from"../../client/acp-content.js";import{eventFromAcpSessionUpdate as y,statusFromAcpSessionUpdate as C}from"../../client/acp-events.js";import{defaultAcpRuntimePool as I}from"../../client/acp-runtime-pool.js";import{resolveCodexAcpLaunch as v}from"./launch.js";import{CodexPromptPerformanceTracker as A,readCodexContextMaintenancePolicy as x}from"./context-maintenance.js";const P={sessionResume:!0,imageInput:!0,fileAttachment:!0,filesystem:!0,terminal:!0,permissionRequest:!0,configOptions:!0,usage:!0,contextUsage:!0,contextCompaction:!0,plan:!0,diff:!0};class F{id="codex-acp";name="Codex ACP";capabilities=P;async createSession(e){return new k(e.cwd,e.providerSessionId??null,e.timeoutMs)}}class k{cwd;providerSessionId;defaultTimeoutMs;lease=null;sessionResponse=null;statusCallback=null;updateCallback=null;permissionCallback=null;userInputCallback=null;abortHandler=null;statusTimeline=[];updates=[];content="";performanceTracker=null;constructor(e,s,t=q()){this.cwd=e,this.providerSessionId=s,this.defaultTimeoutMs=t}async sendPrompt(e){if(!m.existsSync(e.cwd)||!m.statSync(e.cwd).isDirectory())throw new Error(`Project root does not exist or is not a directory: ${e.cwd}`);const s=Date.now();this.statusCallback=e.onStatus??null,this.updateCallback=e.onUpdate??null,this.permissionCallback=e.onPermissionRequest??null,this.userInputCallback=e.onUserInputRequest??null,this.attachAbortSignal(e.signal),this.content="",this.updates.length=0,this.statusTimeline.length=0,this.performanceTracker=new A(s);let t=null,r=null,n=this.providerSessionId,i,c=null;try{this.emitStatus("initializing","Codex ACP 启动中","codex-acp");const a=await this.acquireRuntime(e.cwd);this.lease=a,t=a.client,r=a.started,await this.initialize(a),n=await this.establishSession(a,e),await this.applyConfig(t,n,e),this.emitStatus("thinking","思考中"),c=this.startSlowStatusTimer(),i=await this.promptWithAbort(t,{sessionId:n,prompt:b(e.prompt,e.promptBlocks),timeoutMs:e.timeoutMs??this.defaultTimeoutMs,signal:e.signal});const d=w(i);d&&d!==n&&(n=d,this.providerSessionId=d,a.setProviderSession({providerSessionId:d,sessionResponse:this.sessionResponse}))}finally{c&&clearInterval(c),this.abortHandler&&e.signal&&(e.signal.removeEventListener("abort",this.abortHandler),this.abortHandler=null),this.releaseLease()}return this.emitStatus("completed","已完成"),{providerSessionId:n,cliType:"builtin",agentType:"codex",effectiveConfig:{model:e.model??null,mode:e.mode??null,configOptionValues:e.configOptionValues??null,command:r.command,processPid:r.child.pid??null,provider:"codex-acp",protocol:"acp"},command:r.command,args:r.args,cwd:e.cwd,exitCode:r.child.exitCode,signal:r.child.signalCode,durationMs:Date.now()-s,stdout:t.rpc.stdout,stderr:t.rpc.stderr,content:this.content.trim(),updates:[...this.updates],statusTimeline:[...this.statusTimeline],performance:this.performanceTracker?.snapshot(),contextUsage:O(i),promptResponse:i}}async compactContext(e){if(!m.existsSync(e.cwd)||!m.statSync(e.cwd).isDirectory())throw new Error(`Project root does not exist or is not a directory: ${e.cwd}`);const s=Date.now();this.statusCallback=e.onStatus??null,this.updateCallback=e.onUpdate??null,this.attachAbortSignal(e.signal);let t=this.providerSessionId,r;try{this.emitStatus("initializing","Codex ACP 启动中","codex-acp");const i=await this.acquireRuntime(e.cwd);this.lease=i,await this.initialize(i),t=await this.establishSession(i,e),await this.applyConfig(i.client,t,e);const c=E(e.timeoutMs);r=await this.compactWithAbort(i.client,{sessionId:t,allowRollover:e.allowRollover===!0,timeoutMs:c,signal:e.signal});const a=w(r);a&&a!==t&&(t=a,this.providerSessionId=a,i.setProviderSession({providerSessionId:a,sessionResponse:this.sessionResponse}))}catch(i){if(this.lease&&T(i,e.signal)){const c=this.lease;this.lease=null,await c.dispose().catch(()=>{})}throw this.emitStatus("failed","上下文压缩失败",i instanceof Error?i.message:String(i)),i}finally{this.abortHandler&&e.signal&&(e.signal.removeEventListener("abort",this.abortHandler),this.abortHandler=null),this.releaseLease()}const n=l(r)??{};return{providerSessionId:t||this.providerSessionId||"",contextUsage:S(n.contextUsage),maintenance:l(n.maintenance)??{},durationMs:Date.now()-s}}async cancel(){this.providerSessionId&&this.lease?.client.cancel(this.providerSessionId)}async close(){this.statusCallback=null,this.updateCallback=null,this.permissionCallback=null,this.userInputCallback=null,this.abortHandler&&(this.abortHandler=null),this.releaseLease()}async acquireRuntime(e){const s=v();return I.acquire({providerId:"codex-acp",cwd:e,providerSessionId:this.providerSessionId,command:s.command,args:s.args,onSessionUpdate:t=>this.handleSessionUpdate(t),onClientRequest:async(t,r)=>{if(t==="session/request_permission")return this.requestPermission(r);if(t==="elicitation/create"||t==="session/request_user_input")return this.requestUserInput(r);throw new Error(`Unsupported Codex ACP client request: ${t}`)}})}async initialize(e){e.initialized||(await e.client.initialize(6e4),e.markInitialized())}async establishSession(e,s){const t=e.client,r=M(s);if(e.providerSessionId&&e.sessionEstablished)return this.providerSessionId=e.providerSessionId,this.sessionResponse=e.sessionResponse,e.providerSessionId;if(this.providerSessionId)try{this.emitStatus("resuming","Codex ACP 会话恢复中","session/load");const c=await t.loadSession({sessionId:this.providerSessionId,cwd:s.cwd,config:r},6e4);this.sessionResponse=l(c);const a=f(c)||this.providerSessionId;return this.providerSessionId=a,e.setProviderSession({providerSessionId:a,sessionResponse:this.sessionResponse}),a}catch(c){if(s.requireSessionResume){const a=c instanceof Error?c.message:String(c);throw new Error(`无法恢复导入的 Codex ACP Session ${this.providerSessionId}:${a}`)}this.providerSessionId=null}this.emitStatus("acp","Codex ACP 会话创建中","session/new");const n=await t.newSession({cwd:s.cwd,config:r},12e4);this.sessionResponse=l(n);const i=f(n);if(!i)throw new Error("Codex ACP session/new did not return sessionId");return this.providerSessionId=i,e.setProviderSession({providerSessionId:i,sessionResponse:this.sessionResponse}),i}async applyConfig(e,s,t){t.model&&await e.setSessionConfigOption({sessionId:s,configId:"model",value:t.model},6e4).catch(()=>{});const r=h(t.mode);r&&await e.setSessionConfigOption({sessionId:s,configId:"approvalPolicy",value:r},6e4).catch(()=>{});const n=p(t.mode);n&&await e.setSessionConfigOption({sessionId:s,configId:"sandbox",value:n},6e4).catch(()=>{});for(const[i,c]of Object.entries(t.configOptionValues??{}))["model","mode","sandbox","approvalPolicy"].includes(i)||await e.setSessionConfigOption({sessionId:s,configId:i,value:c},6e4).catch(()=>{})}handleSessionUpdate(e){this.performanceTracker?.recordEvent(),this.updates.push({method:"session/update",params:e});const s=y(e);s&&(s.type==="agent_message_chunk"&&(this.content+=s.text??""),this.updateCallback?.(s));const t=C(e);t&&this.emitStatus(t.phase,t.label,t.detail,t.updateType)}startSlowStatusTimer(){const e=x().slowEventThresholdMs,s=setInterval(()=>{const t=this.performanceTracker,r=Date.now();if(!t?.shouldReportSlow(r,e))return;const n=t.snapshot(r),i=Math.max(1,Math.round(n.silentForMs/6e4));this.emitStatus("thinking","模型长时间处理中",`已 ${i} 分钟没有收到新的 ACP 事件`,"aca.performance.slow")},Math.min(3e4,Math.max(1e3,Math.floor(e/4))));return s.unref(),s}attachAbortSignal(e){if(e){if(this.abortHandler=()=>{this.cancel()},e.aborted)throw new Error("Codex ACP prompt cancelled");e.addEventListener("abort",this.abortHandler,{once:!0})}}async promptWithAbort(e,s){const t=e.prompt({sessionId:s.sessionId,prompt:s.prompt},s.timeoutMs);if(!s.signal)return t;if(s.signal.aborted)throw await this.lease?.dispose().catch(()=>{}),new Error("Codex ACP prompt cancelled");let r=null;const n=new Promise((i,c)=>{r=()=>{e.cancel(s.sessionId),this.lease?.dispose().catch(()=>{}),c(new Error("Codex ACP prompt cancelled"))},s.signal?.addEventListener("abort",r,{once:!0})});try{return await Promise.race([t,n])}finally{r&&s.signal.removeEventListener("abort",r)}}async compactWithAbort(e,s){const t=e.compactSession({sessionId:s.sessionId,allowRollover:s.allowRollover,timeoutMs:s.timeoutMs},s.timeoutMs);if(!s.signal)return t;if(s.signal.aborted)throw await this.lease?.dispose().catch(()=>{}),new Error("Codex ACP 上下文压缩已取消");let r=null;const n=new Promise((i,c)=>{r=()=>{e.cancel(s.sessionId),this.lease?.dispose().catch(()=>{}),c(new Error("Codex ACP 上下文压缩已取消"))},s.signal?.addEventListener("abort",r,{once:!0})});try{return await Promise.race([t,n])}finally{r&&s.signal.removeEventListener("abort",r)}}async requestPermission(e){if(!this.permissionCallback)return{outcome:{outcome:"cancelled"}};const s=e&&typeof e=="object"&&!Array.isArray(e)?e:{},t=s.request&&typeof s.request=="object"&&!Array.isArray(s.request)?s.request:s,n=(Array.isArray(t.options)?t.options:[]).map((i,c)=>{const a=i&&typeof i=="object"&&!Array.isArray(i)?i:{};return{optionId:String(a.optionId??a.id??c),kind:String(a.kind??"choice"),...typeof a.label=="string"?{label:a.label}:{}}});return this.emitStatus("requestPermission","等待授权",R(t),"session/request_permission"),this.permissionCallback({requestId:String(t.requestId??t.id??`codex-acp-permission-${Date.now()}`),params:e,options:n})}async requestUserInput(e){if(!this.userInputCallback)return{outcome:{outcome:"cancelled"}};const s=e&&typeof e=="object"&&!Array.isArray(e)?e:{},t=s.request&&typeof s.request=="object"&&!Array.isArray(s.request)?s.request:s;return this.emitStatus("requestPermission","等待输入",_(t),"elicitation/create"),this.userInputCallback({requestId:String(t.requestId??t.id??`codex-acp-input-${Date.now()}`),prompt:String(t.prompt??t.message??t.question??""),params:e,...typeof t.defaultValue=="string"?{defaultValue:t.defaultValue}:{}})}emitStatus(e,s,t,r){const n={phase:e,label:s,...t?{detail:t}:{},...r?{updateType:r}:{},atMs:Date.now()},i=this.statusTimeline[this.statusTimeline.length-1];i&&i.phase===n.phase&&i.label===n.label&&i.detail===n.detail&&i.updateType===n.updateType||(this.statusTimeline.push(n),this.statusCallback?.(n))}releaseLease(){this.lease?.release(),this.lease=null}}function T(o,e){if(e?.aborted)return!0;const s=o instanceof Error?o.message:String(o||"");return/compact.*timed out|request timed out.*compact|adapter closed|aborted|cancelled/i.test(s)}function f(o){if(!o||typeof o!="object"||Array.isArray(o))return null;const e=o.sessionId;return typeof e=="string"&&e.trim()?e.trim():null}function l(o){return o&&typeof o=="object"&&!Array.isArray(o)?o:null}function p(o){const e=String(o||"").trim();return e?e==="read-only"?"read-only":["agent-full-access","full-access","bypassPermissions","danger-full-access"].includes(e)?"danger-full-access":"workspace-write":null}function h(o){const e=String(o||"").trim();return e?["agent-full-access","full-access","bypassPermissions","danger-full-access"].includes(e)?"never":e==="acceptEdits"||e==="accept-edits"?"on-failure":"on-request":null}function M(o){return{...o.model?{model:o.model}:{},...h(o.mode)?{approvalPolicy:h(o.mode)}:{},...p(o.mode)?{sandbox:p(o.mode)}:{},...o.configOptionValues??{}}}function R(o){const e=o.params&&typeof o.params=="object"&&!Array.isArray(o.params)?o.params:o;for(const s of["command","cwd","reason","grantRoot","method"]){const t=e[s];if(typeof t=="string"&&t.trim())return g(t)}}function _(o){for(const e of["prompt","message","question","title"]){const s=o[e];if(typeof s=="string"&&s.trim())return g(s)}}function g(o){const e=o.replace(/\s+/g," ").trim();return e.length>120?`${e.slice(0,117)}...`:e}function q(){const o=Number.parseInt(process.env.ACA_CODEX_ACP_PROMPT_TIMEOUT_MS??process.env.ACA_ACP_PROMPT_TIMEOUT_MS??"",10);return Number.isInteger(o)&&o>=3e4?o:360*60*1e3}function E(o){const e=Number.isInteger(o)?Number(o):Number.parseInt(process.env.ACA_CODEX_ACP_COMPACT_TIMEOUT_MS??"",10);return Number.isFinite(e)&&e>=3e4?Math.min(e,6e5):12e4}function w(o){if(!o||typeof o!="object"||Array.isArray(o))return null;const e=o;return typeof e.sessionId=="string"&&e.sessionId.trim()?e.sessionId:null}function O(o){const e=l(o),s=l(e?._meta);return S(s?.acaContextUsage)}function S(o){const e=l(o);if(!e)return null;const s=u(e.contextWindow),t=u(e.usedTokens),r=u(e.inputTokens),n=String(e.state||"unknown"),i=["active","compacted","new_thread"].includes(n)?n:"unknown";return{usedTokens:t,inputTokens:r,contextWindow:s,ratio:s>0?Math.max(0,u(e.ratio)||t/s):0,observedAtMs:u(e.observedAtMs)||Date.now(),state:i,maintenanceAction:typeof e.maintenanceAction=="string"?e.maintenanceAction:null}}function u(o){return typeof o=="number"&&Number.isFinite(o)&&o>=0?o:0}export{F as CodexAcpProvider,P as codexAcpCapabilities};
@@ -0,0 +1,3 @@
1
+ import{Command as a}from"commander";import s from"chalk";import f from"node:fs";import u from"node:path";import{spawn as P}from"node:child_process";import{randomUUID as C}from"node:crypto";import{getLogDir as l,getRuntimeStatePath as $}from"../core/paths.js";import{ensureDir as x,readJsonFile as S}from"../core/fs.js";import{isProcessAlive as r,readPidFile as m,removePidFile as d}from"../core/process.js";import{ACA_WORKER_INSTANCE_ENV as y}from"../core/process-identity.js";import{applyRuntimePathOptions as i}from"../core/runtime-options.js";const g=3e4;function G(){return new a("daemon").description("Run aca as a background daemon service").addCommand(c(new a("start").description("Start aca daemon in the background")).allowUnknownOption(!0).action(e=>{i(e);const o=m();if(o&&r(o)){console.log(`Daemon is already running (PID ${o}).`),process.exitCode=1;return}const n=w(h("start"));n.unref(),console.log(`Daemon started (PID ${n.pid})`),D()})).addCommand(c(new a("stop").description("Stop the running aca daemon")).action(async e=>{i(e);const o=m();if(!o){console.log("No daemon PID file found. Daemon is not running.");return}if(!r(o)){console.log(`Daemon process (PID ${o}) is not running. Cleaning up PID file.`),d();return}await I(o)?(d(),console.log(`Daemon stopped successfully (PID ${o}).`)):(console.log(`Daemon (PID ${o}) is still running. You may need to kill it manually: ${s.yellow(`kill -9 ${o}`)}`),process.exitCode=1)})).addCommand(c(new a("status").description("Show daemon status")).action(e=>{i(e);const o=S($()),n=m();if(n&&r(n)){console.log(s.green("● Daemon is running")),console.log(` PID: ${n}`),o?.phase&&console.log(` Phase: ${o.phase}`),o?.startupStage&&console.log(` Stage: ${o.startupStage}`);return}n?(console.log(s.red("● Daemon is not running (stale PID file)")),d()):console.log(s.red("● Daemon is not running")),process.exitCode=1})).addCommand(c(new a("logs").description("Show daemon logs")).option("-n, --lines <count>","number of lines to show","50").action(e=>{i(e);const o=Number.parseInt(e.lines,10)||50,n=E();if(!n){console.log(`No log files found in ${l()}`),process.exitCode=1;return}const t=f.readFileSync(n,"utf8").trimEnd().split(`
2
+ `);console.log(s.dim(`--- ${n} (last ${o} lines) ---`)),console.log(t.slice(-o).join(`
3
+ `))})).addCommand(c(new a("restart").description("Restart the aca daemon")).allowUnknownOption(!0).action(async e=>{i(e);const o=m();if(o&&r(o)&&!await I(o)){console.error(`Daemon restart aborted because PID ${o} did not stop within ${g}ms.`),process.exitCode=1;return}d();const n=w(h("restart"));n.unref(),console.log(`Daemon started (PID ${n.pid})`),D()}))}function c(e){return e.option("--aica-home <path>","Runtime home directory (defaults to ~/.aica)").option("--aica-config <path>","Config file path").option("--aca-home <path>","Deprecated alias for --aica-home").option("--aca-config <path>","Deprecated alias for --aica-config")}function h(e){const o=process.argv.findIndex((t,p)=>p>=2&&t==="daemon"),n=process.argv.findIndex((t,p)=>p>o&&t===e);return n>=0?process.argv.slice(n+1):[]}function w(e){const o=v();return P(o.command,[...o.prefixArgs,"start",...e],{detached:!0,stdio:"ignore",env:{...process.env,[y]:C()}})}function v(){if(process.env.ACA_SINGLE_FILE_WORKER==="1")return{command:process.execPath,prefixArgs:[]};const e=process.argv[1];if(!e)throw new Error("Unable to resolve the current ACA CLI entrypoint");return{command:process.execPath,prefixArgs:[u.resolve(e)]}}function D(){const e=A();console.log(`Use ${s.yellow(`${e} daemon status`)} to check status`),console.log(`Use ${s.yellow(`${e} daemon stop`)} to stop`),console.log(`Use ${s.yellow(`${e} daemon logs`)} to view logs`)}function A(){if(process.env.ACA_SINGLE_FILE_WORKER==="1")return process.execPath;const e=u.basename(process.argv[1]||"");return/worker-cli|aica/i.test(e)?"aica":"aca"}function E(){return x(l()),f.readdirSync(l()).filter(o=>o.endsWith(".log")).map(o=>u.join(l(),o)).sort().at(-1)??null}async function R(e,o){const n=Date.now();for(;Date.now()-n<o;){if(!r(e))return!0;await new Promise(t=>setTimeout(t,100))}return!r(e)}async function I(e,o=g){return r(e)?(process.kill(e,"SIGTERM"),R(e,o)):!0}export{G as createDaemonCommand,D as printStartTips,w as spawnDaemon,I as stopDaemonProcess};
@@ -1 +1 @@
1
- import{Command as k}from"commander";import b from"node:fs";import T from"node:os";import P from"node:path";import t from"node:process";import{randomUUID as R}from"node:crypto";import{Logger as E}from"../core/logger.js";import{loadCredentials as _,saveApiKeyCredential as N}from"../core/auth.js";import{getRuntimeStatePath as D}from"../core/paths.js";import{writeJsonFile as O}from"../core/fs.js";import{HEARTBEAT_INTERVAL_MS as $,startHeartbeatLoop as L}from"../core/heartbeat.js";import{startJobWorkerLoop as U}from"../core/job-worker.js";import{startFileTransferWorkerLoop as W}from"../core/file-transfer-worker.js";import{startMachineFilesystemWorkerLoop as M}from"../core/machine-filesystem-worker.js";import{cleanupOrphanedAcpProcesses as x}from"../core/acp-orphan-cleanup.js";import{defaultAcpRuntimePool as S}from"../acp/client/acp-runtime-pool.js";import{writePidFile as j,removePidFile as F}from"../core/process.js";import{applyRuntimePathOptions as K}from"../core/runtime-options.js";import{loadAcaConfig as V,updateAcaConfig as v}from"../core/aca-config.js";import{acaServerRequest as H}from"../core/aca-server-client.js";import{ACA_WORKER_INSTANCE_ENV as A,ACA_WORKER_PID_ENV as G,ACA_WORKER_START_TICKS_ENV as J,readProcessStartTicks as q}from"../core/process-identity.js";function Se(){return new k("start").description("Start agent service in native mode").option("--aica-home <path>","Runtime home directory (defaults to ~/.aica or AICA_HOME)").option("--aica-config <path>","Config file path (defaults to <aica-home>/config.json)").option("--aca-home <path>","Deprecated alias for --aica-home").option("--aca-config <path>","Deprecated alias for --aica-config").option("--server <url>","ACA Server URL override").option("--machine-id <id>","Machine ID override for this runtime").option("--machine-name <name>","Machine name to register (defaults to hostname)").option("--cli-types <types...>","Specify CLI types to register, `claude` or `codex`").option("--debug","enable debug output").option("--heartbeat-log","output current timestamp every 5 seconds").option("--auth <api-key>","Use a CLI API key for non-interactive authentication").option("--token <enrollment-token>","Use a one-time Web-generated token to register this machine").action(async e=>{await B(e)})}async function B(e){K(e);const s=t.env[A]||R(),o=q(t.pid)||"";t.env[A]=s,t.env[G]=String(t.pid),t.env[J]=o;const a=new E("start",e.debug?"debug":"info"),C=V(),l={...e.server?.trim()?{serverUrl:e.server.trim()}:{},...e.machineId?{machineId:e.machineId.trim()}:{},...e.machineName?{machineName:e.machineName.trim()}:{}};let n=Object.keys(l).length>0?v(l):C;const m=e.machineName?.trim()||n.machineName||T.hostname();if(e.token?.trim()){const r=await H("POST","/api/auth/machine-enroll",{token:e.token.trim(),machineId:n.machineId,machineName:m},{serverUrl:n.serverUrl});n=v({machineId:r.machineId,machineName:m,token:r.token,username:r.username}),a.info(`Machine enrollment completed for user ${r.username}`)}const y=e.auth?N(e.auth,m):_();j(t.pid),w({pid:t.pid,workerInstanceId:s,workerStartTicks:o,phase:"running",startupStage:"local-compatible",machineId:n.machineId||y?.machine?.machineId||"local-machine",machineName:m,cliTypes:e.cliTypes??z(),startedAt:new Date().toISOString(),heartbeatIntervalMs:$,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 ${m}`),a.info(`PID ${t.pid}`);const c=await x();c.terminated>0&&a.warn(`terminated ${c.terminated} orphaned ACP process(es): ${c.pids.join(",")}`);let f=!1,p,h,u,g;const I=async r=>{if(f)return;f=!0,a.info("Shutting down"),p&&clearInterval(p),u?.stop(),g?.stop(),await h?.stop().catch(i=>a.warn(`job worker shutdown failed: ${i instanceof Error?i.message:String(i)}`));const d=S.stats();await S.disposeAll().catch(i=>a.warn(`ACP runtime shutdown failed: ${i instanceof Error?i.message:String(i)}`)),a.info(`ACP runtimes stopped total=${d.total} inUse=${d.inUse} idle=${d.idle}`),w({pid:t.pid,phase:"stopped",stoppedAt:new Date().toISOString(),issues:[]}),F(),t.exit(r)};t.once("SIGINT",()=>{I(130)}),t.once("SIGTERM",()=>{I(0)}),e.heartbeatLog&&setInterval(()=>a.info(`heartbeat: ${new Date().toISOString()}`),5e3),p=L(a),h=U(a),u=W(a),g=M(a),await new Promise(()=>{})}function z(){return["codex","claude"].filter(e=>Q(e))}function Q(e){const s=t.env.PATH||"";for(const o of s.split(":"))try{if(o&&b.existsSync(P.join(o,e)))return!0}catch{}return!1}function w(e){O(D(),e)}export{Se as createStartCommand};
1
+ import{Command as R}from"commander";import _ from"node:fs";import v from"node:os";import f from"node:path";import t from"node:process";import{randomUUID as W}from"node:crypto";import{Logger as $}from"../core/logger.js";import{loadCredentials as N,saveApiKeyCredential as O}from"../core/auth.js";import{getRuntimeStatePath as x}from"../core/paths.js";import{writeJsonFile as L}from"../core/fs.js";import{HEARTBEAT_INTERVAL_MS as M,startHeartbeatLoop as U}from"../core/heartbeat.js";import{startJobWorkerLoop as j}from"../core/job-worker.js";import{startFileTransferWorkerLoop as F}from"../core/file-transfer-worker.js";import{startMachineFilesystemWorkerLoop as H}from"../core/machine-filesystem-worker.js";import{cleanupOrphanedAcpProcesses as K}from"../core/acp-orphan-cleanup.js";import{defaultAcpRuntimePool as S}from"../acp/client/acp-runtime-pool.js";import{isProcessAlive as l,readPidFile as h,writePidFile as V,removePidFile as y}from"../core/process.js";import{applyRuntimePathOptions as C}from"../core/runtime-options.js";import{loadAcaConfig as B,updateAcaConfig as b}from"../core/aca-config.js";import{acaServerRequest as G}from"../core/aca-server-client.js";import{ACA_WORKER_INSTANCE_ENV as P,ACA_WORKER_PID_ENV as J,ACA_WORKER_START_TICKS_ENV as q,readProcessStartTicks as z}from"../core/process-identity.js";import{printStartTips as Q,spawnDaemon as X}from"./daemon.js";function Pe(){return new R("start").description("Start agent service in native mode").option("--aica-home <path>","Runtime home directory (defaults to ~/.aica or AICA_HOME)").option("--aica-config <path>","Config file path (defaults to <aica-home>/config.json)").option("--aca-home <path>","Deprecated alias for --aica-home").option("--aca-config <path>","Deprecated alias for --aica-config").option("--server <url>","ACA Server URL override").option("--machine-id <id>","Machine ID override for this runtime").option("--machine-name <name>","Machine name to register (defaults to hostname)").option("--cli-types <types...>","Specify CLI types to register, `claude` or `codex`").option("--debug","enable debug output").option("--heartbeat-log","output current timestamp every 5 seconds").option("--auth <api-key>","Use a CLI API key for non-interactive authentication").option("--token <enrollment-token>","Use a one-time Web-generated token to register this machine").option("-b, --background","在后台启动 Worker").action(async e=>{if(e.background){await Y(e);return}await te(e)})}async function Y(e){C(e);const r=h();if(r&&l(r))throw new Error(`Worker 已在后台运行,PID ${r}。`);r&&y();const a=X(Z());if(a.unref(),!a.pid)throw new Error("后台 Worker 启动失败:未获得进程 PID。");if(!await ee(a.pid,3e3)&&!l(a.pid))throw new Error(`后台 Worker 启动失败,请检查 ${f.join(t.env.AICA_HOME||t.env.ACA_HOME||f.join(v.homedir(),".aica"),"logs")}。`);console.log(`Worker 已在后台启动,PID ${a.pid}。`),Q()}function Z(){const e=t.argv.findIndex((r,a)=>a>=2&&r==="start");return e<0?[]:t.argv.slice(e+1).filter(r=>r!=="--background"&&r!=="-b")}async function ee(e,r){const a=Date.now();for(;Date.now()-a<r;){if(h()===e)return!0;if(!l(e))return!1;await new Promise(i=>setTimeout(i,50))}return h()===e}async function te(e){C(e);const r=t.env[P]||W(),a=z(t.pid)||"";t.env[P]=r,t.env[J]=String(t.pid),t.env[q]=a;const i=new $("start",e.debug?"debug":"info"),E=B(),u={...e.server?.trim()?{serverUrl:e.server.trim()}:{},...e.machineId?{machineId:e.machineId.trim()}:{},...e.machineName?{machineName:e.machineName.trim()}:{}};let s=Object.keys(u).length>0?b(u):E;const c=e.machineName?.trim()||s.machineName||v.hostname();if(e.token?.trim()){const n=await G("POST","/api/auth/machine-enroll",{token:e.token.trim(),machineId:s.machineId,machineName:c},{serverUrl:s.serverUrl});s=b({machineId:n.machineId,machineName:c,token:n.token,username:n.username}),i.info(`Machine enrollment completed for user ${n.username}`)}const D=e.auth?O(e.auth,c):N();V(t.pid),T({pid:t.pid,workerInstanceId:r,workerStartTicks:a,phase:"running",startupStage:"local-compatible",machineId:s.machineId||D?.machine?.machineId||"local-machine",machineName:c,cliTypes:e.cliTypes??re(),startedAt:new Date().toISOString(),heartbeatIntervalMs:M,issues:[{severity:"info",message:"aca is running in aca-server compatibility mode; ACP execution happens on this machine."}]}),i.info(`Starting aca local-compatible service on machine ${c}`),i.info(`PID ${t.pid}`);const m=await K();m.terminated>0&&i.warn(`terminated ${m.terminated} orphaned ACP process(es): ${m.pids.join(",")}`);let g=!1,d,w,I,A;const k=async n=>{if(g)return;g=!0,i.info("Shutting down"),d&&clearInterval(d),I?.stop(),A?.stop(),await w?.stop().catch(o=>i.warn(`job worker shutdown failed: ${o instanceof Error?o.message:String(o)}`));const p=S.stats();await S.disposeAll().catch(o=>i.warn(`ACP runtime shutdown failed: ${o instanceof Error?o.message:String(o)}`)),i.info(`ACP runtimes stopped total=${p.total} inUse=${p.inUse} idle=${p.idle}`),T({pid:t.pid,phase:"stopped",stoppedAt:new Date().toISOString(),issues:[]}),y(),t.exit(n)};t.once("SIGINT",()=>{k(130)}),t.once("SIGTERM",()=>{k(0)}),e.heartbeatLog&&setInterval(()=>i.info(`heartbeat: ${new Date().toISOString()}`),5e3),d=U(i),w=j(i),I=F(i),A=H(i),await new Promise(()=>{})}function re(){return["codex","claude"].filter(e=>ie(e))}function ie(e){const r=t.env.PATH||"";for(const a of r.split(":"))try{if(a&&_.existsSync(f.join(a,e)))return!0}catch{}return!1}function T(e){L(x(),e)}export{Pe as createStartCommand};
@@ -0,0 +1,8 @@
1
+ import{Command as I}from"commander";import C from"node:crypto";import h from"node:fs";import P from"node:os";import f from"node:path";import x from"node:readline/promises";import{stdin as S,stdout as c}from"node:process";import{extractCodexSession as _}from"../acp/providers/codex/session-import.js";import{acaServerRequest as s}from"../core/aca-server-client.js";import{loadAcaConfig as l,upsertLocalProject as E}from"../core/aca-config.js";import{applyRuntimePathOptions as T}from"../core/runtime-options.js";function Z(){return new I("codex").description("管理本机 Codex 会话").addCommand(new I("import").description("将本机 Codex Session 导入 ACA Project").argument("<session-id>","Codex Session ID").option("--aica-home <path>","Runtime home directory (defaults to ~/.aica)").option("--aica-config <path>","Config file path").option("--aca-home <path>","Deprecated alias for --aica-home").option("--aca-config <path>","Deprecated alias for --aica-config").option("--path <path>","项目目录,支持相对路径;默认使用当前目录").option("--workspace <id>","新建 Project 时使用的 Workspace ID").option("--project-name <name>","自动创建 Project 时使用的名称").option("--turns <count>","导入最近多少个 Codex Turn,范围 1-20","20").option("--detail <mode>","详情模式:auto、compact、standard 或 diagnostic","auto").option("--title <name>","导入后的 ACA Session 标题").option("--dry-run","只读取和校验,不写入 ACA Server").option("-y, --yes","跳过交互确认,适用于脚本或非交互环境").action(b))}async function b(t,e){T(e);const o=l();if(!o.token)throw new Error("Worker 尚未登录,请先执行 aica login。");await R();const r=await N(o.machineId,t);if(r){console.log(`Codex Session 已存在:session=${r.sessionId} project=${r.projectId} title=${r.title||"未命名"}`);return}const i=k(e.path||process.cwd()),d=q(e.turns),p=await v(),a=D(e.detail,p),n=await _({sessionId:t,turnLimit:d,detailMode:a,diffEnabled:a==="diagnostic"&&p.diffEnabled===!0});M(i,n.cwd);const m=await W({targetPath:i,workspaceId:e.workspace,projectName:e.projectName,machineId:o.machineId}),g=e.title?.trim()||n.title;if(e.dryRun){console.log(JSON.stringify({dryRun:!0,action:m.action,projectId:m.project.project_id,projectName:m.project.name,providerSessionId:n.providerSessionId,path:i,detailMode:a,turns:n.turns.length,truncated:n.truncated,title:g},null,2));return}if(!e.yes&&!await G({sessionId:n.providerSessionId,targetPath:i,plan:m,turnCount:n.turns.length,detailMode:a})){console.log("已取消导入。");return}const u=await A(m);await L();const j=await s("POST","/api/codex-session-imports",{projectId:u.project_id,workspaceId:u.workspace_id,machineId:o.machineId,title:g,import:n}),$=j.item;console.log(`${j.imported?"已导入":"已存在"} session=${String($.session_id||"")} project=${u.project_id} turns=${n.turns.length} mode=${a}`)}async function N(t,e){const o=new URLSearchParams({machineId:t,providerSessionId:e.trim()});try{const r=await s("GET",`/api/codex-session-imports/status?${o.toString()}`);return r.exists?r.item:null}catch(r){const i=r instanceof Error?r.message:String(r);if(/\b404\b/.test(i))return null;throw r}}async function L(){const t=l();await s("POST","/api/heartbeat",{machineId:t.machineId,machineName:t.machineName,workspaceId:t.defaultWorkspaceId,projects:t.projects.map(e=>({projectId:e.projectId,workspaceId:e.workspaceId,name:e.name,rootPath:e.rootPath}))})}async function R(){try{await s("GET","/api/auth/me")}catch(t){const e=t instanceof Error?t.message:String(t);throw/\b401\b|unauthorized/i.test(e)?new Error("Worker 登录已失效,请先执行 aica login 重新登录。"):t}}async function v(){try{return(await s("GET","/api/process-details/capabilities")).item||{}}catch{return{}}}function D(t,e){const o=String(t||"auto").trim().toLowerCase();if(o==="auto")return e.mode==="diagnostic"?"diagnostic":e.mode==="compact"?"compact":"standard";if(o==="compact"||o==="standard"||o==="diagnostic")return o;throw new Error("--detail 必须是 auto、compact、standard 或 diagnostic。")}async function W(t){const e=l(),o=e.projects.find(a=>t.workspaceId&&a.workspaceId!==t.workspaceId?!1:w(a.rootPath)===t.targetPath),r=t.workspaceId||o?.workspaceId||e.defaultWorkspaceId,i=await s("GET",`/api/projects?workspaceId=${encodeURIComponent(r)}`);if(o){const a=i.items.find(n=>n.project_id===o.projectId&&w(n.root_path)===t.targetPath);if(a)return{action:"reuse",project:a}}const d=i.items.find(a=>w(a.root_path)===t.targetPath);if(d)return{action:"attach",project:d};const p=t.projectName?.trim()||o?.name||f.basename(t.targetPath);return{action:"create",project:{project_id:o?.projectId||O(r,t.machineId,t.targetPath,p),workspace_id:r,name:p,root_path:t.targetPath}}}async function A(t){if(t.action==="reuse")return t.project;if(t.action==="attach")return y(t.project),t.project;const e=await s("POST","/api/projects",{projectId:t.project.project_id,workspaceId:t.project.workspace_id,name:t.project.name,rootPath:t.project.root_path,source:"aica-codex-import",projectType:"project"});return y(e.item),e.item}function y(t){E({projectId:t.project_id,workspaceId:t.workspace_id,name:t.name,rootPath:t.root_path})}function M(t,e){const o=h.realpathSync(e);if(t!==o)throw new Error(`指定目录与 Codex Session 工作目录不一致:path=${t} session=${o}`)}function k(t){const e=t==="~"?P.homedir():t.startsWith("~/")?f.join(P.homedir(),t.slice(2)):t,o=f.resolve(e);if(!h.existsSync(o)||!h.statSync(o).isDirectory())throw new Error(`Project 工作目录不存在:${o}`);return h.realpathSync(o)}function w(t){try{return k(t)}catch{return null}}function O(t,e,o,r){const i=r.toLowerCase().replace(/[^a-z0-9._-]+/g,"-").replace(/^-+|-+$/g,"")||"project",d=C.createHash("sha256").update(`${t}\0${e}\0${o}`).digest("hex").slice(0,12);return`aica-${i}-${d}`}async function G(t){if(!S.isTTY||!c.isTTY)throw new Error("当前为非交互环境,请确认参数无误后添加 --yes 执行导入。");const e=t.plan.action==="reuse"?`在已有 Project「${t.plan.project.name}」中创建 Session`:t.plan.action==="attach"?`将目录关联到已有 Project「${t.plan.project.name}」,再创建 Session`:`创建 Project「${t.plan.project.name}」,再创建 Session`;c.write(`
2
+ 即将导入 Codex Session:
3
+ `),c.write(` 项目路径:${t.targetPath}
4
+ `),c.write(` Session ID:${t.sessionId}
5
+ `),c.write(` 执行动作:${e}
6
+ `),c.write(` 历史范围:最近 ${t.turnCount} 个 Turn
7
+ `),c.write(` 详情模式:${t.detailMode}
8
+ `);const o=x.createInterface({input:S,output:c});try{const r=(await o.question("确认导入?[y/N] ")).trim().toLowerCase();return r==="y"||r==="yes"}finally{o.close()}}function q(t){const e=Number.parseInt(String(t||"20"),10);if(!Number.isInteger(e)||e<1||e>20)throw new Error("--turns 必须是 1 到 20 之间的整数。");return e}export{Z as createWorkerCodexCommand};
@@ -1,6 +1,6 @@
1
- import W from"node:crypto";import x from"node:fs";import S from"node:path";import{compactAgentContext as V,sendAgentPrompt as q}from"../acp/agent.js";import{acaServerDownload as D,acaServerRequest as b}from"./aca-server-client.js";import{loadAcaConfig as H}from"./aca-config.js";import{prepareLocalWorktree as G}from"./worktree.js";import{getAcaHome as $}from"./paths.js";import{collectAffectedFiles as N,extractAffectedFilePathsFromAcpRaw as K}from"./affected-files.js";import{resolveJobPermissionPolicy as X}from"./job-permission-policy.js";import{appendCoalescedAcpEvent as Z}from"./acp-event-coalescer.js";import{shouldUploadAcpEvent as Q}from"./acp-event-upload-filter.js";const Y=1500,ee=5e3,M=1e3,te=320,ne=64,O=900,oe=1e4,ae=0,ie=12e3;function re(e){return e?e==="~"?S.dirname($()):e.startsWith("~/")?S.join(S.dirname($()),e.slice(2)):e:""}function se(e){const t=e.project_metadata;return t&&typeof t=="object"&&!Array.isArray(t)&&t.kind==="chat"?!0:typeof e.project_root_path=="string"&&(e.project_root_path.startsWith("~/.aca/chats/")||/(^|\/)\.aca\/chats\//.test(e.project_root_path))}function Ve(e,t={}){let n=!1,o=null,a=null;const i=new Set,c=new Set,u=new Map,s=async()=>{if(n)return;const r=ce();if(U(i.size,r))try{const m=t.loadConfig?.()??H();if(!m.token)return;for(;!n&&U(i.size,r);){const A=t.claimNextJob?await t.claimNextJob(m):await b("POST","/api/client/jobs/claim",{machineId:m.machineId,workspaceId:m.defaultWorkspaceId});if(!A.item)break;const d=A.item;if(n){await(t.cancelClaimedJob?t.cancelClaimedJob(d,m):P(d.job_id,m.machineId,"cancelled")).catch(()=>{});break}const f=d.session_id;c.has(f)&&e.warn(`claimed job=${d.job_id} for already active session=${f}; running anyway to avoid leaving it stuck`),i.add(d.job_id),c.add(f);const I=new AbortController,C=(t.runJob??ue)(d,m,e,I).catch(p=>{e.warn(`job ${d.job_id} failed outside handler: ${p instanceof Error?p.message:String(p)}`)}).finally(()=>{i.delete(d.job_id),c.delete(f),u.delete(d.job_id)});u.set(d.job_id,{controller:I,promise:C})}}catch(m){n||e.warn(`job worker failed: ${m instanceof Error?m.message:String(m)}`)}},w=()=>{if(n||o)return;const r=s();o=r,r.finally(()=>{o===r&&(o=null)})};w();const T=setInterval(()=>{w()},t.claimIntervalMs??Y);return{timer:T,stop:()=>a||(a=(async()=>{if(n)return;n=!0,clearInterval(T);for(const m of u.values())m.controller.abort();await o?.catch(()=>{});for(const m of u.values())m.controller.abort();const r=[...u.values()].map(m=>m.promise);r.length!==0&&await Promise.race([Promise.allSettled(r).then(()=>{}),E(ie)])})(),a)}}function ce(){const e=process.env.ACA_MAX_CONCURRENT_JOBS||process.env.ACA_MAX_CONCURRENT_PROJECT_JOBS||"",t=Number.parseInt(e,10);return!e.trim()||!Number.isFinite(t)||t<0?ae:t===0?0:Math.max(1,Math.min(t,8))}function U(e,t){return t===0||e<t}function le(){const e=Number.parseInt(process.env.ACA_ACP_EVENT_BATCH_INTERVAL_MS||"",10);return Number.isFinite(e)?Math.max(40,Math.min(e,2e3)):te}async function ue(e,t,n,o){const a=e.job_id,i=e.session_id,c=e.workspace_id,u=e.project_id??null,s=e.input??{},w=String(s.control?.type||"").toLowerCase();if(!w&&!s.message?.trim()&&(s.attachments??[]).length===0){await P(a,t.machineId,"job input missing message");return}const T=t.projects.find(p=>p.projectId===u),r=re(T?.rootPath||e.project_root_path||"");if(!r){await P(a,t.machineId,"job project root is unknown");return}se(e)&&x.mkdirSync(r,{recursive:!0}),n.info(`claimed job=${a} session=${i} project=${T?.name??u??"unknown"}`);const m=setInterval(()=>{b("POST",`/api/client/jobs/${encodeURIComponent(a)}/heartbeat`,{machineId:t.machineId}).catch(()=>{}),Re(a,o).catch(()=>{})},ee);let A=0;const d=we(a,t.machineId),f=be(a,t.machineId),I=new Set;let C=null;try{await f.sendNow({phase:"initializing",label:"准备工作区",detail:e.worktree_id?"worktree":"project root"}).catch(()=>{});const p=G({projectId:u,sessionId:i,projectRoot:r,worktree:e});C=p,await Pe(a,t.machineId,p);const g=pe(s.acpRun);if(w==="compact"){if(!e.acp_session_id)throw new Error("Current session does not have an ACP context to compact");const l=await V({agentType:k(g.agentType,"codex"),cliType:k(g.cliType,"builtin"),acpSessionId:e.acp_session_id,cwd:p.cwd,model:s.model,mode:s.mode,configOptionValues:s.configOptionValues,timeoutMs:s.timeoutMs,allowRollover:s.control?.allowRollover===!0,signal:o.signal,onStatus:y=>{f.report({phase:y.phase,label:y.label,detail:y.detail,updateType:y.updateType,raw:y})}});await f.flush(),await b("POST",`/api/client/jobs/${encodeURIComponent(a)}/complete`,{machineId:t.machineId,acpSessionId:l.acpSessionId,suppressTurn:!0,result:{ok:!0,control:{type:"compact",allowRollover:s.control?.allowRollover===!0},acpSessionId:l.acpSessionId,durationMs:l.durationMs,usage:l.contextUsage,maintenance:l.maintenance,worktree:R(p)}}),n.info(`completed context compact job=${a}`);return}const _=await ge({cwd:p.cwd,sessionId:i,jobId:a,attachments:s.attachments??[]});_.length>0&&(n.info(`materialized ${_.length} attachment(s) for job=${a}`),await f.sendNow({phase:"initializing",label:"附件已写入本机",detail:_.map(l=>l.relativePath).join(", "),raw:{attachments:z(_)}}).catch(()=>{}));const j=Ie(s,_),F=_e(j,_),h=await q({agentType:k(g.agentType,"codex"),cliType:k(g.cliType,"builtin"),acpSessionId:e.acp_session_id??null,prompt:j,promptBlocks:F,cwd:p.cwd,model:s.model,mode:s.mode,configOptionValues:s.configOptionValues,canExecuteTools:s.canExecuteTools,timeoutMs:s.timeoutMs,contextWindow:de(g.contextWindow),signal:o.signal,onStatus:l=>{f.report({phase:l.phase,label:l.label,detail:l.detail,updateType:l.updateType,raw:l})},onUpdate:l=>{A+=1;for(const L of K(l.raw))I.add(L);const y={turnId:e.user_turn_id??null,eventType:l.type,label:l.label,text:l.text,status:l.status,toolCallId:l.toolCallId,raw:ye(l.raw,A)};Q(y)&&d.push(y)},onPermissionRequest:l=>{const y=X(s,l);return y?Promise.resolve(y):ve(a,t.machineId,l,o.signal)},onUserInputRequest:l=>ke(a,t.machineId,l,o.signal)});await d.flush(),await f.flush();const J=N({cwd:p.cwd,evidencePaths:I}),v=fe(h.promptResponse);if(!(he(v)||!!h.content)){await P(a,t.machineId,`${h.agentType} stopped: ${v??"unknown"}`,{acpSessionId:h.acpSessionId,stopReason:v,exitCode:h.exitCode,stderr:xe(h.stderr,4e3),worktree:R(p)});return}await b("POST",`/api/client/jobs/${encodeURIComponent(a)}/complete`,{machineId:t.machineId,acpSessionId:h.acpSessionId,assistantText:h.content,result:{ok:!0,acpSessionId:h.acpSessionId,stopReason:v,durationMs:h.durationMs,acpRun:s.acpRun??null,effectiveConfig:h.effectiveConfig??null,performance:h.performance??null,usage:h.contextUsage??null,updateCount:h.updates.length,statusTimeline:h.statusTimeline,worktree:R(p),attachments:z(_),files:J}}),n.info(`completed job=${a}`)}catch(p){await d.flush(),await f.flush();const g=o.signal.aborted?"cancelled":p instanceof Error?p.message:String(p),_=C?N({cwd:C.cwd,evidencePaths:I}):[];await P(a,t.machineId,me(g),{rawError:g,files:_})}finally{await d.flush(),await f.flush(),clearInterval(m)}}function me(e){return/ACP request timed out/i.test(e)?"ACP 长时间没有完成,请稍后重试,或点击停止后重新发送。":/MimoCode request timed out/i.test(e)?"MimoCode 长时间没有完成,请稍后重试,或点击停止后重新发送。":e.replace(/\bsession\/prompt\b/g,"ACP 请求")}function pe(e){return!e||typeof e!="object"||Array.isArray(e)?{}:e}function de(e){return typeof e=="number"&&Number.isFinite(e)&&e>0?e:void 0}function k(...e){for(const t of e)if(typeof t=="string"&&t.trim())return t.trim();return""}function fe(e){if(!e||typeof e!="object"||Array.isArray(e))return;const t=e;if(typeof t.stopReason=="string")return t.stopReason;if(typeof t.reason=="string")return t.reason;const n=t.part;if(n&&typeof n=="object"&&!Array.isArray(n)&&typeof n.reason=="string")return n.reason}function he(e){return e?["end_turn","stop","completed","complete"].includes(e):!1}function we(e,t){let n=[],o=null,a=Promise.resolve();const i=async s=>{s.length!==0&&await b("POST",`/api/client/jobs/${encodeURIComponent(e)}/events/batch`,{machineId:t,events:s})},c=()=>{o&&(clearTimeout(o),o=null);const s=n;return n=[],a=a.then(()=>i(s)).catch(()=>{}),a},u=()=>{o||(o=setTimeout(()=>{c()},le()),o.unref())};return{push(s){if(Z(n,s),n.length>=ne){c();return}u()},flush:c}}function be(e,t){let n=null,o=0,a="",i=null,c=Promise.resolve();const u=r=>JSON.stringify({phase:r.phase??"",label:r.label??"",detail:r.detail??"",updateType:r.updateType??""}),s=async r=>{await b("POST",`/api/client/jobs/${encodeURIComponent(e)}/presence`,{machineId:t,phase:r.phase,label:r.label,detail:r.detail,updateType:r.updateType,raw:r.raw}),o=Date.now(),a=u(r)},w=()=>{if(i&&(clearTimeout(i),i=null),!n)return c;const r=n;return n=null,c=c.then(()=>s(r)).catch(()=>{}),c},T=r=>{i||(i=setTimeout(()=>{w()},r),i.unref())};return{report(r){const m=Date.now(),d=u(r)!==a,f=m-o>=oe;if(!d&&!f)return;n=r;const I=m-o;if(I>=O||o===0){w();return}T(O-I)},sendNow(r){return n=r,w()},flush:w}}function ye(e,t){return e&&typeof e=="object"&&!Array.isArray(e)?{...e,acaSequence:t}:{value:e,acaSequence:t}}function Ie(e,t=[]){const n=e?.message?.trim()??"";if(t.length===0)return n;const a=["用户随消息上传了以下附件,aca 已将附件写入本机工作区。需要读取附件时请直接使用下面的 localPath;图片附件也已作为 ACP image block 随本轮请求传入。",...t.map((i,c)=>`${c+1}. ${i.name} (${i.kind}, ${i.mimeType}, ${Ce(i.size)})
2
- localPath: ${i.path}
3
- workspacePath: ${i.relativePath}`)].join(`
1
+ import W from"node:crypto";import x from"node:fs";import S from"node:path";import{compactAgentContext as V,sendAgentPrompt as q}from"../acp/agent.js";import{acaServerDownload as D,acaServerRequest as b}from"./aca-server-client.js";import{loadAcaConfig as H}from"./aca-config.js";import{prepareLocalWorktree as G}from"./worktree.js";import{getAcaHome as $}from"./paths.js";import{collectAffectedFiles as N,extractAffectedFilePathsFromAcpRaw as K}from"./affected-files.js";import{resolveJobPermissionPolicy as X}from"./job-permission-policy.js";import{appendCoalescedAcpEvent as Z}from"./acp-event-coalescer.js";import{shouldUploadAcpEvent as Q}from"./acp-event-upload-filter.js";const Y=1500,ee=5e3,M=1e3,te=320,ne=64,O=900,oe=1e4,ie=0,ae=12e3;function re(e){return e?e==="~"?S.dirname($()):e.startsWith("~/")?S.join(S.dirname($()),e.slice(2)):e:""}function se(e){const t=e.project_metadata;return t&&typeof t=="object"&&!Array.isArray(t)&&t.kind==="chat"?!0:typeof e.project_root_path=="string"&&(e.project_root_path.startsWith("~/.aca/chats/")||/(^|\/)\.aca\/chats\//.test(e.project_root_path))}function qe(e,t={}){let n=!1,o=null,i=null;const a=new Set,c=new Set,u=new Map,s=async()=>{if(n)return;const r=ce();if(U(a.size,r))try{const m=t.loadConfig?.()??H();if(!m.token)return;for(;!n&&U(a.size,r);){const A=t.claimNextJob?await t.claimNextJob(m):await b("POST","/api/client/jobs/claim",{machineId:m.machineId,workspaceId:m.defaultWorkspaceId});if(!A.item)break;const d=A.item;if(n){await(t.cancelClaimedJob?t.cancelClaimedJob(d,m):P(d.job_id,m.machineId,"cancelled")).catch(()=>{});break}const f=d.session_id;c.has(f)&&e.warn(`claimed job=${d.job_id} for already active session=${f}; running anyway to avoid leaving it stuck`),a.add(d.job_id),c.add(f);const I=new AbortController,C=(t.runJob??ue)(d,m,e,I).catch(p=>{e.warn(`job ${d.job_id} failed outside handler: ${p instanceof Error?p.message:String(p)}`)}).finally(()=>{a.delete(d.job_id),c.delete(f),u.delete(d.job_id)});u.set(d.job_id,{controller:I,promise:C})}}catch(m){n||e.warn(`job worker failed: ${m instanceof Error?m.message:String(m)}`)}},w=()=>{if(n||o)return;const r=s();o=r,r.finally(()=>{o===r&&(o=null)})};w();const T=setInterval(()=>{w()},t.claimIntervalMs??Y);return{timer:T,stop:()=>i||(i=(async()=>{if(n)return;n=!0,clearInterval(T);for(const m of u.values())m.controller.abort();await o?.catch(()=>{});for(const m of u.values())m.controller.abort();const r=[...u.values()].map(m=>m.promise);r.length!==0&&await Promise.race([Promise.allSettled(r).then(()=>{}),E(ae)])})(),i)}}function ce(){const e=process.env.ACA_MAX_CONCURRENT_JOBS||process.env.ACA_MAX_CONCURRENT_PROJECT_JOBS||"",t=Number.parseInt(e,10);return!e.trim()||!Number.isFinite(t)||t<0?ie:t===0?0:Math.max(1,Math.min(t,8))}function U(e,t){return t===0||e<t}function le(){const e=Number.parseInt(process.env.ACA_ACP_EVENT_BATCH_INTERVAL_MS||"",10);return Number.isFinite(e)?Math.max(40,Math.min(e,2e3)):te}async function ue(e,t,n,o){const i=e.job_id,a=e.session_id,c=e.workspace_id,u=e.project_id??null,s=e.input??{},w=String(s.control?.type||"").toLowerCase();if(!w&&!s.message?.trim()&&(s.attachments??[]).length===0){await P(i,t.machineId,"job input missing message");return}const T=t.projects.find(p=>p.projectId===u),r=re(T?.rootPath||e.project_root_path||"");if(!r){await P(i,t.machineId,"job project root is unknown");return}se(e)&&x.mkdirSync(r,{recursive:!0}),n.info(`claimed job=${i} session=${a} project=${T?.name??u??"unknown"}`);const m=setInterval(()=>{b("POST",`/api/client/jobs/${encodeURIComponent(i)}/heartbeat`,{machineId:t.machineId}).catch(()=>{}),xe(i,o).catch(()=>{})},ee);let A=0;const d=be(i,t.machineId),f=ye(i,t.machineId),I=new Set;let C=null;try{await f.sendNow({phase:"initializing",label:"准备工作区",detail:e.worktree_id?"worktree":"project root"}).catch(()=>{});const p=G({projectId:u,sessionId:a,projectRoot:r,worktree:e});C=p,await ve(i,t.machineId,p);const g=de(s.acpRun);if(w==="compact"){if(!e.acp_session_id)throw new Error("Current session does not have an ACP context to compact");const l=await V({agentType:R(g.agentType,"codex"),cliType:R(g.cliType,"builtin"),acpSessionId:e.acp_session_id,cwd:p.cwd,model:s.model,mode:s.mode,configOptionValues:s.configOptionValues,timeoutMs:s.timeoutMs,allowRollover:s.control?.allowRollover===!0,signal:o.signal,onStatus:y=>{f.report({phase:y.phase,label:y.label,detail:y.detail,updateType:y.updateType,raw:y})}});await f.flush(),await b("POST",`/api/client/jobs/${encodeURIComponent(i)}/complete`,{machineId:t.machineId,acpSessionId:l.acpSessionId,suppressTurn:!0,result:{ok:!0,control:{type:"compact",allowRollover:s.control?.allowRollover===!0},acpSessionId:l.acpSessionId,durationMs:l.durationMs,usage:l.contextUsage,maintenance:l.maintenance,worktree:k(p)}}),n.info(`completed context compact job=${i}`);return}const _=await Te({cwd:p.cwd,sessionId:a,jobId:i,attachments:s.attachments??[]});_.length>0&&(n.info(`materialized ${_.length} attachment(s) for job=${i}`),await f.sendNow({phase:"initializing",label:"附件已写入本机",detail:_.map(l=>l.relativePath).join(", "),raw:{attachments:z(_)}}).catch(()=>{}));const j=_e(s,_),F=ge(j,_),h=await q({agentType:R(g.agentType,"codex"),cliType:R(g.cliType,"builtin"),acpSessionId:e.acp_session_id??null,requireSessionResume:me(e.session_raw),prompt:j,promptBlocks:F,cwd:p.cwd,model:s.model,mode:s.mode,configOptionValues:s.configOptionValues,canExecuteTools:s.canExecuteTools,timeoutMs:s.timeoutMs,contextWindow:fe(g.contextWindow),signal:o.signal,onStatus:l=>{f.report({phase:l.phase,label:l.label,detail:l.detail,updateType:l.updateType,raw:l})},onUpdate:l=>{A+=1;for(const L of K(l.raw))I.add(L);const y={turnId:e.user_turn_id??null,eventType:l.type,label:l.label,text:l.text,status:l.status,toolCallId:l.toolCallId,raw:Ie(l.raw,A)};Q(y)&&d.push(y)},onPermissionRequest:l=>{const y=X(s,l);return y?Promise.resolve(y):Re(i,t.machineId,l,o.signal)},onUserInputRequest:l=>ke(i,t.machineId,l,o.signal)});await d.flush(),await f.flush();const J=N({cwd:p.cwd,evidencePaths:I}),v=he(h.promptResponse);if(!(we(v)||!!h.content)){await P(i,t.machineId,`${h.agentType} stopped: ${v??"unknown"}`,{acpSessionId:h.acpSessionId,stopReason:v,exitCode:h.exitCode,stderr:Ee(h.stderr,4e3),worktree:k(p)});return}await b("POST",`/api/client/jobs/${encodeURIComponent(i)}/complete`,{machineId:t.machineId,acpSessionId:h.acpSessionId,assistantText:h.content,result:{ok:!0,acpSessionId:h.acpSessionId,stopReason:v,durationMs:h.durationMs,acpRun:s.acpRun??null,effectiveConfig:h.effectiveConfig??null,performance:h.performance??null,usage:h.contextUsage??null,updateCount:h.updates.length,statusTimeline:h.statusTimeline,worktree:k(p),attachments:z(_),files:J}}),n.info(`completed job=${i}`)}catch(p){await d.flush(),await f.flush();const g=o.signal.aborted?"cancelled":p instanceof Error?p.message:String(p),_=C?N({cwd:C.cwd,evidencePaths:I}):[];await P(i,t.machineId,pe(g),{rawError:g,files:_})}finally{await d.flush(),await f.flush(),clearInterval(m)}}function me(e){if(!e||typeof e!="object"||Array.isArray(e))return!1;const t=e.importedProviderSession;return!!(t&&typeof t=="object"&&!Array.isArray(t)&&t.strictResume===!0)}function pe(e){return/ACP request timed out/i.test(e)?"ACP 长时间没有完成,请稍后重试,或点击停止后重新发送。":/MimoCode request timed out/i.test(e)?"MimoCode 长时间没有完成,请稍后重试,或点击停止后重新发送。":e.replace(/\bsession\/prompt\b/g,"ACP 请求")}function de(e){return!e||typeof e!="object"||Array.isArray(e)?{}:e}function fe(e){return typeof e=="number"&&Number.isFinite(e)&&e>0?e:void 0}function R(...e){for(const t of e)if(typeof t=="string"&&t.trim())return t.trim();return""}function he(e){if(!e||typeof e!="object"||Array.isArray(e))return;const t=e;if(typeof t.stopReason=="string")return t.stopReason;if(typeof t.reason=="string")return t.reason;const n=t.part;if(n&&typeof n=="object"&&!Array.isArray(n)&&typeof n.reason=="string")return n.reason}function we(e){return e?["end_turn","stop","completed","complete"].includes(e):!1}function be(e,t){let n=[],o=null,i=Promise.resolve();const a=async s=>{s.length!==0&&await b("POST",`/api/client/jobs/${encodeURIComponent(e)}/events/batch`,{machineId:t,events:s})},c=()=>{o&&(clearTimeout(o),o=null);const s=n;return n=[],i=i.then(()=>a(s)).catch(()=>{}),i},u=()=>{o||(o=setTimeout(()=>{c()},le()),o.unref())};return{push(s){if(Z(n,s),n.length>=ne){c();return}u()},flush:c}}function ye(e,t){let n=null,o=0,i="",a=null,c=Promise.resolve();const u=r=>JSON.stringify({phase:r.phase??"",label:r.label??"",detail:r.detail??"",updateType:r.updateType??""}),s=async r=>{await b("POST",`/api/client/jobs/${encodeURIComponent(e)}/presence`,{machineId:t,phase:r.phase,label:r.label,detail:r.detail,updateType:r.updateType,raw:r.raw}),o=Date.now(),i=u(r)},w=()=>{if(a&&(clearTimeout(a),a=null),!n)return c;const r=n;return n=null,c=c.then(()=>s(r)).catch(()=>{}),c},T=r=>{a||(a=setTimeout(()=>{w()},r),a.unref())};return{report(r){const m=Date.now(),d=u(r)!==i,f=m-o>=oe;if(!d&&!f)return;n=r;const I=m-o;if(I>=O||o===0){w();return}T(O-I)},sendNow(r){return n=r,w()},flush:w}}function Ie(e,t){return e&&typeof e=="object"&&!Array.isArray(e)?{...e,acaSequence:t}:{value:e,acaSequence:t}}function _e(e,t=[]){const n=e?.message?.trim()??"";if(t.length===0)return n;const i=["用户随消息上传了以下附件,aca 已将附件写入本机工作区。需要读取附件时请直接使用下面的 localPath;图片附件也已作为 ACP image block 随本轮请求传入。",...t.map((a,c)=>`${c+1}. ${a.name} (${a.kind}, ${a.mimeType}, ${Pe(a.size)})
2
+ localPath: ${a.path}
3
+ workspacePath: ${a.relativePath}`)].join(`
4
4
  `);return n?`${n}
5
5
 
6
- ${a}`:a}function _e(e,t){const n=[{type:"text",text:e}];for(const o of t)o.kind!=="image"||!o.mimeType.startsWith("image/")||n.push({type:"image",data:o.base64,mimeType:o.mimeType,uri:o.path});return n}async function ge(e){if(e.attachments.length===0)return[];const t=S.join(e.cwd,".aca","attachments",B(e.sessionId),B(e.jobId));x.mkdirSync(t,{recursive:!0});const n=[];for(const[o,a]of e.attachments.entries()){const i=a.attachmentId?{mimeType:a.mimeType||"application/octet-stream",bytes:await D(`/api/client/attachments/${encodeURIComponent(a.attachmentId)}`)}:Te(a.dataUrl??""),c=a.mimeType||i.mimeType||"application/octet-stream",u=a.kind==="image"||c.startsWith("image/")?"image":"file",s=Se(a.name||`attachment-${o+1}`,c),w=S.join(t,`${String(o+1).padStart(2,"0")}-${s}`);x.writeFileSync(w,i.bytes),n.push({name:a.name||s,mimeType:c,size:i.bytes.byteLength,kind:u,path:w,relativePath:S.relative(e.cwd,w),base64:i.bytes.toString("base64"),sha256:W.createHash("sha256").update(i.bytes).digest("hex")})}return n}function Te(e){const t=e.indexOf(",");if(!e.startsWith("data:")||t<0)throw new Error("attachment dataUrl is not a data URL");const n=e.slice(5,t),o=e.slice(t+1),a=n.split(";").filter(Boolean),i=a[0]?.includes("/")?a[0]:"application/octet-stream";if(!a.includes("base64"))throw new Error("attachment dataUrl is not base64 encoded");return{mimeType:i,bytes:Buffer.from(o,"base64")}}function Se(e,t){const n=S.basename(e).replace(/[<>:"/\\|?*\x00-\x1F]/g,"_").trim(),o=`attachment${Ae(t)}`;return(!n||n==="."||n===".."?o:n).slice(0,180)}function Ae(e){switch(e.toLowerCase()){case"image/png":return".png";case"image/jpeg":return".jpg";case"image/webp":return".webp";case"image/gif":return".gif";case"text/plain":return".txt";case"application/json":return".json";case"text/markdown":case"text/x-markdown":return".md";case"application/pdf":return".pdf";case"application/msword":return".doc";case"application/vnd.openxmlformats-officedocument.wordprocessingml.document":return".docx";case"application/vnd.ms-excel":return".xls";case"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet":return".xlsx";case"application/vnd.ms-powerpoint":return".ppt";case"application/vnd.openxmlformats-officedocument.presentationml.presentation":return".pptx";case"application/zip":return".zip";case"application/x-7z-compressed":return".7z";case"application/x-rar-compressed":case"application/vnd.rar":return".rar";case"application/gzip":return".gz";case"application/x-tar":return".tar";default:return""}}function B(e){return e.replace(/[^a-zA-Z0-9._-]+/g,"-").replace(/^-+|-+$/g,"").slice(0,80)||"item"}function z(e){return e.map(t=>({name:t.name,kind:t.kind,mimeType:t.mimeType,size:t.size,path:t.path,relativePath:t.relativePath,sha256:t.sha256}))}function Ce(e){if(!Number.isFinite(e)||e<=0)return"0 B";const t=["B","KB","MB","GB"];let n=e,o=0;for(;n>=1024&&o<t.length-1;)n/=1024,o+=1;return`${n>=10||o===0?n.toFixed(0):n.toFixed(1)} ${t[o]}`}async function Pe(e,t,n){!n.usedWorktree||!n.worktreeId||await b("POST",`/api/client/jobs/${encodeURIComponent(e)}/worktree`,{machineId:t,baseCommit:n.baseCommit??null,worktreePath:n.cwd,status:n.status??"active",raw:n.raw??R(n)})}function R(e){return{usedWorktree:e.usedWorktree,worktreeId:e.worktreeId??null,cwd:e.cwd,status:e.status??null,baseCommit:e.baseCommit??null,branchName:e.branchName??null,baseBranch:e.baseBranch??null}}async function ve(e,t,n,o){const i=(await b("POST",`/api/client/jobs/${encodeURIComponent(e)}/permissions`,{machineId:t,request:n})).item.permission_id;for(;!o.aborted;){await E(M);const c=await b("GET",`/api/client/permissions/${encodeURIComponent(i)}`);if(c.item.status==="resolved"&&c.item.response){const u=c.item.response.outcome;return u.outcome==="selected"?{outcome:u}:{outcome:{outcome:"cancelled"}}}}return{outcome:{outcome:"cancelled"}}}async function ke(e,t,n,o){const i=(await b("POST",`/api/client/jobs/${encodeURIComponent(e)}/permissions`,{machineId:t,request:{kind:"elicitation",...n}})).item.permission_id;for(;!o.aborted;){await E(M);const c=await b("GET",`/api/client/permissions/${encodeURIComponent(i)}`);if(c.item.status==="resolved"&&c.item.response){const u=c.item.response.outcome;return u.outcome==="submitted"?{outcome:u}:{outcome:{outcome:"cancelled"}}}}return{outcome:{outcome:"cancelled"}}}async function Re(e,t){if(t.signal.aborted)return;const n=await b("GET",`/api/client/jobs/${encodeURIComponent(e)}`);Number(n.item.cancel_requested??0)===1&&t.abort()}async function P(e,t,n,o){await b("POST",`/api/client/jobs/${encodeURIComponent(e)}/fail`,{machineId:t,error:n,result:o})}function E(e){return new Promise(t=>setTimeout(t,e))}function xe(e,t){return e.length<=t?e:`${e.slice(0,t)}...`}export{Ve as startJobWorkerLoop};
6
+ ${i}`:i}function ge(e,t){const n=[{type:"text",text:e}];for(const o of t)o.kind!=="image"||!o.mimeType.startsWith("image/")||n.push({type:"image",data:o.base64,mimeType:o.mimeType,uri:o.path});return n}async function Te(e){if(e.attachments.length===0)return[];const t=S.join(e.cwd,".aca","attachments",B(e.sessionId),B(e.jobId));x.mkdirSync(t,{recursive:!0});const n=[];for(const[o,i]of e.attachments.entries()){const a=i.attachmentId?{mimeType:i.mimeType||"application/octet-stream",bytes:await D(`/api/client/attachments/${encodeURIComponent(i.attachmentId)}`)}:Se(i.dataUrl??""),c=i.mimeType||a.mimeType||"application/octet-stream",u=i.kind==="image"||c.startsWith("image/")?"image":"file",s=Ae(i.name||`attachment-${o+1}`,c),w=S.join(t,`${String(o+1).padStart(2,"0")}-${s}`);x.writeFileSync(w,a.bytes),n.push({name:i.name||s,mimeType:c,size:a.bytes.byteLength,kind:u,path:w,relativePath:S.relative(e.cwd,w),base64:a.bytes.toString("base64"),sha256:W.createHash("sha256").update(a.bytes).digest("hex")})}return n}function Se(e){const t=e.indexOf(",");if(!e.startsWith("data:")||t<0)throw new Error("attachment dataUrl is not a data URL");const n=e.slice(5,t),o=e.slice(t+1),i=n.split(";").filter(Boolean),a=i[0]?.includes("/")?i[0]:"application/octet-stream";if(!i.includes("base64"))throw new Error("attachment dataUrl is not base64 encoded");return{mimeType:a,bytes:Buffer.from(o,"base64")}}function Ae(e,t){const n=S.basename(e).replace(/[<>:"/\\|?*\x00-\x1F]/g,"_").trim(),o=`attachment${Ce(t)}`;return(!n||n==="."||n===".."?o:n).slice(0,180)}function Ce(e){switch(e.toLowerCase()){case"image/png":return".png";case"image/jpeg":return".jpg";case"image/webp":return".webp";case"image/gif":return".gif";case"text/plain":return".txt";case"application/json":return".json";case"text/markdown":case"text/x-markdown":return".md";case"application/pdf":return".pdf";case"application/msword":return".doc";case"application/vnd.openxmlformats-officedocument.wordprocessingml.document":return".docx";case"application/vnd.ms-excel":return".xls";case"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet":return".xlsx";case"application/vnd.ms-powerpoint":return".ppt";case"application/vnd.openxmlformats-officedocument.presentationml.presentation":return".pptx";case"application/zip":return".zip";case"application/x-7z-compressed":return".7z";case"application/x-rar-compressed":case"application/vnd.rar":return".rar";case"application/gzip":return".gz";case"application/x-tar":return".tar";default:return""}}function B(e){return e.replace(/[^a-zA-Z0-9._-]+/g,"-").replace(/^-+|-+$/g,"").slice(0,80)||"item"}function z(e){return e.map(t=>({name:t.name,kind:t.kind,mimeType:t.mimeType,size:t.size,path:t.path,relativePath:t.relativePath,sha256:t.sha256}))}function Pe(e){if(!Number.isFinite(e)||e<=0)return"0 B";const t=["B","KB","MB","GB"];let n=e,o=0;for(;n>=1024&&o<t.length-1;)n/=1024,o+=1;return`${n>=10||o===0?n.toFixed(0):n.toFixed(1)} ${t[o]}`}async function ve(e,t,n){!n.usedWorktree||!n.worktreeId||await b("POST",`/api/client/jobs/${encodeURIComponent(e)}/worktree`,{machineId:t,baseCommit:n.baseCommit??null,worktreePath:n.cwd,status:n.status??"active",raw:n.raw??k(n)})}function k(e){return{usedWorktree:e.usedWorktree,worktreeId:e.worktreeId??null,cwd:e.cwd,status:e.status??null,baseCommit:e.baseCommit??null,branchName:e.branchName??null,baseBranch:e.baseBranch??null}}async function Re(e,t,n,o){const a=(await b("POST",`/api/client/jobs/${encodeURIComponent(e)}/permissions`,{machineId:t,request:n})).item.permission_id;for(;!o.aborted;){await E(M);const c=await b("GET",`/api/client/permissions/${encodeURIComponent(a)}`);if(c.item.status==="resolved"&&c.item.response){const u=c.item.response.outcome;return u.outcome==="selected"?{outcome:u}:{outcome:{outcome:"cancelled"}}}}return{outcome:{outcome:"cancelled"}}}async function ke(e,t,n,o){const a=(await b("POST",`/api/client/jobs/${encodeURIComponent(e)}/permissions`,{machineId:t,request:{kind:"elicitation",...n}})).item.permission_id;for(;!o.aborted;){await E(M);const c=await b("GET",`/api/client/permissions/${encodeURIComponent(a)}`);if(c.item.status==="resolved"&&c.item.response){const u=c.item.response.outcome;return u.outcome==="submitted"?{outcome:u}:{outcome:{outcome:"cancelled"}}}}return{outcome:{outcome:"cancelled"}}}async function xe(e,t){if(t.signal.aborted)return;const n=await b("GET",`/api/client/jobs/${encodeURIComponent(e)}`);Number(n.item.cancel_requested??0)===1&&t.abort()}async function P(e,t,n,o){await b("POST",`/api/client/jobs/${encodeURIComponent(e)}/fail`,{machineId:t,error:n,result:o})}function E(e){return new Promise(t=>setTimeout(t,e))}function Ee(e,t){return e.length<=t?e:`${e.slice(0,t)}...`}export{qe as startJobWorkerLoop};
@@ -1,5 +1,5 @@
1
- import{execFileSync as E}from"node:child_process";import{randomUUID as F}from"node:crypto";import u from"node:fs";import j from"node:os";import c from"node:path";import{acaServerRequest as h}from"./aca-server-client.js";import{loadAcaConfig as M,upsertLocalProject as G}from"./aca-config.js";import{startLongPollWorker as L}from"./long-poll-worker.js";const H=25e3,g=300,O=3e4;let y=null;function Pt(t){return L({run:async()=>{const e=M();if(!e.token)throw new Error("Worker 尚未配置认证令牌");const r=await h("GET",`/api/client/machine-operations/claim?machineId=${encodeURIComponent(e.machineId)}&waitMs=${H}`);if(r.item)try{const o=await W(r.item);await h("POST",`/api/client/machine-operations/${encodeURIComponent(r.item.requestId)}/complete`,{result:o})}catch(o){const n=o instanceof Error?o.message:String(o);t.warn(`machine filesystem request failed request=${r.item.requestId}: ${n}`),await h("POST",`/api/client/machine-operations/${encodeURIComponent(r.item.requestId)}/fail`,{message:n}).catch(()=>{})}},onError:(e,r)=>t.warn(`machine filesystem worker failed, retry in ${r}ms: ${e instanceof Error?e.message:String(e)}`)})}async function W(t){if(t.operation==="filesystem_roots")return tt();if(t.operation==="list_directory")return et({directoryPath:String(t.payload.path||""),includeHidden:t.payload.includeHidden===!0});if(t.operation==="register_project")return at(t.payload);if(t.operation==="list_project_directory")return B(t.payload);if(t.operation==="get_project_git_status")return U(t.payload);if(t.operation==="get_project_git_diff")return z(t.payload);if(t.operation==="get_project_git_log")return K(t.payload);if(t.operation==="get_project_git_commit")return J(t.payload);throw new Error("不支持的 Machine 文件系统操作。 ")}function B(t){const e=w(String(t.rootPath||"")),r=x(String(t.relativePath||"")),o=lt(e,r,!0),n=Math.max(0,Number(t.offset||0)),s=Math.min(300,Math.max(20,Number(t.limit||200))),i=u.readdirSync(o,{withFileTypes:!0}).filter(l=>!l.isSymbolicLink()).map(l=>({name:l.name,path:ut(e,c.join(o,l.name)),type:l.isDirectory()?"directory":l.isFile()?"file":"other",hidden:l.name.startsWith("."),extension:l.isFile()?c.extname(l.name).slice(1).toLowerCase():""})).filter(l=>l.type!=="other").sort((l,p)=>l.type!==p.type?l.type==="directory"?-1:1:l.name.localeCompare(p.name,void 0,{numeric:!0,sensitivity:"base"})),a=i.slice(n,n+s);return{path:r,rootKind:t.rootKind==="worktree"?"worktree":"project",items:a,total:i.length,nextOffset:n+a.length<i.length?n+a.length:null}}const A=500,S=2*1024*1024,d=2*1024*1024;function U(t){const e=w(String(t.rootPath||""));if(!_(e))return{isRepository:!1,branch:null,files:[],ahead:0,behind:0};const o=f(e,["status","--porcelain=v1","-z","--untracked-files=all","--no-renames"],S).split("\0").filter(Boolean).slice(0,A),n=o.map(a=>X(a)).filter(a=>!!a),s=b(f(e,["diff","--no-ext-diff","--numstat","HEAD","--","."],S));for(const a of n){const l=s.get(a.path);if(l)a.additions=l.additions,a.deletions=l.deletions;else if(a.status==="untracked"){const p=$(e,a.path);a.additions=p.additions,a.deletions=p.deletions}}const i=q(e);return{isRepository:!0,branch:Z(e),files:n,ahead:i.ahead,behind:i.behind,truncated:o.length>=A}}function z(t){const e=w(String(t.rootPath||"")),r=x(String(t.relativePath||""));if(!r)throw new Error("Diff 文件路径不能为空。");if(!_(e))return{isRepository:!1,path:r,diff:"",additions:0,deletions:0};const o=D(e,r),n=Y(e,r),s=n?f(e,["diff","--no-ext-diff","--unified=80","HEAD","--",r],d):R(e,["diff","--no-ext-diff","--no-index","--unified=80",process.platform==="win32"?"NUL":"/dev/null",o],d),i=n?b(f(e,["diff","--no-ext-diff","--numstat","HEAD","--",r],32*1024)).get(r):$(e,r);return{isRepository:!0,path:r,diff:N(s),additions:i?.additions??0,deletions:i?.deletions??0,truncated:s.length>d}}function K(t){const e=w(String(t.rootPath||""));if(!_(e))return{isRepository:!1,items:[]};if(!Q(e))return{isRepository:!0,items:[]};const r=Math.min(200,Math.max(20,Number(t.limit||100)));return{isRepository:!0,items:f(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"],S).split("").map(i=>V(i)).filter(i=>!!i),limit:r}}function J(t){const e=w(String(t.rootPath||"")),r=String(t.commit||"").trim();if(!/^[0-9a-f]{7,64}$/i.test(r))throw new Error("Git 提交标识无效。");if(!_(e))return{isRepository:!1,commit:r,diff:"",additions:0,deletions:0};f(e,["cat-file","-e",`${r}^{commit}`],8*1024);const o=f(e,["show","--no-ext-diff","--no-color","--format=","--stat","--patch","--unified=40",r],d),n=[...b(f(e,["show","--no-ext-diff","--numstat","--format=",r],S)).values()];return{isRepository:!0,commit:r,diff:N(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>d}}function V(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 X(t){if(t.length<4)return null;const e=t[0]||" ",r=t[1]||" ",o=x(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 b(t){const e=new Map;for(const r of t.split(/\r?\n/)){const o=r.split(" ");if(o.length<3)continue;const n=/^\d+$/.test(o[0])?Number(o[0]):null,s=/^\d+$/.test(o[1])?Number(o[1]):null,i=x(o.slice(2).join(" "));i&&e.set(i,{additions:n,deletions:s})}return e}function $(t,e){const r=D(t,e),o=R(t,["diff","--no-ext-diff","--no-index","--numstat","--",process.platform==="win32"?"NUL":"/dev/null",r],32*1024);return b(o).values().next().value||{additions:null,deletions:null}}function _(t){try{return f(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 Y(t,e){try{return f(t,["ls-files","--error-unmatch","--",e],8*1024),!0}catch{return!1}}function Q(t){try{return f(t,["rev-parse","--verify","HEAD"],8*1024),!0}catch{return!1}}function Z(t){try{return f(t,["symbolic-ref","--quiet","--short","HEAD"],8*1024).trim()||null}catch{try{return`HEAD (${f(t,["rev-parse","--short","HEAD"],8*1024).trim()})`}catch{return null}}}function q(t){try{const e=f(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 D(t,e){const r=c.resolve(t,...e.split("/")),o=c.relative(t,r);if(o.startsWith("..")||c.isAbsolute(o))throw new Error("Diff 路径超出项目范围。");return r}function f(t,e,r){return E("git",["-C",t,...e],{encoding:"utf8",timeout:2e4,maxBuffer:r,stdio:["ignore","pipe","pipe"]})}function R(t,e,r){try{return f(t,e,r)}catch(o){const n=String(o.stdout||"");if(n)return n;if(Number(o.status)===1)return"";throw o}}function N(t){return t.length<=d?t:`${t.slice(0,d)}
1
+ import{execFileSync as E}from"node:child_process";import{randomUUID as M}from"node:crypto";import u from"node:fs";import j from"node:os";import c from"node:path";import{acaServerRequest as h}from"./aca-server-client.js";import{loadAcaConfig as G,upsertLocalProject as L}from"./aca-config.js";import{startLongPollWorker as O}from"./long-poll-worker.js";const H=25e3,g=300,W=3e4;let y=null;function vt(t){return O({run:async()=>{const e=G();if(!e.token)throw new Error("Worker 尚未配置认证令牌");const r=await h("GET",`/api/client/machine-operations/claim?machineId=${encodeURIComponent(e.machineId)}&waitMs=${H}`);if(r.item)try{const o=await B(r.item);await h("POST",`/api/client/machine-operations/${encodeURIComponent(r.item.requestId)}/complete`,{result:o})}catch(o){const n=o instanceof Error?o.message:String(o);t.warn(`machine filesystem request failed request=${r.item.requestId}: ${n}`),await h("POST",`/api/client/machine-operations/${encodeURIComponent(r.item.requestId)}/fail`,{message:n}).catch(()=>{})}},onError:(e,r)=>t.warn(`machine filesystem worker failed, retry in ${r}ms: ${e instanceof Error?e.message:String(e)}`)})}async function B(t){if(t.operation==="filesystem_roots")return et();if(t.operation==="list_directory")return rt({directoryPath:String(t.payload.path||""),includeHidden:t.payload.includeHidden===!0});if(t.operation==="register_project")return ct(t.payload);if(t.operation==="list_project_directory")return U(t.payload);if(t.operation==="get_project_git_status")return z(t.payload);if(t.operation==="get_project_git_diff")return K(t.payload);if(t.operation==="get_project_git_log")return J(t.payload);if(t.operation==="get_project_git_commit")return V(t.payload);throw new Error("不支持的 Machine 文件系统操作。 ")}function U(t){const e=w(String(t.rootPath||"")),r=_(String(t.relativePath||"")),o=ut(e,r,!0),n=Math.max(0,Number(t.offset||0)),s=Math.min(300,Math.max(20,Number(t.limit||200))),i=u.readdirSync(o,{withFileTypes:!0}).filter(l=>!l.isSymbolicLink()).map(l=>({name:l.name,path:dt(e,c.join(o,l.name)),type:l.isDirectory()?"directory":l.isFile()?"file":"other",hidden:l.name.startsWith("."),extension:l.isFile()?c.extname(l.name).slice(1).toLowerCase():""})).filter(l=>l.type!=="other").sort((l,p)=>l.type!==p.type?l.type==="directory"?-1:1:l.name.localeCompare(p.name,void 0,{numeric:!0,sensitivity:"base"})),a=i.slice(n,n+s);return{path:r,rootKind:t.rootKind==="worktree"?"worktree":"project",items:a,total:i.length,nextOffset:n+a.length<i.length?n+a.length:null}}const A=500,S=2*1024*1024,f=2*1024*1024;function z(t){const e=w(String(t.rootPath||""));if(!b(e))return{isRepository:!1,branch:null,files:[],ahead:0,behind:0};const o=d(e,["status","--porcelain=v1","-z","--untracked-files=all","--no-renames"],S).split("\0").filter(Boolean).slice(0,A),n=o.map(a=>Y(a)).filter(a=>!!a),s=x(d(e,["diff","--no-ext-diff","--numstat","-z","--no-renames","HEAD","--","."],S));for(const a of n){const l=s.get(a.path);if(l)a.additions=l.additions,a.deletions=l.deletions;else if(a.status==="untracked"){const p=$(e,a.path);a.additions=p.additions,a.deletions=p.deletions}}const i=tt(e);return{isRepository:!0,branch:q(e),files:n,ahead:i.ahead,behind:i.behind,truncated:o.length>=A}}function K(t){const e=w(String(t.rootPath||"")),r=_(String(t.relativePath||""));if(!r)throw new Error("Diff 文件路径不能为空。");if(!b(e))return{isRepository:!1,path:r,diff:"",additions:0,deletions:0};const o=R(e,r),n=Q(e,r),s=n?d(e,["diff","--no-ext-diff","--unified=80","HEAD","--",r],f):N(e,["diff","--no-ext-diff","--no-index","--unified=80",process.platform==="win32"?"NUL":"/dev/null",o],f),i=n?x(d(e,["diff","--no-ext-diff","--numstat","-z","--no-renames","HEAD","--",r],32*1024)).get(r):$(e,r);return{isRepository:!0,path:r,diff:T(s),additions:i?.additions??0,deletions:i?.deletions??0,truncated:s.length>f}}function J(t){const e=w(String(t.rootPath||""));if(!b(e))return{isRepository:!1,items:[]};if(!Z(e))return{isRepository:!0,items:[]};const r=Math.min(200,Math.max(20,Number(t.limit||100)));return{isRepository:!0,items:d(e,["log",`--max-count=${r}`,"--date=iso-strict","--pretty=format:%H%x1f%h%x1f%an%x1f%ae%x1f%aI%x1f%D%x1f%s%x1f%b%x1e"],S).split("").map(i=>X(i)).filter(i=>!!i),limit:r}}function V(t){const e=w(String(t.rootPath||"")),r=String(t.commit||"").trim();if(!/^[0-9a-f]{7,64}$/i.test(r))throw new Error("Git 提交标识无效。");if(!b(e))return{isRepository:!1,commit:r,diff:"",additions:0,deletions:0};d(e,["cat-file","-e",`${r}^{commit}`],8*1024);const o=d(e,["show","--no-ext-diff","--no-color","--format=","--stat","--patch","--unified=40",r],f),n=[...x(d(e,["show","--no-ext-diff","--numstat","-z","--no-renames","--format=",r],S)).values()];return{isRepository:!0,commit:r,diff:T(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>f}}function X(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 Y(t){if(t.length<4)return null;const e=t[0]||" ",r=t[1]||" ",o=_(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 x(t){const e=new Map;for(const r of t.split("\0")){if(!r)continue;const o=D(r);if(!o||!o.path)continue;const n=_(o.path);n&&e.set(n,{additions:o.additions,deletions:o.deletions})}return e}function $(t,e){const r=R(t,e),n=N(t,["diff","--no-ext-diff","--no-index","--numstat","-z","--",process.platform==="win32"?"NUL":"/dev/null",r],32*1024).split("\0",1)[0]||"",s=D(n);return s?{additions:s.additions,deletions:s.deletions}:{additions:null,deletions:null}}function D(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 b(t){try{return d(t,["rev-parse","--is-inside-work-tree"],8*1024).trim()==="true"}catch(e){if(String(e.code||"")==="ENOENT")throw new Error("设备未安装 Git。");return!1}}function Q(t,e){try{return d(t,["ls-files","--error-unmatch","--",e],8*1024),!0}catch{return!1}}function Z(t){try{return d(t,["rev-parse","--verify","HEAD"],8*1024),!0}catch{return!1}}function q(t){try{return d(t,["symbolic-ref","--quiet","--short","HEAD"],8*1024).trim()||null}catch{try{return`HEAD (${d(t,["rev-parse","--short","HEAD"],8*1024).trim()})`}catch{return null}}}function tt(t){try{const e=d(t,["rev-list","--left-right","--count","HEAD...@{upstream}"],8192).trim().split(/\s+/).map(Number);return{ahead:Number.isFinite(e[0])?e[0]:0,behind:Number.isFinite(e[1])?e[1]:0}}catch{return{ahead:0,behind:0}}}function R(t,e){const r=c.resolve(t,...e.split("/")),o=c.relative(t,r);if(o.startsWith("..")||c.isAbsolute(o))throw new Error("Diff 路径超出项目范围。");return r}function d(t,e,r){return E("git",["-C",t,...e],{encoding:"utf8",timeout:2e4,maxBuffer:r,stdio:["ignore","pipe","pipe"]})}function N(t,e,r){try{return d(t,e,r)}catch(o){const n=String(o.stdout||"");if(n)return n;if(Number(o.status)===1)return"";throw o}}function T(t){return t.length<=f?t:`${t.slice(0,f)}
2
2
 
3
- [Diff 内容过大,已截断]`}function tt(){const t=process.platform,e=P(t);return{platform:t,roots:e}}function et(t){if(!t.directoryPath.trim())throw new Error("目录路径不能为空。");return process.platform==="win32"?it(t.directoryPath,!!t.includeHidden):nt(t.directoryPath,!!t.includeHidden)}function m(t,e=process.platform){const r=e==="win32"?c.win32:c.posix,o=r.normalize(t),n=r.parse(o).root,s=o.length>n.length?o.replace(/[\\/]+$/,""):o;return e==="win32"?s.toLocaleLowerCase("en-US"):s}function rt(){const t=u.realpathSync(j.homedir()),e=[{id:"home",label:"用户目录",path:t,kind:"home",available:!0}];return t!==c.parse(t).root&&e.push({id:"root",label:"文件系统",path:"/",kind:"root",available:!0}),e}function P(t){if(y?.platform===t&&y.expiresAtMs>Date.now())return y.roots;const e=t==="win32"?ot():rt();return y={platform:t,roots:e,expiresAtMs:Date.now()+O},e}function ot(){const t=u.realpathSync.native(j.homedir()),e=new Map;e.set(m(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=k(T(r));for(const n of o){const s=String(n.Root||"");if(!s)continue;const i=Number(n.DriveType||0),a=String(n.Name||s.replace(/[\\/]+$/,"")),l=String(n.VolumeName||"").trim();e.set(m(s,"win32"),{id:`drive-${a.replace(/[^a-z0-9]+/gi,"").toLowerCase()}`,label:l?`${l} (${a})`:pt(a,i),path:s,kind:mt(i),available:!0})}}catch{const r=c.win32.parse(t).root;e.set(m(r,"win32"),{id:"drive-system",label:`系统盘 (${r.replace(/\\$/,"")})`,path:r,kind:"fixed",available:!0})}return[...e.values()]}function nt(t,e){const r=v(t),n=u.readdirSync(r,{withFileTypes:!0}).filter(i=>i.isDirectory()&&!i.isSymbolicLink()&&(e||!i.name.startsWith("."))),s=n.slice(0,g).map(i=>({name:i.name,path:c.join(r,i.name),kind:"directory",hidden:i.name.startsWith("."),hasChildren:!0}));return s.sort((i,a)=>i.name.localeCompare(a.name,void 0,{numeric:!0,sensitivity:"base"})),C(r,s,n.length>g,"linux")}function it(t,e){const r=v(t),o=["$items = Get-ChildItem -LiteralPath $env:ACA_MACHINE_FS_PATH -Directory -Force -ErrorAction Stop | ForEach-Object {"," [PSCustomObject]@{ Name = $_.Name; FullName = $_.FullName; Attributes = $_.Attributes.ToString() }","}","@($items) | ConvertTo-Json -Compress"].join(`
5
- `),n=k(T(o,{ACA_MACHINE_FS_PATH:r})),s=n.map(i=>{const a=String(i.Attributes||"");return{name:String(i.Name||""),path:String(i.FullName||""),kind:"directory",hidden:/Hidden|System/i.test(a),hasChildren:!0,reparsePoint:/ReparsePoint/i.test(a)}}).filter(i=>i.name&&i.path&&!i.reparsePoint&&(e||!i.hidden)).slice(0,g).sort((i,a)=>i.name.localeCompare(a.name,void 0,{numeric:!0,sensitivity:"base"}));return C(r,s,n.length>g,"win32")}function C(t,e,r,o){const n=o==="win32"?c.win32:c.posix,s=n.parse(t).root,i=m(t,o)===m(s,o)?null:n.dirname(t);return{platform:o,path:t,parentPath:i,breadcrumbs:st(t,o),readable:I(t,u.constants.R_OK),writable:I(t,u.constants.W_OK),items:e,truncated:r}}function st(t,e){const r=e==="win32"?c.win32:c.posix,o=r.parse(t),n=t.slice(o.root.length).split(/[\\/]+/).filter(Boolean),s=[{label:o.root.replace(/[\\/]+$/,"")||o.root,path:o.root}];let i=o.root;for(const a of n)i=r.join(i,a),s.push({label:a,path:i});return s}async function at(t){const e=String(t.workspaceId||"").trim(),r=String(t.name||"").trim(),o=t.projectType==="chat_room"?"chat_room":"project";if(!e||!r)throw new Error("项目名称和工作区不能为空。");const n=v(String(t.path||""));u.accessSync(n,u.constants.R_OK|u.constants.W_OK);const i=(await h("GET",`/api/projects?workspaceId=${encodeURIComponent(e)}`)).items.find(l=>m(l.root_path)===m(n)),a=i||(await h("POST","/api/projects",{projectId:F(),workspaceId:e,name:r,rootPath:n,source:"aica",projectType:o})).item;return G(ct(a)),{project:a,duplicate:!!i}}function ct(t){return{projectId:t.project_id,workspaceId:t.workspace_id,name:t.name,rootPath:t.root_path}}function v(t){if(!t.trim()||!c.isAbsolute(t))throw new Error("必须选择绝对目录路径。");if(ft(t),!u.statSync(t).isDirectory())throw new Error("选择的路径不是目录。");const r=u.realpathSync.native(t);return dt(r),r}function w(t){if(!t.trim()||!c.isAbsolute(t))throw new Error("项目根目录无效。");const e=u.realpathSync.native(t);if(!u.statSync(e).isDirectory())throw new Error("项目根目录不存在。");return e}function x(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 lt(t,e,r){const o=e?c.join(t,...e.split("/")):t,n=u.realpathSync.native(o),s=c.relative(t,n);if(s.startsWith("..")||c.isAbsolute(s))throw new Error("目录超出项目范围。");const i=u.statSync(n);if(r&&!i.isDirectory())throw new Error("请求路径不是目录。");return n}function ut(t,e){return c.relative(t,e).split(c.sep).join("/")}function ft(t){const e=process.platform==="win32"?c.win32:c.posix;if(!P(process.platform).some(o=>{const n=String(o.path||"");if(!n)return!1;const s=e.relative(n,t);return s===""||!s.startsWith("..")&&!e.isAbsolute(s)}))throw new Error("目录不在允许浏览的位置内。")}function dt(t){const e=process.platform==="win32"?c.win32:c.posix;if(!P(process.platform).some(o=>{const n=String(o.path||"");if(!n)return!1;const s=e.relative(n,t);return s===""||!s.startsWith("..")&&!e.isAbsolute(s)}))throw new Error("目录不在允许浏览的位置内。")}function I(t,e){try{return u.accessSync(t,e),!0}catch{return!1}}function T(t,e={}){return E("powershell.exe",["-NoLogo","-NoProfile","-NonInteractive","-Command",t],{encoding:"utf8",windowsHide:!0,timeout:8e3,maxBuffer:4*1024*1024,env:{...process.env,...e}}).replace(/^\uFEFF/,"").trim()}function k(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 mt(t){return t===2?"removable":t===3?"fixed":t===4?"network":t===5?"optical":"drive"}function pt(t,e){return e===2?`移动磁盘 (${t})`:e===4?`网络驱动器 (${t})`:e===5?`光驱 (${t})`:`本地磁盘 (${t})`}export{m as comparableMachinePath,J as getProjectGitCommit,z as getProjectGitDiff,K as getProjectGitLog,U as getProjectGitStatus,et as listMachineDirectory,tt as listMachineFilesystemRoots,B as listProjectDirectory,Pt as startMachineFilesystemWorkerLoop};
3
+ [Diff 内容过大,已截断]`}function et(){const t=process.platform,e=P(t);return{platform:t,roots:e}}function rt(t){if(!t.directoryPath.trim())throw new Error("目录路径不能为空。");return process.platform==="win32"?st(t.directoryPath,!!t.includeHidden):it(t.directoryPath,!!t.includeHidden)}function m(t,e=process.platform){const r=e==="win32"?c.win32:c.posix,o=r.normalize(t),n=r.parse(o).root,s=o.length>n.length?o.replace(/[\\/]+$/,""):o;return e==="win32"?s.toLocaleLowerCase("en-US"):s}function ot(){const t=u.realpathSync(j.homedir()),e=[{id:"home",label:"用户目录",path:t,kind:"home",available:!0}];return t!==c.parse(t).root&&e.push({id:"root",label:"文件系统",path:"/",kind:"root",available:!0}),e}function P(t){if(y?.platform===t&&y.expiresAtMs>Date.now())return y.roots;const e=t==="win32"?nt():ot();return y={platform:t,roots:e,expiresAtMs:Date.now()+W},e}function nt(){const t=u.realpathSync.native(j.homedir()),e=new Map;e.set(m(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=F(k(r));for(const n of o){const s=String(n.Root||"");if(!s)continue;const i=Number(n.DriveType||0),a=String(n.Name||s.replace(/[\\/]+$/,"")),l=String(n.VolumeName||"").trim();e.set(m(s,"win32"),{id:`drive-${a.replace(/[^a-z0-9]+/gi,"").toLowerCase()}`,label:l?`${l} (${a})`:ht(a,i),path:s,kind:pt(i),available:!0})}}catch{const r=c.win32.parse(t).root;e.set(m(r,"win32"),{id:"drive-system",label:`系统盘 (${r.replace(/\\$/,"")})`,path:r,kind:"fixed",available:!0})}return[...e.values()]}function it(t,e){const r=v(t),n=u.readdirSync(r,{withFileTypes:!0}).filter(i=>i.isDirectory()&&!i.isSymbolicLink()&&(e||!i.name.startsWith("."))),s=n.slice(0,g).map(i=>({name:i.name,path:c.join(r,i.name),kind:"directory",hidden:i.name.startsWith("."),hasChildren:!0}));return s.sort((i,a)=>i.name.localeCompare(a.name,void 0,{numeric:!0,sensitivity:"base"})),C(r,s,n.length>g,"linux")}function st(t,e){const r=v(t),o=["$items = Get-ChildItem -LiteralPath $env:ACA_MACHINE_FS_PATH -Directory -Force -ErrorAction Stop | ForEach-Object {"," [PSCustomObject]@{ Name = $_.Name; FullName = $_.FullName; Attributes = $_.Attributes.ToString() }","}","@($items) | ConvertTo-Json -Compress"].join(`
5
+ `),n=F(k(o,{ACA_MACHINE_FS_PATH:r})),s=n.map(i=>{const a=String(i.Attributes||"");return{name:String(i.Name||""),path:String(i.FullName||""),kind:"directory",hidden:/Hidden|System/i.test(a),hasChildren:!0,reparsePoint:/ReparsePoint/i.test(a)}}).filter(i=>i.name&&i.path&&!i.reparsePoint&&(e||!i.hidden)).slice(0,g).sort((i,a)=>i.name.localeCompare(a.name,void 0,{numeric:!0,sensitivity:"base"}));return C(r,s,n.length>g,"win32")}function C(t,e,r,o){const n=o==="win32"?c.win32:c.posix,s=n.parse(t).root,i=m(t,o)===m(s,o)?null:n.dirname(t);return{platform:o,path:t,parentPath:i,breadcrumbs:at(t,o),readable:I(t,u.constants.R_OK),writable:I(t,u.constants.W_OK),items:e,truncated:r}}function at(t,e){const r=e==="win32"?c.win32:c.posix,o=r.parse(t),n=t.slice(o.root.length).split(/[\\/]+/).filter(Boolean),s=[{label:o.root.replace(/[\\/]+$/,"")||o.root,path:o.root}];let i=o.root;for(const a of n)i=r.join(i,a),s.push({label:a,path:i});return s}async function ct(t){const e=String(t.workspaceId||"").trim(),r=String(t.name||"").trim(),o=t.projectType==="chat_room"?"chat_room":"project";if(!e||!r)throw new Error("项目名称和工作区不能为空。");const n=v(String(t.path||""));u.accessSync(n,u.constants.R_OK|u.constants.W_OK);const i=(await h("GET",`/api/projects?workspaceId=${encodeURIComponent(e)}`)).items.find(l=>m(l.root_path)===m(n)),a=i||(await h("POST","/api/projects",{projectId:M(),workspaceId:e,name:r,rootPath:n,source:"aica",projectType:o})).item;return L(lt(a)),{project:a,duplicate:!!i}}function lt(t){return{projectId:t.project_id,workspaceId:t.workspace_id,name:t.name,rootPath:t.root_path}}function v(t){if(!t.trim()||!c.isAbsolute(t))throw new Error("必须选择绝对目录路径。");if(ft(t),!u.statSync(t).isDirectory())throw new Error("选择的路径不是目录。");const r=u.realpathSync.native(t);return mt(r),r}function w(t){if(!t.trim()||!c.isAbsolute(t))throw new Error("项目根目录无效。");const e=u.realpathSync.native(t);if(!u.statSync(e).isDirectory())throw new Error("项目根目录不存在。");return e}function _(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 ut(t,e,r){const o=e?c.join(t,...e.split("/")):t,n=u.realpathSync.native(o),s=c.relative(t,n);if(s.startsWith("..")||c.isAbsolute(s))throw new Error("目录超出项目范围。");const i=u.statSync(n);if(r&&!i.isDirectory())throw new Error("请求路径不是目录。");return n}function dt(t,e){return c.relative(t,e).split(c.sep).join("/")}function ft(t){const e=process.platform==="win32"?c.win32:c.posix;if(!P(process.platform).some(o=>{const n=String(o.path||"");if(!n)return!1;const s=e.relative(n,t);return s===""||!s.startsWith("..")&&!e.isAbsolute(s)}))throw new Error("目录不在允许浏览的位置内。")}function mt(t){const e=process.platform==="win32"?c.win32:c.posix;if(!P(process.platform).some(o=>{const n=String(o.path||"");if(!n)return!1;const s=e.relative(n,t);return s===""||!s.startsWith("..")&&!e.isAbsolute(s)}))throw new Error("目录不在允许浏览的位置内。")}function I(t,e){try{return u.accessSync(t,e),!0}catch{return!1}}function k(t,e={}){return E("powershell.exe",["-NoLogo","-NoProfile","-NonInteractive","-Command",t],{encoding:"utf8",windowsHide:!0,timeout:8e3,maxBuffer:4*1024*1024,env:{...process.env,...e}}).replace(/^\uFEFF/,"").trim()}function F(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 pt(t){return t===2?"removable":t===3?"fixed":t===4?"network":t===5?"optical":"drive"}function ht(t,e){return e===2?`移动磁盘 (${t})`:e===4?`网络驱动器 (${t})`:e===5?`光驱 (${t})`:`本地磁盘 (${t})`}export{m as comparableMachinePath,V as getProjectGitCommit,K as getProjectGitDiff,J as getProjectGitLog,z as getProjectGitStatus,rt as listMachineDirectory,et as listMachineFilesystemRoots,U as listProjectDirectory,vt as startMachineFilesystemWorkerLoop};
@@ -1,2 +1,2 @@
1
1
  #!/usr/bin/env node
2
- import{Command as a}from"commander";import n from"node:path";import{createStartCommand as m}from"./commands/start.js";import{createWorkerAuthCommand as t}from"./commands/worker-auth.js";import{createWorkerProjectCommand as s}from"./commands/worker-project.js";import{DEFAULT_ACA_SERVER_URL as c}from"./core/aca-config.js";process.env.ACA_DEFAULT_SERVER_URL||=c;async function i(r=process.argv){const o=new a;o.name("aica").description("AICA remote Agent worker").version("0.1.6").enablePositionalOptions(),o.addCommand(m()),o.addCommand(t()),o.addCommand(s()),await o.parseAsync(r)}const d=process.argv[1]||"",e=n.basename(d).replace(/\.(?:js|ts|cjs|mjs)$/i,"");(e==="worker-cli"||e==="aica"||e==="aca")&&i().catch(r=>{const o=r instanceof Error?r.message:String(r);console.error(o),process.exitCode=1});export{i as runWorkerCli};
2
+ import{Command as a}from"commander";import m from"node:path";import{createStartCommand as n}from"./commands/start.js";import{createDaemonCommand as t}from"./commands/daemon.js";import{createWorkerAuthCommand as s}from"./commands/worker-auth.js";import{createWorkerCodexCommand as c}from"./commands/worker-codex.js";import{createWorkerProjectCommand as i}from"./commands/worker-project.js";import{DEFAULT_ACA_SERVER_URL as d}from"./core/aca-config.js";process.env.ACA_DEFAULT_SERVER_URL||=d;async function p(r=process.argv){const o=new a;o.name("aica").description("AICA remote Agent worker").version("0.1.8").enablePositionalOptions(),o.addCommand(n()),o.addCommand(t()),o.addCommand(s()),o.addCommand(i()),o.addCommand(c()),await o.parseAsync(r)}const C=process.argv[1]||"",e=m.basename(C).replace(/\.(?:js|ts|cjs|mjs)$/i,"");(e==="worker-cli"||e==="aica"||e==="aca")&&p().catch(r=>{const o=r instanceof Error?r.message:String(r);console.error(o),process.exitCode=1});export{p as runWorkerCli};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yuandc/aica",
3
- "version": "0.1.6",
3
+ "version": "0.1.8",
4
4
  "description": "AICA remote Agent worker",
5
5
  "author": "yuandc",
6
6
  "type": "module",