@yuandc/aica 0.1.21 → 0.1.23

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.
@@ -0,0 +1 @@
1
+ async function c(e){const r=e.signal;if(!r)return e.startRequest();if(r.aborted)throw await n(e.onAbort),new Error(e.cancelledMessage);let t=null;const s=new Promise((a,o)=>{t=()=>{n(e.onAbort),o(new Error(e.cancelledMessage))},r.addEventListener("abort",t,{once:!0})});try{const a=Promise.resolve().then(()=>{if(r.aborted)throw new Error(e.cancelledMessage);return e.startRequest()});return await Promise.race([a,s])}finally{t&&r.removeEventListener("abort",t)}}async function n(e){try{await e()}catch{}}export{c as runAbortableRequest};
@@ -1 +1 @@
1
- import d from"node:fs";import{BoundedItemBuffer as I}from"../../client/bounded-buffer.js";import{promptBlocksToAcpContent as v}from"../../client/acp-content.js";import{eventFromAcpSessionUpdate as A,statusFromAcpSessionUpdate as x}from"../../client/acp-events.js";import{defaultAcpRuntimePool as w}from"../../client/acp-runtime-pool.js";import{resolveCodexAcpLaunch as S}from"./launch.js";import{CodexPromptPerformanceTracker as P,readCodexContextMaintenancePolicy as k}from"./context-maintenance.js";const M={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 N{id="codex-acp";name="Codex ACP";capabilities=M;async createSession(e){return new T(e.cwd,e.providerSessionId??null,e.timeoutMs)}async forkSession(e){if(!d.existsSync(e.cwd)||!d.statSync(e.cwd).isDirectory())throw new Error(`Project root does not exist or is not a directory: ${e.cwd}`);const s=e.sourceProviderSessionId.trim(),t=await w.acquire({providerId:"codex-acp",cwd:e.cwd,providerSessionId:s,...S(),onSessionUpdate:()=>{}});try{t.initialized||(await t.client.initialize(6e4),t.markInitialized()),e.onStatus?.({phase:"forking",label:"正在分叉 Codex 会话",detail:s,updateType:"session/fork",atMs:Date.now()});const r=await t.client.forkSession({sessionId:s,cwd:e.cwd,config:g(e)},e.timeoutMs??12e4),n=p(r);if(!n)throw new Error("Codex ACP session/fork did not return sessionId");return{providerSessionId:n}}finally{t.release()}}}class T{cwd;providerSessionId;defaultTimeoutMs;lease=null;sessionResponse=null;statusCallback=null;updateCallback=null;permissionCallback=null;userInputCallback=null;abortHandler=null;statusTimeline=[];updates=new I(16);content="";performanceTracker=null;constructor(e,s,t=E()){this.cwd=e,this.providerSessionId=s,this.defaultTimeoutMs=t}async sendPrompt(e){if(!d.existsSync(e.cwd)||!d.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.clear(),this.statusTimeline.length=0,this.performanceTracker=new P(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:v(e.prompt,e.promptBlocks),timeoutMs:e.timeoutMs??this.defaultTimeoutMs,signal:e.signal});const u=b(i);u&&u!==n&&(n=u,this.providerSessionId=u,a.setProviderSession({providerSessionId:u,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(),updateCount:this.updates.count(),updates:this.updates.snapshot(),statusTimeline:[...this.statusTimeline],performance:this.performanceTracker?.snapshot(),contextUsage:O(i),promptResponse:i}}async compactContext(e){if(!d.existsSync(e.cwd)||!d.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=U(e.timeoutMs);r=await this.compactWithAbort(i.client,{sessionId:t,allowRollover:e.allowRollover===!0,timeoutMs:c,signal:e.signal});const a=b(r);a&&a!==t&&(t=a,this.providerSessionId=a,i.setProviderSession({providerSessionId:a,sessionResponse:this.sessionResponse}))}catch(i){if(this.lease&&_(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:C(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=S();return w.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=g(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=p(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=p(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=f(t.mode);r&&await e.setSessionConfigOption({sessionId:s,configId:"approvalPolicy",value:r},6e4).catch(()=>{});const n=h(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=A(e);s&&(s.type==="agent_message_chunk"&&(this.content+=s.text??""),this.updateCallback?.(s));const t=x(e);t&&this.emitStatus(t.phase,t.label,t.detail,t.updateType)}startSlowStatusTimer(){const e=k().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","等待输入",q(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 _(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 p(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 h(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 f(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 g(o){return{...o.model?{model:o.model}:{},...f(o.mode)?{approvalPolicy:f(o.mode)}:{},...h(o.mode)?{sandbox:h(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 y(t)}}function q(o){for(const e of["prompt","message","question","title"]){const s=o[e];if(typeof s=="string"&&s.trim())return y(s)}}function y(o){const e=o.replace(/\s+/g," ").trim();return e.length>120?`${e.slice(0,117)}...`:e}function E(){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 U(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 C(s?.acaContextUsage)}function C(o){const e=l(o);if(!e)return null;const s=m(e.contextWindow),t=m(e.usedTokens),r=m(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,m(e.ratio)||t/s):0,observedAtMs:m(e.observedAtMs)||Date.now(),state:i,maintenanceAction:typeof e.maintenanceAction=="string"?e.maintenanceAction:null}}function m(o){return typeof o=="number"&&Number.isFinite(o)&&o>=0?o:0}export{N as CodexAcpProvider,M as codexAcpCapabilities};
1
+ import d from"node:fs";import{runAbortableRequest as S}from"../../client/abortable-request.js";import{BoundedItemBuffer as A}from"../../client/bounded-buffer.js";import{promptBlocksToAcpContent as v}from"../../client/acp-content.js";import{eventFromAcpSessionUpdate as x,statusFromAcpSessionUpdate as P}from"../../client/acp-events.js";import{defaultAcpRuntimePool as w}from"../../client/acp-runtime-pool.js";import{resolveCodexAcpLaunch as g}from"./launch.js";import{CodexPromptPerformanceTracker as k,readCodexContextMaintenancePolicy as M}from"./context-maintenance.js";const R={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 W{id="codex-acp";name="Codex ACP";capabilities=R;async createSession(e){return new T(e.cwd,e.providerSessionId??null,e.timeoutMs)}async forkSession(e){if(!d.existsSync(e.cwd)||!d.statSync(e.cwd).isDirectory())throw new Error(`Project root does not exist or is not a directory: ${e.cwd}`);const s=e.sourceProviderSessionId.trim(),t=await w.acquire({providerId:"codex-acp",cwd:e.cwd,providerSessionId:s,...g(),onSessionUpdate:()=>{}});try{t.initialized||(await t.client.initialize(6e4),t.markInitialized()),e.onStatus?.({phase:"forking",label:"正在分叉 Codex 会话",detail:s,updateType:"session/fork",atMs:Date.now()});const r=await t.client.forkSession({sessionId:s,cwd:e.cwd,config:y(e)},e.timeoutMs??12e4),n=p(r);if(!n)throw new Error("Codex ACP session/fork did not return sessionId");return{providerSessionId:n}}finally{t.release()}}}class T{cwd;providerSessionId;defaultTimeoutMs;lease=null;sessionResponse=null;statusCallback=null;updateCallback=null;permissionCallback=null;userInputCallback=null;abortHandler=null;statusTimeline=[];updates=new A(16);content="";performanceTracker=null;constructor(e,s,t=U()){this.cwd=e,this.providerSessionId=s,this.defaultTimeoutMs=t}async sendPrompt(e){if(!d.existsSync(e.cwd)||!d.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.clear(),this.statusTimeline.length=0,this.performanceTracker=new k(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:v(e.prompt,e.promptBlocks),timeoutMs:e.timeoutMs??this.defaultTimeoutMs,signal:e.signal});const u=C(i);u&&u!==n&&(n=u,this.providerSessionId=u,a.setProviderSession({providerSessionId:u,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(),updateCount:this.updates.count(),updates:this.updates.snapshot(),statusTimeline:[...this.statusTimeline],performance:this.performanceTracker?.snapshot(),contextUsage:D(i),promptResponse:i}}async compactContext(e){if(!d.existsSync(e.cwd)||!d.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=O(e.timeoutMs);r=await this.compactWithAbort(i.client,{sessionId:t,allowRollover:e.allowRollover===!0,timeoutMs:c,signal:e.signal});const a=C(r);a&&a!==t&&(t=a,this.providerSessionId=a,i.setProviderSession({providerSessionId:a,sessionResponse:this.sessionResponse}))}catch(i){if(this.lease&&q(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:I(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=g();return w.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=y(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=p(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=p(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=f(t.mode);r&&await e.setSessionConfigOption({sessionId:s,configId:"approvalPolicy",value:r},6e4).catch(()=>{});const n=h(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=x(e);s&&(s.type==="agent_message_chunk"&&(this.content+=s.text??""),this.updateCallback?.(s));const t=P(e);t&&this.emitStatus(t.phase,t.label,t.detail,t.updateType)}startSlowStatusTimer(){const e=M().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){return S({signal:s.signal,cancelledMessage:"Codex ACP prompt cancelled",startRequest:()=>e.prompt({sessionId:s.sessionId,prompt:s.prompt},s.timeoutMs),onAbort:async()=>{e.cancel(s.sessionId),await this.lease?.dispose()}})}async compactWithAbort(e,s){return S({signal:s.signal,cancelledMessage:"Codex ACP 上下文压缩已取消",startRequest:()=>e.compactSession({sessionId:s.sessionId,allowRollover:s.allowRollover,timeoutMs:s.timeoutMs},s.timeoutMs),onAbort:async()=>{e.cancel(s.sessionId),await this.lease?.dispose()}})}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","等待授权",_(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","等待输入",E(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 q(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 p(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 h(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 f(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 y(o){return{...o.model?{model:o.model}:{},...f(o.mode)?{approvalPolicy:f(o.mode)}:{},...h(o.mode)?{sandbox:h(o.mode)}:{},...o.configOptionValues??{}}}function _(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 b(t)}}function E(o){for(const e of["prompt","message","question","title"]){const s=o[e];if(typeof s=="string"&&s.trim())return b(s)}}function b(o){const e=o.replace(/\s+/g," ").trim();return e.length>120?`${e.slice(0,117)}...`:e}function U(){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 O(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 C(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 D(o){const e=l(o),s=l(e?._meta);return I(s?.acaContextUsage)}function I(o){const e=l(o);if(!e)return null;const s=m(e.contextWindow),t=m(e.usedTokens),r=m(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,m(e.ratio)||t/s):0,observedAtMs:m(e.observedAtMs)||Date.now(),state:i,maintenanceAction:typeof e.maintenanceAction=="string"?e.maintenanceAction:null}}function m(o){return typeof o=="number"&&Number.isFinite(o)&&o>=0?o:0}export{W as CodexAcpProvider,R as codexAcpCapabilities};
@@ -1,5 +1,5 @@
1
- import d from"node:fs";import u from"node:path";import{BoundedItemBuffer as I}from"../../client/bounded-buffer.js";import{contextUsageFromAcpSessionUpdate as w}from"../../client/context-usage.js";import{defaultAcpRuntimePool as b}from"../../client/acp-runtime-pool.js";import{providerEventFromAcpSessionUpdate as A,providerStatusFromAcpSessionUpdate as M}from"../standard-acp-events.js";const v={sessionResume:!0,imageInput:!0,fileAttachment:!0,filesystem:!0,terminal:!0,permissionRequest:!0,configOptions:!0,usage:!0,contextUsage:!0,contextCompaction:!1,plan:!0,diff:!1};class H{id="mimocode";name="MimoCode";capabilities=v;async createSession(e){return new x(e.cwd,e.providerSessionId??null,e.timeoutMs)}}class x{cwd;providerSessionId;defaultTimeoutMs;lease=null;acpSessionResponse=null;statusCallback=null;updateCallback=null;permissionCallback=null;userInputCallback=null;canExecuteTools=!0;abortHandler=null;statusTimeline=[];updates=new I(16);content="";latestContextUsage=null;contextWindow=0;constructor(e,t,s=O()){this.cwd=e,this.providerSessionId=t,this.defaultTimeoutMs=s}async sendPrompt(e){if(!d.existsSync(e.cwd)||!d.statSync(e.cwd).isDirectory())throw new Error(`Project root does not exist or is not a directory: ${e.cwd}`);const t=Date.now();this.statusCallback=e.onStatus??null,this.updateCallback=e.onUpdate??null,this.permissionCallback=e.onPermissionRequest??null,this.userInputCallback=e.onUserInputRequest??null,this.canExecuteTools=e.canExecuteTools!==!1,this.attachAbortSignal(e.signal),this.content="",this.updates.clear(),this.statusTimeline.length=0,this.latestContextUsage=null,this.contextWindow=_(e.contextWindow);let s=null,o=null,n=this.providerSessionId,a;try{this.emitStatus("initializing","MimoCode ACP 启动中","mimo acp");const r=await this.acquireRuntime(e.cwd);this.lease=r,s=r.client,o=r.started,await this.initialize(r),n=await this.establishSession(r,e.cwd),await this.applyConfig(s,n,e),this.emitStatus("thinking","思考中"),a=await s.prompt({sessionId:n,prompt:E(e.prompt,e.promptBlocks)},e.timeoutMs??this.defaultTimeoutMs)}finally{this.abortHandler&&e.signal&&(e.signal.removeEventListener("abort",this.abortHandler),this.abortHandler=null),this.releaseLease()}return this.emitStatus("completed","已完成"),{providerSessionId:n,cliType:"builtin",agentType:"mimo",effectiveConfig:{model:e.model||y(),mode:e.mode||null,configOptionValues:e.configOptionValues??null,command:o.command,processPid:o.child.pid??null,provider:"mimocode",protocol:"acp"},command:o.command,args:o.args,cwd:e.cwd,exitCode:o.child.exitCode,signal:o.child.signalCode,durationMs:Date.now()-t,stdout:s.rpc.stdout,stderr:s.rpc.stderr,content:this.content.trim(),updateCount:this.updates.count(),updates:this.updates.snapshot(),statusTimeline:[...this.statusTimeline],contextUsage:this.latestContextUsage,promptResponse:a}}async cancel(){this.providerSessionId&&this.lease?.client.cancel(this.providerSessionId)}async close(){this.statusCallback=null,this.updateCallback=null,this.permissionCallback=null,this.userInputCallback=null,this.canExecuteTools=!0,this.abortHandler&&(this.abortHandler=null),this.releaseLease()}async acquireRuntime(e){const t=process.env.ACA_MIMO_COMMAND||"mimo",s=["acp","--cwd",e];return b.acquire({providerId:"mimocode",cwd:e,providerSessionId:this.providerSessionId,command:t,args:s,onSessionUpdate:o=>this.handleSessionUpdate(o),onClientRequest:(o,n)=>this.handleClientRequest(o,n)})}async initialize(e){e.initialized||(await e.client.initialize(6e4),e.markInitialized())}async establishSession(e,t){const s=e.client;if(e.providerSessionId&&e.sessionEstablished)return this.providerSessionId=e.providerSessionId,this.acpSessionResponse=e.sessionResponse,e.providerSessionId;if(this.providerSessionId)try{this.emitStatus("resuming","MimoCode 会话恢复中","session/resume");const a=await s.resumeSession({sessionId:this.providerSessionId,cwd:t},6e4);this.acpSessionResponse=f(a);const r=m(a)||this.providerSessionId;return this.providerSessionId=r,e.setProviderSession({providerSessionId:r,sessionResponse:this.acpSessionResponse}),r}catch(a){try{this.emitStatus("resuming","MimoCode 会话加载中","session/load");const r=await s.loadSession({sessionId:this.providerSessionId,cwd:t},6e4);this.acpSessionResponse=f(r);const c=m(r)||this.providerSessionId;return this.providerSessionId=c,e.setProviderSession({providerSessionId:c,sessionResponse:this.acpSessionResponse}),c}catch{this.appendStderr(`MimoCode resume failed, creating new session: ${p(a)}
2
- `),this.providerSessionId=null}}this.emitStatus("acp","MimoCode 会话创建中","session/new");const o=await s.newSession({cwd:t},12e4);this.acpSessionResponse=f(o);const n=m(o);if(!n)throw new Error("MimoCode ACP session/new did not return sessionId");return this.providerSessionId=n,e.setProviderSession({providerSessionId:n,sessionResponse:this.acpSessionResponse}),n}async applyConfig(e,t,s){const o=h(this.acpSessionResponse,"model")||"model",n=h(this.acpSessionResponse,"mode")||"mode",a=T(s.model||y(),s.configOptionValues,this.acpSessionResponse);a&&await e.setSessionConfigOption({sessionId:t,configId:o,value:a},6e4).catch(c=>{this.appendStderr(`MimoCode model config failed: ${p(c)}
3
- `)});const r=k(s.mode);r&&await e.setSessionMode({sessionId:t,modeId:r},6e4).catch(async()=>{await e.setSessionConfigOption({sessionId:t,configId:n,value:r},6e4).catch(c=>{this.appendStderr(`MimoCode mode config failed: ${p(c)}
4
- `)})});for(const[c,g]of Object.entries(s.configOptionValues??{}))["variant","reasoning_effort","effort","model","mode"].includes(c)||await e.setSessionConfigOption({sessionId:t,configId:c,value:g},6e4).catch(C=>{this.appendStderr(`MimoCode config ${c} failed: ${p(C)}
5
- `)})}handleSessionUpdate(e){this.updates.push({method:"session/update",params:e}),this.latestContextUsage=w(e,this.contextWindow)??this.latestContextUsage;const t=A("mimo",e);t&&(t.type==="agent_message_chunk"&&(this.content+=t.text??""),this.updateCallback?.(t));const s=M("mimo",e);s&&this.emitStatus(s.phase,s.label,s.detail,s.updateType)}async handleClientRequest(e,t){if(!this.canExecuteTools&&(e==="fs/read_text_file"||e==="fs/write_text_file"))throw new Error(`Chat Room role policy disabled tool execution: ${e}`);switch(e){case"fs/read_text_file":return this.readTextFile(t);case"fs/write_text_file":return this.writeTextFile(t);case"session/request_permission":return this.requestPermission(t);case"elicitation/create":case"session/request_user_input":return this.requestUserInput(t);default:throw new Error(`Unsupported ACP client request: ${e}`)}}readTextFile(e){const t=S(this.cwd,l(e,"path")||l(e,"filePath"));return{content:d.readFileSync(t,"utf8")}}writeTextFile(e){const t=S(this.cwd,l(e,"path")||l(e,"filePath")),s=l(e,"content")??l(e,"text")??"";return d.mkdirSync(u.dirname(t),{recursive:!0}),d.writeFileSync(t,s,"utf8"),{}}async requestPermission(e){if(!this.permissionCallback)return{outcome:{outcome:"cancelled"}};const t=e&&typeof e=="object"&&!Array.isArray(e)?e:{},o=(Array.isArray(t.options)?t.options:[]).map((n,a)=>{const r=n&&typeof n=="object"&&!Array.isArray(n)?n:{};return{optionId:String(r.optionId??r.id??a),kind:String(r.kind??r.outcome??"choice"),...typeof r.label=="string"?{label:r.label}:{}}});return this.permissionCallback({requestId:String(t.requestId??t.id??`mimo-permission-${Date.now()}`),params:e,options:o})}async requestUserInput(e){if(!this.userInputCallback)return{outcome:{outcome:"cancelled"}};const t=e&&typeof e=="object"&&!Array.isArray(e)?e:{};return this.userInputCallback({requestId:String(t.requestId??t.id??`mimo-input-${Date.now()}`),prompt:String(t.prompt??t.message??t.question??""),params:e,...typeof t.defaultValue=="string"?{defaultValue:t.defaultValue}:{}})}attachAbortSignal(e){if(e){if(this.abortHandler=()=>{this.cancel()},e.aborted)throw new Error("MimoCode prompt cancelled");e.addEventListener("abort",this.abortHandler,{once:!0})}}emitStatus(e,t,s,o){const n={phase:e,label:t,...s?{detail:s}:{},...o?{updateType:o}:{},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))}requireClient(){if(!this.lease)throw new Error("MimoCode ACP client is not started");return this.lease.client}appendStderr(e){const t=this.lease?.client;t&&(t.rpc.stderr+=e)}releaseLease(){this.lease?.release(),this.lease=null}}function m(i){if(!i||typeof i!="object"||Array.isArray(i))return null;const e=i.sessionId;return typeof e=="string"&&e.trim()?e.trim():null}function f(i){return i&&typeof i=="object"&&!Array.isArray(i)?i:null}function _(i){return typeof i=="number"&&Number.isFinite(i)&&i>0?i:0}function h(i,e){const t=i?.configOptions;if(!Array.isArray(t))return null;for(const s of t){if(!s||typeof s!="object"||Array.isArray(s))continue;const o=s;if(o.category===e&&typeof o.id=="string"&&o.id.trim())return o.id.trim()}return null}function T(i,e,t){const s=q(e?.variant,e?.reasoning_effort,e?.effort);if(!s||i.endsWith(`/${s}`))return i;const o=`${i}/${s}`;return R(t,o)?o:i}function R(i,e){const t=i?.configOptions;if(!Array.isArray(t))return!1;for(const s of t){if(!s||typeof s!="object"||Array.isArray(s))continue;const o=s;if(!(o.category!=="model"||!Array.isArray(o.options))&&o.options.some(n=>P(n)===e))return!0}return!1}function P(i){if(typeof i=="string"&&i.trim())return i.trim();if(!i||typeof i!="object"||Array.isArray(i))return null;const e=i;for(const t of["value","id","modelId","model_id"]){const s=e[t];if(typeof s=="string"&&s.trim())return s.trim()}return null}function l(i,e){if(!i||typeof i!="object"||Array.isArray(i))return null;const t=i[e];return typeof t=="string"&&t.trim()?t:null}function S(i,e){if(!e)throw new Error("ACP file request did not include a path");const t=u.resolve(i,e),s=u.resolve(i);if(t!==s&&!t.startsWith(`${s}${u.sep}`))throw new Error(`ACP file request is outside project root: ${e}`);return t}function k(i){const e=String(i||"").trim();return e?["agent","agent-full-access","full-access","bypassPermissions","danger-full-access"].includes(e)?"build":e==="read-only"?"plan":e:null}function q(...i){for(const e of i)if(typeof e=="string"&&e.trim())return e.trim();return null}function p(i){return i instanceof Error?i.message:String(i)}function y(){return process.env.ACA_MIMO_DEFAULT_MODEL||"mimo/mimo-auto"}function E(i,e){return(e?.length?e:[{type:"text",text:i}]).map(s=>s.type==="image"?{type:"image",data:s.data,mimeType:s.mimeType}:{type:"text",text:s.text})}function O(){const i=Number.parseInt(process.env.ACA_MIMO_PROMPT_TIMEOUT_MS??process.env.ACA_ACP_PROMPT_TIMEOUT_MS??"",10);return Number.isInteger(i)&&i>=3e4?i:360*60*1e3}export{H as MimoCodeProvider,v as mimoCodeCapabilities};
1
+ import u from"node:fs";import p from"node:path";import{runAbortableRequest as I}from"../../client/abortable-request.js";import{BoundedItemBuffer as w}from"../../client/bounded-buffer.js";import{contextUsageFromAcpSessionUpdate as b}from"../../client/context-usage.js";import{defaultAcpRuntimePool as A}from"../../client/acp-runtime-pool.js";import{providerEventFromAcpSessionUpdate as M,providerStatusFromAcpSessionUpdate as v}from"../standard-acp-events.js";const x={sessionResume:!0,imageInput:!0,fileAttachment:!0,filesystem:!0,terminal:!0,permissionRequest:!0,configOptions:!0,usage:!0,contextUsage:!0,contextCompaction:!1,plan:!0,diff:!1};class B{id="mimocode";name="MimoCode";capabilities=x;async createSession(e){return new _(e.cwd,e.providerSessionId??null,e.timeoutMs)}}class _{cwd;providerSessionId;defaultTimeoutMs;lease=null;acpSessionResponse=null;statusCallback=null;updateCallback=null;permissionCallback=null;userInputCallback=null;canExecuteTools=!0;abortHandler=null;statusTimeline=[];updates=new w(16);content="";latestContextUsage=null;contextWindow=0;constructor(e,t,s=U()){this.cwd=e,this.providerSessionId=t,this.defaultTimeoutMs=s}async sendPrompt(e){if(!u.existsSync(e.cwd)||!u.statSync(e.cwd).isDirectory())throw new Error(`Project root does not exist or is not a directory: ${e.cwd}`);const t=Date.now();this.statusCallback=e.onStatus??null,this.updateCallback=e.onUpdate??null,this.permissionCallback=e.onPermissionRequest??null,this.userInputCallback=e.onUserInputRequest??null,this.canExecuteTools=e.canExecuteTools!==!1,this.attachAbortSignal(e.signal),this.content="",this.updates.clear(),this.statusTimeline.length=0,this.latestContextUsage=null,this.contextWindow=T(e.contextWindow);let s=null,o=null,n=this.providerSessionId,a;try{this.emitStatus("initializing","MimoCode ACP 启动中","mimo acp");const r=await this.acquireRuntime(e.cwd);this.lease=r,s=r.client,o=r.started,await this.initialize(r),n=await this.establishSession(r,e.cwd),await this.applyConfig(s,n,e),this.emitStatus("thinking","思考中");const c=s,d=n;if(!c||!d)throw new Error("MimoCode ACP session is unavailable");a=await I({signal:e.signal,cancelledMessage:"MimoCode prompt cancelled",startRequest:()=>c.prompt({sessionId:d,prompt:O(e.prompt,e.promptBlocks)},e.timeoutMs??this.defaultTimeoutMs),onAbort:async()=>{c.cancel(d),await this.lease?.dispose()}})}finally{this.abortHandler&&e.signal&&(e.signal.removeEventListener("abort",this.abortHandler),this.abortHandler=null),this.releaseLease()}return this.emitStatus("completed","已完成"),{providerSessionId:n,cliType:"builtin",agentType:"mimo",effectiveConfig:{model:e.model||g(),mode:e.mode||null,configOptionValues:e.configOptionValues??null,command:o.command,processPid:o.child.pid??null,provider:"mimocode",protocol:"acp"},command:o.command,args:o.args,cwd:e.cwd,exitCode:o.child.exitCode,signal:o.child.signalCode,durationMs:Date.now()-t,stdout:s.rpc.stdout,stderr:s.rpc.stderr,content:this.content.trim(),updateCount:this.updates.count(),updates:this.updates.snapshot(),statusTimeline:[...this.statusTimeline],contextUsage:this.latestContextUsage,promptResponse:a}}async cancel(){this.providerSessionId&&this.lease?.client.cancel(this.providerSessionId)}async close(){this.statusCallback=null,this.updateCallback=null,this.permissionCallback=null,this.userInputCallback=null,this.canExecuteTools=!0,this.abortHandler&&(this.abortHandler=null),this.releaseLease()}async acquireRuntime(e){const t=process.env.ACA_MIMO_COMMAND||"mimo",s=["acp","--cwd",e];return A.acquire({providerId:"mimocode",cwd:e,providerSessionId:this.providerSessionId,command:t,args:s,onSessionUpdate:o=>this.handleSessionUpdate(o),onClientRequest:(o,n)=>this.handleClientRequest(o,n)})}async initialize(e){e.initialized||(await e.client.initialize(6e4),e.markInitialized())}async establishSession(e,t){const s=e.client;if(e.providerSessionId&&e.sessionEstablished)return this.providerSessionId=e.providerSessionId,this.acpSessionResponse=e.sessionResponse,e.providerSessionId;if(this.providerSessionId)try{this.emitStatus("resuming","MimoCode 会话恢复中","session/resume");const a=await s.resumeSession({sessionId:this.providerSessionId,cwd:t},6e4);this.acpSessionResponse=h(a);const r=f(a)||this.providerSessionId;return this.providerSessionId=r,e.setProviderSession({providerSessionId:r,sessionResponse:this.acpSessionResponse}),r}catch(a){try{this.emitStatus("resuming","MimoCode 会话加载中","session/load");const r=await s.loadSession({sessionId:this.providerSessionId,cwd:t},6e4);this.acpSessionResponse=h(r);const c=f(r)||this.providerSessionId;return this.providerSessionId=c,e.setProviderSession({providerSessionId:c,sessionResponse:this.acpSessionResponse}),c}catch{this.appendStderr(`MimoCode resume failed, creating new session: ${m(a)}
2
+ `),this.providerSessionId=null}}this.emitStatus("acp","MimoCode 会话创建中","session/new");const o=await s.newSession({cwd:t},12e4);this.acpSessionResponse=h(o);const n=f(o);if(!n)throw new Error("MimoCode ACP session/new did not return sessionId");return this.providerSessionId=n,e.setProviderSession({providerSessionId:n,sessionResponse:this.acpSessionResponse}),n}async applyConfig(e,t,s){const o=S(this.acpSessionResponse,"model")||"model",n=S(this.acpSessionResponse,"mode")||"mode",a=R(s.model||g(),s.configOptionValues,this.acpSessionResponse);a&&await e.setSessionConfigOption({sessionId:t,configId:o,value:a},6e4).catch(c=>{this.appendStderr(`MimoCode model config failed: ${m(c)}
3
+ `)});const r=k(s.mode);r&&await e.setSessionMode({sessionId:t,modeId:r},6e4).catch(async()=>{await e.setSessionConfigOption({sessionId:t,configId:n,value:r},6e4).catch(c=>{this.appendStderr(`MimoCode mode config failed: ${m(c)}
4
+ `)})});for(const[c,d]of Object.entries(s.configOptionValues??{}))["variant","reasoning_effort","effort","model","mode"].includes(c)||await e.setSessionConfigOption({sessionId:t,configId:c,value:d},6e4).catch(C=>{this.appendStderr(`MimoCode config ${c} failed: ${m(C)}
5
+ `)})}handleSessionUpdate(e){this.updates.push({method:"session/update",params:e}),this.latestContextUsage=b(e,this.contextWindow)??this.latestContextUsage;const t=M("mimo",e);t&&(t.type==="agent_message_chunk"&&(this.content+=t.text??""),this.updateCallback?.(t));const s=v("mimo",e);s&&this.emitStatus(s.phase,s.label,s.detail,s.updateType)}async handleClientRequest(e,t){if(!this.canExecuteTools&&(e==="fs/read_text_file"||e==="fs/write_text_file"))throw new Error(`Chat Room role policy disabled tool execution: ${e}`);switch(e){case"fs/read_text_file":return this.readTextFile(t);case"fs/write_text_file":return this.writeTextFile(t);case"session/request_permission":return this.requestPermission(t);case"elicitation/create":case"session/request_user_input":return this.requestUserInput(t);default:throw new Error(`Unsupported ACP client request: ${e}`)}}readTextFile(e){const t=y(this.cwd,l(e,"path")||l(e,"filePath"));return{content:u.readFileSync(t,"utf8")}}writeTextFile(e){const t=y(this.cwd,l(e,"path")||l(e,"filePath")),s=l(e,"content")??l(e,"text")??"";return u.mkdirSync(p.dirname(t),{recursive:!0}),u.writeFileSync(t,s,"utf8"),{}}async requestPermission(e){if(!this.permissionCallback)return{outcome:{outcome:"cancelled"}};const t=e&&typeof e=="object"&&!Array.isArray(e)?e:{},o=(Array.isArray(t.options)?t.options:[]).map((n,a)=>{const r=n&&typeof n=="object"&&!Array.isArray(n)?n:{};return{optionId:String(r.optionId??r.id??a),kind:String(r.kind??r.outcome??"choice"),...typeof r.label=="string"?{label:r.label}:{}}});return this.permissionCallback({requestId:String(t.requestId??t.id??`mimo-permission-${Date.now()}`),params:e,options:o})}async requestUserInput(e){if(!this.userInputCallback)return{outcome:{outcome:"cancelled"}};const t=e&&typeof e=="object"&&!Array.isArray(e)?e:{};return this.userInputCallback({requestId:String(t.requestId??t.id??`mimo-input-${Date.now()}`),prompt:String(t.prompt??t.message??t.question??""),params:e,...typeof t.defaultValue=="string"?{defaultValue:t.defaultValue}:{}})}attachAbortSignal(e){if(e){if(this.abortHandler=()=>{this.cancel()},e.aborted)throw new Error("MimoCode prompt cancelled");e.addEventListener("abort",this.abortHandler,{once:!0})}}emitStatus(e,t,s,o){const n={phase:e,label:t,...s?{detail:s}:{},...o?{updateType:o}:{},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))}requireClient(){if(!this.lease)throw new Error("MimoCode ACP client is not started");return this.lease.client}appendStderr(e){const t=this.lease?.client;t&&(t.rpc.stderr+=e)}releaseLease(){this.lease?.release(),this.lease=null}}function f(i){if(!i||typeof i!="object"||Array.isArray(i))return null;const e=i.sessionId;return typeof e=="string"&&e.trim()?e.trim():null}function h(i){return i&&typeof i=="object"&&!Array.isArray(i)?i:null}function T(i){return typeof i=="number"&&Number.isFinite(i)&&i>0?i:0}function S(i,e){const t=i?.configOptions;if(!Array.isArray(t))return null;for(const s of t){if(!s||typeof s!="object"||Array.isArray(s))continue;const o=s;if(o.category===e&&typeof o.id=="string"&&o.id.trim())return o.id.trim()}return null}function R(i,e,t){const s=E(e?.variant,e?.reasoning_effort,e?.effort);if(!s||i.endsWith(`/${s}`))return i;const o=`${i}/${s}`;return P(t,o)?o:i}function P(i,e){const t=i?.configOptions;if(!Array.isArray(t))return!1;for(const s of t){if(!s||typeof s!="object"||Array.isArray(s))continue;const o=s;if(!(o.category!=="model"||!Array.isArray(o.options))&&o.options.some(n=>q(n)===e))return!0}return!1}function q(i){if(typeof i=="string"&&i.trim())return i.trim();if(!i||typeof i!="object"||Array.isArray(i))return null;const e=i;for(const t of["value","id","modelId","model_id"]){const s=e[t];if(typeof s=="string"&&s.trim())return s.trim()}return null}function l(i,e){if(!i||typeof i!="object"||Array.isArray(i))return null;const t=i[e];return typeof t=="string"&&t.trim()?t:null}function y(i,e){if(!e)throw new Error("ACP file request did not include a path");const t=p.resolve(i,e),s=p.resolve(i);if(t!==s&&!t.startsWith(`${s}${p.sep}`))throw new Error(`ACP file request is outside project root: ${e}`);return t}function k(i){const e=String(i||"").trim();return e?["agent","agent-full-access","full-access","bypassPermissions","danger-full-access"].includes(e)?"build":e==="read-only"?"plan":e:null}function E(...i){for(const e of i)if(typeof e=="string"&&e.trim())return e.trim();return null}function m(i){return i instanceof Error?i.message:String(i)}function g(){return process.env.ACA_MIMO_DEFAULT_MODEL||"mimo/mimo-auto"}function O(i,e){return(e?.length?e:[{type:"text",text:i}]).map(s=>s.type==="image"?{type:"image",data:s.data,mimeType:s.mimeType}:{type:"text",text:s.text})}function U(){const i=Number.parseInt(process.env.ACA_MIMO_PROMPT_TIMEOUT_MS??process.env.ACA_ACP_PROMPT_TIMEOUT_MS??"",10);return Number.isInteger(i)&&i>=3e4?i:360*60*1e3}export{B as MimoCodeProvider,x as mimoCodeCapabilities};
@@ -1,4 +1,4 @@
1
- import p from"node:fs";import m from"node:path";import{BoundedItemBuffer as y}from"../../client/bounded-buffer.js";import{contextUsageFromAcpSessionUpdate as x}from"../../client/context-usage.js";import{defaultAcpRuntimePool as E}from"../../client/acp-runtime-pool.js";import{providerEventFromAcpSessionUpdate as O,providerStatusFromAcpSessionUpdate as P}from"../standard-acp-events.js";const T={sessionResume:!0,imageInput:!0,fileAttachment:!0,filesystem:!0,terminal:!0,permissionRequest:!0,configOptions:!0,usage:!0,contextUsage:!0,contextCompaction:!1,plan:!0,diff:!0};class j{id="opencode";name="OpenCode";capabilities=T;async createSession(e){return new _(e.cwd,e.providerSessionId??null,e.timeoutMs)}}class _{cwd;providerSessionId;defaultTimeoutMs;lease=null;sessionResponse=null;statusCallback=null;updateCallback=null;permissionCallback=null;userInputCallback=null;canExecuteTools=!0;abortHandler=null;statusTimeline=[];updates=new y(16);content="";latestContextUsage=null;contextWindow=0;resumedExistingSession=!1;constructor(e,t,s=$()){this.cwd=e,this.providerSessionId=t,this.defaultTimeoutMs=s}async sendPrompt(e){if(!p.existsSync(e.cwd)||!p.statSync(e.cwd).isDirectory())throw new Error(`Project root does not exist or is not a directory: ${e.cwd}`);const t=Date.now();this.statusCallback=e.onStatus??null,this.updateCallback=e.onUpdate??null,this.permissionCallback=e.onPermissionRequest??null,this.userInputCallback=e.onUserInputRequest??null,this.canExecuteTools=e.canExecuteTools!==!1,this.attachAbortSignal(e.signal),this.content="",this.updates.clear(),this.statusTimeline.length=0,this.latestContextUsage=null,this.contextWindow=q(e.contextWindow);let s=null,n=null,o=this.providerSessionId,r;try{this.emitStatus("initializing","OpenCode ACP 启动中","opencode acp");const l=await this.acquireRuntime(e.cwd);this.lease=l,s=l.client,n=l.started,await this.initialize(l),o=await this.establishSession(l,e),await this.applyConfig(s,o,e),this.emitStatus("thinking","思考中");const a=D(e.prompt,e.promptBlocks);try{r=await this.promptWithAbort(s,{sessionId:o,prompt:a,timeoutMs:e.timeoutMs??this.defaultTimeoutMs,signal:e.signal})}catch(f){if(!this.resumedExistingSession||!U(f))throw f;this.appendStderr(`OpenCode resumed session rejected session/prompt with Invalid params; creating a fresh session and retrying once.
2
- `),await this.lease?.dispose().catch(()=>{}),this.lease=null,this.providerSessionId=null,this.sessionResponse=null,this.resumedExistingSession=!1,this.emitStatus("resuming","OpenCode 会话已失效,正在新建","session/prompt invalid params");const u=await this.acquireRuntime(e.cwd);this.lease=u,s=u.client,n=u.started,await this.initialize(u),o=await this.establishSession(u,e),await this.applyConfig(s,o,e),this.emitStatus("thinking","思考中"),r=await this.promptWithAbort(s,{sessionId:o,prompt:a,timeoutMs:e.timeoutMs??this.defaultTimeoutMs,signal:e.signal})}}finally{this.abortHandler&&e.signal&&(e.signal.removeEventListener("abort",this.abortHandler),this.abortHandler=null),this.releaseLease()}return this.emitStatus("completed","已完成"),{providerSessionId:o,cliType:"builtin",agentType:"opencode",effectiveConfig:{model:h(e.model)||null,mode:e.mode||null,configOptionValues:e.configOptionValues??null,command:n.command,processPid:n.child.pid??null,provider:"opencode",protocol:"acp"},command:n.command,args:n.args,cwd:e.cwd,exitCode:n.child.exitCode,signal:n.child.signalCode,durationMs:Date.now()-t,stdout:s.rpc.stdout,stderr:s.rpc.stderr,content:this.content.trim(),updateCount:this.updates.count(),updates:this.updates.snapshot(),statusTimeline:[...this.statusTimeline],contextUsage:this.latestContextUsage,promptResponse:r}}async cancel(){this.providerSessionId&&this.lease?.client.cancel(this.providerSessionId)}async close(){this.statusCallback=null,this.updateCallback=null,this.permissionCallback=null,this.userInputCallback=null,this.canExecuteTools=!0,this.abortHandler&&(this.abortHandler=null),this.releaseLease()}async acquireRuntime(e){const t=process.env.ACA_OPENCODE_COMMAND||"opencode";return E.acquire({providerId:"opencode",cwd:e,providerSessionId:this.providerSessionId,command:t,args:["acp","--cwd",e],onSessionUpdate:s=>this.handleSessionUpdate(s),onClientRequest:(s,n)=>this.handleClientRequest(s,n)})}async initialize(e){e.initialized||(await e.client.initialize(6e4),e.markInitialized())}async establishSession(e,t){const s=e.client,n=R(t);if(this.resumedExistingSession=!1,e.providerSessionId&&e.sessionEstablished){if(S(e.sessionResponse,t.model))return this.providerSessionId=e.providerSessionId,this.sessionResponse=e.sessionResponse,this.resumedExistingSession=!0,e.providerSessionId;this.providerSessionId=null}else if(this.providerSessionId)try{this.emitStatus("resuming","OpenCode 会话恢复中","session/load");const l=await s.loadSession({sessionId:this.providerSessionId,cwd:t.cwd},6e4);if(this.sessionResponse=d(l),S(this.sessionResponse,t.model)){const a=g(l)||this.providerSessionId;return this.providerSessionId=a,e.setProviderSession({providerSessionId:a,sessionResponse:this.sessionResponse}),this.resumedExistingSession=!0,a}this.appendStderr(`OpenCode model changed from ${C(this.sessionResponse)??"unknown"} to ${h(t.model)||"default"}, creating new session
3
- `),this.providerSessionId=null}catch{this.providerSessionId=null}this.emitStatus("acp","OpenCode 会话创建中","session/new");const o=await s.newSession({cwd:t.cwd,config:n},12e4);this.sessionResponse=d(o);const r=g(o);if(!r)throw new Error("OpenCode ACP session/new did not return sessionId");return this.providerSessionId=r,e.setProviderSession({providerSessionId:r,sessionResponse:this.sessionResponse}),r}async applyConfig(e,t,s){const n=w(s.mode);n&&await e.setSessionMode({sessionId:t,modeId:n},6e4).catch(o=>{this.appendStderr(`OpenCode mode config failed: ${v(o)}
4
- `)})}handleSessionUpdate(e){this.updates.push({method:"session/update",params:e}),this.latestContextUsage=x(e,this.contextWindow)??this.latestContextUsage;const t=O("opencode",e);t&&(t.type==="agent_message_chunk"&&(this.content+=t.text??""),this.updateCallback?.(t));const s=P("opencode",e);s&&this.emitStatus(s.phase,s.label,s.detail,s.updateType)}async handleClientRequest(e,t){if(!this.canExecuteTools&&(e==="fs/read_text_file"||e==="fs/write_text_file"))throw new Error(`Chat Room role policy disabled tool execution: ${e}`);switch(e){case"fs/read_text_file":return this.readTextFile(t);case"fs/write_text_file":return this.writeTextFile(t);case"session/request_permission":return this.requestPermission(t);case"elicitation/create":case"session/request_user_input":return this.requestUserInput(t);default:throw new Error(`Unsupported OpenCode ACP client request: ${e}`)}}readTextFile(e){const t=I(this.cwd,c(e,"path")||c(e,"filePath"));return{content:p.readFileSync(t,"utf8")}}writeTextFile(e){const t=I(this.cwd,c(e,"path")||c(e,"filePath")),s=c(e,"content")??c(e,"text")??"";return p.mkdirSync(m.dirname(t),{recursive:!0}),p.writeFileSync(t,s,"utf8"),{}}attachAbortSignal(e){if(e){if(this.abortHandler=()=>{this.cancel()},e.aborted)throw new Error("OpenCode prompt cancelled");e.addEventListener("abort",this.abortHandler,{once:!0})}}async promptWithAbort(e,t){const s=e.prompt({sessionId:t.sessionId,prompt:t.prompt},t.timeoutMs);if(!t.signal)return s;if(t.signal.aborted)throw await this.lease?.dispose().catch(()=>{}),new Error("OpenCode prompt cancelled");let n=null;const o=new Promise((r,l)=>{n=()=>{e.cancel(t.sessionId),this.lease?.dispose().catch(()=>{}),l(new Error("OpenCode prompt cancelled"))},t.signal?.addEventListener("abort",n,{once:!0})});try{return await Promise.race([s,o])}finally{n&&t.signal.removeEventListener("abort",n)}}async requestPermission(e){if(!this.permissionCallback)return{outcome:{outcome:"cancelled"}};const t=d(e)??{},s=d(t.request)??t,o=(Array.isArray(s.options)?s.options:[]).map((r,l)=>{const a=d(r)??{};return{optionId:String(a.optionId??a.id??l),kind:String(a.kind??a.outcome??"choice"),...typeof a.label=="string"?{label:a.label}:{}}});return this.emitStatus("requestPermission","等待授权",M(s),"session/request_permission"),this.permissionCallback({requestId:String(s.requestId??s.id??`opencode-permission-${Date.now()}`),params:e,options:o})}async requestUserInput(e){if(!this.userInputCallback)return{outcome:{outcome:"cancelled"}};const t=d(e)??{},s=d(t.request)??t;return this.emitStatus("requestPermission","等待输入",k(s),"elicitation/create"),this.userInputCallback({requestId:String(s.requestId??s.id??`opencode-input-${Date.now()}`),prompt:String(s.prompt??s.message??s.question??""),params:e,...typeof s.defaultValue=="string"?{defaultValue:s.defaultValue}:{}})}emitStatus(e,t,s,n){const o={phase:e,label:t,...s?{detail:s}:{},...n?{updateType:n}:{},atMs:Date.now()},r=this.statusTimeline[this.statusTimeline.length-1];r&&r.phase===o.phase&&r.label===o.label&&r.detail===o.detail&&r.updateType===o.updateType||(this.statusTimeline.push(o),this.statusCallback?.(o))}appendStderr(e){const t=this.lease?.client;t&&(t.rpc.stderr+=e)}releaseLease(){this.lease?.release(),this.lease=null}}function g(i){const t=d(i)?.sessionId;return typeof t=="string"&&t.trim()?t.trim():null}function d(i){return i&&typeof i=="object"&&!Array.isArray(i)?i:null}function q(i){return typeof i=="number"&&Number.isFinite(i)&&i>0?i:0}function R(i){const e=h(i.model),t=w(i.mode);return{...A(i.configOptionValues),...e?{model:e}:{},...t?{mode:t}:{}}}function A(i){const e={};for(const[t,s]of Object.entries(i??{}))["model","mode","reasoning_effort","effort","variant"].includes(t)||(e[t]=s);return e}function S(i,e){const t=h(e);if(!t)return!0;const s=C(i);return!s||s===t}function C(i){const t=d(i?.models)?.currentModelId;return typeof t=="string"&&t.trim()?t.trim():null}function w(i){const e=String(i||"").trim();return e?["agent-full-access","full-access","bypassPermissions","danger-full-access"].includes(e)?"build":e==="read-only"?"plan":e:null}function c(i,e){const s=d(i)?.[e];return typeof s=="string"&&s.trim()?s:null}function I(i,e){if(!e)throw new Error("ACP file request did not include a path");const t=m.resolve(i,e),s=m.resolve(i);if(t!==s&&!t.startsWith(`${s}${m.sep}`))throw new Error(`ACP file request is outside project root: ${e}`);return t}function M(i){const e=d(i.params)??i;for(const t of["command","cwd","reason","grantRoot","method"]){const s=e[t];if(typeof s=="string"&&s.trim())return b(s)}}function k(i){for(const e of["prompt","message","question","title"]){const t=i[e];if(typeof t=="string"&&t.trim())return b(t)}}function b(i){const e=i.replace(/\s+/g," ").trim();return e.length>120?`${e.slice(0,117)}...`:e}function v(i){return i instanceof Error?i.message:String(i)}function U(i){return/\binvalid params\b/i.test(v(i))}function D(i,e){return(e?.length?e:[{type:"text",text:i}]).map(s=>s.type==="image"?{type:"image",data:s.data,mimeType:s.mimeType}:{type:"text",text:s.text})}function F(){return process.env.ACA_OPENCODE_DEFAULT_MODEL||""}function h(i){const e=String(i||F()).trim();return e==="default"||e==="opencode-default"?"":e}function $(){const i=Number.parseInt(process.env.ACA_OPENCODE_PROMPT_TIMEOUT_MS??process.env.ACA_ACP_PROMPT_TIMEOUT_MS??"",10);return Number.isInteger(i)&&i>=3e4?i:360*60*1e3}export{j as OpenCodeProvider,T as openCodeCapabilities};
1
+ import p from"node:fs";import m from"node:path";import{runAbortableRequest as v}from"../../client/abortable-request.js";import{BoundedItemBuffer as x}from"../../client/bounded-buffer.js";import{contextUsageFromAcpSessionUpdate as O}from"../../client/context-usage.js";import{defaultAcpRuntimePool as q}from"../../client/acp-runtime-pool.js";import{providerEventFromAcpSessionUpdate as E,providerStatusFromAcpSessionUpdate as R}from"../standard-acp-events.js";const T={sessionResume:!0,imageInput:!0,fileAttachment:!0,filesystem:!0,terminal:!0,permissionRequest:!0,configOptions:!0,usage:!0,contextUsage:!0,contextCompaction:!1,plan:!0,diff:!0};class G{id="opencode";name="OpenCode";capabilities=T;async createSession(e){return new A(e.cwd,e.providerSessionId??null,e.timeoutMs)}}class A{cwd;providerSessionId;defaultTimeoutMs;lease=null;sessionResponse=null;statusCallback=null;updateCallback=null;permissionCallback=null;userInputCallback=null;canExecuteTools=!0;abortHandler=null;statusTimeline=[];updates=new x(16);content="";latestContextUsage=null;contextWindow=0;resumedExistingSession=!1;constructor(e,t,s=z()){this.cwd=e,this.providerSessionId=t,this.defaultTimeoutMs=s}async sendPrompt(e){if(!p.existsSync(e.cwd)||!p.statSync(e.cwd).isDirectory())throw new Error(`Project root does not exist or is not a directory: ${e.cwd}`);const t=Date.now();this.statusCallback=e.onStatus??null,this.updateCallback=e.onUpdate??null,this.permissionCallback=e.onPermissionRequest??null,this.userInputCallback=e.onUserInputRequest??null,this.canExecuteTools=e.canExecuteTools!==!1,this.attachAbortSignal(e.signal),this.content="",this.updates.clear(),this.statusTimeline.length=0,this.latestContextUsage=null,this.contextWindow=M(e.contextWindow);let s=null,o=null,n=this.providerSessionId,r;try{this.emitStatus("initializing","OpenCode ACP 启动中","opencode acp");const d=await this.acquireRuntime(e.cwd);this.lease=d,s=d.client,o=d.started,await this.initialize(d),n=await this.establishSession(d,e),await this.applyConfig(s,n,e),this.emitStatus("thinking","思考中");const a=F(e.prompt,e.promptBlocks);try{r=await this.promptWithAbort(s,{sessionId:n,prompt:a,timeoutMs:e.timeoutMs??this.defaultTimeoutMs,signal:e.signal})}catch(f){if(!this.resumedExistingSession||!D(f))throw f;this.appendStderr(`OpenCode resumed session rejected session/prompt with Invalid params; creating a fresh session and retrying once.
2
+ `),await this.lease?.dispose().catch(()=>{}),this.lease=null,this.providerSessionId=null,this.sessionResponse=null,this.resumedExistingSession=!1,this.emitStatus("resuming","OpenCode 会话已失效,正在新建","session/prompt invalid params");const u=await this.acquireRuntime(e.cwd);this.lease=u,s=u.client,o=u.started,await this.initialize(u),n=await this.establishSession(u,e),await this.applyConfig(s,n,e),this.emitStatus("thinking","思考中"),r=await this.promptWithAbort(s,{sessionId:n,prompt:a,timeoutMs:e.timeoutMs??this.defaultTimeoutMs,signal:e.signal})}}finally{this.abortHandler&&e.signal&&(e.signal.removeEventListener("abort",this.abortHandler),this.abortHandler=null),this.releaseLease()}return this.emitStatus("completed","已完成"),{providerSessionId:n,cliType:"builtin",agentType:"opencode",effectiveConfig:{model:h(e.model)||null,mode:e.mode||null,configOptionValues:e.configOptionValues??null,command:o.command,processPid:o.child.pid??null,provider:"opencode",protocol:"acp"},command:o.command,args:o.args,cwd:e.cwd,exitCode:o.child.exitCode,signal:o.child.signalCode,durationMs:Date.now()-t,stdout:s.rpc.stdout,stderr:s.rpc.stderr,content:this.content.trim(),updateCount:this.updates.count(),updates:this.updates.snapshot(),statusTimeline:[...this.statusTimeline],contextUsage:this.latestContextUsage,promptResponse:r}}async cancel(){this.providerSessionId&&this.lease?.client.cancel(this.providerSessionId)}async close(){this.statusCallback=null,this.updateCallback=null,this.permissionCallback=null,this.userInputCallback=null,this.canExecuteTools=!0,this.abortHandler&&(this.abortHandler=null),this.releaseLease()}async acquireRuntime(e){const t=process.env.ACA_OPENCODE_COMMAND||"opencode";return q.acquire({providerId:"opencode",cwd:e,providerSessionId:this.providerSessionId,command:t,args:["acp","--cwd",e],onSessionUpdate:s=>this.handleSessionUpdate(s),onClientRequest:(s,o)=>this.handleClientRequest(s,o)})}async initialize(e){e.initialized||(await e.client.initialize(6e4),e.markInitialized())}async establishSession(e,t){const s=e.client,o=P(t);if(this.resumedExistingSession=!1,e.providerSessionId&&e.sessionEstablished){if(S(e.sessionResponse,t.model))return this.providerSessionId=e.providerSessionId,this.sessionResponse=e.sessionResponse,this.resumedExistingSession=!0,e.providerSessionId;this.providerSessionId=null}else if(this.providerSessionId)try{this.emitStatus("resuming","OpenCode 会话恢复中","session/load");const d=await s.loadSession({sessionId:this.providerSessionId,cwd:t.cwd},6e4);if(this.sessionResponse=l(d),S(this.sessionResponse,t.model)){const a=g(d)||this.providerSessionId;return this.providerSessionId=a,e.setProviderSession({providerSessionId:a,sessionResponse:this.sessionResponse}),this.resumedExistingSession=!0,a}this.appendStderr(`OpenCode model changed from ${C(this.sessionResponse)??"unknown"} to ${h(t.model)||"default"}, creating new session
3
+ `),this.providerSessionId=null}catch{this.providerSessionId=null}this.emitStatus("acp","OpenCode 会话创建中","session/new");const n=await s.newSession({cwd:t.cwd,config:o},12e4);this.sessionResponse=l(n);const r=g(n);if(!r)throw new Error("OpenCode ACP session/new did not return sessionId");return this.providerSessionId=r,e.setProviderSession({providerSessionId:r,sessionResponse:this.sessionResponse}),r}async applyConfig(e,t,s){const o=w(s.mode);o&&await e.setSessionMode({sessionId:t,modeId:o},6e4).catch(n=>{this.appendStderr(`OpenCode mode config failed: ${y(n)}
4
+ `)})}handleSessionUpdate(e){this.updates.push({method:"session/update",params:e}),this.latestContextUsage=O(e,this.contextWindow)??this.latestContextUsage;const t=E("opencode",e);t&&(t.type==="agent_message_chunk"&&(this.content+=t.text??""),this.updateCallback?.(t));const s=R("opencode",e);s&&this.emitStatus(s.phase,s.label,s.detail,s.updateType)}async handleClientRequest(e,t){if(!this.canExecuteTools&&(e==="fs/read_text_file"||e==="fs/write_text_file"))throw new Error(`Chat Room role policy disabled tool execution: ${e}`);switch(e){case"fs/read_text_file":return this.readTextFile(t);case"fs/write_text_file":return this.writeTextFile(t);case"session/request_permission":return this.requestPermission(t);case"elicitation/create":case"session/request_user_input":return this.requestUserInput(t);default:throw new Error(`Unsupported OpenCode ACP client request: ${e}`)}}readTextFile(e){const t=I(this.cwd,c(e,"path")||c(e,"filePath"));return{content:p.readFileSync(t,"utf8")}}writeTextFile(e){const t=I(this.cwd,c(e,"path")||c(e,"filePath")),s=c(e,"content")??c(e,"text")??"";return p.mkdirSync(m.dirname(t),{recursive:!0}),p.writeFileSync(t,s,"utf8"),{}}attachAbortSignal(e){if(e){if(this.abortHandler=()=>{this.cancel()},e.aborted)throw new Error("OpenCode prompt cancelled");e.addEventListener("abort",this.abortHandler,{once:!0})}}async promptWithAbort(e,t){return v({signal:t.signal,cancelledMessage:"OpenCode prompt cancelled",startRequest:()=>e.prompt({sessionId:t.sessionId,prompt:t.prompt},t.timeoutMs),onAbort:async()=>{e.cancel(t.sessionId),await this.lease?.dispose()}})}async requestPermission(e){if(!this.permissionCallback)return{outcome:{outcome:"cancelled"}};const t=l(e)??{},s=l(t.request)??t,n=(Array.isArray(s.options)?s.options:[]).map((r,d)=>{const a=l(r)??{};return{optionId:String(a.optionId??a.id??d),kind:String(a.kind??a.outcome??"choice"),...typeof a.label=="string"?{label:a.label}:{}}});return this.emitStatus("requestPermission","等待授权",k(s),"session/request_permission"),this.permissionCallback({requestId:String(s.requestId??s.id??`opencode-permission-${Date.now()}`),params:e,options:n})}async requestUserInput(e){if(!this.userInputCallback)return{outcome:{outcome:"cancelled"}};const t=l(e)??{},s=l(t.request)??t;return this.emitStatus("requestPermission","等待输入",U(s),"elicitation/create"),this.userInputCallback({requestId:String(s.requestId??s.id??`opencode-input-${Date.now()}`),prompt:String(s.prompt??s.message??s.question??""),params:e,...typeof s.defaultValue=="string"?{defaultValue:s.defaultValue}:{}})}emitStatus(e,t,s,o){const n={phase:e,label:t,...s?{detail:s}:{},...o?{updateType:o}:{},atMs:Date.now()},r=this.statusTimeline[this.statusTimeline.length-1];r&&r.phase===n.phase&&r.label===n.label&&r.detail===n.detail&&r.updateType===n.updateType||(this.statusTimeline.push(n),this.statusCallback?.(n))}appendStderr(e){const t=this.lease?.client;t&&(t.rpc.stderr+=e)}releaseLease(){this.lease?.release(),this.lease=null}}function g(i){const t=l(i)?.sessionId;return typeof t=="string"&&t.trim()?t.trim():null}function l(i){return i&&typeof i=="object"&&!Array.isArray(i)?i:null}function M(i){return typeof i=="number"&&Number.isFinite(i)&&i>0?i:0}function P(i){const e=h(i.model),t=w(i.mode);return{..._(i.configOptionValues),...e?{model:e}:{},...t?{mode:t}:{}}}function _(i){const e={};for(const[t,s]of Object.entries(i??{}))["model","mode","reasoning_effort","effort","variant"].includes(t)||(e[t]=s);return e}function S(i,e){const t=h(e);if(!t)return!0;const s=C(i);return!s||s===t}function C(i){const t=l(i?.models)?.currentModelId;return typeof t=="string"&&t.trim()?t.trim():null}function w(i){const e=String(i||"").trim();return e?["agent-full-access","full-access","bypassPermissions","danger-full-access"].includes(e)?"build":e==="read-only"?"plan":e:null}function c(i,e){const s=l(i)?.[e];return typeof s=="string"&&s.trim()?s:null}function I(i,e){if(!e)throw new Error("ACP file request did not include a path");const t=m.resolve(i,e),s=m.resolve(i);if(t!==s&&!t.startsWith(`${s}${m.sep}`))throw new Error(`ACP file request is outside project root: ${e}`);return t}function k(i){const e=l(i.params)??i;for(const t of["command","cwd","reason","grantRoot","method"]){const s=e[t];if(typeof s=="string"&&s.trim())return b(s)}}function U(i){for(const e of["prompt","message","question","title"]){const t=i[e];if(typeof t=="string"&&t.trim())return b(t)}}function b(i){const e=i.replace(/\s+/g," ").trim();return e.length>120?`${e.slice(0,117)}...`:e}function y(i){return i instanceof Error?i.message:String(i)}function D(i){return/\binvalid params\b/i.test(y(i))}function F(i,e){return(e?.length?e:[{type:"text",text:i}]).map(s=>s.type==="image"?{type:"image",data:s.data,mimeType:s.mimeType}:{type:"text",text:s.text})}function $(){return process.env.ACA_OPENCODE_DEFAULT_MODEL||""}function h(i){const e=String(i||$()).trim();return e==="default"||e==="opencode-default"?"":e}function z(){const i=Number.parseInt(process.env.ACA_OPENCODE_PROMPT_TIMEOUT_MS??process.env.ACA_ACP_PROMPT_TIMEOUT_MS??"",10);return Number.isInteger(i)&&i>=3e4?i:360*60*1e3}export{G as OpenCodeProvider,T as openCodeCapabilities};
@@ -1 +1 @@
1
- import{Agent as w}from"undici";import{loadAcaConfig as p}from"./aca-config.js";const $=new w({connect:{rejectUnauthorized:!1}});async function S(e,c,n,o={}){const s=p(),t=(o.serverUrl||s.serverUrl).replace(/\/$/,""),i=o.token??s.token,u={...n===void 0?{}:{"content-type":"application/json"},...i?{authorization:`Bearer ${i}`}:{}},a=await fetch(`${t}${c}`,{method:e,headers:u,...n===void 0?{}:{body:JSON.stringify(n)},...h(t)}),f=await a.text(),r=f?JSON.parse(f):null;if(!a.ok){const l=r&&typeof r=="object"&&"message"in r?String(r.message):`HTTP ${a.status}`;throw new Error(l)}return r}async function d(e,c={}){const n=p(),o=(c.serverUrl||n.serverUrl).replace(/\/$/,""),s=c.token??n.token,t=await fetch(`${o}${e}`,{method:"GET",headers:s?{authorization:`Bearer ${s}`}:{},...h(o)});if(!t.ok)throw new Error(`附件下载失败 HTTP ${t.status}`);return Buffer.from(await t.arrayBuffer())}async function k(e,c,n,o={},s={}){const t=p(),i=(s.serverUrl||t.serverUrl).replace(/\/$/,""),u=s.token??t.token,a=await fetch(`${i}${c}`,{method:e,headers:{...o,...u?{authorization:`Bearer ${u}`}:{}},body:n,duplex:"half",...h(i)}),f=await a.text(),r=f?JSON.parse(f):null;if(!a.ok){const l=r&&typeof r=="object"&&"message"in r?String(r.message):`HTTP ${a.status}`;throw new Error(l)}return r}function h(e){return!/^https:/i.test(e)||!g(process.env.ACA_SERVER_INSECURE_TLS)?{}:{dispatcher:$}}function g(e){return e==="1"||e?.toLowerCase()==="true"||e?.toLowerCase()==="yes"}export{d as acaServerDownload,S as acaServerRequest,k as acaServerStreamRequest};
1
+ import{Agent as h}from"undici";import{loadAcaConfig as g}from"./aca-config.js";const d=new h({connect:{rejectUnauthorized:!1}}),S=15e3,$=12e4,U=12e4;async function p(e,r,n,t={}){const a=g(),o=(t.serverUrl||a.serverUrl).replace(/\/$/,""),s=t.token??a.token,f={...n===void 0?{}:{"content-type":"application/json"},...s?{authorization:`Bearer ${s}`}:{}},u=E(t,S);try{const i=await fetch(`${o}${r}`,{method:e,headers:f,signal:u.signal,...n===void 0?{}:{body:JSON.stringify(n)},...w(o)}),l=await i.text(),c=l?JSON.parse(l):null;if(!i.ok){const m=c&&typeof c=="object"&&"message"in c?String(c.message):`HTTP ${i.status}`;throw new Error(m)}return c}catch(i){throw T(i,e,r,u)}finally{u.dispose()}}async function b(e,r={}){const n=g(),t=(r.serverUrl||n.serverUrl).replace(/\/$/,""),a=r.token??n.token,o=E(r,$);try{const s=await fetch(`${t}${e}`,{method:"GET",headers:a?{authorization:`Bearer ${a}`}:{},signal:o.signal,...w(t)});if(!s.ok)throw new Error(`附件下载失败 HTTP ${s.status}`);return Buffer.from(await s.arrayBuffer())}catch(s){throw T(s,"GET",e,o)}finally{o.dispose()}}async function k(e,r,n,t={},a={}){const o=g(),s=(a.serverUrl||o.serverUrl).replace(/\/$/,""),f=a.token??o.token,u=E(a,U);try{const i=await fetch(`${s}${r}`,{method:e,headers:{...t,...f?{authorization:`Bearer ${f}`}:{}},body:n,duplex:"half",signal:u.signal,...w(s)}),l=await i.text(),c=l?JSON.parse(l):null;if(!i.ok){const m=c&&typeof c=="object"&&"message"in c?String(c.message):`HTTP ${i.status}`;throw new Error(m)}return c}catch(i){throw T(i,e,r,u)}finally{u.dispose()}}function E(e,r){const n=v(e.timeoutMs,r),t=new AbortController;let a=!1;const o=()=>t.abort(e.signal?.reason);e.signal?.aborted?o():e.signal?.addEventListener("abort",o,{once:!0});const s=setTimeout(()=>{a=!0,t.abort(new Error(`ACA Server request timed out after ${n}ms`))},n);return s.unref(),{signal:t.signal,timeoutMs:n,timedOut:()=>a,dispose:()=>{clearTimeout(s),e.signal?.removeEventListener("abort",o)}}}function T(e,r,n,t){return t.timedOut()?new Error(`ACA Server 请求超时(${t.timeoutMs}ms):${r} ${n}`,{cause:e}):e instanceof Error?e:new Error(String(e))}function v(e,r){return!Number.isFinite(e)||Number(e)<=0?r:Math.max(250,Math.min(Number(e),30*6e4))}function w(e){return!/^https:/i.test(e)||!A(process.env.ACA_SERVER_INSECURE_TLS)?{}:{dispatcher:d}}function A(e){return e==="1"||e?.toLowerCase()==="true"||e?.toLowerCase()==="yes"}export{b as acaServerDownload,p as acaServerRequest,k as acaServerStreamRequest};
@@ -1 +1 @@
1
- import i from"node:fs";import g from"node:os";import s from"node:path";import{Readable as d}from"node:stream";import{acaServerRequest as p,acaServerStreamRequest as w}from"./aca-server-client.js";import{loadAcaConfig as h}from"./aca-config.js";import{expandWorkerRuntimePath as x}from"./worker-runtime-paths.js";import{startLongPollWorker as y}from"./long-poll-worker.js";const b=25e3;function U(e){return y({run:async()=>{const t=h();if(!t.token)throw new Error("Worker 尚未配置认证令牌");const a=await p("GET",`/api/client/file-requests/claim?machineId=${encodeURIComponent(t.machineId)}&waitMs=${b}`);if(a.item)try{await S(a.item,t,e)}catch(n){const o=n instanceof Error?n.message:String(n);e.warn(`file request failed request=${a.item.requestId}: ${o}`);try{await j(a.item.requestId,o)}catch(r){e.warn(`file request fail callback failed request=${a.item.requestId}: ${r instanceof Error?r.message:String(r)}`)}}},onError:(t,a)=>e.warn(`file transfer worker failed, retry in ${a}ms: ${t instanceof Error?t.message:String(t)}`)})}async function j(e,t){await p("POST",`/api/client/file-requests/${encodeURIComponent(e)}/fail`,{message:t})}async function S(e,t,a){const n=t.projects.find(l=>l.projectId===e.projectId),o=f(e.projectRoot||n?.rootPath||"");if(!o)throw new Error(`file request ${e.requestId} project root is unknown`);const r=e.purpose==="temp-image"?"temp-image":e.purpose==="project-preview"?"project-preview":"project-file",c=r==="temp-image"?I(e.path):v(o,e.path),m=i.statSync(c);if(!m.isFile())throw new Error(`file request ${e.requestId} target is not a file`);if(m.size>e.maxBytes)throw new Error(`file request ${e.requestId} target is too large (${m.size} bytes)`);const u=r==="temp-image"?R(c):$(c);a.info(`streaming file request=${e.requestId} path=${c}`),await w("POST",`/api/client/file-requests/${encodeURIComponent(e.requestId)}/stream`,d.toWeb(i.createReadStream(c)),{"content-type":"application/octet-stream","x-aca-file-name":encodeURIComponent(s.basename(c)),"x-aca-file-size":String(m.size),"x-aca-file-mime":u})}function v(e,t){const a=i.realpathSync(f(e)),n=s.isAbsolute(t)?t:s.join(a,t),o=i.realpathSync(n),r=s.relative(a,o);if(r===""||r.startsWith("..")||s.isAbsolute(r))throw new Error("requested file is outside project root");return o}function I(e){const t=E(e);if(!s.isAbsolute(t))throw new Error("temporary image path must be absolute");const a=i.lstatSync(t);if(a.isSymbolicLink())throw new Error("temporary image cannot be a symbolic link");if(!a.isFile())throw new Error("temporary image target is not a regular file");const n=i.realpathSync(g.tmpdir()),o=i.realpathSync(t),r=s.relative(n,o);if(r===""||r.startsWith("..")||s.isAbsolute(r))throw new Error("temporary image is outside the system temp directory");return o}function R(e){const t=s.extname(e).toLowerCase(),a=Buffer.alloc(16),n=i.openSync(e,"r");let o=0;try{o=i.readSync(n,a,0,a.length,0)}finally{i.closeSync(n)}const r=a.subarray(0,o);if(t===".png"&&r.length>=8&&r.subarray(0,8).equals(Buffer.from([137,80,78,71,13,10,26,10])))return"image/png";if([".jpg",".jpeg"].includes(t)&&r.length>=3&&r[0]===255&&r[1]===216&&r[2]===255)return"image/jpeg";if(t===".gif"&&r.length>=6&&["GIF87a","GIF89a"].includes(r.subarray(0,6).toString("ascii")))return"image/gif";if(t===".webp"&&r.length>=12&&r.subarray(0,4).toString("ascii")==="RIFF"&&r.subarray(8,12).toString("ascii")==="WEBP")return"image/webp";throw new Error("temporary file is not a supported raster image")}function E(e){const t=String(e||"").trim();if(!t.startsWith("file://"))return t;try{return decodeURIComponent(new URL(t).pathname)}catch{throw new Error("temporary image path is invalid")}}function f(e){return x(e)}function $(e){switch(s.extname(e).toLowerCase()){case".pdf":return"application/pdf";case".md":case".markdown":return"text/markdown; charset=utf-8";case".txt":case".log":return"text/plain; charset=utf-8";case".html":case".htm":return"text/html; charset=utf-8";case".css":return"text/css; charset=utf-8";case".js":case".mjs":return"text/javascript; charset=utf-8";case".json":case".map":return"application/json; charset=utf-8";case".xml":return"application/xml; charset=utf-8";case".svg":return"image/svg+xml; charset=utf-8";case".png":return"image/png";case".jpg":case".jpeg":return"image/jpeg";case".gif":return"image/gif";case".webp":return"image/webp";case".avif":return"image/avif";case".ico":return"image/x-icon";case".bmp":return"image/bmp";case".woff":return"font/woff";case".woff2":return"font/woff2";case".ttf":return"font/ttf";case".otf":return"font/otf";case".eot":return"application/vnd.ms-fontobject";case".mp4":return"video/mp4";case".webm":return"video/webm";case".mp3":return"audio/mpeg";case".wav":return"audio/wav";case".ogg":return"audio/ogg";case".webmanifest":return"application/manifest+json; charset=utf-8";case".zip":return"application/zip";case".docx":return"application/vnd.openxmlformats-officedocument.wordprocessingml.document";case".xlsx":return"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";default:return"application/octet-stream"}}export{R as detectSafeTempImageMime,$ as mimeForPath,v as resolveProjectFile,I as resolveTempImageFile,U as startFileTransferWorkerLoop};
1
+ import i from"node:fs";import d from"node:os";import s from"node:path";import{Readable as w}from"node:stream";import{acaServerRequest as p,acaServerStreamRequest as h}from"./aca-server-client.js";import{loadAcaConfig as x}from"./aca-config.js";import{expandWorkerRuntimePath as y}from"./worker-runtime-paths.js";import{startLongPollWorker as b}from"./long-poll-worker.js";const f=25e3,S=f+1e4;function C(e){return b({run:async()=>{const t=x();if(!t.token)throw new Error("Worker 尚未配置认证令牌");const a=await p("GET",`/api/client/file-requests/claim?machineId=${encodeURIComponent(t.machineId)}&waitMs=${f}`,void 0,{timeoutMs:S});if(a.item)try{await v(a.item,t,e)}catch(n){const o=n instanceof Error?n.message:String(n);e.warn(`file request failed request=${a.item.requestId}: ${o}`);try{await j(a.item.requestId,o)}catch(r){e.warn(`file request fail callback failed request=${a.item.requestId}: ${r instanceof Error?r.message:String(r)}`)}}},onError:(t,a)=>e.warn(`file transfer worker failed, retry in ${a}ms: ${t instanceof Error?t.message:String(t)}`)})}async function j(e,t){await p("POST",`/api/client/file-requests/${encodeURIComponent(e)}/fail`,{message:t})}async function v(e,t,a){const n=t.projects.find(g=>g.projectId===e.projectId),o=u(e.projectRoot||n?.rootPath||"");if(!o)throw new Error(`file request ${e.requestId} project root is unknown`);const r=e.purpose==="temp-image"?"temp-image":e.purpose==="project-preview"?"project-preview":"project-file",c=r==="temp-image"?E(e.path):I(o,e.path),m=i.statSync(c);if(!m.isFile())throw new Error(`file request ${e.requestId} target is not a file`);if(m.size>e.maxBytes)throw new Error(`file request ${e.requestId} target is too large (${m.size} bytes)`);const l=r==="temp-image"?R(c):F(c);a.info(`streaming file request=${e.requestId} path=${c}`),await h("POST",`/api/client/file-requests/${encodeURIComponent(e.requestId)}/stream`,w.toWeb(i.createReadStream(c)),{"content-type":"application/octet-stream","x-aca-file-name":encodeURIComponent(s.basename(c)),"x-aca-file-size":String(m.size),"x-aca-file-mime":l})}function I(e,t){const a=i.realpathSync(u(e)),n=s.isAbsolute(t)?t:s.join(a,t),o=i.realpathSync(n),r=s.relative(a,o);if(r===""||r.startsWith("..")||s.isAbsolute(r))throw new Error("requested file is outside project root");return o}function E(e){const t=$(e);if(!s.isAbsolute(t))throw new Error("temporary image path must be absolute");const a=i.lstatSync(t);if(a.isSymbolicLink())throw new Error("temporary image cannot be a symbolic link");if(!a.isFile())throw new Error("temporary image target is not a regular file");const n=i.realpathSync(d.tmpdir()),o=i.realpathSync(t),r=s.relative(n,o);if(r===""||r.startsWith("..")||s.isAbsolute(r))throw new Error("temporary image is outside the system temp directory");return o}function R(e){const t=s.extname(e).toLowerCase(),a=Buffer.alloc(16),n=i.openSync(e,"r");let o=0;try{o=i.readSync(n,a,0,a.length,0)}finally{i.closeSync(n)}const r=a.subarray(0,o);if(t===".png"&&r.length>=8&&r.subarray(0,8).equals(Buffer.from([137,80,78,71,13,10,26,10])))return"image/png";if([".jpg",".jpeg"].includes(t)&&r.length>=3&&r[0]===255&&r[1]===216&&r[2]===255)return"image/jpeg";if(t===".gif"&&r.length>=6&&["GIF87a","GIF89a"].includes(r.subarray(0,6).toString("ascii")))return"image/gif";if(t===".webp"&&r.length>=12&&r.subarray(0,4).toString("ascii")==="RIFF"&&r.subarray(8,12).toString("ascii")==="WEBP")return"image/webp";throw new Error("temporary file is not a supported raster image")}function $(e){const t=String(e||"").trim();if(!t.startsWith("file://"))return t;try{return decodeURIComponent(new URL(t).pathname)}catch{throw new Error("temporary image path is invalid")}}function u(e){return y(e)}function F(e){switch(s.extname(e).toLowerCase()){case".pdf":return"application/pdf";case".md":case".markdown":return"text/markdown; charset=utf-8";case".txt":case".log":return"text/plain; charset=utf-8";case".html":case".htm":return"text/html; charset=utf-8";case".css":return"text/css; charset=utf-8";case".js":case".mjs":return"text/javascript; charset=utf-8";case".json":case".map":return"application/json; charset=utf-8";case".xml":return"application/xml; charset=utf-8";case".svg":return"image/svg+xml; charset=utf-8";case".png":return"image/png";case".jpg":case".jpeg":return"image/jpeg";case".gif":return"image/gif";case".webp":return"image/webp";case".avif":return"image/avif";case".ico":return"image/x-icon";case".bmp":return"image/bmp";case".woff":return"font/woff";case".woff2":return"font/woff2";case".ttf":return"font/ttf";case".otf":return"font/otf";case".eot":return"application/vnd.ms-fontobject";case".mp4":return"video/mp4";case".webm":return"video/webm";case".mp3":return"audio/mpeg";case".wav":return"audio/wav";case".ogg":return"audio/ogg";case".webmanifest":return"application/manifest+json; charset=utf-8";case".zip":return"application/zip";case".docx":return"application/vnd.openxmlformats-officedocument.wordprocessingml.document";case".xlsx":return"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";default:return"application/octet-stream"}}export{R as detectSafeTempImageMime,F as mimeForPath,I as resolveProjectFile,E as resolveTempImageFile,C as startFileTransferWorkerLoop};
@@ -1,6 +1,6 @@
1
- import L from"node:crypto";import E from"node:fs";import x from"node:path";import{compactAgentContext as W,forkAgentSession as V,sendAgentPrompt as q}from"../acp/agent.js";import{acaServerDownload as D,acaServerRequest as y}from"./aca-server-client.js";import{loadAcaConfig as H}from"./aca-config.js";import{prepareLocalWorktree as G}from"./worktree.js";import{expandWorkerRuntimePath as K}from"./worker-runtime-paths.js";import{collectAffectedFiles as N,extractAffectedFilePathsFromAcpRaw as X}from"./affected-files.js";import{resolveJobPermissionPolicy as Z}from"./job-permission-policy.js";import{appendCoalescedAcpEvent as Q}from"./acp-event-coalescer.js";import{shouldUploadAcpEvent as Y}from"./acp-event-upload-filter.js";import{userFacingAcpError as ee}from"../acp/error-display.js";const te=1500,ne=5e3,M=1e3,oe=200,ae=64,O=900,ie=1e4,re=0,se=12e3;function ce(e){return K(e)}function le(e){const t=e.project_metadata;return t&&typeof t=="object"&&!Array.isArray(t)&&t.kind==="chat"?!0:typeof e.project_root_path=="string"&&(e.project_root_path.startsWith("~/.aica/chats/")||e.project_root_path.startsWith("~/.aca/chats/")||/(^|\/)\.aica\/chats\//.test(e.project_root_path)||/(^|\/)\.aca\/chats\//.test(e.project_root_path))}function Ge(e,t={}){let n=!1,a=null,o=null;const r=new Set,l=new Set,u=new Map,i=async()=>{if(n)return;const s=ue();if(U(r.size,s))try{const p=t.loadConfig?.()??H();if(!p.token)return;for(;!n&&U(r.size,s);){const A=t.claimNextJob?await t.claimNextJob(p):await y("POST","/api/client/jobs/claim",{machineId:p.machineId,workspaceId:p.defaultWorkspaceId});if(!A.item)break;const f=A.item;if(n){await(t.cancelClaimedJob?t.cancelClaimedJob(f,p):P(f.job_id,p.machineId,"cancelled")).catch(()=>{});break}const m=f.session_id;l.has(m)&&e.warn(`claimed job=${f.job_id} for already active session=${m}; running anyway to avoid leaving it stuck`),r.add(f.job_id),l.add(m);const _=new AbortController,v=(t.runJob??de)(f,p,e,_).catch(d=>{e.warn(`job ${f.job_id} failed outside handler: ${d instanceof Error?d.message:String(d)}`)}).finally(()=>{r.delete(f.job_id),l.delete(m),u.delete(f.job_id)});u.set(f.job_id,{controller:_,promise:v})}}catch(p){n||e.warn(`job worker failed: ${p instanceof Error?p.message:String(p)}`)}},h=()=>{if(n||a)return;const s=i();a=s,s.finally(()=>{a===s&&(a=null)})};h();const S=setInterval(()=>{h()},t.claimIntervalMs??te);return{timer:S,stop:()=>o||(o=(async()=>{if(n)return;n=!0,clearInterval(S);for(const p of u.values())p.controller.abort();await a?.catch(()=>{});for(const p of u.values())p.controller.abort();const s=[...u.values()].map(p=>p.promise);s.length!==0&&await Promise.race([Promise.allSettled(s).then(()=>{}),j(se)])})(),o)}}function ue(){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?re:t===0?0:Math.max(1,Math.min(t,8))}function U(e,t){return t===0||e<t}function pe(){const e=Number.parseInt(process.env.ACA_ACP_EVENT_BATCH_INTERVAL_MS||"",10);return Number.isFinite(e)?Math.max(40,Math.min(e,2e3)):oe}async function de(e,t,n,a){const o=e.job_id,r=e.session_id,l=e.workspace_id,u=e.project_id??null,i=e.input??{},h=String(i.control?.type||"").toLowerCase();if(!h&&!i.message?.trim()&&(i.attachments??[]).length===0){await P(o,t.machineId,"job input missing message");return}const S=t.projects.find(d=>d.projectId===u),s=ce(S?.rootPath||e.project_root_path||"");if(!s){await P(o,t.machineId,"job project root is unknown");return}le(e)&&E.mkdirSync(s,{recursive:!0}),n.info(`claimed job=${o} session=${r} project=${S?.name??u??"unknown"}`);const p=setInterval(()=>{y("POST",`/api/client/jobs/${encodeURIComponent(o)}/heartbeat`,{machineId:t.machineId}).catch(()=>{}),je(o,a).catch(()=>{})},ne);let A=0;const f=ye(o,t.machineId),m=_e(o,t.machineId),_=new Set;let v=null;try{await m.sendNow({phase:"initializing",label:"准备工作区",detail:e.worktree_id?"worktree":"project root"}).catch(()=>{});const d=G({projectId:u,sessionId:r,projectRoot:s,worktree:e});v=d,await Re(o,t.machineId,d);const I=fe(i.acpRun);if(h==="compact"){if(!e.acp_session_id)throw new Error("Current session does not have an ACP context to compact");const c=await W({agentType:C(I.agentType,"codex"),cliType:C(I.cliType,"builtin"),acpSessionId:e.acp_session_id,cwd:d.cwd,model:i.model,mode:i.mode,configOptionValues:i.configOptionValues,timeoutMs:i.timeoutMs,allowRollover:i.control?.allowRollover===!0,signal:a.signal,onStatus:b=>{m.report({phase:b.phase,label:b.label,detail:b.detail,updateType:b.updateType,raw:b})}});await m.flush(),await y("POST",`/api/client/jobs/${encodeURIComponent(o)}/complete`,{machineId:t.machineId,acpSessionId:c.acpSessionId,suppressTurn:!0,result:{ok:!0,control:{type:"compact",allowRollover:i.control?.allowRollover===!0},acpSessionId:c.acpSessionId,durationMs:c.durationMs,usage:c.contextUsage,maintenance:c.maintenance,worktree:k(d)}}),n.info(`completed context compact job=${o}`);return}if(h==="fork"){const c=String(i.control?.sourceAcpSessionId||"").trim();if(!c)throw new Error("Source session does not have a Codex ACP context to fork");await m.sendNow({phase:"forking",label:"正在分叉 Codex 会话",detail:i.control?.sourceSessionId?`源会话 ${i.control.sourceSessionId}`:void 0}).catch(()=>{});const b=await V({agentType:C(I.agentType,"codex"),cliType:C(I.cliType,"builtin"),sourceProviderSessionId:c,cwd:d.cwd,model:i.model,mode:i.mode,configOptionValues:i.configOptionValues,timeoutMs:i.timeoutMs,signal:a.signal,onStatus:g=>{m.report({phase:g.phase,label:g.label,detail:g.detail,updateType:g.updateType,raw:g})}});await m.flush(),await y("POST",`/api/client/jobs/${encodeURIComponent(o)}/complete`,{machineId:t.machineId,acpSessionId:b.acpSessionId,suppressTurn:!0,result:{ok:!0,control:{type:"fork",sourceSessionId:i.control?.sourceSessionId||null},acpSessionId:b.acpSessionId,worktree:k(d)}}),n.info(`completed Codex session fork job=${o}`);return}const T=await Ae({cwd:d.cwd,sessionId:r,jobId:o,attachments:i.attachments??[]});T.length>0&&(n.info(`materialized ${T.length} attachment(s) for job=${o}`),await m.sendNow({phase:"initializing",label:"附件已写入本机",detail:T.map(c=>c.relativePath).join(", "),raw:{attachments:z(T)}}).catch(()=>{}));const $=Se(i,T),F=ge($,T),w=await q({agentType:C(I.agentType,"codex"),cliType:C(I.cliType,"builtin"),acpSessionId:e.acp_session_id??null,requireSessionResume:me(e.session_raw),prompt:$,promptBlocks:F,cwd:d.cwd,model:i.model,mode:i.mode,configOptionValues:i.configOptionValues,canExecuteTools:i.canExecuteTools,timeoutMs:i.timeoutMs,contextWindow:he(I.contextWindow),signal:a.signal,onStatus:c=>{m.report({phase:c.phase,label:c.label,detail:c.detail,updateType:c.updateType,raw:c})},onUpdate:c=>{A+=1;for(const g of X(c.raw))_.add(g);const b={turnId:e.user_turn_id??null,eventType:c.type,label:c.label,text:c.text,status:c.status,toolCallId:c.toolCallId,raw:Te(c.raw,A)};Y(b)&&f.push(b)},onPermissionRequest:c=>{const b=Z(i,c);return b?Promise.resolve(b):xe(o,t.machineId,c,a.signal)},onUserInputRequest:c=>Ee(o,t.machineId,c,a.signal)});await f.flush(),await m.flush();const J=N({cwd:d.cwd,evidencePaths:_}),R=we(w.promptResponse);if(!(be(R)||!!w.content)){await P(o,t.machineId,`${w.agentType} stopped: ${R??"unknown"}`,{acpSessionId:w.acpSessionId,stopReason:R,exitCode:w.exitCode,stderr:$e(w.stderr,4e3),worktree:k(d)});return}await y("POST",`/api/client/jobs/${encodeURIComponent(o)}/complete`,{machineId:t.machineId,acpSessionId:w.acpSessionId,assistantText:w.content,result:{ok:!0,acpSessionId:w.acpSessionId,stopReason:R,durationMs:w.durationMs,acpRun:i.acpRun??null,effectiveConfig:w.effectiveConfig??null,performance:w.performance??null,usage:w.contextUsage??null,updateCount:w.updateCount,statusTimeline:w.statusTimeline,worktree:k(d),attachments:z(T),files:J}}),n.info(`completed job=${o}`)}catch(d){await f.flush(),await m.flush();const I=a.signal.aborted?"cancelled":d instanceof Error?d.message:String(d),T=v?N({cwd:v.cwd,evidencePaths:_}):[];await P(o,t.machineId,ee(I),{rawError:I,files:T})}finally{await f.flush(),await m.flush(),clearInterval(p)}}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 fe(e){return!e||typeof e!="object"||Array.isArray(e)?{}:e}function he(e){return typeof e=="number"&&Number.isFinite(e)&&e>0?e:void 0}function C(...e){for(const t of e)if(typeof t=="string"&&t.trim())return t.trim();return""}function we(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 be(e){return e?["end_turn","stop","completed","complete"].includes(e):!1}function ye(e,t){let n=[],a=null,o=Promise.resolve();const r=async i=>{i.length!==0&&await y("POST",`/api/client/jobs/${encodeURIComponent(e)}/events/batch`,{machineId:t,events:i})},l=()=>{a&&(clearTimeout(a),a=null);const i=n;return n=[],o=o.then(()=>r(i)).catch(()=>{}),o},u=()=>{a||(a=setTimeout(()=>{l()},pe()),a.unref())};return{push(i){if(Q(n,i),Ie(i)||n.length>=ae){l();return}u()},flush:l}}function Ie(e){return e.eventType==="tool_call"?!0:e.eventType!=="tool_call_update"?!1:["completed","failed","cancelled"].includes(String(e.status||"").toLowerCase())}function _e(e,t){let n=null,a=0,o="",r=null,l=Promise.resolve();const u=s=>JSON.stringify({phase:s.phase??"",label:s.label??"",detail:s.detail??"",updateType:s.updateType??""}),i=async s=>{await y("POST",`/api/client/jobs/${encodeURIComponent(e)}/presence`,{machineId:t,phase:s.phase,label:s.label,detail:s.detail,updateType:s.updateType,raw:s.raw}),a=Date.now(),o=u(s)},h=()=>{if(r&&(clearTimeout(r),r=null),!n)return l;const s=n;return n=null,l=l.then(()=>i(s)).catch(()=>{}),l},S=s=>{r||(r=setTimeout(()=>{h()},s),r.unref())};return{report(s){const p=Date.now(),f=u(s)!==o,m=p-a>=ie;if(!f&&!m)return;n=s;const _=p-a;if(_>=O||a===0){h();return}S(O-_)},sendNow(s){return n=s,h()},flush:h}}function Te(e,t){return e&&typeof e=="object"&&!Array.isArray(e)?{...e,acaSequence:t}:{value:e,acaSequence:t}}function Se(e,t=[]){const n=e?.message?.trim()??"";if(t.length===0)return n;const o=["用户随消息上传了以下附件,aca 已将附件写入本机工作区。需要读取附件时请直接使用下面的 localPath;图片附件也已作为 ACP image block 随本轮请求传入。",...t.map((r,l)=>`${l+1}. ${r.name} (${r.kind}, ${r.mimeType}, ${Pe(r.size)})
1
+ import V from"node:crypto";import $ from"node:fs";import j from"node:path";import{compactAgentContext as q,forkAgentSession as D,sendAgentPrompt as H}from"../acp/agent.js";import{acaServerDownload as G,acaServerRequest as I}from"./aca-server-client.js";import{loadAcaConfig as K}from"./aca-config.js";import{prepareLocalWorktree as X}from"./worktree.js";import{expandWorkerRuntimePath as Z}from"./worker-runtime-paths.js";import{collectAffectedFiles as O,extractAffectedFilePathsFromAcpRaw as Q}from"./affected-files.js";import{resolveJobPermissionPolicy as Y}from"./job-permission-policy.js";import{appendCoalescedAcpEvent as ee}from"./acp-event-coalescer.js";import{shouldUploadAcpEvent as te}from"./acp-event-upload-filter.js";import{userFacingAcpError as ne}from"../acp/error-display.js";const oe=1500,ae=5e3,U=1e3,ie=200,re=64,B=900,se=1e4,ce=0,le=12e3;function ue(e){return Z(e)}function pe(e){const t=e.project_metadata;return t&&typeof t=="object"&&!Array.isArray(t)&&t.kind==="chat"?!0:typeof e.project_root_path=="string"&&(e.project_root_path.startsWith("~/.aica/chats/")||e.project_root_path.startsWith("~/.aca/chats/")||/(^|\/)\.aica\/chats\//.test(e.project_root_path)||/(^|\/)\.aca\/chats\//.test(e.project_root_path))}function Ze(e,t={}){let n=!1,a=null,o=null,r=0,c=0;const u=new Set,i=new Set,m=new Map,C=async()=>{if(n||Date.now()<c)return;const y=de();if(F(u.size,y))try{const p=t.loadConfig?.()??K();if(!p.token)return;for(;!n&&F(u.size,y);){const _=t.claimNextJob?await t.claimNextJob(p):await I("POST","/api/client/jobs/claim",{machineId:p.machineId,workspaceId:p.defaultWorkspaceId});if(r=0,c=0,!_.item)break;const f=_.item;if(n){await(t.cancelClaimedJob?t.cancelClaimedJob(f,p):R(f.job_id,p.machineId,"cancelled")).catch(()=>{});break}const h=f.session_id;i.has(h)&&e.warn(`claimed job=${f.job_id} for already active session=${h}; running anyway to avoid leaving it stuck`),u.add(f.job_id),i.add(h);const k=new AbortController,P=(t.runJob??fe)(f,p,e,k).catch(d=>{e.warn(`job ${f.job_id} failed outside handler: ${d instanceof Error?d.message:String(d)}`)}).finally(()=>{u.delete(f.job_id),i.delete(h),m.delete(f.job_id)});m.set(f.job_id,{controller:k,promise:P})}}catch(p){if(!n){r+=1;const _=Ne(r);c=Date.now()+_,e.warn(`job worker failed, retry in ${_}ms: ${p instanceof Error?p.message:String(p)}`)}}},l=()=>{if(n||a)return;const y=C();a=y,y.finally(()=>{a===y&&(a=null)})};l();const T=setInterval(()=>{l()},t.claimIntervalMs??oe);return{timer:T,stop:()=>o||(o=(async()=>{if(n)return;n=!0,clearInterval(T);for(const p of m.values())p.controller.abort();await a?.catch(()=>{});for(const p of m.values())p.controller.abort();const y=[...m.values()].map(p=>p.promise);y.length!==0&&await Promise.race([Promise.allSettled(y).then(()=>{}),M(le)])})(),o)}}function de(){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?ce:t===0?0:Math.max(1,Math.min(t,8))}function F(e,t){return t===0||e<t}function me(){const e=Number.parseInt(process.env.ACA_ACP_EVENT_BATCH_INTERVAL_MS||"",10);return Number.isFinite(e)?Math.max(40,Math.min(e,2e3)):ie}async function fe(e,t,n,a){const o=e.job_id,r=e.session_id,c=e.workspace_id,u=e.project_id??null,i=e.input??{},m=String(i.control?.type||"").toLowerCase();if(!m&&!i.message?.trim()&&(i.attachments??[]).length===0){await R(o,t.machineId,"job input missing message");return}const C=t.projects.find(d=>d.projectId===u),l=ue(C?.rootPath||e.project_root_path||"");if(!l){await R(o,t.machineId,"job project root is unknown");return}pe(e)&&$.mkdirSync(l,{recursive:!0}),n.info(`claimed job=${o} session=${r} project=${C?.name??u??"unknown"}`);let T=!1,y=!1;const p=setInterval(()=>{T||(T=!0,I("POST",`/api/client/jobs/${encodeURIComponent(o)}/heartbeat`,{machineId:t.machineId}).catch(()=>{}).finally(()=>{T=!1})),y||(y=!0,Me(o,a).catch(()=>{}).finally(()=>{y=!1}))},ae);let _=0;const f=_e(o,t.machineId),h=Te(o,t.machineId),k=new Set;let P=null;try{await h.sendNow({phase:"initializing",label:"准备工作区",detail:e.worktree_id?"worktree":"project root"}).catch(()=>{});const d=X({projectId:u,sessionId:r,projectRoot:l,worktree:e});P=d,await Ee(o,t.machineId,d);const g=we(i.acpRun);if(m==="compact"){if(!e.acp_session_id)throw new Error("Current session does not have an ACP context to compact");const s=await q({agentType:v(g.agentType,"codex"),cliType:v(g.cliType,"builtin"),acpSessionId:e.acp_session_id,cwd:d.cwd,model:i.model,mode:i.mode,configOptionValues:i.configOptionValues,timeoutMs:i.timeoutMs,allowRollover:i.control?.allowRollover===!0,signal:a.signal,onStatus:b=>{h.report({phase:b.phase,label:b.label,detail:b.detail,updateType:b.updateType,raw:b})}});await h.flush(),await I("POST",`/api/client/jobs/${encodeURIComponent(o)}/complete`,{machineId:t.machineId,acpSessionId:s.acpSessionId,suppressTurn:!0,result:{ok:!0,control:{type:"compact",allowRollover:i.control?.allowRollover===!0},acpSessionId:s.acpSessionId,durationMs:s.durationMs,usage:s.contextUsage,maintenance:s.maintenance,worktree:x(d)}}),n.info(`completed context compact job=${o}`);return}if(m==="fork"){const s=String(i.control?.sourceAcpSessionId||"").trim();if(!s)throw new Error("Source session does not have a Codex ACP context to fork");await h.sendNow({phase:"forking",label:"正在分叉 Codex 会话",detail:i.control?.sourceSessionId?`源会话 ${i.control.sourceSessionId}`:void 0}).catch(()=>{});const b=await D({agentType:v(g.agentType,"codex"),cliType:v(g.cliType,"builtin"),sourceProviderSessionId:s,cwd:d.cwd,model:i.model,mode:i.mode,configOptionValues:i.configOptionValues,timeoutMs:i.timeoutMs,signal:a.signal,onStatus:A=>{h.report({phase:A.phase,label:A.label,detail:A.detail,updateType:A.updateType,raw:A})}});await h.flush(),await I("POST",`/api/client/jobs/${encodeURIComponent(o)}/complete`,{machineId:t.machineId,acpSessionId:b.acpSessionId,suppressTurn:!0,result:{ok:!0,control:{type:"fork",sourceSessionId:i.control?.sourceSessionId||null},acpSessionId:b.acpSessionId,worktree:x(d)}}),n.info(`completed Codex session fork job=${o}`);return}const S=await ke({cwd:d.cwd,sessionId:r,jobId:o,attachments:i.attachments??[]});S.length>0&&(n.info(`materialized ${S.length} attachment(s) for job=${o}`),await h.sendNow({phase:"initializing",label:"附件已写入本机",detail:S.map(s=>s.relativePath).join(", "),raw:{attachments:J(S)}}).catch(()=>{}));const N=Ae(i,S),L=Ce(N,S),w=await H({agentType:v(g.agentType,"codex"),cliType:v(g.cliType,"builtin"),acpSessionId:e.acp_session_id??null,requireSessionResume:he(e.session_raw),prompt:N,promptBlocks:L,cwd:d.cwd,model:i.model,mode:i.mode,configOptionValues:i.configOptionValues,canExecuteTools:i.canExecuteTools,timeoutMs:i.timeoutMs,contextWindow:ye(g.contextWindow),signal:a.signal,onStatus:s=>{h.report({phase:s.phase,label:s.label,detail:s.detail,updateType:s.updateType,raw:s})},onUpdate:s=>{_+=1;for(const A of Q(s.raw))k.add(A);const b={turnId:e.user_turn_id??null,eventType:s.type,label:s.label,text:s.text,status:s.status,toolCallId:s.toolCallId,raw:Se(s.raw,_)};te(b)&&f.push(b)},onPermissionRequest:s=>{const b=Y(i,s);return b?Promise.resolve(b):je(o,t.machineId,s,a.signal)},onUserInputRequest:s=>$e(o,t.machineId,s,a.signal)});await f.flush(),await h.flush();const W=O({cwd:d.cwd,evidencePaths:k}),E=be(w.promptResponse);if(!(Ie(E)||!!w.content)){await R(o,t.machineId,`${w.agentType} stopped: ${E??"unknown"}`,{acpSessionId:w.acpSessionId,stopReason:E,exitCode:w.exitCode,stderr:Oe(w.stderr,4e3),worktree:x(d)});return}await I("POST",`/api/client/jobs/${encodeURIComponent(o)}/complete`,{machineId:t.machineId,acpSessionId:w.acpSessionId,assistantText:w.content,result:{ok:!0,acpSessionId:w.acpSessionId,stopReason:E,durationMs:w.durationMs,acpRun:i.acpRun??null,effectiveConfig:w.effectiveConfig??null,performance:w.performance??null,usage:w.contextUsage??null,updateCount:w.updateCount,statusTimeline:w.statusTimeline,worktree:x(d),attachments:J(S),files:W}}),n.info(`completed job=${o}`)}catch(d){await f.flush(),await h.flush();const g=a.signal.aborted?"cancelled":d instanceof Error?d.message:String(d),S=P?O({cwd:P.cwd,evidencePaths:k}):[];await R(o,t.machineId,ne(g),{rawError:g,files:S})}finally{await f.flush(),await h.flush(),clearInterval(p)}}function he(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 we(e){return!e||typeof e!="object"||Array.isArray(e)?{}:e}function ye(e){return typeof e=="number"&&Number.isFinite(e)&&e>0?e:void 0}function v(...e){for(const t of e)if(typeof t=="string"&&t.trim())return t.trim();return""}function be(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 Ie(e){return e?["end_turn","stop","completed","complete"].includes(e):!1}function _e(e,t){let n=[],a=null,o=Promise.resolve();const r=async i=>{i.length!==0&&await I("POST",`/api/client/jobs/${encodeURIComponent(e)}/events/batch`,{machineId:t,events:i})},c=()=>{a&&(clearTimeout(a),a=null);const i=n;return n=[],o=o.then(()=>r(i)).catch(()=>{}),o},u=()=>{a||(a=setTimeout(()=>{c()},me()),a.unref())};return{push(i){if(ee(n,i),ge(i)||n.length>=re){c();return}u()},flush:c}}function ge(e){return e.eventType==="tool_call"?!0:e.eventType!=="tool_call_update"?!1:["completed","failed","cancelled"].includes(String(e.status||"").toLowerCase())}function Te(e,t){let n=null,a=0,o="",r=null,c=Promise.resolve();const u=l=>JSON.stringify({phase:l.phase??"",label:l.label??"",detail:l.detail??"",updateType:l.updateType??""}),i=async l=>{await I("POST",`/api/client/jobs/${encodeURIComponent(e)}/presence`,{machineId:t,phase:l.phase,label:l.label,detail:l.detail,updateType:l.updateType,raw:l.raw}),a=Date.now(),o=u(l)},m=()=>{if(r&&(clearTimeout(r),r=null),!n)return c;const l=n;return n=null,c=c.then(()=>i(l)).catch(()=>{}),c},C=l=>{r||(r=setTimeout(()=>{m()},l),r.unref())};return{report(l){const T=Date.now(),p=u(l)!==o,_=T-a>=se;if(!p&&!_)return;n=l;const f=T-a;if(f>=B||a===0){m();return}C(B-f)},sendNow(l){return n=l,m()},flush:m}}function Se(e,t){return e&&typeof e=="object"&&!Array.isArray(e)?{...e,acaSequence:t}:{value:e,acaSequence:t}}function Ae(e,t=[]){const n=e?.message?.trim()??"";if(t.length===0)return n;const o=["用户随消息上传了以下附件,aca 已将附件写入本机工作区。需要读取附件时请直接使用下面的 localPath;图片附件也已作为 ACP image block 随本轮请求传入。",...t.map((r,c)=>`${c+1}. ${r.name} (${r.kind}, ${r.mimeType}, ${Re(r.size)})
2
2
  localPath: ${r.path}
3
3
  workspacePath: ${r.relativePath}`)].join(`
4
4
  `);return n?`${n}
5
5
 
6
- ${o}`:o}function ge(e,t){const n=[{type:"text",text:e}];for(const a of t)a.kind!=="image"||!a.mimeType.startsWith("image/")||n.push({type:"image",data:a.base64,mimeType:a.mimeType,uri:a.path});return n}async function Ae(e){if(e.attachments.length===0)return[];const t=x.join(e.cwd,".aca","attachments",B(e.sessionId),B(e.jobId));E.mkdirSync(t,{recursive:!0});const n=[];for(const[a,o]of e.attachments.entries()){const r=o.attachmentId?{mimeType:o.mimeType||"application/octet-stream",bytes:await D(`/api/client/attachments/${encodeURIComponent(o.attachmentId)}`)}:Ce(o.dataUrl??""),l=o.mimeType||r.mimeType||"application/octet-stream",u=o.kind==="image"||l.startsWith("image/")?"image":"file",i=ve(o.name||`attachment-${a+1}`,l),h=x.join(t,`${String(a+1).padStart(2,"0")}-${i}`);E.writeFileSync(h,r.bytes),n.push({name:o.name||i,mimeType:l,size:r.bytes.byteLength,kind:u,path:h,relativePath:x.relative(e.cwd,h),base64:r.bytes.toString("base64"),sha256:L.createHash("sha256").update(r.bytes).digest("hex")})}return n}function Ce(e){const t=e.indexOf(",");if(!e.startsWith("data:")||t<0)throw new Error("attachment dataUrl is not a data URL");const n=e.slice(5,t),a=e.slice(t+1),o=n.split(";").filter(Boolean),r=o[0]?.includes("/")?o[0]:"application/octet-stream";if(!o.includes("base64"))throw new Error("attachment dataUrl is not base64 encoded");return{mimeType:r,bytes:Buffer.from(a,"base64")}}function ve(e,t){const n=x.basename(e).replace(/[<>:"/\\|?*\x00-\x1F]/g,"_").trim(),a=`attachment${ke(t)}`;return(!n||n==="."||n===".."?a:n).slice(0,180)}function ke(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,a=0;for(;n>=1024&&a<t.length-1;)n/=1024,a+=1;return`${n>=10||a===0?n.toFixed(0):n.toFixed(1)} ${t[a]}`}async function Re(e,t,n){!n.usedWorktree||!n.worktreeId||await y("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 xe(e,t,n,a){const r=(await y("POST",`/api/client/jobs/${encodeURIComponent(e)}/permissions`,{machineId:t,request:n})).item.permission_id;for(;!a.aborted;){await j(M);const l=await y("GET",`/api/client/permissions/${encodeURIComponent(r)}`);if(l.item.status==="resolved"&&l.item.response){const u=l.item.response.outcome;return u.outcome==="selected"?{outcome:u}:{outcome:{outcome:"cancelled"}}}}return{outcome:{outcome:"cancelled"}}}async function Ee(e,t,n,a){const r=(await y("POST",`/api/client/jobs/${encodeURIComponent(e)}/permissions`,{machineId:t,request:{kind:"elicitation",...n}})).item.permission_id;for(;!a.aborted;){await j(M);const l=await y("GET",`/api/client/permissions/${encodeURIComponent(r)}`);if(l.item.status==="resolved"&&l.item.response){const u=l.item.response.outcome;return u.outcome==="submitted"?{outcome:u}:{outcome:{outcome:"cancelled"}}}}return{outcome:{outcome:"cancelled"}}}async function je(e,t){if(t.signal.aborted)return;const n=await y("GET",`/api/client/jobs/${encodeURIComponent(e)}`);Number(n.item.cancel_requested??0)===1&&t.abort()}async function P(e,t,n,a){await y("POST",`/api/client/jobs/${encodeURIComponent(e)}/fail`,{machineId:t,error:n,result:a})}function j(e){return new Promise(t=>setTimeout(t,e))}function $e(e,t){return e.length<=t?e:`${e.slice(0,t)}...`}export{Ge as startJobWorkerLoop};
6
+ ${o}`:o}function Ce(e,t){const n=[{type:"text",text:e}];for(const a of t)a.kind!=="image"||!a.mimeType.startsWith("image/")||n.push({type:"image",data:a.base64,mimeType:a.mimeType,uri:a.path});return n}async function ke(e){if(e.attachments.length===0)return[];const t=j.join(e.cwd,".aca","attachments",z(e.sessionId),z(e.jobId));$.mkdirSync(t,{recursive:!0});const n=[];for(const[a,o]of e.attachments.entries()){const r=o.attachmentId?{mimeType:o.mimeType||"application/octet-stream",bytes:await G(`/api/client/attachments/${encodeURIComponent(o.attachmentId)}`)}:ve(o.dataUrl??""),c=o.mimeType||r.mimeType||"application/octet-stream",u=o.kind==="image"||c.startsWith("image/")?"image":"file",i=Pe(o.name||`attachment-${a+1}`,c),m=j.join(t,`${String(a+1).padStart(2,"0")}-${i}`);$.writeFileSync(m,r.bytes),n.push({name:o.name||i,mimeType:c,size:r.bytes.byteLength,kind:u,path:m,relativePath:j.relative(e.cwd,m),base64:r.bytes.toString("base64"),sha256:V.createHash("sha256").update(r.bytes).digest("hex")})}return n}function ve(e){const t=e.indexOf(",");if(!e.startsWith("data:")||t<0)throw new Error("attachment dataUrl is not a data URL");const n=e.slice(5,t),a=e.slice(t+1),o=n.split(";").filter(Boolean),r=o[0]?.includes("/")?o[0]:"application/octet-stream";if(!o.includes("base64"))throw new Error("attachment dataUrl is not base64 encoded");return{mimeType:r,bytes:Buffer.from(a,"base64")}}function Pe(e,t){const n=j.basename(e).replace(/[<>:"/\\|?*\x00-\x1F]/g,"_").trim(),a=`attachment${xe(t)}`;return(!n||n==="."||n===".."?a:n).slice(0,180)}function xe(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 z(e){return e.replace(/[^a-zA-Z0-9._-]+/g,"-").replace(/^-+|-+$/g,"").slice(0,80)||"item"}function J(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 Re(e){if(!Number.isFinite(e)||e<=0)return"0 B";const t=["B","KB","MB","GB"];let n=e,a=0;for(;n>=1024&&a<t.length-1;)n/=1024,a+=1;return`${n>=10||a===0?n.toFixed(0):n.toFixed(1)} ${t[a]}`}async function Ee(e,t,n){!n.usedWorktree||!n.worktreeId||await I("POST",`/api/client/jobs/${encodeURIComponent(e)}/worktree`,{machineId:t,baseCommit:n.baseCommit??null,worktreePath:n.cwd,status:n.status??"active",raw:n.raw??x(n)})}function x(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 je(e,t,n,a){const r=(await I("POST",`/api/client/jobs/${encodeURIComponent(e)}/permissions`,{machineId:t,request:n})).item.permission_id;for(;!a.aborted;){await M(U);const c=await I("GET",`/api/client/permissions/${encodeURIComponent(r)}`);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 $e(e,t,n,a){const r=(await I("POST",`/api/client/jobs/${encodeURIComponent(e)}/permissions`,{machineId:t,request:{kind:"elicitation",...n}})).item.permission_id;for(;!a.aborted;){await M(U);const c=await I("GET",`/api/client/permissions/${encodeURIComponent(r)}`);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 Me(e,t){if(t.signal.aborted)return;const n=await I("GET",`/api/client/jobs/${encodeURIComponent(e)}`);Number(n.item.cancel_requested??0)===1&&t.abort()}async function R(e,t,n,a){await I("POST",`/api/client/jobs/${encodeURIComponent(e)}/fail`,{machineId:t,error:n,result:a})}function M(e){return new Promise(t=>setTimeout(t,e))}function Ne(e){return Math.min(1e4,500*2**Math.min(5,Math.max(0,e-1)))+Math.floor(Math.random()*250)}function Oe(e,t){return e.length<=t?e:`${e.slice(0,t)}...`}export{Ze as startJobWorkerLoop};
@@ -1,5 +1,5 @@
1
- import{execFileSync as h}from"node:child_process";import{randomUUID as L}from"node:crypto";import l from"node:fs";import k from"node:os";import c from"node:path";import{acaServerRequest as y}from"./aca-server-client.js";import{loadAcaConfig as O,upsertLocalProject as W}from"./aca-config.js";import{startLongPollWorker as B}from"./long-poll-worker.js";import{expandWorkerRuntimePath as U}from"./worker-runtime-paths.js";const z=25e3,S=300,K=3e4;let b=null;function Ct(t){return B({run:async()=>{const e=O();if(!e.token)throw new Error("Worker 尚未配置认证令牌");const r=await y("GET",`/api/client/machine-operations/claim?machineId=${encodeURIComponent(e.machineId)}&waitMs=${z}`);if(r.item)try{const o=await J(r.item);await y("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 y("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 J(t){if(t.operation==="filesystem_roots")return ct();if(t.operation==="list_directory")return ut({directoryPath:String(t.payload.path||""),includeHidden:t.payload.includeHidden===!0});if(t.operation==="register_project")return ht(t.payload);if(t.operation==="list_project_directory")return Y(t.payload);if(t.operation==="get_project_git_status")return $(t.payload);if(t.operation==="get_project_git_diff")return Q(t.payload);if(t.operation==="get_project_git_log")return Z(t.payload);if(t.operation==="get_project_git_commit")return q(t.payload);if(t.operation==="get_worktree_status")return P(t.payload);if(t.operation==="commit_worktree")return V(t.payload);if(t.operation==="cleanup_worktree")return X(t.payload);throw new Error("不支持的 Machine 文件系统操作。 ")}function P(t){const e=j(String(t.worktreePath||""));if(!l.existsSync(e))return{exists:!1,isRepository:!1,dirty:!1,changedFiles:0,ahead:0};const r=m(e);if(!p(r))return{exists:!0,isRepository:!1,dirty:ot(r),changedFiles:null,ahead:0};const o=$({rootPath:r}),n=String(t.baseCommit||"").trim(),s=n&&/^[0-9a-f]{7,64}$/i.test(n)?rt(r,n):0;return{exists:!0,isRepository:!0,dirty:Array.isArray(o.files)&&o.files.length>0,changedFiles:Array.isArray(o.files)?o.files.length:0,branch:o.branch,ahead:s,files:Array.isArray(o.files)?o.files.slice(0,50):[],truncated:o.truncated===!0}}function V(t){const e=m(String(t.worktreePath||"")),r=String(t.message||"").trim();if(!r)throw new Error("提交说明不能为空。");if(!p(e))throw new Error("当前 Worktree 不是 Git 仓库,无法提交。");h("git",["-C",e,"add","-A"],{stdio:"pipe",timeout:3e4});try{return h("git",["-C",e,"diff","--cached","--quiet"],{stdio:"pipe",timeout:1e4}),{committed:!1,reason:"no_changes",...P(t)}}catch(o){if(Number(o.status)!==1)throw o}return h("git",["-C",e,"commit","-m",r],{stdio:"pipe",timeout:6e4}),{committed:!0,commit:f(e,["rev-parse","HEAD"],8*1024).trim(),...P(t)}}function X(t){const e=j(String(t.worktreePath||"")),r=m(String(t.projectRoot||""));if(d(e)===d(r))throw new Error("不能清理项目主目录。");if(!l.existsSync(e))return{removed:!1,reason:"not_found"};const o=m(e),n=String(t.branchName||"").trim(),s=p(o)&&p(r);if(s){if(h("git",["-C",r,"worktree","remove","--force",o],{stdio:"pipe",timeout:6e4}),t.deleteBranch===!0&&n)try{h("git",["-C",r,"branch","-D",n],{stdio:"pipe",timeout:3e4})}catch(i){const a=String(i.stderr||i);if(!/not found|not exist/i.test(a))throw i}}else l.rmSync(o,{recursive:!0,force:!0,maxRetries:2,retryDelay:200});return{removed:!0,branchDeleted:!!(s&&t.deleteBranch===!0&&n)}}function Y(t){const e=m(String(t.rootPath||"")),r=_(String(t.relativePath||"")),o=gt(e,r,!0),n=Math.max(0,Number(t.offset||0)),s=Math.min(300,Math.max(20,Number(t.limit||200))),i=l.readdirSync(o,{withFileTypes:!0}).filter(u=>!u.isSymbolicLink()).map(u=>({name:u.name,path:yt(e,c.join(o,u.name)),type:u.isDirectory()?"directory":u.isFile()?"file":"other",hidden:u.name.startsWith("."),extension:u.isFile()?c.extname(u.name).slice(1).toLowerCase():""})).filter(u=>u.type!=="other").sort((u,g)=>u.type!==g.type?u.type==="directory"?-1:1:u.name.localeCompare(g.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 D=500,x=2*1024*1024,w=2*1024*1024;function $(t){const e=m(String(t.rootPath||""));if(!p(e))return{isRepository:!1,branch:null,files:[],ahead:0,behind:0};const o=f(e,["status","--porcelain=v1","-z","--untracked-files=all","--no-renames"],x).split("\0").filter(Boolean).slice(0,D),n=o.map(a=>et(a)).filter(a=>!!a),s=v(f(e,["diff","--no-ext-diff","--numstat","-z","--no-renames","HEAD","--","."],x));for(const a of n){const u=s.get(a.path);if(u)a.additions=u.additions,a.deletions=u.deletions;else if(a.status==="untracked"){const g=R(e,a.path);a.additions=g.additions,a.deletions=g.deletions}}const i=at(e);return{isRepository:!0,branch:st(e),files:n,ahead:i.ahead,behind:i.behind,truncated:o.length>=D}}function Q(t){const e=m(String(t.rootPath||"")),r=_(String(t.relativePath||""));if(!r)throw new Error("Diff 文件路径不能为空。");if(!p(e))return{isRepository:!1,path:r,diff:"",additions:0,deletions:0};const o=N(e,r),n=nt(e,r),s=n?f(e,["diff","--no-ext-diff","--unified=80","HEAD","--",r],w):T(e,["diff","--no-ext-diff","--no-index","--unified=80",process.platform==="win32"?"NUL":"/dev/null",o],w),i=n?v(f(e,["diff","--no-ext-diff","--numstat","-z","--no-renames","HEAD","--",r],32*1024)).get(r):R(e,r);return{isRepository:!0,path:r,diff:I(s),additions:i?.additions??0,deletions:i?.deletions??0,truncated:s.length>w}}function Z(t){const e=m(String(t.rootPath||""));if(!p(e))return{isRepository:!1,items:[]};if(!it(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"],x).split("").map(i=>tt(i)).filter(i=>!!i),limit:r}}function q(t){const e=m(String(t.rootPath||"")),r=String(t.commit||"").trim();if(!/^[0-9a-f]{7,64}$/i.test(r))throw new Error("Git 提交标识无效。");if(!p(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],w),n=[...v(f(e,["show","--no-ext-diff","--numstat","-z","--no-renames","--format=",r],x)).values()];return{isRepository:!0,commit:r,diff:I(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>w}}function tt(t){const e=t.replace(/^\s+|\s+$/g,"");if(!e)return null;const r=e.split(""),o=String(r[0]||"").trim();return o?{hash:o,shortHash:String(r[1]||o.slice(0,8)).trim(),authorName:String(r[2]||"").trim(),authorEmail:String(r[3]||"").trim(),authoredAt:String(r[4]||"").trim(),refs:String(r[5]||"").trim(),subject:String(r[6]||"").trim(),body:r.slice(7).join("").trim()}:null}function et(t){if(t.length<4)return null;const e=t[0]||" ",r=t[1]||" ",o=_(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 v(t){const e=new Map;for(const r of t.split("\0")){if(!r)continue;const o=C(r);if(!o||!o.path)continue;const n=_(o.path);n&&e.set(n,{additions:o.additions,deletions:o.deletions})}return e}function R(t,e){const r=N(t,e),n=T(t,["diff","--no-ext-diff","--no-index","--numstat","-z","--",process.platform==="win32"?"NUL":"/dev/null",r],32*1024).split("\0",1)[0]||"",s=C(n);return s?{additions:s.additions,deletions:s.deletions}:{additions:null,deletions:null}}function C(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 p(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 rt(t,e){try{const r=f(t,["rev-list","--count",`${e}..HEAD`],8192).trim(),o=Number(r);return Number.isSafeInteger(o)&&o>0?o:0}catch{return 0}}function ot(t){try{return l.readdirSync(t).length>0}catch{return!1}}function nt(t,e){try{return f(t,["ls-files","--error-unmatch","--",e],8*1024),!0}catch{return!1}}function it(t){try{return f(t,["rev-parse","--verify","HEAD"],8*1024),!0}catch{return!1}}function st(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 at(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 N(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 h("git",["-C",t,...e],{encoding:"utf8",timeout:2e4,maxBuffer:r,stdio:["ignore","pipe","pipe"]})}function T(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 I(t){return t.length<=w?t:`${t.slice(0,w)}
1
+ import{execFileSync as h}from"node:child_process";import{randomUUID as H}from"node:crypto";import l from"node:fs";import k from"node:os";import c from"node:path";import{acaServerRequest as y}from"./aca-server-client.js";import{loadAcaConfig as W,upsertLocalProject as B}from"./aca-config.js";import{startLongPollWorker as U}from"./long-poll-worker.js";import{expandWorkerRuntimePath as z}from"./worker-runtime-paths.js";const D=25e3,K=D+1e4,S=300,J=3e4;let b=null;function Nt(t){return U({run:async()=>{const e=W();if(!e.token)throw new Error("Worker 尚未配置认证令牌");const r=await y("GET",`/api/client/machine-operations/claim?machineId=${encodeURIComponent(e.machineId)}&waitMs=${D}`,void 0,{timeoutMs:K});if(r.item)try{const o=await V(r.item);await y("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 y("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 V(t){if(t.operation==="filesystem_roots")return ut();if(t.operation==="list_directory")return lt({directoryPath:String(t.payload.path||""),includeHidden:t.payload.includeHidden===!0});if(t.operation==="register_project")return wt(t.payload);if(t.operation==="list_project_directory")return Q(t.payload);if(t.operation==="get_project_git_status")return $(t.payload);if(t.operation==="get_project_git_diff")return Z(t.payload);if(t.operation==="get_project_git_log")return q(t.payload);if(t.operation==="get_project_git_commit")return tt(t.payload);if(t.operation==="get_worktree_status")return P(t.payload);if(t.operation==="commit_worktree")return X(t.payload);if(t.operation==="cleanup_worktree")return Y(t.payload);throw new Error("不支持的 Machine 文件系统操作。 ")}function P(t){const e=j(String(t.worktreePath||""));if(!l.existsSync(e))return{exists:!1,isRepository:!1,dirty:!1,changedFiles:0,ahead:0};const r=m(e);if(!p(r))return{exists:!0,isRepository:!1,dirty:nt(r),changedFiles:null,ahead:0};const o=$({rootPath:r}),n=String(t.baseCommit||"").trim(),s=n&&/^[0-9a-f]{7,64}$/i.test(n)?ot(r,n):0;return{exists:!0,isRepository:!0,dirty:Array.isArray(o.files)&&o.files.length>0,changedFiles:Array.isArray(o.files)?o.files.length:0,branch:o.branch,ahead:s,files:Array.isArray(o.files)?o.files.slice(0,50):[],truncated:o.truncated===!0}}function X(t){const e=m(String(t.worktreePath||"")),r=String(t.message||"").trim();if(!r)throw new Error("提交说明不能为空。");if(!p(e))throw new Error("当前 Worktree 不是 Git 仓库,无法提交。");h("git",["-C",e,"add","-A"],{stdio:"pipe",timeout:3e4});try{return h("git",["-C",e,"diff","--cached","--quiet"],{stdio:"pipe",timeout:1e4}),{committed:!1,reason:"no_changes",...P(t)}}catch(o){if(Number(o.status)!==1)throw o}return h("git",["-C",e,"commit","-m",r],{stdio:"pipe",timeout:6e4}),{committed:!0,commit:f(e,["rev-parse","HEAD"],8*1024).trim(),...P(t)}}function Y(t){const e=j(String(t.worktreePath||"")),r=m(String(t.projectRoot||""));if(d(e)===d(r))throw new Error("不能清理项目主目录。");if(!l.existsSync(e))return{removed:!1,reason:"not_found"};const o=m(e),n=String(t.branchName||"").trim(),s=p(o)&&p(r);if(s){if(h("git",["-C",r,"worktree","remove","--force",o],{stdio:"pipe",timeout:6e4}),t.deleteBranch===!0&&n)try{h("git",["-C",r,"branch","-D",n],{stdio:"pipe",timeout:3e4})}catch(i){const a=String(i.stderr||i);if(!/not found|not exist/i.test(a))throw i}}else l.rmSync(o,{recursive:!0,force:!0,maxRetries:2,retryDelay:200});return{removed:!0,branchDeleted:!!(s&&t.deleteBranch===!0&&n)}}function Q(t){const e=m(String(t.rootPath||"")),r=x(String(t.relativePath||"")),o=yt(e,r,!0),n=Math.max(0,Number(t.offset||0)),s=Math.min(300,Math.max(20,Number(t.limit||200))),i=l.readdirSync(o,{withFileTypes:!0}).filter(u=>!u.isSymbolicLink()).map(u=>({name:u.name,path:St(e,c.join(o,u.name)),type:u.isDirectory()?"directory":u.isFile()?"file":"other",hidden:u.name.startsWith("."),extension:u.isFile()?c.extname(u.name).slice(1).toLowerCase():""})).filter(u=>u.type!=="other").sort((u,g)=>u.type!==g.type?u.type==="directory"?-1:1:u.name.localeCompare(g.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 R=500,_=2*1024*1024,w=2*1024*1024;function $(t){const e=m(String(t.rootPath||""));if(!p(e))return{isRepository:!1,branch:null,files:[],ahead:0,behind:0};const o=f(e,["status","--porcelain=v1","-z","--untracked-files=all","--no-renames"],_).split("\0").filter(Boolean).slice(0,R),n=o.map(a=>rt(a)).filter(a=>!!a),s=v(f(e,["diff","--no-ext-diff","--numstat","-z","--no-renames","HEAD","--","."],_));for(const a of n){const u=s.get(a.path);if(u)a.additions=u.additions,a.deletions=u.deletions;else if(a.status==="untracked"){const g=C(e,a.path);a.additions=g.additions,a.deletions=g.deletions}}const i=ct(e);return{isRepository:!0,branch:at(e),files:n,ahead:i.ahead,behind:i.behind,truncated:o.length>=R}}function Z(t){const e=m(String(t.rootPath||"")),r=x(String(t.relativePath||""));if(!r)throw new Error("Diff 文件路径不能为空。");if(!p(e))return{isRepository:!1,path:r,diff:"",additions:0,deletions:0};const o=T(e,r),n=it(e,r),s=n?f(e,["diff","--no-ext-diff","--unified=80","HEAD","--",r],w):I(e,["diff","--no-ext-diff","--no-index","--unified=80",process.platform==="win32"?"NUL":"/dev/null",o],w),i=n?v(f(e,["diff","--no-ext-diff","--numstat","-z","--no-renames","HEAD","--",r],32*1024)).get(r):C(e,r);return{isRepository:!0,path:r,diff:F(s),additions:i?.additions??0,deletions:i?.deletions??0,truncated:s.length>w}}function q(t){const e=m(String(t.rootPath||""));if(!p(e))return{isRepository:!1,items:[]};if(!st(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"],_).split("").map(i=>et(i)).filter(i=>!!i),limit:r}}function tt(t){const e=m(String(t.rootPath||"")),r=String(t.commit||"").trim();if(!/^[0-9a-f]{7,64}$/i.test(r))throw new Error("Git 提交标识无效。");if(!p(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],w),n=[...v(f(e,["show","--no-ext-diff","--numstat","-z","--no-renames","--format=",r],_)).values()];return{isRepository:!0,commit:r,diff:F(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>w}}function et(t){const e=t.replace(/^\s+|\s+$/g,"");if(!e)return null;const r=e.split(""),o=String(r[0]||"").trim();return o?{hash:o,shortHash:String(r[1]||o.slice(0,8)).trim(),authorName:String(r[2]||"").trim(),authorEmail:String(r[3]||"").trim(),authoredAt:String(r[4]||"").trim(),refs:String(r[5]||"").trim(),subject:String(r[6]||"").trim(),body:r.slice(7).join("").trim()}:null}function rt(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 v(t){const e=new Map;for(const r of t.split("\0")){if(!r)continue;const o=N(r);if(!o||!o.path)continue;const n=x(o.path);n&&e.set(n,{additions:o.additions,deletions:o.deletions})}return e}function C(t,e){const r=T(t,e),n=I(t,["diff","--no-ext-diff","--no-index","--numstat","-z","--",process.platform==="win32"?"NUL":"/dev/null",r],32*1024).split("\0",1)[0]||"",s=N(n);return s?{additions:s.additions,deletions:s.deletions}:{additions:null,deletions:null}}function N(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 p(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 ot(t,e){try{const r=f(t,["rev-list","--count",`${e}..HEAD`],8192).trim(),o=Number(r);return Number.isSafeInteger(o)&&o>0?o:0}catch{return 0}}function nt(t){try{return l.readdirSync(t).length>0}catch{return!1}}function it(t,e){try{return f(t,["ls-files","--error-unmatch","--",e],8*1024),!0}catch{return!1}}function st(t){try{return f(t,["rev-parse","--verify","HEAD"],8*1024),!0}catch{return!1}}function at(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 ct(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 T(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 h("git",["-C",t,...e],{encoding:"utf8",timeout:2e4,maxBuffer:r,stdio:["ignore","pipe","pipe"]})}function I(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 F(t){return t.length<=w?t:`${t.slice(0,w)}
2
2
 
3
- [Diff 内容过大,已截断]`}function ct(){const t=process.platform,e=A(t);return{platform:t,roots:e}}function ut(t){if(!t.directoryPath.trim())throw new Error("目录路径不能为空。");return process.platform==="win32"?mt(t.directoryPath,!!t.includeHidden):dt(t.directoryPath,!!t.includeHidden)}function d(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 lt(){const t=l.realpathSync(k.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 A(t){if(b?.platform===t&&b.expiresAtMs>Date.now())return b.roots;const e=t==="win32"?ft():lt();return b={platform:t,roots:e,expiresAtMs:Date.now()+K},e}function ft(){const t=l.realpathSync.native(k.homedir()),e=new Map;e.set(d(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=H(M(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(/[\\/]+$/,"")),u=String(n.VolumeName||"").trim();e.set(d(s,"win32"),{id:`drive-${a.replace(/[^a-z0-9]+/gi,"").toLowerCase()}`,label:u?`${u} (${a})`:_t(a,i),path:s,kind:xt(i),available:!0})}}catch{const r=c.win32.parse(t).root;e.set(d(r,"win32"),{id:"drive-system",label:`系统盘 (${r.replace(/\\$/,"")})`,path:r,kind:"fixed",available:!0})}return[...e.values()]}function dt(t,e){const r=E(t),n=l.readdirSync(r,{withFileTypes:!0}).filter(i=>i.isDirectory()&&!i.isSymbolicLink()&&(e||!i.name.startsWith("."))),s=n.slice(0,S).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"})),F(r,s,n.length>S,"linux")}function mt(t,e){const r=E(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=H(M(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,S).sort((i,a)=>i.name.localeCompare(a.name,void 0,{numeric:!0,sensitivity:"base"}));return F(r,s,n.length>S,"win32")}function F(t,e,r,o){const n=o==="win32"?c.win32:c.posix,s=n.parse(t).root,i=d(t,o)===d(s,o)?null:n.dirname(t);return{platform:o,path:t,parentPath:i,breadcrumbs:pt(t,o),readable:G(t,l.constants.R_OK),writable:G(t,l.constants.W_OK),items:e,truncated:r}}function pt(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 ht(t){const e=String(t.workspaceId||"").trim(),r=String(t.name||"").trim(),o=t.projectType==="chat_room"?"chat_room":"project";if(!e||!r)throw new Error("项目名称和工作区不能为空。");const n=E(String(t.path||""));l.accessSync(n,l.constants.R_OK|l.constants.W_OK);const i=(await y("GET",`/api/projects?workspaceId=${encodeURIComponent(e)}`)).items.find(u=>d(u.root_path)===d(n)),a=i||(await y("POST","/api/projects",{projectId:L(),workspaceId:e,name:r,rootPath:n,source:"aica",projectType:o})).item;return W(wt(a)),{project:a,duplicate:!!i}}function wt(t){return{projectId:t.project_id,workspaceId:t.workspace_id,name:t.name,rootPath:t.root_path}}function E(t){if(!t.trim()||!c.isAbsolute(t))throw new Error("必须选择绝对目录路径。");if(St(t),!l.statSync(t).isDirectory())throw new Error("选择的路径不是目录。");const r=l.realpathSync.native(t);return bt(r),r}function m(t){const e=j(t),r=l.realpathSync.native(e);if(!l.statSync(r).isDirectory())throw new Error("项目根目录不存在。");return r}function j(t){const e=U(t);if(!e||!c.isAbsolute(e))throw new Error("项目根目录无效。");return c.resolve(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 gt(t,e,r){const o=e?c.join(t,...e.split("/")):t,n=l.realpathSync.native(o),s=c.relative(t,n);if(s.startsWith("..")||c.isAbsolute(s))throw new Error("目录超出项目范围。");const i=l.statSync(n);if(r&&!i.isDirectory())throw new Error("请求路径不是目录。");return n}function yt(t,e){return c.relative(t,e).split(c.sep).join("/")}function St(t){const e=process.platform==="win32"?c.win32:c.posix;if(!A(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 bt(t){const e=process.platform==="win32"?c.win32:c.posix;if(!A(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 G(t,e){try{return l.accessSync(t,e),!0}catch{return!1}}function M(t,e={}){return h("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 H(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 xt(t){return t===2?"removable":t===3?"fixed":t===4?"network":t===5?"optical":"drive"}function _t(t,e){return e===2?`移动磁盘 (${t})`:e===4?`网络驱动器 (${t})`:e===5?`光驱 (${t})`:`本地磁盘 (${t})`}export{X as cleanupWorktree,V as commitWorktree,d as comparableMachinePath,q as getProjectGitCommit,Q as getProjectGitDiff,Z as getProjectGitLog,$ as getProjectGitStatus,P as getWorktreeStatus,ut as listMachineDirectory,ct as listMachineFilesystemRoots,Y as listProjectDirectory,Ct as startMachineFilesystemWorkerLoop};
3
+ [Diff 内容过大,已截断]`}function ut(){const t=process.platform,e=E(t);return{platform:t,roots:e}}function lt(t){if(!t.directoryPath.trim())throw new Error("目录路径不能为空。");return process.platform==="win32"?pt(t.directoryPath,!!t.includeHidden):mt(t.directoryPath,!!t.includeHidden)}function d(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 ft(){const t=l.realpathSync(k.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 E(t){if(b?.platform===t&&b.expiresAtMs>Date.now())return b.roots;const e=t==="win32"?dt():ft();return b={platform:t,roots:e,expiresAtMs:Date.now()+J},e}function dt(){const t=l.realpathSync.native(k.homedir()),e=new Map;e.set(d(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=O(L(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(/[\\/]+$/,"")),u=String(n.VolumeName||"").trim();e.set(d(s,"win32"),{id:`drive-${a.replace(/[^a-z0-9]+/gi,"").toLowerCase()}`,label:u?`${u} (${a})`:Pt(a,i),path:s,kind:xt(i),available:!0})}}catch{const r=c.win32.parse(t).root;e.set(d(r,"win32"),{id:"drive-system",label:`系统盘 (${r.replace(/\\$/,"")})`,path:r,kind:"fixed",available:!0})}return[...e.values()]}function mt(t,e){const r=A(t),n=l.readdirSync(r,{withFileTypes:!0}).filter(i=>i.isDirectory()&&!i.isSymbolicLink()&&(e||!i.name.startsWith("."))),s=n.slice(0,S).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"})),M(r,s,n.length>S,"linux")}function pt(t,e){const r=A(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=O(L(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,S).sort((i,a)=>i.name.localeCompare(a.name,void 0,{numeric:!0,sensitivity:"base"}));return M(r,s,n.length>S,"win32")}function M(t,e,r,o){const n=o==="win32"?c.win32:c.posix,s=n.parse(t).root,i=d(t,o)===d(s,o)?null:n.dirname(t);return{platform:o,path:t,parentPath:i,breadcrumbs:ht(t,o),readable:G(t,l.constants.R_OK),writable:G(t,l.constants.W_OK),items:e,truncated:r}}function ht(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 wt(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=A(String(t.path||""));l.accessSync(n,l.constants.R_OK|l.constants.W_OK);const i=(await y("GET",`/api/projects?workspaceId=${encodeURIComponent(e)}`)).items.find(u=>d(u.root_path)===d(n)),a=i||(await y("POST","/api/projects",{projectId:H(),workspaceId:e,name:r,rootPath:n,source:"aica",projectType:o})).item;return B(gt(a)),{project:a,duplicate:!!i}}function gt(t){return{projectId:t.project_id,workspaceId:t.workspace_id,name:t.name,rootPath:t.root_path}}function A(t){if(!t.trim()||!c.isAbsolute(t))throw new Error("必须选择绝对目录路径。");if(bt(t),!l.statSync(t).isDirectory())throw new Error("选择的路径不是目录。");const r=l.realpathSync.native(t);return _t(r),r}function m(t){const e=j(t),r=l.realpathSync.native(e);if(!l.statSync(r).isDirectory())throw new Error("项目根目录不存在。");return r}function j(t){const e=z(t);if(!e||!c.isAbsolute(e))throw new Error("项目根目录无效。");return c.resolve(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 yt(t,e,r){const o=e?c.join(t,...e.split("/")):t,n=l.realpathSync.native(o),s=c.relative(t,n);if(s.startsWith("..")||c.isAbsolute(s))throw new Error("目录超出项目范围。");const i=l.statSync(n);if(r&&!i.isDirectory())throw new Error("请求路径不是目录。");return n}function St(t,e){return c.relative(t,e).split(c.sep).join("/")}function bt(t){const e=process.platform==="win32"?c.win32:c.posix;if(!E(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 _t(t){const e=process.platform==="win32"?c.win32:c.posix;if(!E(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 G(t,e){try{return l.accessSync(t,e),!0}catch{return!1}}function L(t,e={}){return h("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 O(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 xt(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{Y as cleanupWorktree,X as commitWorktree,d as comparableMachinePath,tt as getProjectGitCommit,Z as getProjectGitDiff,q as getProjectGitLog,$ as getProjectGitStatus,P as getWorktreeStatus,lt as listMachineDirectory,ut as listMachineFilesystemRoots,Q as listProjectDirectory,Nt as startMachineFilesystemWorkerLoop};
@@ -1,2 +1,2 @@
1
1
  #!/usr/bin/env node
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.21").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};
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.23").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.21",
3
+ "version": "0.1.23",
4
4
  "description": "AICA remote Agent worker",
5
5
  "author": "yuandc",
6
6
  "type": "module",