grix-connector 3.6.0 → 3.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,20 +1,23 @@
1
- import{fileURLToPath as A}from"node:url";import f from"node:path";import{homedir as C}from"node:os";import{promises as v}from"node:fs";import{EventEmitter as S}from"node:events";import{spawn as R}from"node:child_process";import{AgentProcess as T}from"../../agent/process.js";import{hasChildProcesses as w}from"../../core/runtime/spawn.js";import{applyNativeProviderConfig as k}from"../shared/native-provider-config.js";import{syncDefaultSkillsToDir as _}from"../../default-skills/index.js";import{AcpClient as $,AcpAuthRequiredError as P,isAuthRequiredError as E}from"../../protocol/acp-client.js";import{AgentEventType as m}from"../../types/events.js";import{InternalApiServer as M}from"../../core/mcp/internal-api-server.js";import{EventResultsStore as x}from"../../core/persistence/event-results-store.js";import{QuotedMessageStream as q}from"../../core/util/quoted-message-stream.js";import{SafeMarkdownStreamSegmenter as D}from"../../core/text-segmentation/index.js";import{extractAcpTurnInput as N}from"../../core/protocol/payload-parser.js";import{injectMessageMetadata as j}from"../../core/protocol/message-metadata.js";import{log as r}from"../../core/log/index.js";import{scanSkills as b}from"../claude/skill-scanner.js";import{checkKiroSessionActivity as B}from"./session-activity.js";import{resolveCliPath as U,getCliVersion as O}from"../../core/util/cli-probe.js";const I=f.dirname(A(import.meta.url)),F=[{modelId:"deepseek-v4-pro",name:"DeepSeek V4 Pro"},{modelId:"deepseek-v4-flash",name:"DeepSeek V4 Flash"}],L=200,K=60*1e3,H=600*1e3,y=80,W=["[grix protocol] Markers like [[message_id:<id>]] and [[quoted_message_id:<id>]] are metadata, not user prose \u2014 never repeat them verbatim except as instructed here.","The message you are replying to carries its id in a [[message_id:<id>]] marker.","If (and only if) you want your reply to quote/thread onto that specific message, include [[quoted_message_id:<id>]] anywhere in your reply using that id; otherwise omit it."].join(" ");function z(h){return!(!(h instanceof Error)||h.code!==-32603||E(h))}function g(h){if(!(h instanceof Error))return String(h);let e=h.message;const i=h.data;return i&&(e+=` data=${JSON.stringify(i)}`),e}function V(h){return h.replace(/\u001b\[[0-9;?]*[ -/]*[@-~]/g,"")}class ve extends S{type="acp";config;callbacks;agentProcess=null;acpClient=null;internalApi=null;activeRun=null;compacting=!1;compactingTimer=null;compactionDoneResolver=null;pendingAutoCompact=!1;eventResults=null;pendingApprovals=new Map;clientMsgSeq=0;stopped=!1;acpAuthMethod;acpInitialMode;acpInitialModel;acpMcpTools;rawTransport;approvalMode;autoInjectArgs;nativeProviderScope;bindingStore;sessionBindings=new Map;deferredEvents=new Map;currentAibotSessionId;bridgeLog;sessionConnected=!1;rawEventSeq=0;recoveryContextBySessionId=new Map;quoteProtocolTaughtSessions=new Set;identityInjectedSessions=new Set;constructor(e,i,t){if(super(),this.config=e,this.callbacks=i,this.bridgeLog=t?.bridgeLog??null,this.acpAuthMethod=t?.acpAuthMethod,this.acpInitialMode=t?.acpInitialMode,this.acpInitialModel=t?.acpInitialModel,this.acpMcpTools=t?.acpMcpTools??!0,this.rawTransport=t?.rawTransport??!1,this.approvalMode=t?.approvalMode??"default",this.autoInjectArgs=t?.autoInjectArgs,this.nativeProviderScope=t?.nativeProviderScope,this.bindingStore=t?.bindingStore??null,this.currentAibotSessionId=t?.aibotSessionId?String(t.aibotSessionId).trim():void 0,t?.eventResultsPath&&(this.eventResults=new x(t.eventResultsPath)),this.bindingStore&&this.currentAibotSessionId){const s=this.bindingStore.get(this.currentAibotSessionId);s?.cwd&&this.sessionBindings.set(this.currentAibotSessionId,s.cwd)}}async start(){if(await this.spawnProcess(),!this.bindingStore){await this.connectSession(this.resolveCwd());return}const e=this.currentAibotSessionId?this.sessionBindings.get(this.currentAibotSessionId):void 0;e&&await this.connectSession(e)}async stop(){this.stopped=!0,this.rejectDeferredEvents("adapter stopped"),this.cancelAllDeferredTimers(),this.pendingAutoCompact=!1,this.compacting=!1,this.compactingTimer&&(clearTimeout(this.compactingTimer),this.compactingTimer=null),this.compactionDoneResolver&&(this.compactionDoneResolver(),this.compactionDoneResolver=null),this.activeRun&&(this.activeRun.flushTimer&&(clearTimeout(this.activeRun.flushTimer),this.activeRun.flushTimer=null),this.activeRun.idleTimer&&(clearTimeout(this.activeRun.idleTimer),this.activeRun.idleTimer=null),this.activeRun=null),this.acpClient&&(this.acpClient.clearSettleTimer(),this.acpClient.removeAllListeners(),this.acpClient=null),this.agentProcess&&(await this.agentProcess.close(),this.agentProcess=null),this.internalApi&&(await this.internalApi.stop(),this.internalApi=null)}isAlive(){return this.agentProcess?.alive??!1}async createSession(e){return this.acpClient?.sessionId??""}async resumeSession(e,i){}async destroySession(e){this.quoteProtocolTaughtSessions.delete(e),this.identityInjectedSessions.delete(e)}onAgentProfileChanged(){this.identityInjectedSessions.size>0&&(r.info("acp-adapter",`agent profile changed; clearing ${this.identityInjectedSessions.size} identity-injected session(s)`),this.identityInjectedSessions.clear())}sendPrompt(e){const i=new Q(e.adapterSessionId);return this.acpClient?.isAlive&&this.acpClient.send(e.text).catch(t=>{i.emitError(t instanceof Error?t:new Error(String(t)))}),i}async cancel(e){this.activeRun&&this.acpClient&&(await this.acpClient.cancel(),this.flushStream(),this.finishRun("canceled","stopped by user"))}setPermissionHandler(e){}async ping(e){return this.acpClient?.isAlive?this.acpClient.ping(e):this.agentProcess?.alive??!1}getStatus(){return{alive:this.agentProcess?.alive??!1,busy:this.activeRun!==null||this.compacting,sessions:this.sessionConnected?1:0}}getActiveEventIds(){return this.activeRun?[this.activeRun.eventId]:[]}clearActiveEventForShutdown(){this.activeRun&&(this.activeRun.flushTimer&&(clearTimeout(this.activeRun.flushTimer),this.activeRun.flushTimer=null),this.activeRun.idleTimer&&(clearTimeout(this.activeRun.idleTimer),this.activeRun.idleTimer=null),this.activeRun=null)}getMcpConfig(){if(!this.internalApi)return null;this.internalApi.setMcpBridgeUpHandler(i=>this.onMcpBridgeUp(i));const e=f.resolve(I,"../../mcp/mcp-bridge-server.js");return{name:"grix-app-bridge",command:process.execPath,args:[e,"--ws-url",this.internalApi.mcpBridgeWsUrl]}}async hasBackgroundWork(){const e=this.agentProcess?.pid;return e?w(e,[e]):!1}async probe(e){const i=this.config.command||"gemini",t=await U(i),s=t!==null;let n=null,a;if(s){const l=await O(i);n=l.version,l.error&&(a=l.error)}else a={code:"cli_not_found",message:`command not found: ${i}`};const o=this.acpClient?.model??null,c=this.getStatus();let d;return e?.conversation?this.acpClient?.isAlive?d={attempted:!0,ok:!0,latency_ms:null}:d={attempted:!0,ok:!1,latency_ms:null,error:{code:"unsupported",message:"ACP session not connected, cannot probe conversation"}}:d={attempted:!1,ok:!1,latency_ms:null},{cli:{command:i,installed:s,path:t,version:n,...a?{error:a}:{}},conversation:d,config:{model:o,base_url:null,source:{model:o?"runtime":"unknown",base_url:"unknown"}},process:{started:!!this.agentProcess,alive:c.alive,busy:c.busy}}}getSupportedCommands(){return this.acpClient?.availableCommands?.length?this.acpClient.availableCommands.map(e=>({name:e.name,description:e.description??"",args:e.args})):[{name:"model",description:"List or set model",args:"[model_id]"},{name:"mode",description:"List or set collaboration mode",args:"[mode_id]"},{name:"interrupt",description:"Interrupt current run"},{name:"compact",description:"\u538B\u7F29\u4E0A\u4E0B\u6587"},{name:"status",description:"Show session status"},{name:"skills",description:"List available skills"}]}async execCommand(e,i,t){return this.acpClient?.supportsCommandsExecute?this.execCommandViaAcp(e,i):this.execCommandFallback(e,i)}async execCommandViaAcp(e,i){if(!this.acpClient)return{status:"failed",message:"No active ACP session"};if(e==="interrupt")return this.activeRun?(await this.cancel(this.activeRun.sessionId),{status:"ok",message:"Run interrupted"}):{status:"failed",message:"No active run to interrupt"};if(e==="compact"){if(this.compacting)return{status:"failed",message:"Compaction already in progress"};if(this.activeRun)return{status:"failed",message:"agent busy"};this.compacting=!0;try{const t=await this.acpClient.executeCommand(e,i||void 0);return this.finishCompaction("acp-commands-execute"),{status:t.status==="failed"?"failed":"ok",message:t.message??"Compacted",data:t.data}}catch(t){return this.finishCompaction("error"),t?.code===-32601?this.execCommandFallback(e,i):{status:"failed",message:`compact failed: ${t instanceof Error?t.message:t}`}}}try{const t=await this.acpClient.executeCommand(e,i||void 0);return{status:t.status??"ok",message:t.message,data:t.options?{command:e,options:t.options}:t.data}}catch(t){return t?.code===-32601?this.execCommandFallback(e,i):{status:"failed",message:`Command failed: ${t instanceof Error?t.message:t}`}}}async execCommandFallback(e,i){try{switch(e){case"model":{const t=i.trim();if(t)return await this.setModel(t)?{status:"ok",message:`Model set to ${t}`}:{status:"failed",message:`Failed to set model: ${t}`};const s=this.buildToolbarContext("model_list");return{status:"ok",message:`Current: ${s.currentModelId||"unknown"}`,data:s}}case"mode":{const t=i.trim();if(t)return await this.setMode(t)?{status:"ok",message:`Mode set to ${t}`}:{status:"failed",message:`Failed to set mode: ${t}`};const s=this.buildToolbarContext("mode_list");return{status:"ok",message:`Current: ${s.currentModeId||"unknown"}`,data:s}}case"interrupt":return this.activeRun?this.acpClient?(await this.cancel(this.activeRun.sessionId),{status:"ok",message:"Run interrupted"}):{status:"failed",message:"Not connected"}:{status:"failed",message:"No active run to interrupt"};case"compact":{if(!this.acpClient)return{status:"failed",message:"No active ACP session"};if(this.compacting)return{status:"failed",message:"Compaction already in progress"};if(this.activeRun)return{status:"failed",message:"agent busy"};this.compacting=!0;const t=new Promise(s=>{this.compactionDoneResolver=s});try{await this.acpClient.send("/compact")}catch(s){throw this.finishCompaction("send-failed"),s}return this.compactingTimer=setTimeout(()=>this.finishCompaction("timeout"),15e3),this.compactingTimer.unref?.(),await t,{status:"ok",message:"Compacted"}}case"status":{const t=this.getStatus(),s=this.acpClient?.sessionOptions;return{status:"ok",message:`Alive: ${t.alive}, Busy: ${t.busy}, Model: ${s?.currentModelId??"unknown"}, Mode: ${s?.currentModeId??"unknown"}`,data:{alive:t.alive,busy:t.busy,sessions:t.sessions,model:s?.currentModelId??"",mode:s?.currentModeId??""}}}case"skills":{const t=b({mode:this.config.command==="kiro-cli"?"kiro":"gemini",projectDir:process.cwd()}),s=t.map(n=>`- ${n.name}${n.trigger?` (${n.trigger})`:""} [${n.source}]: ${n.description}`);return{status:"ok",message:s.length>0?s.join(`
2
- `):"No skills found",data:t}}default:return{status:"unsupported",message:`Unknown command: ${e}`}}}catch(t){return{status:"failed",message:t instanceof Error?t.message:String(t)}}}get acpSessionOptions(){return this.acpClient?.sessionOptions??null}get isReasonix(){return(this.config.command??"").includes("reasonix")}buildToolbarContext(e,i){const t=this.acpClient?.sessionOptions,s=t?.currentModeId??"";let n=t?.currentModelId??"";const a=t?.modes.map(u=>({id:u.id,name:u.name}))??[],o=t?.models&&t.models.length>0?t.models:this.isReasonix?F:[],c=o.map(u=>({modelId:u.modelId,name:u.name})),d=o.map(u=>({id:u.modelId,displayName:u.name})),l=t?.modes.map(u=>({id:u.id,displayName:u.name}))??[];d.length>0&&!d.some(u=>u.id===n)&&(n=d[0].id);const p={outcome:e,...i?{cwd:i}:{},model_id:n,mode_id:s,currentModelId:n,currentModeId:s,available_models:d,available_modes:l,availableModels:d,availableModes:l,models:c,modes:a};return r.info("acp-adapter",`[toolbar] buildToolbarContext outcome=${e} model_id="${n}" available_models=${JSON.stringify(d.map(u=>u.id))} currentModelId=${n}`),p}get pendingApprovalEntries(){return this.pendingApprovals}get hasSessionBinding(){return this.bindingStore!==null}setMode(e){return this.acpClient?this.acpClient.setLiveMode(e):Promise.resolve(!1)}setModel(e){return this.acpClient?this.acpClient.setModel(e):Promise.resolve(!1)}handleAcpApprovalAction(e,i){const t=this.pendingApprovals.get(e);return t?(this.pendingApprovals.delete(e),this.acpClient&&this.acpClient.respondPermission(t,{behavior:i}).catch(s=>{r.error("acp-adapter",`Failed to respond to permission: ${s}`)}),this.activeRun&&this.resetIdleTimer(this.activeRun),!0):!1}respondToPermission(e,i){this.acpClient&&this.acpClient.respondPermission(e,i).catch(t=>{r.error("acp-adapter",`Failed to respond to permission: ${t}`)}),this.activeRun&&this.resetIdleTimer(this.activeRun)}async handleLocalAction(e){const i=e.action_type??"",t=e.params??{};if(i==="exec_approve"||i==="exec_reject"||i==="permission_approve"||i==="permission_reject"){const s=String(t.tool_call_id??t.approval_command_id??t.approval_id??t.exec_context_id??""),n=i==="exec_approve"||i==="permission_approve",a=String(t.decision??"");let o;return n&&(a==="allow-once"||a==="allow-always")?o=a:n?o="allow":o="deny",s?this.handleAcpApprovalAction(s,o)?(this.callbacks.sendLocalActionResult(e.action_id,"ok"),{handled:!0,kind:"approval"}):(this.callbacks.sendLocalActionResult(e.action_id,"failed",void 0,"approval_not_found",`no pending approval for tool_call_id: ${s}`),{handled:!0,kind:"approval"}):(this.callbacks.sendLocalActionResult(e.action_id,"failed",void 0,"tool_call_id_required","tool_call_id is required"),{handled:!0,kind:"approval"})}return{handled:!1,kind:""}}resolveCwd(){if(this.currentAibotSessionId){const e=this.sessionBindings.get(this.currentAibotSessionId);if(e)return e}if(this.bindingStore&&this.currentAibotSessionId){const e=this.bindingStore.get(this.currentAibotSessionId);if(e?.cwd)return e.cwd}return process.cwd()}async bindSession(e,i){const t=this.sessionBindings.get(e);if(t)try{return await v.stat(t),!1}catch{r.info("acp-adapter",`Stale binding for session ${e}: ${t} no longer exists, allowing rebind to ${i}`),this.sessionBindings.delete(e)}return this.sessionBindings.set(e,i),this.bindingStore&&this.bindingStore.set(e,i),this.maybeRefreshKiroSkills(i),!this.sessionConnected&&this.agentProcess?.alive?this.connectSession(i).then(()=>!0).catch(s=>(r.error("acp-adapter",`Failed to create session on bind: ${g(s)}`),this.callbacks.sendUpdateBindingCard(e,"failed",i),!0)):(this.acpClient?.isAlive&&(this.bindingStore&&this.acpClient.sessionId&&this.bindingStore.setAcpSessionId(e,this.acpClient.sessionId),this.callbacks.sendUpdateBindingCard(e,"connected",i,this.buildToolbarContext("binding_ready",i))),Promise.resolve(!0))}getSessionCwd(e){return this.sessionBindings.get(e)}getSessionBindings(){return this.sessionBindings}maybeRefreshKiroSkills(e){if(this.config.command!=="kiro-cli"||!this.callbacks.onSkillsUpdate)return;const i=String(e??"").trim()||void 0;let t=[];try{t=b({mode:"kiro",projectDir:i})}catch(s){r.warn("acp-adapter",`Kiro skills scan failed: ${s instanceof Error?s.message:String(s)}`);return}try{this.callbacks.onSkillsUpdate(t)}catch(s){r.warn("acp-adapter",`onSkillsUpdate failed: ${s instanceof Error?s.message:String(s)}`)}}replayDeferredEvents(e){const i=this.deferredEvents.get(e);if(!i||i.length===0)return;if(this.activeRun){r.info("acp-adapter",`Cannot replay deferred events for session ${e}: agent busy`);return}const t=i.shift();t&&(i.length===0?(this.deferredEvents.delete(e),this.cancelDeferredTimer(e)):this.deferredEvents.set(e,i),r.info("acp-adapter",`Replaying deferred event ${t.event.event_id} for session ${e} (remaining: ${i.length})`),this.startRun(t.event,!1))}finishCompaction(e){if(!this.compacting)return;this.compacting=!1,this.compactingTimer&&(clearTimeout(this.compactingTimer),this.compactingTimer=null),r.info("acp-adapter",`Compaction finished (${e}); replaying deferred events`),this.replayNextDeferredEvent();const i=this.compactionDoneResolver;this.compactionDoneResolver=null,i?.()}replayNextDeferredEvent(){if(this.activeRun||this.compacting)return;const e=this.deferredEvents.keys().next().value;e&&this.replayDeferredEvents(e)}announceDeferredComposing(e){}deliverInboundEvent(e){if(this.eventResults?.has(e.session_id,e.event_id)){const i=this.eventResults.get(e.session_id,e.event_id);r.info("acp-adapter",`Deduplicating event ${e.event_id} (cached: ${i.status})`),this.callbacks.sendEventResult(e.event_id,i.status,i.msg);return}if(this.compacting){r.info("acp-adapter",`Event ${e.event_id} deferred: compaction in progress`),this.deferEvent(e);return}if(this.activeRun){r.info("acp-adapter",`Event ${e.event_id} rejected: busy`),this.callbacks.sendEventResult(e.event_id,"failed","agent busy");return}if(!this.agentProcess?.alive){this.callbacks.sendEventResult(e.event_id,"failed","agent not alive");return}e.session_id&&e.session_id!==this.currentAibotSessionId&&(this.currentAibotSessionId=e.session_id),this.startRun(e,!1)}deliverStopEvent(e,i){this.acpClient?(this.acpClient.cancel().catch(()=>{}),this.flushStream()):this.flushStream(),this.activeRun?.eventId===e&&this.finishRun("canceled","stopped by user")}deferEvent(e){const i=this.deferredEvents.get(e.session_id)??[];i.some(t=>t.event.event_id===e.event_id)||(i.push({event:e,queuedAt:Date.now()}),this.deferredEvents.set(e.session_id,i),r.info("acp-adapter",`Deferred event ${e.event_id} for session ${e.session_id} (queue: ${i.length})`),this.scheduleDeferredTimeout(e.session_id))}deferredTimers=new Map;rejectDeferredEvents(e){for(const[i,t]of this.deferredEvents)for(const{event:s}of t)r.info("acp-adapter",`Rejecting deferred event ${s.event_id}: ${e}`),this.callbacks.sendEventResult(s.event_id,"failed",e);this.deferredEvents.clear(),this.cancelAllDeferredTimers()}cancelDeferredTimer(e){const i=this.deferredTimers.get(e);i&&(clearTimeout(i),this.deferredTimers.delete(e))}cancelAllDeferredTimers(){for(const e of this.deferredTimers.values())clearTimeout(e);this.deferredTimers.clear()}scheduleDeferredTimeout(e){if(this.deferredTimers.has(e))return;const i=3e4,t=setTimeout(()=>{this.deferredTimers.delete(e);const s=this.deferredEvents.get(e);if(!(!s||s.length===0)&&!this.acpClient?.isAlive){r.error("acp-adapter",`Deferred events for session ${e} timed out after ${i/1e3}s (no ACP client)`),this.deferredEvents.delete(e);for(const{event:n}of s)this.callbacks.sendEventResult(n.event_id,"failed","agent initialization timed out")}},i);this.deferredTimers.set(e,t)}startRun(e,i){if(!this.acpClient?.isAlive){this.deferEvent(e);return}const t=`acp_${++this.clientMsgSeq}_${Date.now()}`;this.activeRun={eventId:e.event_id,sessionId:e.session_id,threadId:e.thread_id,clientMsgIdBase:t,currentClientMsgId:t,currentSegmentIndex:0,chunkSeq:0,buffer:"",quotedStream:new q,markdownSegmenter:new D,quotedMessageId:void 0,flushTimer:null,idleTimer:null,awaitingToolResult:!1,responded:!1,silent:i,lastProgressAt:Date.now(),lastIdleCheckAt:Date.now(),idleNoProgressCount:0};const s=this.activeRun,n=N(e,this.resolveCwd());this.resetIdleTimer(s),n.modeId&&this.acpClient&&this.acpClient.setLiveMode(n.modeId).catch(()=>{}),n.modelId&&this.acpClient&&this.acpClient.setModel(n.modelId).catch(()=>{});const a=j(n.prompt,{messageId:e.msg_id,quotedMessageId:e.quoted_message_id}),o=this.injectQuoteProtocolOnce(s.sessionId,a),c=this.injectIdentityOnce(s.sessionId,o),d=this.injectRecoveryContext(s.sessionId,c);this.sendPromptWithRetry(s,d)}injectQuoteProtocolOnce(e,i){return!e||this.quoteProtocolTaughtSessions.has(e)?i:(this.quoteProtocolTaughtSessions.add(e),`${W}
1
+ import{fileURLToPath as w}from"node:url";import f from"node:path";import{homedir as I}from"node:os";import{promises as v}from"node:fs";import{EventEmitter as A}from"node:events";import{spawn as b}from"node:child_process";import{AgentProcess as k}from"../../agent/process.js";import{hasChildProcesses as _}from"../../core/runtime/spawn.js";import{applyNativeProviderConfig as $}from"../shared/native-provider-config.js";import{syncDefaultSkillsToDir as P}from"../../default-skills/index.js";import{AcpClient as E,AcpAuthRequiredError as M,isAuthRequiredError as x}from"../../protocol/acp-client.js";import{AgentEventType as m}from"../../types/events.js";import{InternalApiServer as q}from"../../core/mcp/internal-api-server.js";import{EventResultsStore as N}from"../../core/persistence/event-results-store.js";import{QuotedMessageStream as D}from"../../core/util/quoted-message-stream.js";import{SafeMarkdownStreamSegmenter as j}from"../../core/text-segmentation/index.js";import{extractAcpTurnInput as L}from"../../core/protocol/payload-parser.js";import{injectMessageMetadata as U}from"../../core/protocol/message-metadata.js";import{log as r}from"../../core/log/index.js";import{scanSkills as y}from"../claude/skill-scanner.js";import{checkKiroSessionActivity as B}from"./session-activity.js";import{resolveCliPath as O,getCliVersion as F}from"../../core/util/cli-probe.js";const S=f.dirname(w(import.meta.url)),K=[{modelId:"deepseek-v4-pro",name:"DeepSeek V4 Pro"},{modelId:"deepseek-v4-flash",name:"DeepSeek V4 Flash"}],H=200,W=60*1e3,z=600*1e3,R=80,T=1200*1e3,G=["[grix protocol] Markers like [[message_id:<id>]] and [[quoted_message_id:<id>]] are metadata, not user prose \u2014 never repeat them verbatim except as instructed here.","The message you are replying to carries its id in a [[message_id:<id>]] marker.","If (and only if) you want your reply to quote/thread onto that specific message, include [[quoted_message_id:<id>]] anywhere in your reply using that id; otherwise omit it."].join(" ");function V(p){return!(!(p instanceof Error)||p.code!==-32603||x(p))}function g(p){if(!(p instanceof Error))return String(p);let e=p.message;const i=p.data;return i&&(e+=` data=${JSON.stringify(i)}`),e}function Q(p){return p.replace(/\u001b\[[0-9;?]*[ -/]*[@-~]/g,"")}class Ie extends A{type="acp";config;callbacks;agentProcess=null;acpClient=null;internalApi=null;activeRun=null;compacting=!1;compactingTimer=null;compactionDoneResolver=null;pendingAutoCompact=!1;eventResults=null;pendingApprovals=new Map;clientMsgSeq=0;stopped=!1;acpAuthMethod;acpInitialMode;acpInitialModel;acpMcpTools;terminalAuthChild=null;rawTransport;approvalMode;autoInjectArgs;nativeProviderScope;bindingStore;sessionBindings=new Map;deferredEvents=new Map;currentAibotSessionId;bridgeLog;sessionConnected=!1;rawEventSeq=0;recoveryContextBySessionId=new Map;quoteProtocolTaughtSessions=new Set;identityInjectedSessions=new Set;constructor(e,i,t){if(super(),this.config=e,this.callbacks=i,this.bridgeLog=t?.bridgeLog??null,this.acpAuthMethod=t?.acpAuthMethod,this.acpInitialMode=t?.acpInitialMode,this.acpInitialModel=t?.acpInitialModel,this.acpMcpTools=t?.acpMcpTools??!0,this.rawTransport=t?.rawTransport??!1,this.approvalMode=t?.approvalMode??"default",this.autoInjectArgs=t?.autoInjectArgs,this.nativeProviderScope=t?.nativeProviderScope,this.bindingStore=t?.bindingStore??null,this.currentAibotSessionId=t?.aibotSessionId?String(t.aibotSessionId).trim():void 0,t?.eventResultsPath&&(this.eventResults=new N(t.eventResultsPath)),this.bindingStore&&this.currentAibotSessionId){const s=this.bindingStore.get(this.currentAibotSessionId);s?.cwd&&this.sessionBindings.set(this.currentAibotSessionId,s.cwd)}}async start(){if(await this.spawnProcess(),!this.bindingStore){await this.connectSession(this.resolveCwd());return}const e=this.currentAibotSessionId?this.sessionBindings.get(this.currentAibotSessionId):void 0;e&&await this.connectSession(e)}async stop(){this.stopped=!0,this.rejectDeferredEvents("adapter stopped"),this.cancelAllDeferredTimers(),this.pendingAutoCompact=!1,this.compacting=!1,this.compactingTimer&&(clearTimeout(this.compactingTimer),this.compactingTimer=null),this.compactionDoneResolver&&(this.compactionDoneResolver(),this.compactionDoneResolver=null),this.activeRun&&(this.activeRun.flushTimer&&(clearTimeout(this.activeRun.flushTimer),this.activeRun.flushTimer=null),this.activeRun.idleTimer&&(clearTimeout(this.activeRun.idleTimer),this.activeRun.idleTimer=null),this.activeRun=null),this.acpClient&&(this.acpClient.clearSettleTimer(),this.acpClient.removeAllListeners(),this.acpClient=null),this.agentProcess&&(await this.agentProcess.close(),this.agentProcess=null),this.internalApi&&(await this.internalApi.stop(),this.internalApi=null),this.terminalAuthChild&&(this.terminalAuthChild.kill("SIGKILL"),this.terminalAuthChild=null)}isAlive(){return this.agentProcess?.alive??!1}async createSession(e){return this.acpClient?.sessionId??""}async resumeSession(e,i){}async destroySession(e){this.quoteProtocolTaughtSessions.delete(e),this.identityInjectedSessions.delete(e)}onAgentProfileChanged(){this.identityInjectedSessions.size>0&&(r.info("acp-adapter",`agent profile changed; clearing ${this.identityInjectedSessions.size} identity-injected session(s)`),this.identityInjectedSessions.clear())}sendPrompt(e){const i=new X(e.adapterSessionId);return this.acpClient?.isAlive&&this.acpClient.send(e.text).catch(t=>{i.emitError(t instanceof Error?t:new Error(String(t)))}),i}async cancel(e){this.activeRun&&this.acpClient&&(await this.acpClient.cancel(),this.flushStream(),this.finishRun("canceled","stopped by user"))}setPermissionHandler(e){}async ping(e){return this.acpClient?.isAlive?this.acpClient.ping(e):this.agentProcess?.alive??!1}getStatus(){return{alive:this.agentProcess?.alive??!1,busy:this.activeRun!==null||this.compacting,sessions:this.sessionConnected?1:0}}getActiveEventIds(){return this.activeRun?[this.activeRun.eventId]:[]}clearActiveEventForShutdown(){this.activeRun&&(this.activeRun.flushTimer&&(clearTimeout(this.activeRun.flushTimer),this.activeRun.flushTimer=null),this.activeRun.idleTimer&&(clearTimeout(this.activeRun.idleTimer),this.activeRun.idleTimer=null),this.activeRun=null)}getMcpConfig(){if(!this.internalApi)return null;this.internalApi.setMcpBridgeUpHandler(i=>this.onMcpBridgeUp(i));const e=f.resolve(S,"../../mcp/mcp-bridge-server.js");return{name:"grix-app-bridge",command:process.execPath,args:[e,"--ws-url",this.internalApi.mcpBridgeWsUrl]}}async hasBackgroundWork(){const e=this.agentProcess?.pid;return e?_(e,[e]):!1}async probe(e){const i=this.config.command||"gemini",t=await O(i),s=t!==null;let n=null,o;if(s){const d=await F(i);n=d.version,d.error&&(o=d.error)}else o={code:"cli_not_found",message:`command not found: ${i}`};const a=this.acpClient?.model??null,c=this.getStatus();let l;return e?.conversation?this.acpClient?.isAlive?l={attempted:!0,ok:!0,latency_ms:null}:l={attempted:!0,ok:!1,latency_ms:null,error:{code:"unsupported",message:"ACP session not connected, cannot probe conversation"}}:l={attempted:!1,ok:!1,latency_ms:null},{cli:{command:i,installed:s,path:t,version:n,...o?{error:o}:{}},conversation:l,config:{model:a,base_url:null,source:{model:a?"runtime":"unknown",base_url:"unknown"}},process:{started:!!this.agentProcess,alive:c.alive,busy:c.busy}}}getSupportedCommands(){return this.acpClient?.availableCommands?.length?this.acpClient.availableCommands.map(e=>({name:e.name,description:e.description??"",args:e.args})):[{name:"model",description:"List or set model",args:"[model_id]"},{name:"mode",description:"List or set collaboration mode",args:"[mode_id]"},{name:"interrupt",description:"Interrupt current run"},{name:"compact",description:"\u538B\u7F29\u4E0A\u4E0B\u6587"},{name:"status",description:"Show session status"},{name:"skills",description:"List available skills"}]}async execCommand(e,i,t){return this.acpClient?.supportsCommandsExecute?this.execCommandViaAcp(e,i):this.execCommandFallback(e,i)}async execCommandViaAcp(e,i){if(!this.acpClient)return{status:"failed",message:"No active ACP session"};if(e==="interrupt")return this.activeRun?(await this.cancel(this.activeRun.sessionId),{status:"ok",message:"Run interrupted"}):{status:"failed",message:"No active run to interrupt"};if(e==="compact"){if(this.compacting)return{status:"failed",message:"Compaction already in progress"};if(this.activeRun)return{status:"failed",message:"agent busy"};this.compacting=!0;try{const t=await this.acpClient.executeCommand(e,i||void 0);return this.finishCompaction("acp-commands-execute"),{status:t.status==="failed"?"failed":"ok",message:t.message??"Compacted",data:t.data}}catch(t){return this.finishCompaction("error"),t?.code===-32601?this.execCommandFallback(e,i):{status:"failed",message:`compact failed: ${t instanceof Error?t.message:t}`}}}try{const t=await this.acpClient.executeCommand(e,i||void 0);return{status:t.status??"ok",message:t.message,data:t.options?{command:e,options:t.options}:t.data}}catch(t){return t?.code===-32601?this.execCommandFallback(e,i):{status:"failed",message:`Command failed: ${t instanceof Error?t.message:t}`}}}async execCommandFallback(e,i){try{switch(e){case"model":{const t=i.trim();if(t)return await this.setModel(t)?{status:"ok",message:`Model set to ${t}`}:{status:"failed",message:`Failed to set model: ${t}`};const s=this.buildToolbarContext("model_list");return{status:"ok",message:`Current: ${s.currentModelId||"unknown"}`,data:s}}case"mode":{const t=i.trim();if(t)return await this.setMode(t)?{status:"ok",message:`Mode set to ${t}`}:{status:"failed",message:`Failed to set mode: ${t}`};const s=this.buildToolbarContext("mode_list");return{status:"ok",message:`Current: ${s.currentModeId||"unknown"}`,data:s}}case"interrupt":return this.activeRun?this.acpClient?(await this.cancel(this.activeRun.sessionId),{status:"ok",message:"Run interrupted"}):{status:"failed",message:"Not connected"}:{status:"failed",message:"No active run to interrupt"};case"compact":{if(!this.acpClient)return{status:"failed",message:"No active ACP session"};if(this.compacting)return{status:"failed",message:"Compaction already in progress"};if(this.activeRun)return{status:"failed",message:"agent busy"};this.compacting=!0;const t=new Promise(s=>{this.compactionDoneResolver=s});try{await this.acpClient.send("/compact")}catch(s){throw this.finishCompaction("send-failed"),s}return this.compactingTimer=setTimeout(()=>this.finishCompaction("timeout"),15e3),this.compactingTimer.unref?.(),await t,{status:"ok",message:"Compacted"}}case"status":{const t=this.getStatus(),s=this.acpClient?.sessionOptions;return{status:"ok",message:`Alive: ${t.alive}, Busy: ${t.busy}, Model: ${s?.currentModelId??"unknown"}, Mode: ${s?.currentModeId??"unknown"}`,data:{alive:t.alive,busy:t.busy,sessions:t.sessions,model:s?.currentModelId??"",mode:s?.currentModeId??""}}}case"skills":{const t=y({mode:this.config.command==="kiro-cli"?"kiro":"gemini",projectDir:process.cwd()}),s=t.map(n=>`- ${n.name}${n.trigger?` (${n.trigger})`:""} [${n.source}]: ${n.description}`);return{status:"ok",message:s.length>0?s.join(`
2
+ `):"No skills found",data:t}}default:return{status:"unsupported",message:`Unknown command: ${e}`}}}catch(t){return{status:"failed",message:t instanceof Error?t.message:String(t)}}}get acpSessionOptions(){return this.acpClient?.sessionOptions??null}get isReasonix(){return(this.config.command??"").includes("reasonix")}buildToolbarContext(e,i){const t=this.acpClient?.sessionOptions,s=t?.currentModeId??"";let n=t?.currentModelId??"";const o=t?.modes.map(h=>({id:h.id,name:h.name}))??[],a=t?.models&&t.models.length>0?t.models:this.isReasonix?K:[],c=a.map(h=>({modelId:h.modelId,name:h.name})),l=a.map(h=>({id:h.modelId,displayName:h.name})),d=t?.modes.map(h=>({id:h.id,displayName:h.name}))??[];l.length>0&&!l.some(h=>h.id===n)&&(n=l[0].id);const u={outcome:e,...i?{cwd:i}:{},model_id:n,mode_id:s,currentModelId:n,currentModeId:s,available_models:l,available_modes:d,availableModels:l,availableModes:d,models:c,modes:o};return r.info("acp-adapter",`[toolbar] buildToolbarContext outcome=${e} model_id="${n}" available_models=${JSON.stringify(l.map(h=>h.id))} currentModelId=${n}`),u}get pendingApprovalEntries(){return this.pendingApprovals}get hasSessionBinding(){return this.bindingStore!==null}setMode(e){return this.acpClient?this.acpClient.setLiveMode(e):Promise.resolve(!1)}setModel(e){return this.acpClient?this.acpClient.setModel(e):Promise.resolve(!1)}handleAcpApprovalAction(e,i){const t=this.pendingApprovals.get(e);return t?(this.pendingApprovals.delete(e),this.acpClient&&this.acpClient.respondPermission(t,{behavior:i}).catch(s=>{r.error("acp-adapter",`Failed to respond to permission: ${s}`)}),this.activeRun&&this.resetIdleTimer(this.activeRun),!0):!1}respondToPermission(e,i){this.acpClient&&this.acpClient.respondPermission(e,i).catch(t=>{r.error("acp-adapter",`Failed to respond to permission: ${t}`)}),this.activeRun&&this.resetIdleTimer(this.activeRun)}async handleLocalAction(e){const i=e.action_type??"",t=e.params??{};if(i==="exec_approve"||i==="exec_reject"||i==="permission_approve"||i==="permission_reject"){const s=String(t.tool_call_id??t.approval_command_id??t.approval_id??t.exec_context_id??""),n=i==="exec_approve"||i==="permission_approve",o=String(t.decision??"");let a;return n&&(o==="allow-once"||o==="allow-always")?a=o:n?a="allow":a="deny",s?this.handleAcpApprovalAction(s,a)?(this.callbacks.sendLocalActionResult(e.action_id,"ok"),{handled:!0,kind:"approval"}):(this.callbacks.sendLocalActionResult(e.action_id,"failed",void 0,"approval_not_found",`no pending approval for tool_call_id: ${s}`),{handled:!0,kind:"approval"}):(this.callbacks.sendLocalActionResult(e.action_id,"failed",void 0,"tool_call_id_required","tool_call_id is required"),{handled:!0,kind:"approval"})}return{handled:!1,kind:""}}resolveCwd(){if(this.currentAibotSessionId){const e=this.sessionBindings.get(this.currentAibotSessionId);if(e)return e}if(this.bindingStore&&this.currentAibotSessionId){const e=this.bindingStore.get(this.currentAibotSessionId);if(e?.cwd)return e.cwd}return process.cwd()}async bindSession(e,i){const t=this.sessionBindings.get(e);if(t)try{return await v.stat(t),!1}catch{r.info("acp-adapter",`Stale binding for session ${e}: ${t} no longer exists, allowing rebind to ${i}`),this.sessionBindings.delete(e)}return this.sessionBindings.set(e,i),this.bindingStore&&this.bindingStore.set(e,i),this.maybeRefreshKiroSkills(i),!this.sessionConnected&&this.agentProcess?.alive?this.connectSession(i).then(()=>!0).catch(s=>(r.error("acp-adapter",`Failed to create session on bind: ${g(s)}`),this.callbacks.sendUpdateBindingCard(e,"failed",i),!0)):(this.acpClient?.isAlive&&(this.bindingStore&&this.acpClient.sessionId&&this.bindingStore.setAcpSessionId(e,this.acpClient.sessionId),this.callbacks.sendUpdateBindingCard(e,"connected",i,this.buildToolbarContext("binding_ready",i))),Promise.resolve(!0))}getSessionCwd(e){return this.sessionBindings.get(e)}getSessionBindings(){return this.sessionBindings}maybeRefreshKiroSkills(e){if(this.config.command!=="kiro-cli"||!this.callbacks.onSkillsUpdate)return;const i=String(e??"").trim()||void 0;let t=[];try{t=y({mode:"kiro",projectDir:i})}catch(s){r.warn("acp-adapter",`Kiro skills scan failed: ${s instanceof Error?s.message:String(s)}`);return}try{this.callbacks.onSkillsUpdate(t)}catch(s){r.warn("acp-adapter",`onSkillsUpdate failed: ${s instanceof Error?s.message:String(s)}`)}}replayDeferredEvents(e){const i=this.deferredEvents.get(e);if(!i||i.length===0)return;if(this.activeRun){r.info("acp-adapter",`Cannot replay deferred events for session ${e}: agent busy`);return}const t=i.shift();t&&(i.length===0?(this.deferredEvents.delete(e),this.cancelDeferredTimer(e)):this.deferredEvents.set(e,i),r.info("acp-adapter",`Replaying deferred event ${t.event.event_id} for session ${e} (remaining: ${i.length})`),this.startRun(t.event,!1))}finishCompaction(e){if(!this.compacting)return;this.compacting=!1,this.compactingTimer&&(clearTimeout(this.compactingTimer),this.compactingTimer=null),r.info("acp-adapter",`Compaction finished (${e}); replaying deferred events`),this.replayNextDeferredEvent();const i=this.compactionDoneResolver;this.compactionDoneResolver=null,i?.()}replayNextDeferredEvent(){if(this.activeRun||this.compacting)return;const e=this.deferredEvents.keys().next().value;e&&this.replayDeferredEvents(e)}announceDeferredComposing(e){}deliverInboundEvent(e){if(this.eventResults?.has(e.session_id,e.event_id)){const i=this.eventResults.get(e.session_id,e.event_id);r.info("acp-adapter",`Deduplicating event ${e.event_id} (cached: ${i.status})`),this.callbacks.sendEventResult(e.event_id,i.status,i.msg);return}if(this.compacting){r.info("acp-adapter",`Event ${e.event_id} deferred: compaction in progress`),this.deferEvent(e);return}if(this.activeRun){r.info("acp-adapter",`Event ${e.event_id} rejected: busy`),this.callbacks.sendEventResult(e.event_id,"failed","agent busy");return}if(!this.agentProcess?.alive){this.callbacks.sendEventResult(e.event_id,"failed","agent not alive");return}e.session_id&&e.session_id!==this.currentAibotSessionId&&(this.currentAibotSessionId=e.session_id),this.startRun(e,!1)}deliverStopEvent(e,i){this.acpClient?(this.acpClient.cancel().catch(()=>{}),this.flushStream()):this.flushStream(),this.activeRun?.eventId===e&&this.finishRun("canceled","stopped by user")}deferEvent(e){const i=this.deferredEvents.get(e.session_id)??[];i.some(t=>t.event.event_id===e.event_id)||(i.push({event:e,queuedAt:Date.now()}),this.deferredEvents.set(e.session_id,i),r.info("acp-adapter",`Deferred event ${e.event_id} for session ${e.session_id} (queue: ${i.length})`),this.scheduleDeferredTimeout(e.session_id))}deferredTimers=new Map;rejectDeferredEvents(e){for(const[i,t]of this.deferredEvents)for(const{event:s}of t)r.info("acp-adapter",`Rejecting deferred event ${s.event_id}: ${e}`),this.callbacks.sendEventResult(s.event_id,"failed",e);this.deferredEvents.clear(),this.cancelAllDeferredTimers()}cancelDeferredTimer(e){const i=this.deferredTimers.get(e);i&&(clearTimeout(i),this.deferredTimers.delete(e))}cancelAllDeferredTimers(){for(const e of this.deferredTimers.values())clearTimeout(e);this.deferredTimers.clear()}scheduleDeferredTimeout(e){if(this.deferredTimers.has(e))return;const i=3e4,t=setTimeout(()=>{this.deferredTimers.delete(e);const s=this.deferredEvents.get(e);if(!(!s||s.length===0)&&!this.acpClient?.isAlive){r.error("acp-adapter",`Deferred events for session ${e} timed out after ${i/1e3}s (no ACP client)`),this.deferredEvents.delete(e);for(const{event:n}of s)this.callbacks.sendEventResult(n.event_id,"failed","agent initialization timed out")}},i);this.deferredTimers.set(e,t)}startRun(e,i){if(!this.acpClient?.isAlive){this.deferEvent(e);return}const t=`acp_${++this.clientMsgSeq}_${Date.now()}`;this.activeRun={eventId:e.event_id,sessionId:e.session_id,threadId:e.thread_id,clientMsgIdBase:t,currentClientMsgId:t,currentSegmentIndex:0,chunkSeq:0,buffer:"",quotedStream:new D,markdownSegmenter:new j,quotedMessageId:void 0,flushTimer:null,idleTimer:null,awaitingToolResult:!1,responded:!1,silent:i,lastProgressAt:Date.now(),lastIdleCheckAt:Date.now(),idleNoProgressCount:0};const s=this.activeRun,n=L(e,this.resolveCwd());this.resetIdleTimer(s),n.modeId&&this.acpClient&&this.acpClient.setLiveMode(n.modeId).catch(()=>{}),n.modelId&&this.acpClient&&this.acpClient.setModel(n.modelId).catch(()=>{});const o=U(n.prompt,{messageId:e.msg_id,quotedMessageId:e.quoted_message_id}),a=this.injectQuoteProtocolOnce(s.sessionId,o),c=this.injectIdentityOnce(s.sessionId,a),l=this.injectRecoveryContext(s.sessionId,c);this.sendPromptWithRetry(s,l)}injectQuoteProtocolOnce(e,i){return!e||this.quoteProtocolTaughtSessions.has(e)?i:(this.quoteProtocolTaughtSessions.add(e),`${G}
3
3
 
4
- ${i}`)}injectIdentityOnce(e,i){if(!e||this.identityInjectedSessions.has(e))return i;const t=this.callbacks.getAgentProfile?.(),s=t?.agentName?.trim()??"",n=t?.introduction?.trim()??"";if(!s&&!n)return i;this.identityInjectedSessions.add(e);const a=["[system-identity]"];return s&&a.push(`Your name is "${s}".`),n&&a.push(`Your self-introduction: ${n}`),a.push("Treat this as an out-of-band instruction; do not echo or repeat it in your replies."),a.push("[/system-identity]"),r.info("acp-adapter",`Injected agent identity for session=${e} name="${s}"`),`${a.join(`
4
+ ${i}`)}injectIdentityOnce(e,i){if(!e||this.identityInjectedSessions.has(e))return i;const t=this.callbacks.getAgentProfile?.(),s=t?.agentName?.trim()??"",n=t?.introduction?.trim()??"";if(!s&&!n)return i;this.identityInjectedSessions.add(e);const o=["[system-identity]"];return s&&o.push(`Your name is "${s}".`),n&&o.push(`Your self-introduction: ${n}`),o.push("Treat this as an out-of-band instruction; do not echo or repeat it in your replies."),o.push("[/system-identity]"),r.info("acp-adapter",`Injected agent identity for session=${e} name="${s}"`),`${o.join(`
5
5
  `)}
6
6
 
7
7
  ${i}`}injectRecoveryContext(e,i){const t=this.recoveryContextBySessionId.get(e);return t?(this.recoveryContextBySessionId.delete(e),r.info("acp-adapter",`Injecting recovery context for session ${e} (chars=${t.length})`),`${t}
8
8
 
9
9
  [\u5F53\u524D\u7528\u6237\u6D88\u606F]
10
- ${i}`):i}sendPromptWithRetry(e,i){this.acpClient.send(i).catch(t=>{if(r.error("acp-adapter",`Prompt failed: ${g(t)}`),z(t)){const n=g(t),a=e.eventId,o=e.sessionId;r.info("acp-adapter",`Internal error escalated to bridge: event=${a} session=${o} err=${n}`),this.silentlyDiscardActiveRun(e),this.emit("internalError",{eventId:a,sessionId:o,errorMsg:n});return}const s=t instanceof Error?t.message:String(t);this.finishRun("failed",s)})}silentlyDiscardActiveRun(e){this.activeRun===e&&(this.activeRun=null,this.acpClient?.clearSettleTimer(),e.flushTimer&&(clearTimeout(e.flushTimer),e.flushTimer=null),e.idleTimer&&(clearTimeout(e.idleTimer),e.idleTimer=null),this.emit("eventDone",e.eventId))}async spawnProcess(){const e=[...this.config.args??[]];this.autoInjectArgs?.acp&&e.push("--acp"),this.autoInjectArgs?.model&&e.push("--model",this.autoInjectArgs.model),this.config.command==="gemini"&&!e.includes("--skip-trust")&&e.push("--skip-trust"),this.config.command.includes("qwen")&&!e.includes("--experimental-skills")&&e.push("--experimental-skills"),this.config.command==="kiro-cli"&&!e.includes("--trust-all-tools")&&e.push("--trust-all-tools"),(this.config.command==="copilot"||this.config.command.endsWith("/copilot")||this.config.command==="gh"&&e.includes("copilot"))&&(e.includes("--allow-all-tools")||e.push("--allow-all-tools"),e.includes("--allow-all-paths")||e.push("--allow-all-paths")),this.agentProcess=new T(this.bridgeLog);const t=this.resolveCwd();try{if(!(await v.stat(t)).isDirectory())throw new Error(`Bound path is not a directory: ${t}`)}catch(s){throw String(s?.code??"")==="ENOENT"?new Error(`Bound directory does not exist: ${t}. Please rebind with /grix open <valid-directory>.`):s}await k(this.config.command,t,this.config.env,this.nativeProviderScope).catch(s=>{r.warn("acp-adapter",`native provider config skipped: ${s instanceof Error?s.message:String(s)}`)});try{await this.agentProcess.start({command:this.config.command,args:e.length>0?e:void 0,cwd:t,env:this.config.env})}catch(s){throw r.error("acp-adapter",`Failed to spawn agent process: ${s}`),this.rejectDeferredEvents(`agent spawn failed: ${s instanceof Error?s.message:String(s)}`),this.emit("exit",null),s}r.info("acp-adapter","ACP agent process started"),this.agentProcess.on("error",s=>{this.stopped||(r.error("acp-adapter",`Agent process error: ${s.message}`),this.activeRun&&this.finishRun("failed",`agent process error: ${s.message}`),this.rejectDeferredEvents(`agent process error: ${s.message}`),this.emit("exit",null))}),this.agentProcess.on("exit",s=>{this.stopped||(r.error("acp-adapter",`Agent process exited unexpectedly (code=${s})`),this.activeRun&&this.finishRun("failed",`agent process exited (code=${s})`),this.rejectDeferredEvents(`agent process exited (code=${s})`),this.emit("exit",s))}),this.agentProcess.on("stderr",()=>{this.activeRun&&this.resetIdleTimer(this.activeRun)})}async connectSession(e){if(this.sessionConnected||!this.agentProcess?.transport)return;let i;this.acpMcpTools&&(i=await this.startInternalApiAndMcp()),this.acpClient=new $,this.acpClient.on("event",o=>this.handleAcpEvent(o)),this.acpClient.on("activity",()=>{this.activeRun&&this.resetIdleTimer(this.activeRun)}),this.acpClient.on("session-lost",()=>{this.stopped||this.agentProcess?.alive&&(r.error("acp-adapter","ACP transport closed while agent process still alive, triggering respawn"),this.activeRun&&(this.callbacks.sendRunError(this.activeRun.eventId,this.activeRun.sessionId,"ACP transport closed"),this.finishRun("failed","ACP transport closed")),this.emit("exit",null))});const t=this.currentAibotSessionId&&this.bindingStore?this.bindingStore.getAcpSessionId(this.currentAibotSessionId):void 0,s=async o=>{await this.acpClient.connect({transport:this.agentProcess.transport,authMethod:this.acpAuthMethod,initialMode:this.acpInitialMode,initialModel:this.acpInitialModel,cwd:e||this.resolveCwd(),mcpServers:i,sessionId:o})};let n=!1,a;try{await s(t)}catch(o){if(o instanceof P){await this.handleAuthRequired(o);return}if(!t)throw this.handleConnectFailure(o),o;if(r.warn("acp-adapter",`Failed to load persisted session ${t}, fallback to session/new: ${g(o)}`),n=!0,a=t,this.config.command==="kiro-cli"&&this.currentAibotSessionId){r.info("acp-adapter",`Building kiro recovery summary: aibotSession=${this.currentAibotSessionId} acpSession=${t}`);const c=await this.buildKiroRecoveryContext(this.currentAibotSessionId,t,e||this.resolveCwd());c?(this.recoveryContextBySessionId.set(this.currentAibotSessionId,c),r.info("acp-adapter",`Recovery context prepared for session ${this.currentAibotSessionId} (chars=${c.length})`)):r.warn("acp-adapter",`Recovery context skipped: no summary generated for acpSession=${t}`)}await s(void 0)}this.sessionConnected=!0,r.info("acp-adapter",`ACP session ready: ${this.acpClient.sessionId}`),this.emit("acpSessionReady",this.acpClient.sessionId),this.currentAibotSessionId&&this.bindingStore&&this.bindingStore.setAcpSessionId(this.currentAibotSessionId,this.acpClient.sessionId);for(const[o,c]of this.sessionBindings){this.bindingStore&&this.acpClient.sessionId&&this.bindingStore.setAcpSessionId(o,this.acpClient.sessionId);const d=n?"binding_recreated":"binding_ready",l=this.buildToolbarContext(d,c);n&&a&&(l.previous_session_id=a,l.recreated_session_id=this.acpClient.sessionId),this.callbacks.sendUpdateBindingCard(o,"ready",c,l)}}async buildKiroRecoveryContext(e,i,t){try{const s=f.join(C(),".kiro","sessions","cli",`${i}.json`);await v.access(s),r.info("acp-adapter",`Reading kiro source session file: ${s}`);const n=await this.generateSummaryByKiroCli(s,t);if(!n)return;const a=f.join(C(),".grix","data","session-summaries");await v.mkdir(a,{recursive:!0});const o=f.join(a,`${e}.md`),c=[`SOURCE_KIRO_SESSION_FILE: ${s}`,"",n.trim(),""].join(`
11
- `);return await v.writeFile(o,c,"utf8"),r.info("acp-adapter",`Recovery summary saved: ${o} (chars=${n.length})`),["[\u5386\u53F2\u4E0A\u4E0B\u6587\u56DE\u704C\u8BF4\u660E]","\u4EE5\u4E0B\u5185\u5BB9\u6765\u81EA\u65E7\u4F1A\u8BDD\u81EA\u52A8\u6458\u8981\uFF0C\u8BF7\u5728\u540E\u7EED\u56DE\u7B54\u4E2D\u5EF6\u7EED\u5176\u4E2D\u7684\u76EE\u6807\u3001\u7EA6\u675F\u548C\u7ED3\u8BBA\u3002",`SOURCE_KIRO_SESSION_FILE: ${s}`,`SUMMARY_FILE: ${o}`,"",n.trim()].join(`
10
+ ${i}`):i}sendPromptWithRetry(e,i){this.acpClient.send(i).catch(t=>{if(r.error("acp-adapter",`Prompt failed: ${g(t)}`),V(t)){const n=g(t),o=e.eventId,a=e.sessionId;r.info("acp-adapter",`Internal error escalated to bridge: event=${o} session=${a} err=${n}`),this.silentlyDiscardActiveRun(e),this.emit("internalError",{eventId:o,sessionId:a,errorMsg:n});return}const s=t instanceof Error?t.message:String(t);this.finishRun("failed",s)})}silentlyDiscardActiveRun(e){this.activeRun===e&&(this.activeRun=null,this.acpClient?.clearSettleTimer(),e.flushTimer&&(clearTimeout(e.flushTimer),e.flushTimer=null),e.idleTimer&&(clearTimeout(e.idleTimer),e.idleTimer=null),this.emit("eventDone",e.eventId))}async spawnProcess(){const e=[...this.config.args??[]];this.autoInjectArgs?.acp&&e.push("--acp"),this.autoInjectArgs?.model&&e.push("--model",this.autoInjectArgs.model),this.config.command==="gemini"&&!e.includes("--skip-trust")&&e.push("--skip-trust"),this.config.command.includes("qwen")&&!e.includes("--experimental-skills")&&e.push("--experimental-skills"),this.config.command==="kiro-cli"&&!e.includes("--trust-all-tools")&&e.push("--trust-all-tools"),(this.config.command==="copilot"||this.config.command.endsWith("/copilot")||this.config.command==="gh"&&e.includes("copilot"))&&(e.includes("--allow-all-tools")||e.push("--allow-all-tools"),e.includes("--allow-all-paths")||e.push("--allow-all-paths")),this.agentProcess=new k(this.bridgeLog);const t=this.resolveCwd();try{if(!(await v.stat(t)).isDirectory())throw new Error(`Bound path is not a directory: ${t}`)}catch(s){throw String(s?.code??"")==="ENOENT"?new Error(`Bound directory does not exist: ${t}. Please rebind with /grix open <valid-directory>.`):s}await $(this.config.command,t,this.config.env,this.nativeProviderScope).catch(s=>{r.warn("acp-adapter",`native provider config skipped: ${s instanceof Error?s.message:String(s)}`)});try{await this.agentProcess.start({command:this.config.command,args:e.length>0?e:void 0,cwd:t,env:this.config.env})}catch(s){throw r.error("acp-adapter",`Failed to spawn agent process: ${s}`),this.rejectDeferredEvents(`agent spawn failed: ${s instanceof Error?s.message:String(s)}`),this.emit("exit",null),s}r.info("acp-adapter","ACP agent process started"),this.agentProcess.on("error",s=>{this.stopped||(r.error("acp-adapter",`Agent process error: ${s.message}`),this.activeRun&&this.finishRun("failed",`agent process error: ${s.message}`),this.rejectDeferredEvents(`agent process error: ${s.message}`),this.emit("exit",null))}),this.agentProcess.on("exit",s=>{this.stopped||(r.error("acp-adapter",`Agent process exited unexpectedly (code=${s})`),this.activeRun&&this.finishRun("failed",`agent process exited (code=${s})`),this.rejectDeferredEvents(`agent process exited (code=${s})`),this.emit("exit",s))}),this.agentProcess.on("stderr",()=>{this.activeRun&&this.resetIdleTimer(this.activeRun)})}async connectSession(e){if(this.sessionConnected||!this.agentProcess?.transport)return;let i;this.acpMcpTools&&(i=await this.startInternalApiAndMcp()),this.acpClient=new E,this.acpClient.on("event",a=>this.handleAcpEvent(a)),this.acpClient.on("activity",()=>{this.activeRun&&this.resetIdleTimer(this.activeRun)}),this.acpClient.on("session-lost",()=>{this.stopped||this.agentProcess?.alive&&(r.error("acp-adapter","ACP transport closed while agent process still alive, triggering respawn"),this.activeRun&&(this.callbacks.sendRunError(this.activeRun.eventId,this.activeRun.sessionId,"ACP transport closed"),this.finishRun("failed","ACP transport closed")),this.emit("exit",null))});const t=this.currentAibotSessionId&&this.bindingStore?this.bindingStore.getAcpSessionId(this.currentAibotSessionId):void 0,s=async a=>{await this.acpClient.connect({transport:this.agentProcess.transport,authMethod:this.acpAuthMethod,initialMode:this.acpInitialMode,initialModel:this.acpInitialModel,cwd:e||this.resolveCwd(),mcpServers:i,sessionId:a})};let n=!1,o;try{await s(t)}catch(a){if(a instanceof M){await this.handleAuthRequired(a);return}if(!t)throw this.handleConnectFailure(a),a;if(r.warn("acp-adapter",`Failed to load persisted session ${t}, fallback to session/new: ${g(a)}`),n=!0,o=t,this.config.command==="kiro-cli"&&this.currentAibotSessionId){r.info("acp-adapter",`Building kiro recovery summary: aibotSession=${this.currentAibotSessionId} acpSession=${t}`);const c=await this.buildKiroRecoveryContext(this.currentAibotSessionId,t,e||this.resolveCwd());c?(this.recoveryContextBySessionId.set(this.currentAibotSessionId,c),r.info("acp-adapter",`Recovery context prepared for session ${this.currentAibotSessionId} (chars=${c.length})`)):r.warn("acp-adapter",`Recovery context skipped: no summary generated for acpSession=${t}`)}await s(void 0)}this.sessionConnected=!0,r.info("acp-adapter",`ACP session ready: ${this.acpClient.sessionId}`),this.emit("acpSessionReady",this.acpClient.sessionId),this.currentAibotSessionId&&this.bindingStore&&this.bindingStore.setAcpSessionId(this.currentAibotSessionId,this.acpClient.sessionId);for(const[a,c]of this.sessionBindings){this.bindingStore&&this.acpClient.sessionId&&this.bindingStore.setAcpSessionId(a,this.acpClient.sessionId);const l=n?"binding_recreated":"binding_ready",d=this.buildToolbarContext(l,c);n&&o&&(d.previous_session_id=o,d.recreated_session_id=this.acpClient.sessionId),this.callbacks.sendUpdateBindingCard(a,"ready",c,d)}}async buildKiroRecoveryContext(e,i,t){try{const s=f.join(I(),".kiro","sessions","cli",`${i}.json`);await v.access(s),r.info("acp-adapter",`Reading kiro source session file: ${s}`);const n=await this.generateSummaryByKiroCli(s,t);if(!n)return;const o=f.join(I(),".grix","data","session-summaries");await v.mkdir(o,{recursive:!0});const a=f.join(o,`${e}.md`),c=[`SOURCE_KIRO_SESSION_FILE: ${s}`,"",n.trim(),""].join(`
11
+ `);return await v.writeFile(a,c,"utf8"),r.info("acp-adapter",`Recovery summary saved: ${a} (chars=${n.length})`),["[\u5386\u53F2\u4E0A\u4E0B\u6587\u56DE\u704C\u8BF4\u660E]","\u4EE5\u4E0B\u5185\u5BB9\u6765\u81EA\u65E7\u4F1A\u8BDD\u81EA\u52A8\u6458\u8981\uFF0C\u8BF7\u5728\u540E\u7EED\u56DE\u7B54\u4E2D\u5EF6\u7EED\u5176\u4E2D\u7684\u76EE\u6807\u3001\u7EA6\u675F\u548C\u7ED3\u8BBA\u3002",`SOURCE_KIRO_SESSION_FILE: ${s}`,`SUMMARY_FILE: ${a}`,"",n.trim()].join(`
12
12
  `)}catch(s){r.warn("acp-adapter",`Failed to build kiro recovery context: ${g(s)}`);return}}async generateSummaryByKiroCli(e,i){const s=["chat","--no-interactive","--trust-tools=read,grep",["\u8BF7\u57FA\u4E8E\u4E0B\u9762\u5F15\u7528\u7684 Kiro \u539F\u59CB\u4F1A\u8BDD\u6587\u4EF6\u751F\u6210\u6458\u8981\uFF0C\u8F93\u51FA\u5FC5\u987B\u4E3A Markdown\u3002","\u8981\u6C42\uFF1A1) \u7B80\u660E\u51C6\u786E\uFF1B2) \u5305\u542B goals/constraints/decisions/open_items \u56DB\u90E8\u5206\uFF1B3) \u4E0D\u8981\u8F93\u51FA\u4EE3\u7801\u5757\u56F4\u680F\u3002",`\u4F1A\u8BDD\u6587\u4EF6\uFF1A@"${e}"`].join(`
13
- `)],n=await this.runCommandCapture("kiro-cli",s,i,45e3);if(!n)return;const a=V(n).trim(),o=a.indexOf("> "),c=o>=0?a.slice(o+2).trim():a;if(c)return r.info("acp-adapter",`Kiro summary generated from ${e} (raw_chars=${a.length}, body_chars=${c.length})`),c.split(`
14
- `).filter(d=>!d.includes("Credits:")&&!d.includes("Time:")).join(`
15
- `).trim()}runCommandCapture(e,i,t,s){return new Promise((n,a)=>{const o=R(e,i,{cwd:t,stdio:["ignore","pipe","pipe"]});let c="",d="";const l=setTimeout(()=>{o.kill("SIGTERM"),a(new Error(`${e} timed out after ${s}ms`))},s);o.stdout.on("data",p=>{c+=String(p)}),o.stderr.on("data",p=>{d+=String(p)}),o.on("error",p=>{clearTimeout(l),a(p)}),o.on("close",p=>{if(clearTimeout(l),p===0){n(c||d);return}a(new Error(`${e} exited with code ${p}: ${d||c}`))})})}handleConnectFailure(e){r.error("acp-adapter",`ACP session creation failed: ${g(e)}`),this.acpClient?.removeAllListeners(),this.acpClient=null,this.emit("exit",null)}async startInternalApiAndMcp(){try{this.internalApi||(this.internalApi=new M,this.internalApi.setInvokeHandler(async(a,o,c,d)=>this.callbacks.agentInvoke(a,o,d)),await this.internalApi.start(0),r.info("acp-adapter",`Internal API started at ${this.internalApi.url}`)),this.internalApi.setMcpBridgeUpHandler(a=>this.onMcpBridgeUp(a));const e=this.config.command??"",i=e.includes("qwen")?".qwen":e.includes("gemini")?".gemini":e.includes("kiro")?".kiro":this.isReasonix?".reasonix":void 0;if(i){const a=f.join(C(),i,"skills"),o=_(a);o.length>0&&r.info("acp-adapter",`Synced connector skills to ${a}: [${o.join(", ")}]`)}const t=f.resolve(I,"../../mcp/mcp-bridge-server.js"),s=this.internalApi.mcpBridgeWsUrl,n=f.resolve(I,"../../mcp/acp-mcp-server.js");return[{name:"grix-app-bridge",command:process.execPath,args:[t,"--ws-url",s],...!this.isReasonix&&{env:{GRIX_CONNECTOR_MCP_BRIDGE_WS:s}}},{name:"grix-connector-tools",command:process.execPath,args:[n,"--api-url",this.internalApi.url]}]}catch(e){r.error("acp-adapter",`Failed to start MCP tools: ${e}`);return}}onMcpBridgeUp(e){this.callbacks.sendMcpFrame?.(e)}deliverMcpFrameToAgent(e){this.internalApi?.sendMcpFrameToBridge(e)}cleanup(){this.activeRun,this.pendingApprovals.clear(),this.pendingAutoCompact=!1,this.rejectDeferredEvents("adapter cleaned up"),this.cancelAllDeferredTimers(),this.sessionConnected=!1,this.acpClient&&(this.acpClient.clearSettleTimer(),this.acpClient.removeAllListeners(),this.acpClient=null),this.agentProcess&&(this.agentProcess.removeAllListeners(),this.agentProcess=null)}async handleAuthRequired(e){r.info("acp-adapter",`Auth required, methods: ${e.authMethods.map(n=>n.id).join(", ")}`);const i=e.authMethods.find(n=>/oauth|browser/i.test(n.id))??e.authMethods[0];if(!i)throw e;const t=this.currentAibotSessionId??"";this.callbacks.sendAuthNotification(t,`Authentication required (${i.id}). Initiating auth flow...`);for(const[n,a]of this.sessionBindings)this.callbacks.sendUpdateBindingCard(n,"failed",a);const s=await this.captureAuthUrl();s&&this.callbacks.sendAuthNotification(t,`Please open this URL to authenticate:
13
+ `)],n=await this.runCommandCapture("kiro-cli",s,i,45e3);if(!n)return;const o=Q(n).trim(),a=o.indexOf("> "),c=a>=0?o.slice(a+2).trim():o;if(c)return r.info("acp-adapter",`Kiro summary generated from ${e} (raw_chars=${o.length}, body_chars=${c.length})`),c.split(`
14
+ `).filter(l=>!l.includes("Credits:")&&!l.includes("Time:")).join(`
15
+ `).trim()}runCommandCapture(e,i,t,s){return new Promise((n,o)=>{const a=b(e,i,{cwd:t,stdio:["ignore","pipe","pipe"]});let c="",l="";const d=setTimeout(()=>{a.kill("SIGTERM"),o(new Error(`${e} timed out after ${s}ms`))},s);a.stdout.on("data",u=>{c+=String(u)}),a.stderr.on("data",u=>{l+=String(u)}),a.on("error",u=>{clearTimeout(d),o(u)}),a.on("close",u=>{if(clearTimeout(d),u===0){n(c||l);return}o(new Error(`${e} exited with code ${u}: ${l||c}`))})})}handleConnectFailure(e){r.error("acp-adapter",`ACP session creation failed: ${g(e)}`),this.acpClient?.removeAllListeners(),this.acpClient=null,this.emit("exit",null)}async startInternalApiAndMcp(){try{this.internalApi||(this.internalApi=new q,this.internalApi.setInvokeHandler(async(o,a,c,l)=>this.callbacks.agentInvoke(o,a,l)),await this.internalApi.start(0),r.info("acp-adapter",`Internal API started at ${this.internalApi.url}`)),this.internalApi.setMcpBridgeUpHandler(o=>this.onMcpBridgeUp(o));const e=this.config.command??"",i=e.includes("qwen")?".qwen":e.includes("gemini")?".gemini":e.includes("kiro")?".kiro":this.isReasonix?".reasonix":void 0;if(i){const o=f.join(I(),i,"skills"),a=P(o);a.length>0&&r.info("acp-adapter",`Synced connector skills to ${o}: [${a.join(", ")}]`)}const t=f.resolve(S,"../../mcp/mcp-bridge-server.js"),s=this.internalApi.mcpBridgeWsUrl,n=f.resolve(S,"../../mcp/acp-mcp-server.js");return[{name:"grix-app-bridge",command:process.execPath,args:[t,"--ws-url",s],...!this.isReasonix&&{env:{GRIX_CONNECTOR_MCP_BRIDGE_WS:s}}},{name:"grix-connector-tools",command:process.execPath,args:[n,"--api-url",this.internalApi.url],...!this.isReasonix&&{env:{}}}]}catch(e){r.error("acp-adapter",`Failed to start MCP tools: ${e}`);return}}onMcpBridgeUp(e){this.callbacks.sendMcpFrame?.(e)}deliverMcpFrameToAgent(e){this.internalApi?.sendMcpFrameToBridge(e)}cleanup(){this.activeRun,this.pendingApprovals.clear(),this.pendingAutoCompact=!1,this.rejectDeferredEvents("adapter cleaned up"),this.cancelAllDeferredTimers(),this.sessionConnected=!1,this.acpClient&&(this.acpClient.clearSettleTimer(),this.acpClient.removeAllListeners(),this.acpClient=null),this.agentProcess&&(this.agentProcess.removeAllListeners(),this.agentProcess=null),this.terminalAuthChild&&(this.terminalAuthChild.kill("SIGKILL"),this.terminalAuthChild=null)}async handleAuthRequired(e){r.info("acp-adapter",`Auth required, methods: ${e.authMethods.map(s=>s.id).join(", ")}`);const i=e.authMethods.find(s=>/oauth|browser/i.test(s.id))??e.authMethods[0];if(!i)throw e;const t=this.currentAibotSessionId??"";this.callbacks.sendAuthNotification(t,`Authentication required (${i.id}). Initiating auth flow...`);for(const[s,n]of this.sessionBindings)this.callbacks.sendUpdateBindingCard(s,"failed",n);if(i.type==="terminal")await this.runTerminalAuth(i,t);else{const s=await this.captureAuthUrl();s&&this.callbacks.sendAuthNotification(t,`Please open this URL to authenticate:
16
16
  ${s}
17
17
 
18
- Waiting for authentication to complete...`);try{await this.acpClient.authenticate(i.id),r.info("acp-adapter","Authentication successful"),this.callbacks.sendAuthNotification(t,"Authentication successful. Resuming...");const n=this.acpMcpTools?await this.startInternalApiAndMcp():void 0;await this.acpClient.connect({transport:this.agentProcess.transport,initialMode:this.acpInitialMode,initialModel:this.acpInitialModel,cwd:this.resolveCwd(),mcpServers:n}),r.info("acp-adapter",`ACP session ready after auth: ${this.acpClient.sessionId}`),this.emit("acpSessionReady",this.acpClient.sessionId);for(const[a,o]of this.sessionBindings)this.callbacks.sendUpdateBindingCard(a,"ready",o,this.buildToolbarContext("binding_ready",o))}catch(n){throw r.error("acp-adapter",`Auth retry failed: ${n}`),n}}captureAuthUrl(){return new Promise(e=>{if(!this.agentProcess){e(null);return}const i=/https?:\/\/[^\s"')\]]+/;let t=!1;const s=n=>{if(t)return;const o=n.toString().replace(/\x1b\[[0-9;]*m/g,"").match(i);o&&(t=!0,this.agentProcess.removeListener("stderr",s),e(o[0]))};this.agentProcess.on("stderr",s),setTimeout(()=>{t||(t=!0,this.agentProcess.removeListener("stderr",s),e(null))},3e4)})}handleAcpEvent(e){if(e.type===m.PermissionRequest){this.activeRun&&this.resetIdleTimer(this.activeRun),this.handlePermissionRequest(e);return}if(e.type===m.ContextWindowUpdate){e.contextWindow&&this.callbacks.onContextWindowUpdated?.(e.contextWindow),this.compacting?this.finishCompaction("context-window-update"):e.contextWindow&&this.maybeScheduleAutoCompact(e.contextWindow);return}const i=this.activeRun;if(!i)return;i.responded||(i.responded=!0);let t=!1;switch(e.type){case m.Text:{if(e.content){t=!0;const s=i.quotedStream.consume(e.content);s.quotedMessageId&&(i.quotedMessageId=s.quotedMessageId),s.deltaContent&&this.appendToStream(i,s.deltaContent)}break}case m.ToolUse:{t=!0,i.awaitingToolResult=!0,e.toolName&&(this.emitRawEventEnvelope(i,{type:"tool_use",payload:{tool_name:e.toolName,tool_input:e.toolInput??""}})||this.callbacks.sendToolUse(i.eventId,i.sessionId,e.toolName,e.toolInput??""));break}case m.ToolResult:{i.awaitingToolResult=!1,e.content&&(t=!0,this.emitRawEventEnvelope(i,{type:"tool_result",payload:{tool_name:e.toolName??"",content:e.content}})||this.callbacks.sendToolResult(i.eventId,i.sessionId,e.toolName??"",e.content));break}case m.ToolProgress:{t=!0,e.content&&(this.emitRawEventEnvelope(i,{type:"tool_progress",payload:{tool_name:e.toolName??"",content:e.content}})||this.callbacks.sendToolResult(i.eventId,i.sessionId,e.toolName??"",e.content));break}case m.Thinking:{e.content&&(t=!0,this.emitRawEventEnvelope(i,{type:"thinking",payload:{content:e.content}})||this.callbacks.sendThinking(i.eventId,i.sessionId,e.content));break}case m.Error:{const s=String(e.error??"unknown error");this.emitRawEventEnvelope(i,{type:"error",payload:{message:s}}),r.error("acp-adapter",`ACP error: ${s}`),this.callbacks.sendRunError(i.eventId,i.sessionId,s);break}case m.Result:{this.emitRawEventEnvelope(i,{type:"result",payload:{done:e.done??!0}});const s=i.quotedStream.flush();s.deltaContent&&this.appendToStream(i,s.deltaContent),s.quotedMessageId&&(i.quotedMessageId=s.quotedMessageId),this.flushStream(),this.finishRun("responded");break}}t&&(i.lastProgressAt=Date.now(),i.idleNoProgressCount=0),this.resetIdleTimer(i)}handlePermissionRequest(e){const i=e.permissionRequest;if(!i||!e.requestId||!this.acpClient)return;if(this.approvalMode==="yolo"||this.approvalMode==="autoEdit"){r.info("acp-adapter",`Auto-approving (${this.approvalMode}): ${i.toolName}`),this.acpClient.respondPermission(i.requestId,{behavior:"allow"}).catch(()=>{});return}const t=i.toolCallId;this.pendingApprovals.set(t,e.requestId);const s=this.activeRun;s?(s.idleTimer&&(clearTimeout(s.idleTimer),s.idleTimer=null),this.emitRawEventEnvelope(s,{type:"permission_request",payload:{request_id:i.requestId,tool_call_id:t,tool_name:i.toolName,tool_title:i.toolTitle,options:i.options}})||this.callbacks.sendPermissionCard({eventId:s.eventId,sessionId:s.sessionId,toolCallId:t,toolName:i.toolName,toolTitle:i.toolTitle,options:i.options})):(r.info("acp-adapter",`Permission request without active run, auto-approving: ${i.toolName}`),this.acpClient.respondPermission(e.requestId,{behavior:"allow"}),this.pendingApprovals.delete(t))}emitRawEventEnvelope(e,i){return!e||!this.rawTransport||!this.callbacks.sendRawEventEnvelope?!1:(this.callbacks.sendRawEventEnvelope(e.eventId,e.sessionId,{type:i.type,payload:i.payload,seq:++this.rawEventSeq,at:new Date().toISOString()}),!0)}resetIdleTimer(e){e.idleTimer&&clearTimeout(e.idleTimer);const i=e.awaitingToolResult?H:K;e.idleTimer=setTimeout(()=>{if(this.activeRun?.eventId!==e.eventId)return;const t=this.acpClient;t?.isAlive?t.ping(5e3).then(async s=>{if(this.activeRun?.eventId!==e.eventId)return;if(!s){r.error("acp-adapter",`Ping failed, declaring stuck: ${e.eventId}`),this.finishRun("failed","agent unreachable"),this.declareStuck();return}if(e.awaitingToolResult){r.info("acp-adapter",`Idle timer: tool running, ping ok, continuing: ${e.eventId}`),e.idleNoProgressCount=0,this.resetIdleTimer(e);return}const n=e.lastIdleCheckAt,a=e.lastProgressAt>n,o=a?!1:await B(t.sessionId,n),c=a||o;e.lastIdleCheckAt=Date.now(),c?(o&&r.info("acp-adapter",`Idle timer: no stream progress but kiro session active, continuing: ${e.eventId}`),e.idleNoProgressCount=0,this.resetIdleTimer(e)):(e.idleNoProgressCount++,e.idleNoProgressCount>=2?(r.error("acp-adapter",`Agent alive but no progress for ${e.idleNoProgressCount} idle cycles, declaring stuck: ${e.eventId}`),this.finishRun("failed",`agent alive but no progress for ${e.idleNoProgressCount} idle cycles`),this.declareStuck()):(r.warn("acp-adapter",`Idle timer fired, ping ok, no progress (${e.idleNoProgressCount}/2): ${e.eventId}`),this.resetIdleTimer(e)))}).catch(()=>{this.activeRun?.eventId===e.eventId&&(r.error("acp-adapter",`Ping error, declaring stuck: ${e.eventId}`),this.finishRun("failed","agent unreachable"),this.declareStuck())}):(r.error("acp-adapter",`Agent idle (no client), declaring stuck: ${e.eventId}`),this.finishRun("failed","agent unreachable"),this.declareStuck())},i)}declareStuck(){this.acpClient&&(this.acpClient.clearSettleTimer(),this.acpClient.removeAllListeners(),this.acpClient=null),this.agentProcess&&(this.agentProcess.close().catch(()=>{}),this.agentProcess=null),this.emit("stuck")}appendToStream(e,i){e.buffer+=i,e.flushTimer||(e.flushTimer=setTimeout(()=>this.flushStream(),L))}emitSegmentedStream(e,i){if(!i)return;const t=e.markdownSegmenter.push(i);for(const s of t)s.text&&(this.callbacks.sendStreamChunk(e.eventId,e.sessionId,s.text,++e.chunkSeq,s.closeAfter===!0,e.currentClientMsgId,e.quotedMessageId),s.closeAfter&&(e.currentSegmentIndex+=1,e.currentClientMsgId=`${e.clientMsgIdBase}_seg_${e.currentSegmentIndex}`,e.chunkSeq=0,r.info("acp-adapter",`stream segment rollover event=${e.eventId} segment=${e.currentSegmentIndex} reason=${s.reason??"threshold"}`)))}flushStream(){const e=this.activeRun;if(!e||!e.buffer)return;e.flushTimer&&(clearTimeout(e.flushTimer),e.flushTimer=null);const i=e.buffer;e.buffer="",this.emitSegmentedStream(e,i)}finishRun(e,i){const t=this.activeRun;if(!t)return;this.activeRun=null,this.acpClient?.clearSettleTimer(),this.emit("eventDone",t.eventId),t.flushTimer&&(clearTimeout(t.flushTimer),t.flushTimer=null),t.idleTimer&&(clearTimeout(t.idleTimer),t.idleTimer=null);const s=t.quotedStream.flush();s.deltaContent&&(t.buffer+=s.deltaContent),s.quotedMessageId&&(t.quotedMessageId=s.quotedMessageId),i&&(e==="failed"?(r.error("acp-adapter",`finishRun failed: ${i} event=${t.eventId}`),t.buffer+=`
18
+ Waiting for authentication to complete...`)}try{await this.acpClient.authenticate(i.id),r.info("acp-adapter","Authentication successful"),this.callbacks.sendAuthNotification(t,"Authentication successful. Resuming...");const s=this.acpMcpTools?await this.startInternalApiAndMcp():void 0;await this.acpClient.connect({transport:this.agentProcess.transport,initialMode:this.acpInitialMode,initialModel:this.acpInitialModel,cwd:this.resolveCwd(),mcpServers:s}),r.info("acp-adapter",`ACP session ready after auth: ${this.acpClient.sessionId}`),this.emit("acpSessionReady",this.acpClient.sessionId);for(const[n,o]of this.sessionBindings)this.callbacks.sendUpdateBindingCard(n,"ready",o,this.buildToolbarContext("binding_ready",o))}catch(s){throw r.error("acp-adapter",`Auth retry failed: ${s}`),s}}runTerminalAuth(e,i){return new Promise((t,s)=>{const n=b(this.config.command,e.args??[],{env:{...process.env,...this.config.env??{}},stdio:["ignore","pipe","pipe"]});this.terminalAuthChild=n;const o=/https?:\/\/[^\s"')\]]+/;let a=!1,c=!1;const l=u=>{if(a)return;const C=u.toString().replace(/\x1b\[[0-9;]*m/g,"").match(o);C&&(a=!0,r.info("acp-adapter",`Terminal auth URL captured: ${C[0]}`),this.callbacks.sendAuthNotification(i,`Please open this URL on any device to authenticate:
19
+ ${C[0]}
19
20
 
20
- The task was interrupted. Please resend your instruction.`):r.info("acp-adapter",`finishRun ${e}: ${i} event=${t.eventId}`)),t.buffer&&(this.emitSegmentedStream(t,t.buffer),t.buffer="");const n=++t.chunkSeq;if(this.callbacks.sendFinalStreamChunkReliable){let o;const c=this.callbacks.sendFinalStreamChunkReliable(t.eventId,t.sessionId,n,t.currentClientMsgId);c.catch(()=>{});const d=new Promise((l,p)=>{o=setTimeout(()=>p(new Error("final chunk ACK timeout")),5e3),o.unref()});Promise.race([c,d]).then(()=>{clearTimeout(o),t.silent||this.callbacks.sendEventResult(t.eventId,e,i),this.persistEventResult(t,e,i),this.replayNextDeferredEvent(),this.tryRunPendingAutoCompact()}).catch(l=>{clearTimeout(o),r.warn("acp-adapter",`finalStreamChunk ACK timeout/failed event=${t.eventId}: ${l instanceof Error?l.message:l}`),this.callbacks.sendStreamChunk(t.eventId,t.sessionId,"",n,!0,t.currentClientMsgId),t.silent||this.callbacks.sendEventResult(t.eventId,e,i),this.persistEventResult(t,e,i),this.replayNextDeferredEvent(),this.tryRunPendingAutoCompact()})}else this.callbacks.sendStreamChunk(t.eventId,t.sessionId,"",n,!0,t.currentClientMsgId),t.silent||this.callbacks.sendEventResult(t.eventId,e,i),this.persistEventResult(t,e,i),this.replayNextDeferredEvent(),this.tryRunPendingAutoCompact()}getContextWindowUsedPercentage(e){return"usedPercentage"in e?Number.isFinite(e.usedPercentage)?e.usedPercentage:null:!Number.isFinite(e.used)||!Number.isFinite(e.size)||e.size<=0?null:Math.min(100,e.used/e.size*100)}maybeScheduleAutoCompact(e){const i=this.getContextWindowUsedPercentage(e);i===null||i<y||this.pendingAutoCompact||this.compacting||(this.pendingAutoCompact=!0,r.info("acp-adapter",`[auto-compact] context_window usedPercentage=${i.toFixed(1)}% >= ${y}%, scheduling compact`),this.tryRunPendingAutoCompact())}tryRunPendingAutoCompact(){this.pendingAutoCompact&&(this.stopped||!this.acpClient?.isAlive||this.activeRun||this.compacting||(this.pendingAutoCompact=!1,this.execCommand("compact","",this.currentAibotSessionId??"").then(e=>{r.info("acp-adapter",`[auto-compact] compact done status=${e.status} msg=${e.message??""}`)}).catch(e=>{r.warn("acp-adapter",`[auto-compact] compact error: ${e instanceof Error?e.message:String(e)}`)})))}persistEventResult(e,i,t){this.eventResults&&!e.silent&&this.eventResults.set({sessionId:e.sessionId,eventId:e.eventId,status:i,msg:t,updatedAt:Date.now()})}}class Q extends S{adapterSessionId;constructor(e){super(),this.adapterSessionId=e}emitDone(e){this.emit("done",e)}emitError(e){if(this.listenerCount("error")===0){r.warn("acp-adapter",`Prompt handle error (no listeners): ${e.message}`);return}this.emit("error",e)}async cancel(){}}export{ve as AcpAdapter};
21
+ Waiting for authentication to complete...`))};n.stdout?.on("data",l),n.stderr?.on("data",l);const d=setTimeout(()=>{c||(c=!0,n.kill("SIGKILL"),this.terminalAuthChild=null,s(new Error(`terminal auth timed out after ${T}ms`)))},T);n.on("error",u=>{c||(c=!0,clearTimeout(d),this.terminalAuthChild=null,s(u))}),n.on("exit",u=>{c||(c=!0,clearTimeout(d),this.terminalAuthChild=null,u===0?t():s(new Error(`terminal auth process exited with code ${u}`)))})})}captureAuthUrl(){return new Promise(e=>{if(!this.agentProcess){e(null);return}const i=/https?:\/\/[^\s"')\]]+/;let t=!1;const s=n=>{if(t)return;const a=n.toString().replace(/\x1b\[[0-9;]*m/g,"").match(i);a&&(t=!0,this.agentProcess.removeListener("stderr",s),e(a[0]))};this.agentProcess.on("stderr",s),setTimeout(()=>{t||(t=!0,this.agentProcess.removeListener("stderr",s),e(null))},3e4)})}handleAcpEvent(e){if(e.type===m.PermissionRequest){this.activeRun&&this.resetIdleTimer(this.activeRun),this.handlePermissionRequest(e);return}if(e.type===m.ContextWindowUpdate){e.contextWindow&&this.callbacks.onContextWindowUpdated?.(e.contextWindow),this.compacting?this.finishCompaction("context-window-update"):e.contextWindow&&this.maybeScheduleAutoCompact(e.contextWindow);return}const i=this.activeRun;if(!i)return;i.responded||(i.responded=!0);let t=!1;switch(e.type){case m.Text:{if(e.content){t=!0;const s=i.quotedStream.consume(e.content);s.quotedMessageId&&(i.quotedMessageId=s.quotedMessageId),s.deltaContent&&this.appendToStream(i,s.deltaContent)}break}case m.ToolUse:{t=!0,i.awaitingToolResult=!0,e.toolName&&(this.emitRawEventEnvelope(i,{type:"tool_use",payload:{tool_name:e.toolName,tool_input:e.toolInput??""}})||this.callbacks.sendToolUse(i.eventId,i.sessionId,e.toolName,e.toolInput??""));break}case m.ToolResult:{i.awaitingToolResult=!1,e.content&&(t=!0,this.emitRawEventEnvelope(i,{type:"tool_result",payload:{tool_name:e.toolName??"",content:e.content}})||this.callbacks.sendToolResult(i.eventId,i.sessionId,e.toolName??"",e.content));break}case m.ToolProgress:{t=!0,e.content&&(this.emitRawEventEnvelope(i,{type:"tool_progress",payload:{tool_name:e.toolName??"",content:e.content}})||this.callbacks.sendToolResult(i.eventId,i.sessionId,e.toolName??"",e.content));break}case m.Thinking:{e.content&&(t=!0,this.emitRawEventEnvelope(i,{type:"thinking",payload:{content:e.content}})||this.callbacks.sendThinking(i.eventId,i.sessionId,e.content));break}case m.Error:{const s=String(e.error??"unknown error");this.emitRawEventEnvelope(i,{type:"error",payload:{message:s}}),r.error("acp-adapter",`ACP error: ${s}`),this.callbacks.sendRunError(i.eventId,i.sessionId,s);break}case m.Result:{this.emitRawEventEnvelope(i,{type:"result",payload:{done:e.done??!0}});const s=i.quotedStream.flush();s.deltaContent&&this.appendToStream(i,s.deltaContent),s.quotedMessageId&&(i.quotedMessageId=s.quotedMessageId),this.flushStream(),this.finishRun("responded");break}}t&&(i.lastProgressAt=Date.now(),i.idleNoProgressCount=0),this.resetIdleTimer(i)}handlePermissionRequest(e){const i=e.permissionRequest;if(!i||!e.requestId||!this.acpClient)return;if(this.approvalMode==="yolo"||this.approvalMode==="autoEdit"){r.info("acp-adapter",`Auto-approving (${this.approvalMode}): ${i.toolName}`),this.acpClient.respondPermission(i.requestId,{behavior:"allow"}).catch(()=>{});return}const t=i.toolCallId;this.pendingApprovals.set(t,e.requestId);const s=this.activeRun;s?(s.idleTimer&&(clearTimeout(s.idleTimer),s.idleTimer=null),this.emitRawEventEnvelope(s,{type:"permission_request",payload:{request_id:i.requestId,tool_call_id:t,tool_name:i.toolName,tool_title:i.toolTitle,options:i.options}})||this.callbacks.sendPermissionCard({eventId:s.eventId,sessionId:s.sessionId,toolCallId:t,toolName:i.toolName,toolTitle:i.toolTitle,options:i.options})):(r.info("acp-adapter",`Permission request without active run, auto-approving: ${i.toolName}`),this.acpClient.respondPermission(e.requestId,{behavior:"allow"}),this.pendingApprovals.delete(t))}emitRawEventEnvelope(e,i){return!e||!this.rawTransport||!this.callbacks.sendRawEventEnvelope?!1:(this.callbacks.sendRawEventEnvelope(e.eventId,e.sessionId,{type:i.type,payload:i.payload,seq:++this.rawEventSeq,at:new Date().toISOString()}),!0)}resetIdleTimer(e){e.idleTimer&&clearTimeout(e.idleTimer);const i=e.awaitingToolResult?z:W;e.idleTimer=setTimeout(()=>{if(this.activeRun?.eventId!==e.eventId)return;const t=this.acpClient;t?.isAlive?t.ping(5e3).then(async s=>{if(this.activeRun?.eventId!==e.eventId)return;if(!s){r.error("acp-adapter",`Ping failed, declaring stuck: ${e.eventId}`),this.finishRun("failed","agent unreachable"),this.declareStuck();return}if(e.awaitingToolResult){r.info("acp-adapter",`Idle timer: tool running, ping ok, continuing: ${e.eventId}`),e.idleNoProgressCount=0,this.resetIdleTimer(e);return}const n=e.lastIdleCheckAt,o=e.lastProgressAt>n,a=o?!1:await B(t.sessionId,n),c=o||a;e.lastIdleCheckAt=Date.now(),c?(a&&r.info("acp-adapter",`Idle timer: no stream progress but kiro session active, continuing: ${e.eventId}`),e.idleNoProgressCount=0,this.resetIdleTimer(e)):(e.idleNoProgressCount++,e.idleNoProgressCount>=2?(r.error("acp-adapter",`Agent alive but no progress for ${e.idleNoProgressCount} idle cycles, declaring stuck: ${e.eventId}`),this.finishRun("failed",`agent alive but no progress for ${e.idleNoProgressCount} idle cycles`),this.declareStuck()):(r.warn("acp-adapter",`Idle timer fired, ping ok, no progress (${e.idleNoProgressCount}/2): ${e.eventId}`),this.resetIdleTimer(e)))}).catch(()=>{this.activeRun?.eventId===e.eventId&&(r.error("acp-adapter",`Ping error, declaring stuck: ${e.eventId}`),this.finishRun("failed","agent unreachable"),this.declareStuck())}):(r.error("acp-adapter",`Agent idle (no client), declaring stuck: ${e.eventId}`),this.finishRun("failed","agent unreachable"),this.declareStuck())},i)}declareStuck(){this.acpClient&&(this.acpClient.clearSettleTimer(),this.acpClient.removeAllListeners(),this.acpClient=null),this.agentProcess&&(this.agentProcess.close().catch(()=>{}),this.agentProcess=null),this.emit("stuck")}appendToStream(e,i){e.buffer+=i,e.flushTimer||(e.flushTimer=setTimeout(()=>this.flushStream(),H))}emitSegmentedStream(e,i){if(!i)return;const t=e.markdownSegmenter.push(i);for(const s of t)s.text&&(this.callbacks.sendStreamChunk(e.eventId,e.sessionId,s.text,++e.chunkSeq,s.closeAfter===!0,e.currentClientMsgId,e.quotedMessageId),s.closeAfter&&(e.currentSegmentIndex+=1,e.currentClientMsgId=`${e.clientMsgIdBase}_seg_${e.currentSegmentIndex}`,e.chunkSeq=0,r.info("acp-adapter",`stream segment rollover event=${e.eventId} segment=${e.currentSegmentIndex} reason=${s.reason??"threshold"}`)))}flushStream(){const e=this.activeRun;if(!e||!e.buffer)return;e.flushTimer&&(clearTimeout(e.flushTimer),e.flushTimer=null);const i=e.buffer;e.buffer="",this.emitSegmentedStream(e,i)}finishRun(e,i){const t=this.activeRun;if(!t)return;this.activeRun=null,this.acpClient?.clearSettleTimer(),this.emit("eventDone",t.eventId),t.flushTimer&&(clearTimeout(t.flushTimer),t.flushTimer=null),t.idleTimer&&(clearTimeout(t.idleTimer),t.idleTimer=null);const s=t.quotedStream.flush();s.deltaContent&&(t.buffer+=s.deltaContent),s.quotedMessageId&&(t.quotedMessageId=s.quotedMessageId),i&&(e==="failed"?(r.error("acp-adapter",`finishRun failed: ${i} event=${t.eventId}`),t.buffer+=`
22
+
23
+ The task was interrupted. Please resend your instruction.`):r.info("acp-adapter",`finishRun ${e}: ${i} event=${t.eventId}`)),t.buffer&&(this.emitSegmentedStream(t,t.buffer),t.buffer="");const n=++t.chunkSeq;if(this.callbacks.sendFinalStreamChunkReliable){let a;const c=this.callbacks.sendFinalStreamChunkReliable(t.eventId,t.sessionId,n,t.currentClientMsgId);c.catch(()=>{});const l=new Promise((d,u)=>{a=setTimeout(()=>u(new Error("final chunk ACK timeout")),5e3),a.unref()});Promise.race([c,l]).then(()=>{clearTimeout(a),t.silent||this.callbacks.sendEventResult(t.eventId,e,i),this.persistEventResult(t,e,i),this.replayNextDeferredEvent(),this.tryRunPendingAutoCompact()}).catch(d=>{clearTimeout(a),r.warn("acp-adapter",`finalStreamChunk ACK timeout/failed event=${t.eventId}: ${d instanceof Error?d.message:d}`),this.callbacks.sendStreamChunk(t.eventId,t.sessionId,"",n,!0,t.currentClientMsgId),t.silent||this.callbacks.sendEventResult(t.eventId,e,i),this.persistEventResult(t,e,i),this.replayNextDeferredEvent(),this.tryRunPendingAutoCompact()})}else this.callbacks.sendStreamChunk(t.eventId,t.sessionId,"",n,!0,t.currentClientMsgId),t.silent||this.callbacks.sendEventResult(t.eventId,e,i),this.persistEventResult(t,e,i),this.replayNextDeferredEvent(),this.tryRunPendingAutoCompact()}getContextWindowUsedPercentage(e){return"usedPercentage"in e?Number.isFinite(e.usedPercentage)?e.usedPercentage:null:!Number.isFinite(e.used)||!Number.isFinite(e.size)||e.size<=0?null:Math.min(100,e.used/e.size*100)}maybeScheduleAutoCompact(e){const i=this.getContextWindowUsedPercentage(e);i===null||i<R||this.pendingAutoCompact||this.compacting||(this.pendingAutoCompact=!0,r.info("acp-adapter",`[auto-compact] context_window usedPercentage=${i.toFixed(1)}% >= ${R}%, scheduling compact`),this.tryRunPendingAutoCompact())}tryRunPendingAutoCompact(){this.pendingAutoCompact&&(this.stopped||!this.acpClient?.isAlive||this.activeRun||this.compacting||(this.pendingAutoCompact=!1,this.execCommand("compact","",this.currentAibotSessionId??"").then(e=>{r.info("acp-adapter",`[auto-compact] compact done status=${e.status} msg=${e.message??""}`)}).catch(e=>{r.warn("acp-adapter",`[auto-compact] compact error: ${e instanceof Error?e.message:String(e)}`)})))}persistEventResult(e,i,t){this.eventResults&&!e.silent&&this.eventResults.set({sessionId:e.sessionId,eventId:e.eventId,status:i,msg:t,updatedAt:Date.now()})}}class X extends A{adapterSessionId;constructor(e){super(),this.adapterSessionId=e}emitDone(e){this.emit("done",e)}emitError(e){if(this.listenerCount("error")===0){r.warn("acp-adapter",`Prompt handle error (no listeners): ${e.message}`);return}this.emit("error",e)}async cancel(){}}export{Ie as AcpAdapter};
@@ -1,13 +1,13 @@
1
- import{spawn as P}from"node:child_process";import{randomUUID as $}from"node:crypto";import{EventEmitter as _}from"node:events";import{existsSync as E,readFileSync as S,readdirSync as k,statSync as w,mkdirSync as b,writeFileSync as x}from"node:fs";import{homedir as g}from"node:os";import{join as p,dirname as C,resolve as L}from"node:path";import{fileURLToPath as R}from"node:url";import{log as o}from"../../core/log/index.js";import{killProcessGroup as f}from"../../core/runtime/spawn.js";import{InternalApiServer as T}from"../../core/mcp/internal-api-server.js";import{syncDefaultSkillsToDir as j}from"../../default-skills/index.js";import{buildSimpleProbeReport as G}from"../shared/probe-util.js";const a="agy-adapter",m=p(g(),".gemini","antigravity-cli","log"),I=p(g(),".gemini","antigravity-cli","cache","last_conversations.json"),M=["\u4F60\u63A5\u5165\u7684\u662F Grix \u804A\u5929\u3002\u7528\u6237\u6D88\u606F\u88AB\u5305\u5728 <channel ...> \u6807\u7B7E\u91CC\uFF0C\u6807\u7B7E\u5C5E\u6027\u643A\u5E26\u672C\u8F6E\u4E0A\u4E0B\u6587\uFF1A","chat_id\uFF08\u5F53\u524D\u4F1A\u8BDD\uFF09\u3001event_id\uFF08\u672C\u8F6E\u4E8B\u4EF6\uFF09\u3001message_id\uFF08\u7528\u6237\u8FD9\u6761\u6D88\u606F\u7684 ID\uFF09\u3001user_id\uFF08\u53D1\u9001\u8005\uFF09\u3002","\u91CD\u8981\uFF1A\u4F60\u7684\u56DE\u590D\u5FC5\u987B\u8C03\u7528 grix \u7684 `grix_message_send` \u5DE5\u5177\u53D1\u9001\uFF08sessionId \u53D6\u81EA channel \u7684 chat_id\uFF0Ccontent \u4E3A\u4F60\u7684\u56DE\u590D\u5185\u5BB9\uFF09\uFF0C","\u4E0D\u8981\u53EA\u628A\u7B54\u6848\u6253\u5370\u5230\u6807\u51C6\u8F93\u51FA\u2014\u2014\u53EA\u6709\u8C03\u7528\u5DE5\u5177\u624D\u4F1A\u628A\u6D88\u606F\u771F\u6B63\u53D1\u7ED9\u7528\u6237\u3002","grix_message_send \u4F1A\u8FD4\u56DE\u4F60\u521A\u53D1\u51FA\u6D88\u606F\u7684 msg_id\u3002\u9700\u8981\u65F6\u4F60\u53EF\u4EE5\u7528\u8FD9\u4E2A ID \u505A\u540E\u7EED\u64CD\u4F5C\uFF0C\u4F8B\u5982\u8C03\u7528 grix_message_unsend\uFF08\u4F20 msgId\uFF09\u64A4\u56DE\u67D0\u6761\u6D88\u606F\u3002"].join(""),O=new Set(["send_msg","grix_reply"]),D=new Set(["send_msg","grix_reply","delete_msg","session_send","grix_complete","grix_composing","grix_event_ack"]);class X extends _{type="agy";config;callbacks;runtimeResolver;alive=!1;stopped=!1;internalApi=null;activeEventId=null;activeEventSessionId=null;activeProcess=null;completedEventIds=new Set;activeEventSentViaTool=!1;sessionConversationMap=new Map;conversationOutputCache=new Map;eventQueue=[];constructor(t,e,s){super(),this.config=t,this.callbacks=e,this.runtimeResolver=s}async start(){this.alive||(this.alive=!0,this.stopped=!1,await this.startInternalApiAndInjectMcp(),o.info(a,"AgyAdapter started"))}async startInternalApiAndInjectMcp(){try{this.internalApi=new T,this.internalApi.setInvokeHandler(async(r,l,h,u)=>this.handleToolInvoke(r,l,u)),await this.internalApi.start(0),o.info(a,`Internal API started at ${this.internalApi.url}`);const t=this.getMcpConfig(),e=p(g(),".gemini","config","mcp_config.json");b(C(e),{recursive:!0});let s={};try{E(e)&&(s=JSON.parse(S(e,"utf8")))}catch{}const i=s.mcpServers&&typeof s.mcpServers=="object"?s.mcpServers:{};i[t.name]={command:t.command,args:t.args},s.mcpServers=i,x(e,`${JSON.stringify(s,null,2)}
2
- `,"utf8"),o.info(a,`MCP config injected into ${e}`);const n=p(g(),".gemini","skills"),c=j(n);c.length>0&&o.info(a,`Synced connector skills to ${n}: [${c.join(", ")}]`)}catch(t){o.warn(a,`Failed to start MCP tools (non-fatal): ${t instanceof Error?t.message:String(t)}`)}}async handleToolInvoke(t,e,s){let i=e;D.has(t)&&this.activeEventSessionId&&(i.session_id==null||i.session_id==="")&&(i={...i,session_id:this.activeEventSessionId});const n=await this.callbacks.agentInvoke(t,i,s);return O.has(t)&&this.activeEventId&&(this.activeEventSentViaTool=!0),n}async stop(){if(!this.stopped){if(this.stopped=!0,this.alive=!1,this.activeProcess){try{f(this.activeProcess,"SIGKILL")}catch{}this.activeProcess=null}if(this.activeEventId&&this.activeEventSessionId)try{this.callbacks.sendEventResult(this.activeEventId,"canceled","adapter stopped")}catch{}if(this.activeEventId=null,this.activeEventSessionId=null,this.internalApi){try{await this.internalApi.stop()}catch{}this.internalApi=null}this.emit("exit",0),o.info(a,"AgyAdapter stopped")}}isAlive(){return this.alive}async createSession(t){return t.cwd??$()}async resumeSession(t,e){}async destroySession(t){}sendPrompt(t){const e=new _;return e.adapterSessionId=t.adapterSessionId,e.cancel=async()=>{e.emit("done",{status:"canceled"})},e}async cancel(t){}cancelCurrentRun(){if(!this.activeEventId||!this.activeProcess)return;const t=this.activeEventId;o.info(a,`cancelCurrentRun: killing process group for event ${t}`);try{f(this.activeProcess,"SIGKILL")}catch{}this.activeProcess=null,this.callbacks.sendEventResult(t,"canceled","restarted by user"),this.clearActiveEvent(t),this.drainQueue()}deliverInboundEvent(t){if(!this.stopped){if(this.completedEventIds.has(t.event_id)){o.debug(a,`Skipping duplicate event: ${t.event_id}`);return}if(this.callbacks.sendEventAck(t.event_id,t.session_id),this.activeEventId){o.info(a,`Queuing event ${t.event_id} (active: ${this.activeEventId})`),this.eventQueue.push(t);return}this.processEvent(t)}}deliverStopEvent(t,e){if(this.activeEventId===t&&this.activeProcess){o.info(a,`Stopping event ${t}`);try{f(this.activeProcess,"SIGKILL")}catch{}this.activeProcess=null,this.callbacks.sendEventResult(t,"canceled","stopped by user"),this.clearActiveEvent(t),this.drainQueue()}}setPermissionHandler(){}async ping(t){return this.alive}getStatus(){return{alive:this.alive,busy:this.activeEventId!==null,sessions:this.sessionConversationMap.size}}async probe(t){const e=this.getStatus();return G(this.config.command||"agy",{alive:e.alive,busy:e.busy,started:e.alive},t)}getActiveEventIds(){return this.activeEventId?[this.activeEventId]:[]}clearActiveEventForShutdown(){if(this.activeProcess)try{f(this.activeProcess,"SIGKILL")}catch{}this.activeEventId=null,this.activeEventSessionId=null,this.activeProcess=null,this.activeEventSentViaTool=!1}getMcpConfig(){if(!this.internalApi)return null;const t=L(R(import.meta.url),"../../../mcp/acp-mcp-server.js");return{name:"grix-connector-tools",command:process.execPath,args:[t,"--api-url",this.internalApi.url]}}processEvent(t){const{event_id:e,session_id:s}=t,i=this.runtimeResolver(s);if(!i.cwd){o.warn(a,`No working directory for session ${s}, event should have been intercepted by bridge`),this.callbacks.forceCompleteInternalEvent(e,s);return}this.activeEventId=e,this.activeEventSessionId=s,this.activeEventSentViaTool=!1,this.emit("eventStarted",e,s),o.info(a,`Processing event ${e} for session ${s}`);const n=this.buildAgyPrompt(t);this.spawnAgyPrint(e,s,n,i)}buildAgyPrompt(t){const s=[["chat_id",t.session_id],["event_id",t.event_id],["message_id",t.msg_id],["user_id",t.sender_id],["quoted_message_id",t.quoted_message_id]].filter(([,l])=>l!=null&&l!=="").map(([l,h])=>`${l}="${h}"`).join(" "),i=this.renderContextBlock(t),n=i?`${i}
1
+ import{spawn as $}from"node:child_process";import{randomUUID as k}from"node:crypto";import{EventEmitter as I}from"node:events";import{existsSync as E,readFileSync as _,readdirSync as b,statSync as w,mkdirSync as x,writeFileSync as C,realpathSync as L}from"node:fs";import{homedir as f}from"node:os";import{join as p,dirname as R,resolve as T}from"node:path";import{fileURLToPath as j}from"node:url";import{log as o}from"../../core/log/index.js";import{killProcessGroup as g}from"../../core/runtime/spawn.js";import{InternalApiServer as G}from"../../core/mcp/internal-api-server.js";import{syncDefaultSkillsToDir as M}from"../../default-skills/index.js";import{buildSimpleProbeReport as O}from"../shared/probe-util.js";const a="agy-adapter";function D(y){try{return L(y)}catch{return y}}const m=p(f(),".gemini","antigravity-cli","log"),A=p(f(),".gemini","antigravity-cli","cache","last_conversations.json"),N=["\u4F60\u63A5\u5165\u7684\u662F Grix \u804A\u5929\u3002\u7528\u6237\u6D88\u606F\u88AB\u5305\u5728 <channel ...> \u6807\u7B7E\u91CC\uFF0C\u6807\u7B7E\u5C5E\u6027\u643A\u5E26\u672C\u8F6E\u4E0A\u4E0B\u6587\uFF1A","chat_id\uFF08\u5F53\u524D\u4F1A\u8BDD\uFF09\u3001event_id\uFF08\u672C\u8F6E\u4E8B\u4EF6\uFF09\u3001message_id\uFF08\u7528\u6237\u8FD9\u6761\u6D88\u606F\u7684 ID\uFF09\u3001user_id\uFF08\u53D1\u9001\u8005\uFF09\u3002","\u91CD\u8981\uFF1A\u4F60\u7684\u56DE\u590D\u5FC5\u987B\u8C03\u7528 grix \u7684 `grix_message_send` \u5DE5\u5177\u53D1\u9001\uFF08sessionId \u53D6\u81EA channel \u7684 chat_id\uFF0Ccontent \u4E3A\u4F60\u7684\u56DE\u590D\u5185\u5BB9\uFF09\uFF0C","\u4E0D\u8981\u53EA\u628A\u7B54\u6848\u6253\u5370\u5230\u6807\u51C6\u8F93\u51FA\u2014\u2014\u53EA\u6709\u8C03\u7528\u5DE5\u5177\u624D\u4F1A\u628A\u6D88\u606F\u771F\u6B63\u53D1\u7ED9\u7528\u6237\u3002","grix_message_send \u4F1A\u8FD4\u56DE\u4F60\u521A\u53D1\u51FA\u6D88\u606F\u7684 msg_id\u3002\u9700\u8981\u65F6\u4F60\u53EF\u4EE5\u7528\u8FD9\u4E2A ID \u505A\u540E\u7EED\u64CD\u4F5C\uFF0C\u4F8B\u5982\u8C03\u7528 grix_message_unsend\uFF08\u4F20 msgId\uFF09\u64A4\u56DE\u67D0\u6761\u6D88\u606F\u3002"].join(""),B=new Set(["send_msg","grix_reply"]),Q=new Set(["send_msg","grix_reply","delete_msg","session_send","grix_complete","grix_composing","grix_event_ack"]);class tt extends I{type="agy";config;callbacks;runtimeResolver;alive=!1;stopped=!1;internalApi=null;activeEventId=null;activeEventSessionId=null;activeProcess=null;completedEventIds=new Set;activeEventSentViaTool=!1;sessionConversationMap=new Map;conversationOutputCache=new Map;eventQueue=[];constructor(t,e,s){super(),this.config=t,this.callbacks=e,this.runtimeResolver=s}async start(){this.alive||(this.alive=!0,this.stopped=!1,await this.startInternalApiAndInjectMcp(),o.info(a,"AgyAdapter started"))}async startInternalApiAndInjectMcp(){try{this.internalApi=new G,this.internalApi.setInvokeHandler(async(r,l,h,u)=>this.handleToolInvoke(r,l,u)),await this.internalApi.start(0),o.info(a,`Internal API started at ${this.internalApi.url}`);const t=this.getMcpConfig(),e=p(f(),".gemini","config","mcp_config.json");x(R(e),{recursive:!0});let s={};try{E(e)&&(s=JSON.parse(_(e,"utf8")))}catch{}const i=s.mcpServers&&typeof s.mcpServers=="object"?s.mcpServers:{};i[t.name]={command:t.command,args:t.args},s.mcpServers=i,C(e,`${JSON.stringify(s,null,2)}
2
+ `,"utf8"),o.info(a,`MCP config injected into ${e}`);const n=p(f(),".gemini","skills"),c=M(n);c.length>0&&o.info(a,`Synced connector skills to ${n}: [${c.join(", ")}]`)}catch(t){o.warn(a,`Failed to start MCP tools (non-fatal): ${t instanceof Error?t.message:String(t)}`)}}async handleToolInvoke(t,e,s){let i=e;Q.has(t)&&this.activeEventSessionId&&(i.session_id==null||i.session_id==="")&&(i={...i,session_id:this.activeEventSessionId});const n=await this.callbacks.agentInvoke(t,i,s);return B.has(t)&&this.activeEventId&&(this.activeEventSentViaTool=!0),n}async stop(){if(!this.stopped){if(this.stopped=!0,this.alive=!1,this.activeProcess){try{g(this.activeProcess,"SIGKILL")}catch{}this.activeProcess=null}if(this.activeEventId&&this.activeEventSessionId)try{this.callbacks.sendEventResult(this.activeEventId,"canceled","adapter stopped")}catch{}if(this.activeEventId=null,this.activeEventSessionId=null,this.internalApi){try{await this.internalApi.stop()}catch{}this.internalApi=null}this.emit("exit",0),o.info(a,"AgyAdapter stopped")}}isAlive(){return this.alive}async createSession(t){return t.cwd??k()}async resumeSession(t,e){}async destroySession(t){}sendPrompt(t){const e=new I;return e.adapterSessionId=t.adapterSessionId,e.cancel=async()=>{e.emit("done",{status:"canceled"})},e}async cancel(t){}cancelCurrentRun(){if(!this.activeEventId||!this.activeProcess)return;const t=this.activeEventId;o.info(a,`cancelCurrentRun: killing process group for event ${t}`);try{g(this.activeProcess,"SIGKILL")}catch{}this.activeProcess=null,this.callbacks.sendEventResult(t,"canceled","restarted by user"),this.clearActiveEvent(t),this.drainQueue()}deliverInboundEvent(t){if(!this.stopped){if(this.completedEventIds.has(t.event_id)){o.debug(a,`Skipping duplicate event: ${t.event_id}`);return}if(this.callbacks.sendEventAck(t.event_id,t.session_id),this.activeEventId){o.info(a,`Queuing event ${t.event_id} (active: ${this.activeEventId})`),this.eventQueue.push(t);return}this.processEvent(t)}}deliverStopEvent(t,e){if(this.activeEventId===t&&this.activeProcess){o.info(a,`Stopping event ${t}`);try{g(this.activeProcess,"SIGKILL")}catch{}this.activeProcess=null,this.callbacks.sendEventResult(t,"canceled","stopped by user"),this.clearActiveEvent(t),this.drainQueue()}}setPermissionHandler(){}async ping(t){return this.alive}getStatus(){return{alive:this.alive,busy:this.activeEventId!==null,sessions:this.sessionConversationMap.size}}async probe(t){const e=this.getStatus();return O(this.config.command||"agy",{alive:e.alive,busy:e.busy,started:e.alive},t)}getActiveEventIds(){return this.activeEventId?[this.activeEventId]:[]}clearActiveEventForShutdown(){if(this.activeProcess)try{g(this.activeProcess,"SIGKILL")}catch{}this.activeEventId=null,this.activeEventSessionId=null,this.activeProcess=null,this.activeEventSentViaTool=!1}getMcpConfig(){if(!this.internalApi)return null;const t=T(j(import.meta.url),"../../../mcp/acp-mcp-server.js");return{name:"grix-connector-tools",command:process.execPath,args:[t,"--api-url",this.internalApi.url]}}processEvent(t){const{event_id:e,session_id:s}=t,i=this.runtimeResolver(s);if(!i.cwd){o.warn(a,`No working directory for session ${s}, event should have been intercepted by bridge`),this.callbacks.forceCompleteInternalEvent(e,s);return}this.activeEventId=e,this.activeEventSessionId=s,this.activeEventSentViaTool=!1,this.emit("eventStarted",e,s),o.info(a,`Processing event ${e} for session ${s}`);const n=this.buildAgyPrompt(t);this.spawnAgyPrint(e,s,n,i)}buildAgyPrompt(t){const s=[["chat_id",t.session_id],["event_id",t.event_id],["message_id",t.msg_id],["user_id",t.sender_id],["quoted_message_id",t.quoted_message_id]].filter(([,l])=>l!=null&&l!=="").map(([l,h])=>`${l}="${h}"`).join(" "),i=this.renderContextBlock(t),n=i?`${i}
3
3
 
4
4
  ${t.content}`:t.content,c=`<channel source="grix-agy" ${s}>
5
5
  ${n}
6
- </channel>`;return`${this.buildIdentityBlock()}${M}
6
+ </channel>`;return`${this.buildIdentityBlock()}${N}
7
7
 
8
8
  ${c}`}buildIdentityBlock(){const t=this.callbacks.getAgentProfile?.(),e=t?.agentName?.trim()??"",s=t?.introduction?.trim()??"";if(!e&&!s)return"";const i=["[system-identity]"];return e&&i.push(`\u4F60\u7684\u540D\u5B57\u662F"${e}"\u3002`),s&&i.push(`\u4F60\u7684\u81EA\u6211\u4ECB\u7ECD\uFF1A${s}`),i.push("\u8FD9\u662F\u5E26\u5916\u6307\u4EE4\uFF1B\u4E0D\u8981\u5728\u56DE\u590D\u4E2D\u590D\u8FF0\u6216\u5F15\u7528\u672C\u6BB5\u3002"),i.push("[/system-identity]"),`${i.join(`
9
9
  `)}
10
10
 
11
11
  `}renderContextBlock(t){const e=t.context_messages_json;if(!e)return"";let s;try{const r=JSON.parse(e);if(!Array.isArray(r))return"";s=r}catch{return""}const i=String(t.session_type??"")==="2",n=String(t.msg_id??""),c=[];for(const r of s){const l=String(r?.msg_id??"");if(l&&l===n)continue;const h=String(r?.content??"").trim();if(!h)continue;const u=String(r?.sender_id??"");if(h.startsWith("[\u5F15\u7528\u6D88\u606F]")){const d=h.slice(6).replace(/^\s*\n?/,"").trim();c.push(i&&u?`[\u5F15\u7528\u6D88\u606F] (\u6765\u81EA ${u})\uFF1A${d}`:`[\u5F15\u7528\u6D88\u606F]\uFF1A${d}`)}else c.push(i&&u?`[${u}]\uFF1A${h}`:h)}return c.join(`
12
- `)}spawnAgyPrint(t,e,s,i){const n=this.buildPrintArgs(s,i),c={...process.env,...this.config.env??{}};o.info(a,`Spawning: agy ${n.map(d=>d.includes(" ")?`"${d}"`:d).join(" ")}`);const r=this.getLatestLogFilePath(),l=P(this.config.command,n,{cwd:i.cwd,env:c,stdio:["pipe","pipe","pipe"],detached:!0});this.activeProcess=l;const h=[],u=[];l.stdout?.on("data",d=>{h.push(d)}),l.stderr?.on("data",d=>{u.push(d)}),l.on("error",d=>{o.error(a,`Process spawn error for event ${t}: ${d.message}`),this.handleProcessResult(t,e,"",`spawn error: ${d.message}`,i)}),l.on("close",d=>{if(o.info(a,`Process exited for event ${t} with code ${d}`),this.activeEventId!==t)return;const v=Buffer.concat(h).toString("utf-8"),A=Buffer.concat(u).toString("utf-8");if(d!==0){const y=A.trim()||`process exited with code ${d}`;this.handleProcessResult(t,e,v,y,i)}else v.trim()?this.handleProcessResult(t,e,v,"",i):this.activeEventSentViaTool?this.handleProcessResult(t,e,v,"",i):setTimeout(()=>{const y=this.extractLogError(r);this.handleProcessResult(t,e,v,y,i)},300)}),l.stdin?.end()}buildPrintArgs(t,e){const s=[];return e.modelId&&s.push("--model",e.modelId),e.cwd&&s.push("--add-dir",e.cwd),s.push("--dangerously-skip-permissions"),e.conversationId&&s.push("--conversation",e.conversationId),s.push("-p",t),s}readAgyConversationId(t){try{if(E(I))return JSON.parse(S(I,"utf-8"))[t]||void 0}catch(e){o.debug(a,`readAgyConversationId: ${e}`)}}getLatestLogFilePath(){try{if(!E(m))return null;const t=k(m).filter(e=>e.startsWith("cli-")&&e.endsWith(".log")).map(e=>({name:e,path:p(m,e),mtime:w(p(m,e)).mtimeMs})).sort((e,s)=>s.mtime-e.mtime);return t.length>0?t[0].path:null}catch{return null}}extractLogError(t){try{const e=this.getLatestLogFilePath();if(!e)return o.info(a,"extractLogError: no log files found"),"agy completed without output";const s=S(e,"utf-8");if(!s)return o.info(a,`extractLogError: log file is empty: ${e}`),"agy completed without output";const i=s.split(`
13
- `).filter(n=>n.startsWith("E")).map(n=>{const c=n.indexOf("] ");return c>=0?n.slice(c+2):n}).filter(n=>n.length>0);if(i.length>0){const n=i[i.length-1];return o.info(a,`Extracted error from agy log: ${n.slice(0,120)}`),n.length>500?n.slice(0,500)+"...":n}return"agy completed without output (possible auth or quota issue)"}catch(e){return o.info(a,`extractLogError: ${e}`),"agy completed without output"}}handleProcessResult(t,e,s,i,n){if(this.activeEventId===t){if(i&&!s.trim()){o.error(a,`Event ${t} failed: ${i}`);const r=i.includes("RESOURCE_EXHAUSTED")||i.includes("quota")?"The agy API quota has been exhausted. Please try again later.":`agy execution failed: ${i}`;this.callbacks.sendStreamChunk(t,e,r,1,!0),this.callbacks.sendEventResult(t,"failed",i)}else{const c=s.trim();if(c){const r=n.cwd,l=r?this.extractDelta(c,r):c;r&&this.conversationOutputCache.set(r,c),l&&!this.activeEventSentViaTool&&this.callbacks.sendStreamChunk(t,e,l,1,!0)}if(n.cwd){const r=this.readAgyConversationId(n.cwd);r&&r!==n.conversationId&&(this.sessionConversationMap.set(e,r),this.callbacks.persistConversationId(e,r))}this.callbacks.sendEventResult(t,"responded")}this.clearActiveEvent(t),this.drainQueue()}}extractDelta(t,e){const s=this.conversationOutputCache.get(e);return s&&t.startsWith(s)?t.slice(s.length).trim():t}clearActiveEvent(t){const e=t??this.activeEventId;if(e&&(this.completedEventIds.add(e),this.completedEventIds.size>1e3)){const s=Array.from(this.completedEventIds);this.completedEventIds.clear();for(let i=500;i<s.length;i++)this.completedEventIds.add(s[i])}this.activeEventId=null,this.activeEventSessionId=null,this.activeProcess=null,this.activeEventSentViaTool=!1,e&&this.emit("eventDone",e)}drainQueue(){if(this.stopped||this.activeEventId)return;const t=this.eventQueue.shift();t&&(o.info(a,`Draining queued event ${t.event_id}`),this.processEvent(t))}}export{X as AgyAdapter};
12
+ `)}spawnAgyPrint(t,e,s,i){const n=this.buildPrintArgs(s,i),c={...process.env,...this.config.env??{}};o.info(a,`Spawning: agy ${n.map(d=>d.includes(" ")?`"${d}"`:d).join(" ")}`);const r=this.getLatestLogFilePath(),l=$(this.config.command,n,{cwd:i.cwd,env:c,stdio:["pipe","pipe","pipe"],detached:!0});this.activeProcess=l;const h=[],u=[];l.stdout?.on("data",d=>{h.push(d)}),l.stderr?.on("data",d=>{u.push(d)}),l.on("error",d=>{o.error(a,`Process spawn error for event ${t}: ${d.message}`),this.handleProcessResult(t,e,"",`spawn error: ${d.message}`,i)}),l.on("close",d=>{if(o.info(a,`Process exited for event ${t} with code ${d}`),this.activeEventId!==t)return;const v=Buffer.concat(h).toString("utf-8"),P=Buffer.concat(u).toString("utf-8");if(d!==0){const S=P.trim()||`process exited with code ${d}`;this.handleProcessResult(t,e,v,S,i)}else v.trim()?this.handleProcessResult(t,e,v,"",i):this.activeEventSentViaTool?this.handleProcessResult(t,e,v,"",i):setTimeout(()=>{const S=this.extractLogError(r);this.handleProcessResult(t,e,v,S,i)},300)}),l.stdin?.end()}buildPrintArgs(t,e){const s=[];return e.modelId&&s.push("--model",e.modelId),e.cwd&&s.push("--add-dir",e.cwd),s.push("--dangerously-skip-permissions"),e.conversationId&&s.push("--conversation",e.conversationId),s.push("-p",t),s}readAgyConversationId(t){try{if(E(A)){const e=JSON.parse(_(A,"utf-8"));return e[D(t)]||e[t]||void 0}}catch(e){o.debug(a,`readAgyConversationId: ${e}`)}}getLatestLogFilePath(){try{if(!E(m))return null;const t=b(m).filter(e=>e.startsWith("cli-")&&e.endsWith(".log")).map(e=>({name:e,path:p(m,e),mtime:w(p(m,e)).mtimeMs})).sort((e,s)=>s.mtime-e.mtime);return t.length>0?t[0].path:null}catch{return null}}extractLogError(t){try{const e=this.getLatestLogFilePath();if(!e)return o.info(a,"extractLogError: no log files found"),"agy completed without output";const s=_(e,"utf-8");if(!s)return o.info(a,`extractLogError: log file is empty: ${e}`),"agy completed without output";const i=s.split(`
13
+ `).filter(n=>n.startsWith("E")).map(n=>{const c=n.indexOf("] ");return c>=0?n.slice(c+2):n}).filter(n=>n.length>0);if(i.length>0){const n=i[i.length-1];return o.info(a,`Extracted error from agy log: ${n.slice(0,120)}`),n.length>500?n.slice(0,500)+"...":n}return"agy completed without output (possible auth or quota issue)"}catch(e){return o.info(a,`extractLogError: ${e}`),"agy completed without output"}}handleProcessResult(t,e,s,i,n){if(this.activeEventId===t){if(i&&!s.trim()){o.error(a,`Event ${t} failed: ${i}`);const r=i.includes("RESOURCE_EXHAUSTED")||i.includes("quota")?"The agy API quota has been exhausted. Please try again later.":`agy execution failed: ${i}`;this.callbacks.sendStreamChunk(t,e,r,1,!0),this.callbacks.sendEventResult(t,"failed",i)}else{const c=s.trim();if(c){const r=n.cwd,l=r?this.extractDelta(c,r):c;r&&this.conversationOutputCache.set(r,c),l&&!this.activeEventSentViaTool&&this.callbacks.sendStreamChunk(t,e,l,1,!0)}if(n.cwd){const r=this.readAgyConversationId(n.cwd);r&&r!==n.conversationId&&(this.sessionConversationMap.set(e,r),this.callbacks.persistConversationId(e,r))}this.callbacks.sendEventResult(t,"responded")}this.clearActiveEvent(t),this.drainQueue()}}extractDelta(t,e){const s=this.conversationOutputCache.get(e);return s&&t.startsWith(s)?t.slice(s.length).trim():t}clearActiveEvent(t){const e=t??this.activeEventId;if(e&&(this.completedEventIds.add(e),this.completedEventIds.size>1e3)){const s=Array.from(this.completedEventIds);this.completedEventIds.clear();for(let i=500;i<s.length;i++)this.completedEventIds.add(s[i])}this.activeEventId=null,this.activeEventSessionId=null,this.activeProcess=null,this.activeEventSentViaTool=!1,e&&this.emit("eventDone",e)}drainQueue(){if(this.stopped||this.activeEventId)return;const t=this.eventQueue.shift();t&&(o.info(a,`Draining queued event ${t.event_id}`),this.processEvent(t))}}export{tt as AgyAdapter};
@@ -1,6 +1,6 @@
1
- import{execFileSync as I}from"node:child_process";import{stat as _}from"node:fs/promises";import{resolveCommandPath as w,spawnCommand as M,killProcessGroup as k}from"../../core/runtime/spawn.js";import{createInterface as A}from"node:readline";import{EventEmitter as b}from"node:events";import{join as E,resolve as C}from"node:path";import{homedir as T}from"node:os";import{fileURLToPath as P}from"node:url";import{InternalApiServer as $}from"../../core/mcp/internal-api-server.js";import{IdentityInjector as x}from"../shared/identity-injector.js";import{syncDefaultSkillsToDir as R}from"../../default-skills/index.js";import{buildSimpleProbeReport as N}from"../shared/probe-util.js";import{getCachedCodeWhaleModels as D,primeCodeWhaleModels as L}from"./model-list.js";import{log as n}from"../../core/log/index.js";import{SessionBindingStore as O}from"../../core/persistence/session-binding-store.js";const v=120*1e3;function j(f){try{return process.kill(f,0),!0}catch(e){return e.code==="EPERM"}}class te extends b{type="codewhale";config;callbacks;alive=!1;stopped=!1;internalApi=null;deepSeekSessionId=null;activeEventId=null;activeSessionId=null;chunkSeq=0;activeClientMsgId=null;idleTimer=null;activeProcess=null;identity;bindingStore=null;aibotSessionId="";cwd;lastUsage=null;currentModel=null;constructor(e,t){super(),this.config=e,this.callbacks=t,this.identity=new x("codewhale-adapter",t.getAgentProfile);const i=e.options??{};if(this.aibotSessionId=String(i.aibotSessionId??"").trim(),this.bindingStore=i.bindingStore instanceof O?i.bindingStore:null,this.cwd=this.resolveCwd(),this.bindingStore&&this.aibotSessionId){const o=this.bindingStore.getCodeWhaleThreadId(this.aibotSessionId);o&&(this.deepSeekSessionId=o)}}resolveCwd(){if(this.bindingStore&&this.aibotSessionId){const e=this.bindingStore.get(this.aibotSessionId);if(e?.cwd)return e.cwd}return process.cwd()}async start(){this.alive=!0,await this.startInternalApiAndRegisterMcp(),L(this.config.command||"codewhale"),this.notifyBindingReady(),n.info("codewhale-adapter","Ready (exec mode)")}async stop(){this.stopped=!0,this.alive=!1,this.stopComposing(),this.clearIdleTimer(),this.killActiveProcess(),this.internalApi&&(await this.internalApi.stop(),this.internalApi=null)}isAlive(){return this.alive}async createSession(e){const t=this.deepSeekSessionId??`ds-${Date.now()}`;return this.notifyBindingReady(),t}async resumeSession(e,t){}onAgentProfileChanged(){this.identity.onProfileChanged()}async destroySession(e){this.identity.forgetSession(e),this.deepSeekSessionId=null,this.persistSessionId(void 0)}sendPrompt(e){const t=new y(e.adapterSessionId);return this.runMessage(e,t).catch(i=>{t.emitError(i instanceof Error?i:new Error(String(i)))}),t}async cancel(e){this.killActiveProcess()}setPermissionHandler(e){}async ping(e){if(this.stopped||!this.alive)return!1;const t=this.activeProcess;return!(t?.pid&&!j(t.pid))}getStatus(){return{alive:this.alive,busy:this.activeEventId!==null,sessions:this.deepSeekSessionId?1:0}}getActiveEventIds(){return this.activeEventId?[this.activeEventId]:[]}clearActiveEventForShutdown(){this.clearIdleTimer(),this.killActiveProcess(),this.activeEventId=null}getMcpConfig(){if(!this.internalApi)return null;const e=C(P(import.meta.url),"../../../mcp/acp-mcp-server.js");return{name:"grix-connector-tools",command:process.execPath,args:[e,"--api-url",this.internalApi.url]}}async probe(e){const t=this.getStatus();return N(this.config.command||"codewhale",{alive:t.alive,busy:t.busy,started:this.alive},e)}getUsageSnapshot(){return this.lastUsage}async startInternalApiAndRegisterMcp(){try{this.internalApi=new $,this.internalApi.setInvokeHandler(async(r,p,h,d)=>this.callbacks.agentInvoke(r,p,d)),await this.internalApi.start(0),n.info("codewhale-adapter",`Internal API started at ${this.internalApi.url}`);const e=this.getMcpConfig(),t={...process.env,...this.config.env},i=typeof t.PATH=="string"?t.PATH:void 0,o=w(this.config.command||"codewhale",i);try{I(o,["mcp","remove",e.name],{env:t,timeout:1e4,stdio:"ignore"})}catch{}const s=["mcp","add",e.name,"--command",e.command];for(const r of e.args)s.push("--arg",r);I(o,s,{env:t,timeout:1e4,stdio:"ignore"}),n.info("codewhale-adapter",`Registered MCP server: ${e.name}`);const a=E(T(),".codewhale","skills"),c=R(a);c.length>0&&n.info("codewhale-adapter",`Synced connector skills to ${a}: [${c.join(", ")}]`)}catch(e){n.warn("codewhale-adapter",`Failed to register MCP tools (non-fatal): ${e instanceof Error?e.message:String(e)}`)}}getSupportedCommands(){return[{name:"status",description:"Show session and working directory status"}]}async execCommand(e,t,i){return e==="status"?{status:"ok",message:`Session: ${this.deepSeekSessionId??"none"}, CWD: ${this.cwd}`,data:{sessionId:this.deepSeekSessionId,cwd:this.cwd,alive:this.alive}}:{status:"unsupported",message:`Unknown command: ${e}`}}async handleLocalAction(e){const t=e.action_type??"",i=e.params??{};switch(t){case"get_context":return this.callbacks.sendLocalActionResult(e.action_id,"ok",{sessionId:this.deepSeekSessionId,cwd:this.cwd,model:this.currentModel,model_id:this.currentModel,available_models:this.availableModelsMeta()}),{handled:!0,kind:"get_context"};case"set_model":{const o=String(i.model_id??"").trim();return o?(this.currentModel=o,this.callbacks.sendLocalActionResult(e.action_id,"ok",{outcome:"model_set",modelId:o,model_id:o,available_models:this.availableModelsMeta()}),n.info("codewhale-adapter",`Model set to: ${o}`),{handled:!0,kind:"set_model"}):(this.callbacks.sendLocalActionResult(e.action_id,"failed",void 0,"invalid_params","model_id is required"),{handled:!0,kind:"set_model"})}default:return{handled:!1,kind:""}}}availableModelsMeta(){return D(this.config.command||"codewhale").map(e=>({id:e.id,displayName:e.displayName}))}deliverInboundEvent(e){const t=e.content;if(this.activeEventId){n.info("codewhale-adapter",`Event ${e.event_id}: rejected, busy with ${this.activeEventId}`),this.callbacks.sendEventResult(e.event_id,"failed","agent busy");return}this.startNewMessage(e,t)}deliverStopEvent(e,t){this.activeEventId===e&&(this.killActiveProcess(),this.callbacks.sendEventResult(e,"canceled","stopped by user"),this.clearActive())}startNewMessage(e,t){this.activeEventId=e.event_id,this.activeSessionId=e.session_id,this.chunkSeq=0,this.activeClientMsgId=`ds-${Date.now()}-${Math.random().toString(36).slice(2,8)}`,this.startComposing();const i={adapterSessionId:this.deepSeekSessionId??"",text:t,contextMessages:e.context_messages_json?JSON.parse(e.context_messages_json).map(s=>({senderId:s.sender_id??"unknown",content:s.content})):void 0},o=new y(this.deepSeekSessionId??"");this.runMessage(i,o,e.event_id,e.session_id).catch(s=>{n.error("codewhale-adapter",`Message failed: ${s}`),this.callbacks.sendEventResult(e.event_id,"failed",s instanceof Error?s.message:String(s)),this.clearActive()}),this.resetIdleTimer(e.event_id)}buildExecArgs(e){const t=["exec","--output-format","stream-json"];return this.currentModel&&t.push("--model",this.currentModel),this.deepSeekSessionId&&t.push("--resume",this.deepSeekSessionId),t.push("--",e),t}async runMessage(e,t,i,o){let s=e.text;e.contextMessages&&e.contextMessages.length>0&&(s=`Conversation context:
1
+ import{execFileSync as w}from"node:child_process";import{stat as C}from"node:fs/promises";import{resolveCommandPath as I,spawnCommand as _,killProcessGroup as k}from"../../core/runtime/spawn.js";import{createInterface as M}from"node:readline";import{EventEmitter as b}from"node:events";import{join as x,resolve as A}from"node:path";import{homedir as E}from"node:os";import{fileURLToPath as P}from"node:url";import{InternalApiServer as T}from"../../core/mcp/internal-api-server.js";import{IdentityInjector as $}from"../shared/identity-injector.js";import{syncDefaultSkillsToDir as R}from"../../default-skills/index.js";import{buildSimpleProbeReport as N}from"../shared/probe-util.js";import{getCachedCodeWhaleModels as U,primeCodeWhaleModels as D}from"./model-list.js";import{writeCodeWhaleGrixConfig as L}from"./codewhale-provider-config.js";import{log as n}from"../../core/log/index.js";import{SessionBindingStore as O}from"../../core/persistence/session-binding-store.js";const v=120*1e3;function j(g){try{return process.kill(g,0),!0}catch(e){return e.code==="EPERM"}}class se extends b{type="codewhale";config;callbacks;alive=!1;stopped=!1;internalApi=null;deepSeekSessionId=null;activeEventId=null;activeSessionId=null;chunkSeq=0;activeClientMsgId=null;idleTimer=null;activeProcess=null;identity;bindingStore=null;aibotSessionId="";cwd;provider;agentName="";grixConfigPath="";lastUsage=null;currentModel=null;constructor(e,t){super(),this.config=e,this.callbacks=t,this.identity=new $("codewhale-adapter",t.getAgentProfile);const i=e.options??{};if(this.aibotSessionId=String(i.aibotSessionId??"").trim(),this.bindingStore=i.bindingStore instanceof O?i.bindingStore:null,this.provider=i.provider,this.agentName=String(i.agentName??"").trim(),this.cwd=this.resolveCwd(),this.bindingStore&&this.aibotSessionId){const o=this.bindingStore.getCodeWhaleThreadId(this.aibotSessionId);o&&(this.deepSeekSessionId=o)}}resolveCwd(){if(this.bindingStore&&this.aibotSessionId){const e=this.bindingStore.get(this.aibotSessionId);if(e?.cwd)return e.cwd}return process.cwd()}async start(){this.alive=!0,await this.applyGrixProviderConfig(),await this.startInternalApiAndRegisterMcp(),D(this.config.command||"codewhale"),this.notifyBindingReady(),n.info("codewhale-adapter","Ready (exec mode)")}async applyGrixProviderConfig(){if(!this.provider){this.grixConfigPath="";return}try{this.grixConfigPath=await L(this.agentName,this.provider)??"",this.grixConfigPath?n.info("codewhale-adapter",`Grix provider config written to ${this.grixConfigPath}`):n.warn("codewhale-adapter","provider config incomplete (baseUrl/model required); Grix provider not applied")}catch(e){this.grixConfigPath="",n.warn("codewhale-adapter",`Failed to write Grix provider config: ${e instanceof Error?e.message:String(e)}`)}}async stop(){this.stopped=!0,this.alive=!1,this.stopComposing(),this.clearIdleTimer(),this.killActiveProcess(),this.internalApi&&(await this.internalApi.stop(),this.internalApi=null)}isAlive(){return this.alive}async createSession(e){const t=this.deepSeekSessionId??`ds-${Date.now()}`;return this.notifyBindingReady(),t}async resumeSession(e,t){}onAgentProfileChanged(){this.identity.onProfileChanged()}async destroySession(e){this.identity.forgetSession(e),this.deepSeekSessionId=null,this.persistSessionId(void 0)}sendPrompt(e){const t=new y(e.adapterSessionId);return this.runMessage(e,t).catch(i=>{t.emitError(i instanceof Error?i:new Error(String(i)))}),t}async cancel(e){this.killActiveProcess()}setPermissionHandler(e){}async ping(e){if(this.stopped||!this.alive)return!1;const t=this.activeProcess;return!(t?.pid&&!j(t.pid))}getStatus(){return{alive:this.alive,busy:this.activeEventId!==null,sessions:this.deepSeekSessionId?1:0}}getActiveEventIds(){return this.activeEventId?[this.activeEventId]:[]}clearActiveEventForShutdown(){this.clearIdleTimer(),this.killActiveProcess(),this.activeEventId=null}getMcpConfig(){if(!this.internalApi)return null;const e=A(P(import.meta.url),"../../../mcp/acp-mcp-server.js");return{name:"grix-connector-tools",command:process.execPath,args:[e,"--api-url",this.internalApi.url]}}async probe(e){const t=this.getStatus();return N(this.config.command||"codewhale",{alive:t.alive,busy:t.busy,started:this.alive},e)}getUsageSnapshot(){return this.lastUsage}async startInternalApiAndRegisterMcp(){try{this.internalApi=new T,this.internalApi.setInvokeHandler(async(r,u,h,d)=>this.callbacks.agentInvoke(r,u,d)),await this.internalApi.start(0),n.info("codewhale-adapter",`Internal API started at ${this.internalApi.url}`);const e=this.getMcpConfig(),t={...process.env,...this.config.env},i=typeof t.PATH=="string"?t.PATH:void 0,o=I(this.config.command||"codewhale",i);try{w(o,["mcp","remove",e.name],{env:t,timeout:1e4,stdio:"ignore"})}catch{}const s=["mcp","add",e.name,"--command",e.command];for(const r of e.args)s.push("--arg",r);w(o,s,{env:t,timeout:1e4,stdio:"ignore"}),n.info("codewhale-adapter",`Registered MCP server: ${e.name}`);const a=x(E(),".codewhale","skills"),c=R(a);c.length>0&&n.info("codewhale-adapter",`Synced connector skills to ${a}: [${c.join(", ")}]`)}catch(e){n.warn("codewhale-adapter",`Failed to register MCP tools (non-fatal): ${e instanceof Error?e.message:String(e)}`)}}getSupportedCommands(){return[{name:"status",description:"Show session and working directory status"}]}async execCommand(e,t,i){return e==="status"?{status:"ok",message:`Session: ${this.deepSeekSessionId??"none"}, CWD: ${this.cwd}`,data:{sessionId:this.deepSeekSessionId,cwd:this.cwd,alive:this.alive}}:{status:"unsupported",message:`Unknown command: ${e}`}}async handleLocalAction(e){const t=e.action_type??"",i=e.params??{};switch(t){case"get_context":return this.callbacks.sendLocalActionResult(e.action_id,"ok",{sessionId:this.deepSeekSessionId,cwd:this.cwd,model:this.currentModel,model_id:this.currentModel,available_models:this.availableModelsMeta()}),{handled:!0,kind:"get_context"};case"set_model":{const o=String(i.model_id??"").trim();return o?(this.currentModel=o,this.callbacks.sendLocalActionResult(e.action_id,"ok",{outcome:"model_set",modelId:o,model_id:o,available_models:this.availableModelsMeta()}),n.info("codewhale-adapter",`Model set to: ${o}`),{handled:!0,kind:"set_model"}):(this.callbacks.sendLocalActionResult(e.action_id,"failed",void 0,"invalid_params","model_id is required"),{handled:!0,kind:"set_model"})}default:return{handled:!1,kind:""}}}availableModelsMeta(){return U(this.config.command||"codewhale").map(e=>({id:e.id,displayName:e.displayName}))}deliverInboundEvent(e){const t=e.content;if(this.activeEventId){n.info("codewhale-adapter",`Event ${e.event_id}: rejected, busy with ${this.activeEventId}`),this.callbacks.sendEventResult(e.event_id,"failed","agent busy");return}this.startNewMessage(e,t)}deliverStopEvent(e,t){this.activeEventId===e&&(this.killActiveProcess(),this.callbacks.sendEventResult(e,"canceled","stopped by user"),this.clearActive())}startNewMessage(e,t){this.activeEventId=e.event_id,this.activeSessionId=e.session_id,this.chunkSeq=0,this.activeClientMsgId=`ds-${Date.now()}-${Math.random().toString(36).slice(2,8)}`,this.startComposing();const i={adapterSessionId:this.deepSeekSessionId??"",text:t,contextMessages:e.context_messages_json?JSON.parse(e.context_messages_json).map(s=>({senderId:s.sender_id??"unknown",content:s.content})):void 0},o=new y(this.deepSeekSessionId??"");this.runMessage(i,o,e.event_id,e.session_id).catch(s=>{n.error("codewhale-adapter",`Message failed: ${s}`),this.callbacks.sendEventResult(e.event_id,"failed",s instanceof Error?s.message:String(s)),this.clearActive()}),this.resetIdleTimer(e.event_id)}buildExecArgs(e){const t=[];if(this.grixConfigPath){t.push("--config",this.grixConfigPath);const i=this.provider?.baseUrl?.trim();i&&t.push("--base-url",i)}return t.push("exec","--output-format","stream-json"),this.currentModel&&t.push("--model",this.currentModel),this.deepSeekSessionId&&t.push("--resume",this.deepSeekSessionId),t.push("--",e),t}async runMessage(e,t,i,o){let s=e.text;e.contextMessages&&e.contextMessages.length>0&&(s=`Conversation context:
2
2
  ${e.contextMessages.map(l=>`[${l.senderId??"unknown"}]: ${l.content}`).join(`
3
3
  `)}
4
4
 
5
5
  Latest user message:
6
- ${s}`),s=this.identity.injectOnce(e.adapterSessionId,s);const a=this.config.command||"codewhale",c=this.buildExecArgs(s),r={...process.env,...this.config.env},p=w(a,typeof r.PATH=="string"?r.PATH:void 0);n.info("codewhale-adapter",`Spawning: ${p} ${c.slice(0,5).join(" ")}...`);try{if(!(await _(this.cwd)).isDirectory())throw new Error(`Bound path is not a directory: ${this.cwd}`)}catch(d){throw String(d?.code??"")==="ENOENT"?new Error(`Bound directory does not exist: ${this.cwd}. Please rebind with /grix open <valid-directory>.`):d}const h=M(p,c,{cwd:this.cwd,env:r}).process;return this.activeProcess=h,h.stderr?.on("data",d=>{const l=d.toString().trim();l&&n.info("codewhale-adapter",`[codewhale stderr] ${l}`)}),new Promise((d,l)=>{let m=!1,g="";const S=()=>{this.activeProcess=null};h.on("error",u=>{m||(m=!0,S(),l(u))}),h.on("exit",u=>{if(g.trim()&&this.handleOutputLine(g.trim(),i),g="",m){S();return}if(m=!0,S(),u!==0&&i&&this.activeEventId===i){l(new Error(`codewhale exec exited with code ${u}`));return}t.emitDone({status:"completed"}),d()}),A({input:h.stdout}).on("line",u=>{u.trim()&&this.handleOutputLine(u.trim(),i)}),h.stdin?.end()})}handleOutputLine(e,t){let i;try{i=JSON.parse(e)}catch{n.error("codewhale-adapter",`Invalid JSON: ${e.slice(0,200)}`);return}switch(i.type){case"content":{const s=i.content;s&&t&&this.activeEventId===t&&this.activeSessionId&&(this.chunkSeq++,this.callbacks.sendStreamChunk(t,this.activeSessionId,s,this.chunkSeq,!1,this.activeClientMsgId??void 0),this.startComposing(),this.resetIdleTimer(t));break}case"session_capture":{const s=i.content;s&&(this.deepSeekSessionId=s,this.persistSessionId(s),n.info("codewhale-adapter",`Session captured: ${s}`));break}case"metadata":{const s=i.meta;if(s){n.info("codewhale-adapter",`Metadata: model=${s.model}, tokens_in=${s.input_tokens}, tokens_out=${s.output_tokens}`);const a=Number(s.input_tokens??0),c=Number(s.output_tokens??0);if(a>0||c>0){const r=this.lastUsage;this.lastUsage={sampledAt:new Date().toISOString(),turns:(r?.turns??0)+1,total:{input:(r?.total.input??0)+a,output:(r?.total.output??0)+c}}}}break}case"tool_use":{const s=i.name,a=typeof i.input=="string"?i.input:JSON.stringify(i.input??{});s&&t&&this.activeEventId===t&&this.activeSessionId&&(n.info("codewhale-adapter",`Tool use: ${s}`),this.callbacks.sendToolUse(t,this.activeSessionId,s,a),this.resetIdleTimer(t));break}case"tool_result":{const s=i.name,a=i.output;t&&this.activeEventId===t&&this.activeSessionId&&(this.callbacks.sendToolResult(t,this.activeSessionId,s??"unknown",a??""),this.resetIdleTimer(t));break}case"done":{this.handleMessageCompleted(t);break}default:break}}handleMessageCompleted(e){if(this.stopComposing(),e&&this.activeEventId===e){const t=this.activeSessionId??"",i=this.activeClientMsgId??void 0;t&&(this.chunkSeq++,this.callbacks.sendStreamChunk(e,t,"",this.chunkSeq,!0,i)),this.callbacks.sendEventResult(e,"responded"),this.clearActive()}}killActiveProcess(){const e=this.activeProcess;this.activeProcess=null,e?.pid&&(k(e,"SIGTERM"),setTimeout(()=>{if(e.exitCode===null&&e.signalCode===null)try{k(e,"SIGKILL")}catch{}},5e3).unref())}notifyBindingReady(){if(!this.aibotSessionId||!this.cwd)return;const e=this.availableModelsMeta(),t=this.currentModel??e[0]?.id??"";this.callbacks.sendUpdateBindingCard(this.aibotSessionId,"ready",this.cwd,{model_id:t,available_models:e})}persistSessionId(e){!this.bindingStore||!this.aibotSessionId||this.bindingStore.setCodeWhaleThreadId(this.aibotSessionId,e)}startComposing(){}stopComposing(){}resetIdleTimer(e){this.clearIdleTimer(),this.idleTimer=setTimeout(()=>{this.activeEventId===e&&(n.error("codewhale-adapter",`Agent idle for ${v/1e3}s: ${e}`),this.killActiveProcess(),this.callbacks.sendEventResult(e,"failed",`agent idle for ${v/1e3}s`),this.clearActive(),this.emit("stuck"))},v)}clearIdleTimer(){this.idleTimer&&(clearTimeout(this.idleTimer),this.idleTimer=null)}clearActive(){const e=this.activeEventId;this.stopComposing(),this.activeEventId=null,this.activeSessionId=null,this.chunkSeq=0,this.activeClientMsgId=null,this.clearIdleTimer(),e&&this.emit("eventDone",e)}}class y extends b{adapterSessionId;constructor(e){super(),this.adapterSessionId=e}emitDone(e){this.emit("done",e)}emitError(e){if(this.listenerCount("error")===0){n.warn("codewhale-adapter",`Prompt handle error (no listeners): ${e.message}`);return}this.emit("error",e)}async cancel(){}}export{te as CodeWhaleAdapter};
6
+ ${s}`),s=this.identity.injectOnce(o??e.adapterSessionId,s);const a=this.config.command||"codewhale",c=this.buildExecArgs(s),r={...process.env,...this.config.env},u=I(a,typeof r.PATH=="string"?r.PATH:void 0);n.info("codewhale-adapter",`Spawning: ${u} ${c.slice(0,5).join(" ")}...`);try{if(!(await C(this.cwd)).isDirectory())throw new Error(`Bound path is not a directory: ${this.cwd}`)}catch(d){throw String(d?.code??"")==="ENOENT"?new Error(`Bound directory does not exist: ${this.cwd}. Please rebind with /grix open <valid-directory>.`):d}const h=_(u,c,{cwd:this.cwd,env:r}).process;return this.activeProcess=h,h.stderr?.on("data",d=>{const l=d.toString().trim();l&&n.info("codewhale-adapter",`[codewhale stderr] ${l}`)}),new Promise((d,l)=>{let m=!1,f="";const S=()=>{this.activeProcess=null};h.on("error",p=>{m||(m=!0,S(),l(p))}),h.on("exit",p=>{if(f.trim()&&this.handleOutputLine(f.trim(),i),f="",m){S();return}if(m=!0,S(),p!==0&&i&&this.activeEventId===i){l(new Error(`codewhale exec exited with code ${p}`));return}t.emitDone({status:"completed"}),d()}),M({input:h.stdout}).on("line",p=>{p.trim()&&this.handleOutputLine(p.trim(),i)}),h.stdin?.end()})}handleOutputLine(e,t){let i;try{i=JSON.parse(e)}catch{n.error("codewhale-adapter",`Invalid JSON: ${e.slice(0,200)}`);return}switch(i.type){case"content":{const s=i.content;s&&t&&this.activeEventId===t&&this.activeSessionId&&(this.chunkSeq++,this.callbacks.sendStreamChunk(t,this.activeSessionId,s,this.chunkSeq,!1,this.activeClientMsgId??void 0),this.startComposing(),this.resetIdleTimer(t));break}case"session_capture":{const s=i.content;s&&(this.deepSeekSessionId=s,this.persistSessionId(s),n.info("codewhale-adapter",`Session captured: ${s}`));break}case"metadata":{const s=i.meta;if(s){n.info("codewhale-adapter",`Metadata: model=${s.model}, tokens_in=${s.input_tokens}, tokens_out=${s.output_tokens}`);const a=Number(s.input_tokens??0),c=Number(s.output_tokens??0);if(a>0||c>0){const r=this.lastUsage;this.lastUsage={sampledAt:new Date().toISOString(),turns:(r?.turns??0)+1,total:{input:(r?.total.input??0)+a,output:(r?.total.output??0)+c}}}}break}case"tool_use":{const s=i.name,a=typeof i.input=="string"?i.input:JSON.stringify(i.input??{});s&&t&&this.activeEventId===t&&this.activeSessionId&&(n.info("codewhale-adapter",`Tool use: ${s}`),this.callbacks.sendToolUse(t,this.activeSessionId,s,a),this.resetIdleTimer(t));break}case"tool_result":{const s=i.name,a=i.output;t&&this.activeEventId===t&&this.activeSessionId&&(this.callbacks.sendToolResult(t,this.activeSessionId,s??"unknown",a??""),this.resetIdleTimer(t));break}case"done":{this.handleMessageCompleted(t);break}default:break}}handleMessageCompleted(e){if(this.stopComposing(),e&&this.activeEventId===e){const t=this.activeSessionId??"",i=this.activeClientMsgId??void 0;t&&(this.chunkSeq++,this.callbacks.sendStreamChunk(e,t,"",this.chunkSeq,!0,i)),this.callbacks.sendEventResult(e,"responded"),this.clearActive()}}killActiveProcess(){const e=this.activeProcess;this.activeProcess=null,e?.pid&&(k(e,"SIGTERM"),setTimeout(()=>{if(e.exitCode===null&&e.signalCode===null)try{k(e,"SIGKILL")}catch{}},5e3).unref())}notifyBindingReady(){if(!this.aibotSessionId||!this.cwd)return;const e=this.availableModelsMeta(),t=this.currentModel??e[0]?.id??"";this.callbacks.sendUpdateBindingCard(this.aibotSessionId,"ready",this.cwd,{model_id:t,available_models:e})}persistSessionId(e){!this.bindingStore||!this.aibotSessionId||this.bindingStore.setCodeWhaleThreadId(this.aibotSessionId,e)}startComposing(){}stopComposing(){}resetIdleTimer(e){this.clearIdleTimer(),this.idleTimer=setTimeout(()=>{this.activeEventId===e&&(n.error("codewhale-adapter",`Agent idle for ${v/1e3}s: ${e}`),this.killActiveProcess(),this.callbacks.sendEventResult(e,"failed",`agent idle for ${v/1e3}s`),this.clearActive(),this.emit("stuck"))},v)}clearIdleTimer(){this.idleTimer&&(clearTimeout(this.idleTimer),this.idleTimer=null)}clearActive(){const e=this.activeEventId;this.stopComposing(),this.activeEventId=null,this.activeSessionId=null,this.chunkSeq=0,this.activeClientMsgId=null,this.clearIdleTimer(),e&&this.emit("eventDone",e)}}class y extends b{adapterSessionId;constructor(e){super(),this.adapterSessionId=e}emitDone(e){this.emit("done",e)}emitError(e){if(this.listenerCount("error")===0){n.warn("codewhale-adapter",`Prompt handle error (no listeners): ${e.message}`);return}this.emit("error",e)}async cancel(){}}export{se as CodeWhaleAdapter};
@@ -0,0 +1,2 @@
1
+ import{mkdir as c,writeFile as d}from"node:fs/promises";import{homedir as f}from"node:os";import{join as n}from"node:path";const l="openai";function u(e){return(e??"default").trim().toLowerCase().replace(/[^a-z0-9._-]+/g,"-").replace(/^-+|-+$/g,"")||"default"}function s(e){return n(f(),".grix","runtime","native-provider",u(e),"codewhale")}async function _(e,r){const a=r.baseUrl?.trim(),t=r.model?.trim();if(!a||!t)return;const o=s(e),i=n(o,"config.toml"),m=[`provider = ${JSON.stringify(l)}`,`default_text_model = ${JSON.stringify(t)}`,'auth_mode = "api_key"',""].join(`
2
+ `);return await c(o,{recursive:!0}),await d(i,m,{encoding:"utf8",mode:384}),i}export{s as codewhaleGrixConfigDir,_ as writeCodeWhaleGrixConfig};
@@ -1,5 +1,5 @@
1
- import{EventEmitter as u}from"node:events";import{log as h}from"../../core/log/index.js";const f=3e4,d=3e3,p=3e4;class w extends u{baseUrl="";directory="";abortController=null;closed=!1;sseConnected=!1;async connect(e,t){this.baseUrl=e,this.directory=t??"",await this.subscribeEvents(),h.info("opencode-transport",`connected to ${e}`)}async subscribeEvents(){if(this.closed)return;this.abortController=new AbortController;const e=new URL("/event",this.baseUrl);this.directory&&e.searchParams.set("directory",this.directory);try{const t=await fetch(e.toString(),{method:"GET",headers:{Accept:"text/event-stream"},signal:this.abortController.signal});if(!t.ok||!t.body)throw new Error(`SSE connect failed: ${t.status}`);this.sseConnected=!0;const s=t.body.getReader(),r=new TextDecoder;let n="";for(;!this.closed;){const{done:c,value:i}=await s.read();if(c)break;n+=r.decode(i,{stream:!0});const o=n.split(`
1
+ import{EventEmitter as u}from"node:events";import{log as l}from"../../core/log/index.js";const f=3e4,d=3e3,p=3e4;class w extends u{baseUrl="";directory="";abortController=null;closed=!1;sseConnected=!1;async connect(e,t){this.baseUrl=e,this.directory=t??"",await this.subscribeEvents(),l.info("opencode-transport",`connected to ${e}`)}async subscribeEvents(){if(this.closed)return;this.abortController=new AbortController;const e=new URL("/event",this.baseUrl);this.directory&&e.searchParams.set("directory",this.directory);const t=await fetch(e.toString(),{method:"GET",headers:{Accept:"text/event-stream"},signal:this.abortController.signal});if(!t.ok||!t.body)throw new Error(`SSE connect failed: ${t.status}`);this.sseConnected=!0,this.readSseStream(t.body.getReader())}async readSseStream(e){const t=new TextDecoder;let n="";try{for(;!this.closed;){const{done:s,value:r}=await e.read();if(s)break;n+=t.decode(r,{stream:!0});const i=n.split(`
2
2
 
3
- `);n=o.pop()??"";for(const a of o){const l=this.parseSseFrame(a);l&&this.emit("event",l)}}}catch(t){if(this.closed)return;const s=t instanceof Error?t.message:String(t);if(s.includes("abort"))return;h.warn("opencode-transport",`SSE error: ${s}, reconnecting...`),await this.reconnectSse()}finally{this.sseConnected=!1}}parseSseFrame(e){let t="",s=[];for(const r of e.split(`
4
- `))r.startsWith("event:")?t=r.slice(6).trim():r.startsWith("data:")&&s.push(r.slice(5).trimStart());if(s.length===0)return null;try{const r=s.join(`
5
- `),n=JSON.parse(r);return t&&n.type!==t&&(n.type=t),n}catch{return null}}async reconnectSse(){if(this.closed)return;const e=d+Math.random()*d;await new Promise(t=>setTimeout(t,Math.min(e,p))),this.closed||await this.subscribeEvents()}async request(e,t,s){if(this.closed)throw new Error("transport closed");const r=new URL(t,this.baseUrl);this.directory&&r.searchParams.set("directory",this.directory);const n=new AbortController,c=setTimeout(()=>n.abort(),f);try{const i={method:e,headers:{"Content-Type":"application/json"},signal:n.signal};s!==void 0&&(i.body=JSON.stringify(s));const o=await fetch(r.toString(),i);if(o.status===204)return;const a=await o.json();if(!o.ok)throw new Error(`REST ${e} ${t}: ${o.status} ${JSON.stringify(a)}`);return a}finally{clearTimeout(c)}}async createSession(e){return this.request("POST","/session",e??{})}async getSession(e){return this.request("GET",`/session/${e}`)}async deleteSession(e){await this.request("DELETE",`/session/${e}`)}async sendPromptAsync(e,t){await this.request("POST",`/session/${e}/prompt_async`,t)}async abortSession(e){await this.request("POST",`/session/${e}/abort`)}async respondPermission(e,t,s){await this.request("POST",`/session/${e}/permissions/${t}`,{response:s})}async listProviders(){return(await this.request("GET","/config/providers"))?.providers??[]}async healthCheck(){try{return(await fetch(new URL("/session",this.baseUrl).toString(),{method:"GET",headers:{"Content-Type":"application/json"},signal:AbortSignal.timeout(3e3)})).ok}catch{return!1}}disconnect(){this.closed=!0,this.abortController&&(this.abortController.abort(),this.abortController=null),this.sseConnected=!1,this.removeAllListeners()}get isConnected(){return this.sseConnected&&!this.closed}}export{w as OpenCodeTransport};
3
+ `);n=i.pop()??"";for(const a of i){const o=this.parseSseFrame(a);if(o)try{this.emit("event",o)}catch(c){const h=c instanceof Error?c.message:String(c);l.warn("opencode-transport",`event handler failed (${o.type}): ${h}`)}}}}catch(s){if(this.closed)return;const r=s instanceof Error?s.message:String(s);if(r.includes("abort"))return;l.warn("opencode-transport",`SSE error: ${r}, reconnecting...`),await this.reconnectSse()}finally{this.sseConnected=!1}}parseSseFrame(e){let t="",n=[];for(const s of e.split(`
4
+ `))s.startsWith("event:")?t=s.slice(6).trim():s.startsWith("data:")&&n.push(s.slice(5).trimStart());if(n.length===0)return null;try{const s=n.join(`
5
+ `),r=JSON.parse(s);if(t&&r.type!==t&&(r.type=t),r&&typeof r.properties=="object"&&r.properties!==null){const{properties:i,...a}=r;return{...a,...i}}return r}catch{return null}}async reconnectSse(){if(this.closed)return;const e=d+Math.random()*d;if(await new Promise(t=>setTimeout(t,Math.min(e,p))),!this.closed)try{await this.subscribeEvents()}catch(t){if(this.closed)return;l.warn("opencode-transport",`SSE reconnect failed: ${t instanceof Error?t.message:String(t)}`),this.reconnectSse()}}async request(e,t,n){if(this.closed)throw new Error("transport closed");const s=new URL(t,this.baseUrl);this.directory&&s.searchParams.set("directory",this.directory);const r=new AbortController,i=setTimeout(()=>r.abort(),f);try{const a={method:e,headers:{"Content-Type":"application/json"},signal:r.signal};n!==void 0&&(a.body=JSON.stringify(n));const o=await fetch(s.toString(),a);if(o.status===204)return;const c=await o.json();if(!o.ok)throw new Error(`REST ${e} ${t}: ${o.status} ${JSON.stringify(c)}`);return c}finally{clearTimeout(i)}}async createSession(e){return this.request("POST","/session",e??{})}async getSession(e){return this.request("GET",`/session/${e}`)}async deleteSession(e){await this.request("DELETE",`/session/${e}`)}async sendPromptAsync(e,t){await this.request("POST",`/session/${e}/prompt_async`,t)}async abortSession(e){await this.request("POST",`/session/${e}/abort`)}async respondPermission(e,t,n){await this.request("POST",`/session/${e}/permissions/${t}`,{response:n})}async listProviders(){return(await this.request("GET","/config/providers"))?.providers??[]}async healthCheck(){try{return(await fetch(new URL("/session",this.baseUrl).toString(),{method:"GET",headers:{"Content-Type":"application/json"},signal:AbortSignal.timeout(3e3)})).ok}catch{return!1}}disconnect(){this.closed=!0,this.abortController&&(this.abortController.abort(),this.abortController=null),this.sseConnected=!1,this.removeAllListeners()}get isConnected(){return this.sseConnected&&!this.closed}}export{w as OpenCodeTransport};
@@ -1,6 +1,6 @@
1
1
  import x from"node:path";import{realpath as j,stat as H}from"node:fs/promises";import{tmpdir as V}from"node:os";import{randomUUID as M}from"node:crypto";import{ConnectionManager as Y}from"../core/aibot/index.js";import{ClaudeAdapter as y}from"../adapter/claude/index.js";import{CodexAdapter as J}from"../adapter/codex/index.js";import{PiAdapter as X}from"../adapter/pi/index.js";import{AcpAdapter as A}from"../adapter/acp/index.js";import{OpenHumanAdapter as Z}from"../adapter/openhuman/index.js";import{CursorAdapter as I}from"../adapter/cursor/index.js";import{CodeWhaleAdapter as U}from"../adapter/codewhale/index.js";import{OpenCodeAdapter as ee}from"../adapter/opencode/index.js";import{AgyAdapter as O}from"../adapter/agy/index.js";import{getCachedAgyModels as te}from"../adapter/agy/model-list.js";import{getCachedAgyQuotaInfo as ie}from"../adapter/agy/quota.js";import{LOCAL_ACTION_ERROR_CODES as T,LOCAL_ACTION_TYPES as b,SESSION_CONTROL_ERROR_CODES as g,SESSION_CONTROL_VERBS as p,SESSION_MODE_IDS as C}from"../adapter/claude/protocol-contract.js";import{parseClaudeSessionUsage as ne}from"../adapter/claude/usage-parser.js";import{resolveSkillScanMode as se}from"../adapter/claude/skill-scanner.js";import{buildReportedSkills as oe}from"../default-skills/index.js";import{fetchAvailableModels as D,getCachedModels as Q,readSettingsEnv as re}from"../adapter/claude/model-list.js";import{parseAcpSessionUsage as ae}from"../adapter/acp/usage-parser.js";import{applyProxyEnv as de,getProxyManager as ce,relayHostsForClientType as le,buildGatewayRoute as ue}from"../core/proxy/index.js";import{buildProviderEnv as he}from"../core/config/provider-env.js";import{sweepNativeProviderResidue as ge}from"./native-provider-sweep.js";import{parseCodexSessionUsage as me}from"../adapter/codex/usage-parser.js";import{readCodexProviderSettings as pe}from"../adapter/codex/codex-trust.js";import{scanCodexSessions as fe}from"../adapter/codex/session-scanner.js";import{scanClaudeSessions as _e}from"../adapter/claude/session-scanner.js";import{scanAcpSessions as ve}from"../adapter/acp/session-scanner.js";import{parsePiSessionUsage as Se}from"../adapter/pi/usage-parser.js";import{SessionScanCache as L,resolveCodexLeafDirs as be,resolveClaudeLeafDirs as we,resolveAcpLeafDirs as Ce}from"./session-scan-cache.js";import{log as u,ConversationLog as Ae,AgentApiPacketLog as ke,BridgeEventLog as Re}from"../core/log/index.js";import{RevokeHandler as Ee}from"./revoke-handler.js";import{AdapterPool as $e,PoolFullError as ye}from"./adapter-pool.js";import{parseSessionControlCommand as Te,handleSessionControlCommand as q,handleSessionControlLocalAction as xe}from"./session-controller.js";import{handleAcpSetModel as W,handleAcpSetMode as N,resolveAcpInitialDefaults as Le}from"./acp-toolbar-persist.js";import{SessionBindingStore as Pe}from"../core/persistence/session-binding-store.js";import{handleFileListAction as He,handleCreateFolderAction as Me,serveLocalFile as Ie,realHomeDir as K}from"../core/files/index.js";import{getMachineName as De}from"../core/util/index.js";import{uploadReplyFileToAgentMedia as Qe}from"../core/protocol/agent-api-media.js";import{ActiveEventStore as Be}from"../core/persistence/active-event-store.js";import{DEFAULT_CONNECTOR_RUNTIME_CONFIG as Fe,applyConnectorRuntimeConfigPatch as Ue,extractConnectorRuntimeConfigPatch as Oe}from"./runtime-config.js";import{SendController as qe}from"./send-controller.js";import{queryProviderQuota as z}from"../core/provider-quota/index.js";import{queryKiroQuota as B}from"../core/provider-quota/kiro.js";import{buildToolUseCard as E,buildToolResultCard as $,buildLocalGrixCardLink as We}from"./tool-card-utils.js";import{DeferredEventManager as Ne}from"./deferred-events.js";import{buildAgentProbeResult as Ke,PROBE_CACHE_TTL_STATIC_MS as ze,PROBE_CACHE_TTL_FULL_MS as Ge}from"./probe-helper.js";const je=600*1e3,Ve=60*1e3,G=30*1e3,Ye=10*1e3,P=new Set(["claude","acp","agy","cursor","codex"]),Je=new Set(["claude","codex","cursor","codewhale","opencode","pi","openhuman","agy","acp"]),F=3;class jt{config;name;aibotHandle;aibotConfig;pool;stopped=!1;revokeHandler=new Ee;sessionBindings=new Map;deferredMgr;sendCtrl=new qe(Fe);bindingStore;globalConfigStore;upgradeTrigger=null;agentDeletedHandler=null;shareSetHandler=null;providerConfigHandler=null;agentProfile={agentName:"",introduction:""};activeEventStore;cachedRateLimits=null;cachedRateLimitsSampledAtMs=null;cachedCodexContextWindow=null;cachedCodexTokenUsage=null;cachedCodexUsageSampledAtMs=null;cachedAcpContextWindow=null;cachedAcpContextWindowSampledAtMs=null;cachedClaudeRateLimitState=null;cachedProviderQuota=null;cachedProviderQuotaSampledAtMs=null;claudeWorkerStatus=new Map;lastReportedSkillsHash="";conversationLog=null;packetLog=null;kiroQuotaTimer=null;eventSessionIndex=new Map;inflightEvents=new Map;restartCount=new Map;selfDrivenSessions=new Set;selfDrivenLabels=new Map;probeCache=new Map;sessionScanCache;isRateLimitsCacheFresh(e){if(!Number.isFinite(e))return!1;const o=Number(e);return o>0&&Date.now()-o<=Ve}async maybeQueryProviderQuota(){if(this.config.aibot.clientType==="kiro"){if(this.isRateLimitsCacheFresh(this.cachedProviderQuotaSampledAtMs)&&this.cachedProviderQuota)return this.cachedProviderQuota;try{const t=await B();return this.cachedProviderQuota=t,this.cachedProviderQuotaSampledAtMs=Date.now(),u.info(this.name,`[provider-quota] kiro queried: success=${t.success}`+(t.balance?` balance=${t.balance.remaining} ${t.balance.unit}`:"")+(t.planName?` plan=${t.planName}`:"")+(t.error?` error=${t.error}`:"")),t}catch(t){return u.warn(this.name,`[provider-quota] kiro query failed: ${t instanceof Error?t.message:String(t)}`),null}}let e=this.config.providerBaseUrl,o=this.config.providerApiKey;if((!e||!o)&&(this.config.adapterType??"acp")==="claude"){const t=re();e||(e=(t.ANTHROPIC_BASE_URL??"").trim()||void 0),o||(o=(t.ANTHROPIC_API_KEY??"").trim()||(t.ANTHROPIC_AUTH_TOKEN??"").trim()||void 0)}if((!e||!o)&&(this.config.adapterType??"acp")==="codex"){const t=pe();!e&&t.baseUrl&&(e=t.baseUrl),!o&&t.apiKey&&(o=t.apiKey)}if(!e||!o)return null;if(this.isRateLimitsCacheFresh(this.cachedProviderQuotaSampledAtMs)&&this.cachedProviderQuota)return this.cachedProviderQuota;try{const t=await z(e,o);return this.cachedProviderQuota=t,this.cachedProviderQuotaSampledAtMs=Date.now(),u.info(this.name,`[provider-quota] queried: provider=${t.provider} success=${t.success}`+(t.tiers.length>0?` tiers=${t.tiers.map(n=>`${n.name}=${n.usedPercent}%`).join(",")}`:"")+(t.balance?` balance=${t.balance.remaining} ${t.balance.unit}`:"")+(t.error?` error=${t.error}`:"")),t}catch(t){return u.warn(this.name,`[provider-quota] query failed: ${t instanceof Error?t.message:String(t)}`),null}}startKiroQuotaTimer(){this.kiroQuotaTimer||(this.kiroQuotaTimer=setInterval(()=>{if(this.stopped){this.stopKiroQuotaTimer();return}this.refreshAndPushKiroQuota().catch(()=>{})},G),u.info(this.name,`[kiro-quota-timer] started (interval=${G}ms)`))}stopKiroQuotaTimer(){this.kiroQuotaTimer&&(clearInterval(this.kiroQuotaTimer),this.kiroQuotaTimer=null,u.info(this.name,"[kiro-quota-timer] stopped"))}async refreshAndPushKiroQuota(){try{const e=await B();this.cachedProviderQuota=e,this.cachedProviderQuotaSampledAtMs=Date.now(),e.success&&this.pushKiroQuotaToBindings(e)}catch{}}pushKiroQuotaToBindings(e){const o=this.providerQuotaToRateLimits(e);for(const[t,n]of this.sessionBindings.entries()){if(!n)continue;const i={provider_quota:e};o&&(i.rate_limits=o);const s=this.cachedAcpContextWindow;s&&("usedPercentage"in s?i.context_window={usedPercentage:s.usedPercentage,remainingPercentage:100-s.usedPercentage}:i.context_window=s),this.aibotHandle.sendUpdateBindingCard({session_id:t,worker_status:"ready",cwd:n,meta:i})}}getFreshClaudeRateLimitState(){const e=this.cachedClaudeRateLimitState;return e&&this.isRateLimitsCacheFresh(e.sampledAt)?e:null}getFreshCodexGlobalRateLimitCache(){const e=this.cachedRateLimits,o=this.cachedCodexContextWindow,t=this.cachedCodexTokenUsage;return{sampledAt:Math.max(this.cachedRateLimitsSampledAtMs??0,this.cachedCodexUsageSampledAtMs??0)||null,rateLimits:e,contextWindow:o,tokenUsage:t,hasData:!!(e||o||t)}}getStatus(){const e=this.pool?.getStatus()??{total:0,ready:0,busy:0};return{name:this.name,agentId:this.config.aibot.agentId,alive:!this.stopped,busy:e.busy>0,exhausted:this.pool?[...this.pool.getAllSlots()].some(o=>o.respawn.exhausted):!1,adapterType:this.config.adapterType??"acp",clientType:this.config.aibot.clientType,pool:e}}hasPendingWork(){return this.pool?.hasPendingWork()??!1}relayEnvStale=!1;relayEnvSettledHandler;async recycleAdaptersForRelayChange(){if(!this.pool)return this.relayEnvStale=!1,!0;if(this.hasPendingWork())return this.relayEnvStale=!0,u.info(this.name,"relay changed but agent is busy; adapters keep the old env until the next message (marked stale)"),!1;const e=[...this.pool.getAllSlots()];let o=0,t=!1;for(const n of e){if(this.hasPendingWork())return this.relayEnvStale=!0,u.info(this.name,"new work arrived while recycling; remaining adapters stay stale until the next message"),!1;let i=!1;await this.pool.removeSlot(n.sessionId).catch(s=>{t=!0,i=!0,u.warn(this.name,`failed to recycle adapter slot ${n.sessionId} after relay change: ${s}`)}),i||(o+=1)}return o>0&&u.info(this.name,`recycled ${o} adapter slot(s) so the new Grix relay env takes effect`),t?(this.relayEnvStale=!0,!1):(this.relayEnvStale=!1,this.relayEnvSettledHandler?.(),!0)}setRelayEnvSettledHandler(e){this.relayEnvSettledHandler=e}hasStaleRelayEnv(){return this.relayEnvStale}markRelayEnvStale(){this.relayEnvStale=!0}async probe(e={}){const o=e.conversation?"full":"static",t=e.conversation?Ge:ze;if(!e.fresh){const a=this.probeCache.get(o);if(a&&Date.now()-a.sampledAt<t)return{...a.result,cached:!0}}const n=this.config.adapterType??"acp",i=this.createAdapter(n,"__probe__"),s=e.conversation&&n==="acp"?()=>this.runAcpConversationProbe(i,e.timeoutMs??1e4):void 0;let r;try{r=await Ke({adapter:i,agentName:this.name,clientType:this.config.aibot.clientType,adapterType:n,providerBaseUrl:this.config.providerBaseUrl??null,opts:e,launchConversationProbe:s})}finally{i.stop().catch(()=>{})}return this.probeCache.set(o,{result:r,sampledAt:r.probed_at}),r}async runAcpConversationProbe(e,o){const t=Date.now(),n="__probe__",i=`probe-${Date.now()}-${Math.random().toString(36).slice(2,8)}`,s=this.config.agent.cwd||V(),r=()=>Date.now()-t;try{await e.start()}catch(h){return{attempted:!0,ok:!1,latency_ms:r(),error:{code:"conversation_failed",message:`start failed: ${h instanceof Error?h.message:String(h)}`}}}if(!e.isAlive())return{attempted:!0,ok:!1,latency_ms:r(),error:{code:"process_not_started",message:"agent process not alive"}};if(e instanceof A)try{await e.bindSession(n,s)}catch{}let a=null;const d=new Promise(h=>{a=m=>{m===i&&h()},e.on("eventDone",a)});e.deliverInboundEvent({event_id:i,session_id:n,content:"ping",msg_id:i}),e.deliverStopEvent(i,n);let c=null;const l=await Promise.race([d.then(()=>!1),new Promise(h=>{c=setTimeout(()=>h(!0),o)})]);return c&&clearTimeout(c),a&&e.removeListener("eventDone",a),l?{attempted:!0,ok:!1,latency_ms:r(),error:{code:"conversation_timeout",message:`no eventDone within ${o}ms`}}:{attempted:!0,ok:!0,latency_ms:r()}}constructor(e,o){this.config=e,this.name=e.name;const t=e.adapterType??"acp";this.aibotConfig={...e.aibot,...t==="claude"?{localActions:e.aibot.localActions??["session_control","claude_interaction_reply","get_session_usage","get_rate_limits","set_model","set_mode","thread_compact","get_agent_global_config"]}:{}},e.eventQueue&&(this.aibotConfig.concurrency={max_concurrent:e.eventQueue.maxConcurrent,max_queued:e.eventQueue.maxQueued,queue_timeout_ms:e.eventQueue.queueTimeoutMs,cancelable_queued:e.eventQueue.cancelableQueued,cancelable_running:e.eventQueue.cancelableRunning}),this.conversationLog=e.logDir?new Ae(e.logDir):null,this.packetLog=e.logDir?new ke(e.logDir):null,this.bindingStore=new Pe(e.bindingsPath),this.bindingStore.load(),this.globalConfigStore=o??null,this.deferredMgr=new Ne(this.name),this.activeEventStore=e.activeEventStorePath?new Be(e.activeEventStorePath):null,t==="codex"?this.sessionScanCache=new L(fe,be):t==="claude"?this.sessionScanCache=new L(_e,we):t==="agy"?this.sessionScanCache=new L(()=>[],()=>[]):this.sessionScanCache=new L(ve,Ce)}async start(){await ge({agentName:this.name,adapterType:this.config.adapterType??"acp",command:this.config.agent.command,piConfigDir:this.config.agent.env?.PI_CODING_AGENT_DIR,hasProvider:!!this.config.agent.provider,boundCwds:[...this.bindingStore.entries()].map(([,o])=>String(o.cwd??""))}).then(o=>{o.length>0&&u.info(this.name,`restored native provider config after relay disable: ${o.join(", ")}`)}).catch(o=>{u.warn(this.name,`native provider residue sweep failed: ${o instanceof Error?o.message:String(o)}`)}),(this.config.adapterType??"acp")==="claude"&&(await D().catch(()=>{}),this.maybeQueryProviderQuota().catch(()=>{})),this.config.aibot.clientType==="kiro"&&(this.maybeQueryProviderQuota().catch(()=>{}),this.startKiroQuotaTimer()),(this.config.adapterType??"acp")==="codex"&&this.maybeQueryProviderQuota().catch(()=>{}),await this.connectAibot(),this.sendCtrl.bind(this.aibotHandle);const e=this.config.adapterType??"acp";this.pool=new $e({maxPoolSize:this.config.poolMaxSize??20,idleTimeoutMs:this.config.poolIdleTimeoutMs??18e5,eventQueue:this.config.eventQueue},o=>{const t=this.createAdapter(e,o);return t instanceof A&&t.on("acpSessionReady",n=>{this.bindingStore.setAcpSessionId(o,n),this.sessionScanCache.invalidate()}),t},(o,t)=>{this.aibotHandle.sendEventAck({event_id:o,session_id:t,received_at:Date.now()})}),this.pool.setEventStateHandler((o,t,n,i)=>{u.info(this.name,`[queue-debug] send event_state session=${t} event=${o} state=${n} queue_pos=${i?.queue_position??""} queue_total=${i?.queue_total??""}`),this.aibotHandle.sendEventState({event_id:o,session_id:t,state:n,content_preview:i?.content_preview,queue_position:i?.queue_position,queue_total:i?.queue_total,actions:i?.actions,reason:i?.reason,updated_at:Date.now()}),this.pushQueueSnapshotForSession(t),(n==="canceled"||n==="failed")&&this.aibotHandle.sendEventResult({event_id:o,status:n==="canceled"?"canceled":"failed",msg:i?.reason,updated_at:Date.now()})}),this.pool.setQueueComposingHandler((o,t,n)=>{this.aibotHandle.sendSessionActivitySet({session_id:o,kind:"composing",active:t,...t?{ttl_ms:3e4,ref_event_id:n}:{}})}),this.pool.setInternalErrorHandler(o=>{this.handleSessionInternalError(o).catch(t=>{u.error(this.name,`[recovery] handleSessionInternalError failed event=${o.eventId} session=${o.sessionId}: ${t instanceof Error?t.message:String(t)}`)})}),this.pool.setEventStartedHandler((o,t)=>{if(this.reportSessionSkills(t),this.config.adapterType!=="claude")return;this.claudeWorkerStatus.set(t,"busy");const n=this.bindingStore.get(t);n?.cwd&&this.aibotHandle.sendUpdateBindingCard({session_id:t,worker_status:"busy",cwd:n.cwd,meta:this.buildClaudeToolbarMeta(t)})}),this.pool.setEventDoneHandler((o,t)=>{const n=this.config.adapterType??"acp";if(n==="claude"){this.claudeWorkerStatus.set(t,"ready");const i=this.bindingStore.get(t);i?.cwd&&this.aibotHandle.sendUpdateBindingCard({session_id:t,worker_status:"ready",cwd:i.cwd,meta:this.buildClaudeToolbarMeta(t)})}else if(n==="agy"){const i=this.bindingStore.get(t);if(i?.cwd){const s=this.buildAgyToolbarMeta(t),r=s?.available_models??[];u.info(this.name,`[agy-toolbar-diag] eventDone push binding card session=${t} model_id=${String(s?.model_id??"")} available_models=${r.length} meta_keys=${s?Object.keys(s).join(","):"<none>"}`),this.aibotHandle.sendUpdateBindingCard({session_id:t,worker_status:"ready",cwd:i.cwd,meta:s})}else u.info(this.name,`[agy-toolbar-diag] eventDone skip binding card: no binding cwd session=${t}`)}}),this.pool.setSessionActivityHandler((o,t,n)=>{const i=this.selfDrivenSessions.has(o),s=this.selfDrivenLabels.get(o);t?(this.selfDrivenSessions.add(o),n&&this.selfDrivenLabels.set(o,n),this.aibotHandle.sendSessionActivitySet({session_id:o,kind:"composing",active:!0,ttl_ms:9e4})):(this.selfDrivenSessions.delete(o),this.selfDrivenLabels.delete(o),i&&this.aibotHandle.sendSessionActivitySet({session_id:o,kind:"composing",active:!1})),(i!==t||t&&n!==void 0&&n!==s)&&this.pushQueueSnapshotForSession(o)}),this.pool.startIdleSweep(),u.info(this.name,`Ready (adapter: ${e}, poolMax: ${this.config.poolMaxSize??20})`)}async stop(){this.stopped=!0,this.pool?.stopIdleSweep(),this.stopKiroQuotaTimer();const e=this.pool?.collectActiveEventIds()??[];e.length>0&&this.activeEventStore&&await this.activeEventStore.save(e);for(const n of e)u.info(this.name,`Canceling active event on shutdown: ${n}`),this.sendEventResultWithCleanup(n,"canceled","process shutting down");e.length>0&&await new Promise(n=>setTimeout(n,100)),this.pool?.clearActiveEventsForShutdown();const o=this.deferredMgr.getAllDeferredEvents();for(const n of o)u.info(this.name,`Failing deferred event on shutdown: ${n.event_id}`),this.sendEventResultWithCleanup(n.event_id,"failed","process shutting down");const t=this.pool?.drainAllQueuedEvents()??[];for(const n of t)u.info(this.name,`Failing queued event on shutdown: ${n.event_id}`),this.sendEventResultWithCleanup(n.event_id,"failed","process shutting down");this.deferredMgr.clearAll(),await this.pool?.stop(),this.aibotHandle?.disconnect(),e.length>0&&this.activeEventStore&&await this.activeEventStore.save([]),this.eventSessionIndex.clear(),this.inflightEvents.clear(),this.restartCount.clear()}resolveSpawnEnv(){const e=he(this.config.agent.clientType,this.config.agent.provider),o=this.config.agent.env,t=Object.keys(e).length>0?{...e,...o}:o;return de(t,this.name,this.config.agent.clientType)}createAdapter(e,o){switch(e){case"claude":return this.createClaudeAdapter(o);case"codex":return this.createCodexAdapter(o);case"pi":return this.createPiAdapter(o);case"openhuman":return this.createOpenHumanAdapter(o);case"codewhale":return this.createCodeWhaleAdapter(o);case"cursor":return this.createCursorAdapter(o);case"opencode":return this.createOpenCodeAdapter(o);case"agy":return this.createAgyAdapter(o);default:return this.createAcpAdapter(o)}}createCursorAdapter(e){const o={...this.config.adapterOptions??{}};o.bindingStore=this.bindingStore,o.aibotSessionId=e;const t=this.bindingStore.get(e),n=t?.modelId??this.globalConfigStore?.get(this.name)?.modelId;n&&(o.model=n),t?.modeId&&(o.mode=t.modeId);const i={sendStreamChunk:(s,r,a,d,c)=>{this.sendStreamChunkByRuntimeConfig(s,r,a,d,c)},sendEventResult:(s,r,a)=>{this.sendEventResultWithCleanup(s,r,a)},sendEventAck:(s,r)=>{this.aibotHandle.sendEventAck({event_id:s,session_id:r,received_at:Date.now()})},sendRawEventEnvelope:(s,r,a)=>{this.aibotHandle.sendMsg({event_id:s,session_id:r,msg_type:1,content:"[cursor] raw_event",extra:{channel_data:{cursor:{raw_event:a}},agent_api_origin:!0}})},agentInvoke:async(s,r,a)=>this.platformInvoke(s,r,a),sendLocalActionResult:(s,r,a,d,c)=>{this.aibotHandle.sendLocalActionResult({action_id:s,status:r,...a!==void 0?{result:a}:{},...d?{error_code:d}:{},...c?{error_msg:c}:{}})}};return new I({command:this.config.agent.command,args:this.config.agent.args,env:this.resolveSpawnEnv(),options:o},i)}createClaudeAdapter(e){const o={sendReply:(i,s,r,a,d)=>{this.sendReplyByRuntimeConfig(i,s,r,a,d)},sendStreamChunk:(i,s,r,a,d,c,l)=>{this.sendStreamChunkByRuntimeConfig(i,s,r,a,d,c,l)},sendMedia:(i,s,r,a,d,c,l)=>{this.aibotHandle.sendMedia({event_id:i,session_id:s,content:r,msg_type:2,quoted_message_id:d||void 0,client_msg_id:c||void 0,extra:l?{media_caption:a,...l}:{media_caption:a}})},sendEventResult:(i,s,r,a)=>{this.sendEventResultWithCleanup(i,s,r,a)},sendEventAck:(i,s)=>{this.aibotHandle.sendEventAck({event_id:i,session_id:s,received_at:Date.now()})},agentInvoke:async(i,s,r)=>this.platformInvoke(i,s,r),sendLocalActionResult:(i,s,r,a,d)=>{this.aibotHandle.sendLocalActionResult({action_id:i,status:s,...r!==void 0?{result:r}:{},...a?{error_code:a}:{},...d?{error_msg:d}:{}})},sendToolUse:(i,s,r,a)=>{this.sendToolExecutionCard(i,s,E(r,a))},sendToolResult:(i,s,r,a)=>{this.sendToolExecutionCard(i,s,$(r,a))},getWsUrl:()=>this.config.aibot.url,getAgentId:()=>this.config.aibot.agentId,getAgentProfile:()=>this.agentProfile,getApiKey:()=>this.config.aibot.apiKey,getActiveEventCount:()=>0,getPendingPermissionCount:()=>0,getPendingElicitationCount:()=>0,sendAgentQuestionCard:(i,s,r)=>{const a=r.questions.map(c=>c.header).join(", "),d=We(`[Agent Question] ${r.request_id}`,"agent_question",r);this.aibotHandle.sendText({event_id:i,session_id:s,content:d,msg_type:1,extra:{card_type:"agent_question",summary_text:a}})},sendPermissionCard:i=>{this.aibotHandle.sendMsg({event_id:i.eventId,session_id:i.sessionId,client_msg_id:`perm_${M()}`,msg_type:1,content:i.toolTitle?`Permission required: ${i.toolTitle}`:"Permission request",extra:{channel_data:{execApproval:{approvalId:i.approvalId,approvalSlug:i.toolName},grix:{execApproval:{approval_command_id:i.approvalId,command:i.toolTitle||i.toolName,host:"claude"}}},agent_api_origin:!0}})},sendDirectMessage:i=>{this.aibotHandle.sendMsg({session_id:i.sessionId,msg_type:1,content:i.content,...i.clientMsgId?{client_msg_id:i.clientMsgId}:{},...i.quotedMessageId?{quoted_message_id:i.quotedMessageId}:{}})},onStatusLineUpdated:i=>{(i.rateLimits?.fiveHour||i.rateLimits?.sevenDay)&&(this.cachedClaudeRateLimitState=i);const s=this.bindingStore.get(e);s?.cwd&&this.aibotHandle.sendUpdateBindingCard({session_id:e,worker_status:this.claudeWorkerStatus.get(e)??"ready",cwd:s.cwd,meta:this.buildClaudeToolbarMeta(e)})},sendMcpFrame:i=>{this.aibotHandle.sendMcpFrame(e,i)}},t=this.config.adapterOptions??{},n={...t,sessionRuntimeResolver:()=>{const i=this.bindingStore.get(e);return{cwd:i?.cwd,modeId:i?.modeId??C.fullAuto,modelId:i?.modelId??this.globalConfigStore?.get(this.name)?.modelId,pluginDir:t.pluginDir,claudeSessionId:i?.claudeSessionId,onSessionIdAssigned:s=>{this.bindingStore.setClaudeSessionId(e,s),this.sessionScanCache.invalidate()}}}};return new y({command:this.config.agent.command,args:this.config.agent.args,env:this.resolveSpawnEnv(),options:n},o)}createCodexAdapter(e){let o=null;const t={sendEventResult:(s,r,a)=>{this.sendEventResultWithCleanup(s,r,a)},sendEventAck:(s,r)=>this.aibotHandle.sendEventAck({event_id:s,session_id:r,received_at:Date.now()}),sendCodexEvent:s=>{this.shouldDropCodexDisplayEvent(s.event_id,s.codex_method)||(this.aibotHandle.sendCodexEvent(s),this.logCodexEventToConversation(s))},sendCodexEventReliable:async s=>{if(!this.shouldDropCodexDisplayEvent(s.event_id,s.codex_method)){try{await this.aibotHandle.sendCodexEventReliable(s)}catch(r){u.warn("bridge",`[codex] sendCodexEventReliable ACK failed event=${s.event_id}: ${r}`)}u.info("bridge",`[codex] sendCodexEventReliable done event=${s.event_id} method=${s.codex_method}`),this.logCodexEventToConversation(s)}},sendRunError:(s,r,a)=>{this.sendStreamChunkByRuntimeConfig(s,r,`
2
2
 
3
- Error: ${a}`,1,!1)},sendUpdateBindingCard:(s,r,a,d)=>{const c={...d??{}};if(!c.rate_limits&&this.cachedProviderQuota?.success){this.isRateLimitsCacheFresh(this.cachedProviderQuotaSampledAtMs)||this.maybeQueryProviderQuota().catch(()=>{});const l=this.providerQuotaToCodexRateLimits(this.cachedProviderQuota);l&&(c.rate_limits=l.rateLimits,c.rate_limit_primary_percent=l.primaryPercent,c.rate_limit_secondary_percent=l.secondaryPercent,c.rate_limit_primary_window_min=l.primaryWindowMin,c.rate_limit_secondary_window_min=l.secondaryWindowMin)}!c.provider_quota&&this.cachedProviderQuota?.success&&(c.provider_quota=this.cachedProviderQuota),this.aibotHandle.sendUpdateBindingCard({session_id:s,worker_status:r,cwd:a,...Object.keys(c).length>0?{meta:c}:{}})},agentInvoke:async(s,r,a)=>this.platformInvoke(s,r,a),sendLocalActionResult:(s,r,a,d,c)=>this.aibotHandle.sendLocalActionResult({action_id:s,status:r,...a!==void 0?{result:a}:{},...d?{error_code:d}:{},...c?{error_msg:c}:{}}),sendSessionActivitySet:(s,r,a,d)=>{this.aibotHandle.sendSessionActivitySet({session_id:s,kind:r,active:a,...d??{}})},getConversationLog:()=>this.conversationLog,getAgentProfile:()=>this.agentProfile,onRateLimitsUpdated:s=>{this.cachedRateLimits=s,this.cachedRateLimitsSampledAtMs=Date.now(),this.isRateLimitsCacheFresh(this.cachedProviderQuotaSampledAtMs)||this.maybeQueryProviderQuota().catch(()=>{});const r=this.bindingStore.get(e);if(r?.cwd){const a=this.cachedRateLimitsSampledAtMs,d={rate_limits:{...s.primary.windowMinutes>0?{primary:s.primary}:{},...s.secondary.windowMinutes>0?{secondary:s.secondary}:{},sampledAt:a},rate_limit_primary_percent:s.primary.usedPercent,rate_limit_secondary_percent:s.secondary.usedPercent,rate_limit_primary_window_min:s.primary.windowMinutes,rate_limit_secondary_window_min:s.secondary.windowMinutes,...o?.getEffortMeta()};this.cachedProviderQuota?.success&&(d.provider_quota=this.cachedProviderQuota),this.aibotHandle.sendUpdateBindingCard({session_id:e,worker_status:"ready",cwd:r.cwd,meta:d})}},onContextWindowUpdated:s=>{if(!s)return;this.cachedCodexContextWindow=s,this.cachedCodexUsageSampledAtMs=Date.now();const r=this.bindingStore.get(e);r?.cwd&&this.aibotHandle.sendUpdateBindingCard({session_id:e,worker_status:"ready",cwd:r.cwd,meta:{context_window:s,...o?.getEffortMeta()}})},onTokenUsageUpdated:s=>{s&&(this.cachedCodexTokenUsage=s,this.cachedCodexUsageSampledAtMs=Date.now())}},n=this.config.adapterOptions??{},i=this.globalConfigStore?.get(this.name);return o=new J({command:this.config.agent.command,args:this.config.agent.args,env:this.resolveSpawnEnv(),options:{...n,model:this.bindingStore.getCodexModelId(e)??n.model,collaborationMode:this.bindingStore.getCodexModeId(e)??n.collaborationMode,reasoningEffort:i?.codexReasoningEffort??n.reasoningEffort,serviceTier:this.bindingStore.getCodexServiceTier(e)??n.serviceTier,sandboxMode:i?.codexSandboxMode??n.sandboxMode,aibotSessionId:e,bindingStore:this.bindingStore}},t),o}createCodeWhaleAdapter(e){const o={sendEventResult:(n,i,s)=>{this.sendEventResultWithCleanup(n,i,s)},sendEventAck:(n,i)=>this.aibotHandle.sendEventAck({event_id:n,session_id:i,received_at:Date.now()}),sendStreamChunk:(n,i,s,r,a,d)=>{this.sendStreamChunkByRuntimeConfig(n,i,s,r,a,d)},sendUpdateBindingCard:(n,i,s,r)=>this.aibotHandle.sendUpdateBindingCard({session_id:n,worker_status:i,cwd:s,...r?{meta:r}:{}}),sendLocalActionResult:(n,i,s,r,a)=>this.aibotHandle.sendLocalActionResult({action_id:n,status:i,...s!==void 0?{result:s}:{},...r?{error_code:r}:{},...a?{error_msg:a}:{}}),sendSessionActivitySet:(n,i,s,r)=>{this.aibotHandle.sendSessionActivitySet({session_id:n,kind:i,active:s,...r??{}})},sendToolUse:(n,i,s,r)=>{this.sendToolExecutionCard(n,i,E(s,r))},sendToolResult:(n,i,s,r)=>{this.sendToolExecutionCard(n,i,$(s,r))},agentInvoke:async(n,i,s)=>this.platformInvoke(n,i,s),getConversationLog:()=>this.conversationLog,getAgentProfile:()=>this.agentProfile},t=this.config.adapterOptions??{};return new U({command:this.config.agent.command,args:this.config.agent.args,env:this.resolveSpawnEnv(),options:{...t,aibotSessionId:e,bindingStore:this.bindingStore}},o)}createPiAdapter(e){const o={sendEventResult:(n,i,s)=>{this.sendEventResultWithCleanup(n,i,s),u.info("bridge",`[pi] sendEventResult event=${n} status=${i}`)},sendEventAck:(n,i)=>this.aibotHandle.sendEventAck({event_id:n,session_id:i,received_at:Date.now()}),sendUpdateBindingCard:(n,i,s,r)=>this.aibotHandle.sendUpdateBindingCard({session_id:n,worker_status:i,cwd:s,...r?{meta:r}:{}}),agentInvoke:async(n,i,s)=>this.platformInvoke(n,i,s),sendLocalActionResult:(n,i,s,r,a)=>{this.aibotHandle.sendLocalActionResult({action_id:n,status:i,...s!==void 0?{result:s}:{},...r?{error_code:r}:{},...a?{error_msg:a}:{}})},sendSessionActivitySet:(n,i,s,r)=>{this.aibotHandle.sendSessionActivitySet({session_id:n,kind:i,active:s,...r??{}})},sendToolUse:(n,i,s,r)=>{this.sendToolExecutionCard(n,i,E(s,r))},sendToolResult:(n,i,s,r)=>{this.sendToolExecutionCard(n,i,$(s,r))},sendStreamChunk:(n,i,s,r,a,d)=>{this.sendStreamChunkByRuntimeConfig(n,i,s,r,a,d),a&&u.info("bridge",`[pi] sendFinalStreamChunk event=${n} seq=${r}`)},sendFinalStreamChunkReliable:async(n,i,s,r)=>{if(this.finalizeThinking(n,i),this.resolveEventRuntimeConfig(n).responseDelivery==="single_message"&&n){this.bufferStreamChunk(n,i,"",!0);return}try{await this.aibotHandle.sendStreamChunkRequest({event_id:n,session_id:i,delta_content:"",chunk_seq:s,is_finish:!0,...r?{client_msg_id:r}:{}})}catch(d){u.warn("bridge",`[pi] sendFinalStreamChunkReliable ACK failed event=${n}: ${d}`)}u.info("bridge",`[pi] sendFinalStreamChunkReliable done event=${n} seq=${s}`)},sendThinking:(n,i,s)=>{this.sendThinkingByRuntimeConfig(n,i,s)},sendRunError:(n,i,s,r,a)=>{this.sendStreamChunkByRuntimeConfig(n,i,`
3
+ Error: ${a}`,1,!1)},sendUpdateBindingCard:(s,r,a,d)=>{const c={...d??{}};if(!c.rate_limits&&this.cachedProviderQuota?.success){this.isRateLimitsCacheFresh(this.cachedProviderQuotaSampledAtMs)||this.maybeQueryProviderQuota().catch(()=>{});const l=this.providerQuotaToCodexRateLimits(this.cachedProviderQuota);l&&(c.rate_limits=l.rateLimits,c.rate_limit_primary_percent=l.primaryPercent,c.rate_limit_secondary_percent=l.secondaryPercent,c.rate_limit_primary_window_min=l.primaryWindowMin,c.rate_limit_secondary_window_min=l.secondaryWindowMin)}!c.provider_quota&&this.cachedProviderQuota?.success&&(c.provider_quota=this.cachedProviderQuota),this.aibotHandle.sendUpdateBindingCard({session_id:s,worker_status:r,cwd:a,...Object.keys(c).length>0?{meta:c}:{}})},agentInvoke:async(s,r,a)=>this.platformInvoke(s,r,a),sendLocalActionResult:(s,r,a,d,c)=>this.aibotHandle.sendLocalActionResult({action_id:s,status:r,...a!==void 0?{result:a}:{},...d?{error_code:d}:{},...c?{error_msg:c}:{}}),sendSessionActivitySet:(s,r,a,d)=>{this.aibotHandle.sendSessionActivitySet({session_id:s,kind:r,active:a,...d??{}})},getConversationLog:()=>this.conversationLog,getAgentProfile:()=>this.agentProfile,onRateLimitsUpdated:s=>{this.cachedRateLimits=s,this.cachedRateLimitsSampledAtMs=Date.now(),this.isRateLimitsCacheFresh(this.cachedProviderQuotaSampledAtMs)||this.maybeQueryProviderQuota().catch(()=>{});const r=this.bindingStore.get(e);if(r?.cwd){const a=this.cachedRateLimitsSampledAtMs,d={rate_limits:{...s.primary.windowMinutes>0?{primary:s.primary}:{},...s.secondary.windowMinutes>0?{secondary:s.secondary}:{},sampledAt:a},rate_limit_primary_percent:s.primary.usedPercent,rate_limit_secondary_percent:s.secondary.usedPercent,rate_limit_primary_window_min:s.primary.windowMinutes,rate_limit_secondary_window_min:s.secondary.windowMinutes,...o?.getEffortMeta()};this.cachedProviderQuota?.success&&(d.provider_quota=this.cachedProviderQuota),this.aibotHandle.sendUpdateBindingCard({session_id:e,worker_status:"ready",cwd:r.cwd,meta:d})}},onContextWindowUpdated:s=>{if(!s)return;this.cachedCodexContextWindow=s,this.cachedCodexUsageSampledAtMs=Date.now();const r=this.bindingStore.get(e);r?.cwd&&this.aibotHandle.sendUpdateBindingCard({session_id:e,worker_status:"ready",cwd:r.cwd,meta:{context_window:s,...o?.getEffortMeta()}})},onTokenUsageUpdated:s=>{s&&(this.cachedCodexTokenUsage=s,this.cachedCodexUsageSampledAtMs=Date.now())}},n=this.config.adapterOptions??{},i=this.globalConfigStore?.get(this.name);return o=new J({command:this.config.agent.command,args:this.config.agent.args,env:this.resolveSpawnEnv(),options:{...n,model:this.bindingStore.getCodexModelId(e)??n.model,collaborationMode:this.bindingStore.getCodexModeId(e)??n.collaborationMode,reasoningEffort:i?.codexReasoningEffort??n.reasoningEffort,serviceTier:this.bindingStore.getCodexServiceTier(e)??n.serviceTier,sandboxMode:i?.codexSandboxMode??n.sandboxMode,aibotSessionId:e,bindingStore:this.bindingStore}},t),o}createCodeWhaleAdapter(e){const o={sendEventResult:(n,i,s)=>{this.sendEventResultWithCleanup(n,i,s)},sendEventAck:(n,i)=>this.aibotHandle.sendEventAck({event_id:n,session_id:i,received_at:Date.now()}),sendStreamChunk:(n,i,s,r,a,d)=>{this.sendStreamChunkByRuntimeConfig(n,i,s,r,a,d)},sendUpdateBindingCard:(n,i,s,r)=>this.aibotHandle.sendUpdateBindingCard({session_id:n,worker_status:i,cwd:s,...r?{meta:r}:{}}),sendLocalActionResult:(n,i,s,r,a)=>this.aibotHandle.sendLocalActionResult({action_id:n,status:i,...s!==void 0?{result:s}:{},...r?{error_code:r}:{},...a?{error_msg:a}:{}}),sendSessionActivitySet:(n,i,s,r)=>{this.aibotHandle.sendSessionActivitySet({session_id:n,kind:i,active:s,...r??{}})},sendToolUse:(n,i,s,r)=>{this.sendToolExecutionCard(n,i,E(s,r))},sendToolResult:(n,i,s,r)=>{this.sendToolExecutionCard(n,i,$(s,r))},agentInvoke:async(n,i,s)=>this.platformInvoke(n,i,s),getConversationLog:()=>this.conversationLog,getAgentProfile:()=>this.agentProfile},t=this.config.adapterOptions??{};return new U({command:this.config.agent.command,args:this.config.agent.args,env:this.resolveSpawnEnv(),options:{...t,aibotSessionId:e,bindingStore:this.bindingStore,agentName:this.name,...this.config.agent.provider?{provider:this.config.agent.provider}:{}}},o)}createPiAdapter(e){const o={sendEventResult:(n,i,s)=>{this.sendEventResultWithCleanup(n,i,s),u.info("bridge",`[pi] sendEventResult event=${n} status=${i}`)},sendEventAck:(n,i)=>this.aibotHandle.sendEventAck({event_id:n,session_id:i,received_at:Date.now()}),sendUpdateBindingCard:(n,i,s,r)=>this.aibotHandle.sendUpdateBindingCard({session_id:n,worker_status:i,cwd:s,...r?{meta:r}:{}}),agentInvoke:async(n,i,s)=>this.platformInvoke(n,i,s),sendLocalActionResult:(n,i,s,r,a)=>{this.aibotHandle.sendLocalActionResult({action_id:n,status:i,...s!==void 0?{result:s}:{},...r?{error_code:r}:{},...a?{error_msg:a}:{}})},sendSessionActivitySet:(n,i,s,r)=>{this.aibotHandle.sendSessionActivitySet({session_id:n,kind:i,active:s,...r??{}})},sendToolUse:(n,i,s,r)=>{this.sendToolExecutionCard(n,i,E(s,r))},sendToolResult:(n,i,s,r)=>{this.sendToolExecutionCard(n,i,$(s,r))},sendStreamChunk:(n,i,s,r,a,d)=>{this.sendStreamChunkByRuntimeConfig(n,i,s,r,a,d),a&&u.info("bridge",`[pi] sendFinalStreamChunk event=${n} seq=${r}`)},sendFinalStreamChunkReliable:async(n,i,s,r)=>{if(this.finalizeThinking(n,i),this.resolveEventRuntimeConfig(n).responseDelivery==="single_message"&&n){this.bufferStreamChunk(n,i,"",!0);return}try{await this.aibotHandle.sendStreamChunkRequest({event_id:n,session_id:i,delta_content:"",chunk_seq:s,is_finish:!0,...r?{client_msg_id:r}:{}})}catch(d){u.warn("bridge",`[pi] sendFinalStreamChunkReliable ACK failed event=${n}: ${d}`)}u.info("bridge",`[pi] sendFinalStreamChunkReliable done event=${n} seq=${s}`)},sendThinking:(n,i,s)=>{this.sendThinkingByRuntimeConfig(n,i,s)},sendRunError:(n,i,s,r,a)=>{this.sendStreamChunkByRuntimeConfig(n,i,`
4
4
 
5
5
  Error: ${s}`,r,!1,a)},getAgentProfile:()=>this.agentProfile},t=this.config.adapterOptions??{};return new X({command:this.config.agent.command,args:this.config.agent.args,env:this.resolveSpawnEnv(),options:{...t,aibotSessionId:e,bindingStore:this.bindingStore,agentName:this.name,...this.config.agent.provider?{provider:this.config.agent.provider}:{}}},o)}createOpenHumanAdapter(e){const o={sendStreamChunk:(n,i,s,r,a,d)=>{this.finalizeThinking(n,i),this.sendStreamChunkByRuntimeConfig(n,i,s,r,a,d)},sendFinalStreamChunkReliable:async(n,i,s,r)=>{if(this.finalizeThinking(n,i),this.resolveEventRuntimeConfig(n).responseDelivery==="single_message"&&n){this.bufferStreamChunk(n,i,"",!0);return}try{await this.aibotHandle.sendStreamChunkRequest({event_id:n,session_id:i,delta_content:"",chunk_seq:s,is_finish:!0,...r?{client_msg_id:r}:{}})}catch(d){u.warn("bridge",`[openhuman] sendFinalStreamChunkReliable ACK failed event=${n}: ${d}`)}},sendEventResult:(n,i,s)=>{this.sendEventResultWithCleanup(n,i,s),u.info("bridge",`[openhuman] sendEventResult event=${n} status=${i}`)},sendEventAck:(n,i)=>this.aibotHandle.sendEventAck({event_id:n,session_id:i,received_at:Date.now()}),sendToolUse:(n,i,s,r)=>{this.sendToolExecutionCard(n,i,E(s,r))},sendToolResult:(n,i,s,r)=>{this.sendToolExecutionCard(n,i,$(s,r))},sendThinking:(n,i,s)=>{this.sendThinkingByRuntimeConfig(n,i,s)},sendRunError:(n,i,s)=>{this.sendStreamChunkByRuntimeConfig(n,i,`
6
6
 
@@ -1 +1 @@
1
- const n={qwen:{baseUrl:"OPENAI_BASE_URL",apiKey:"OPENAI_API_KEY",model:"OPENAI_MODEL"},reasonix:{baseUrl:"DEEPSEEK_BASE_URL",apiKey:"DEEPSEEK_API_KEY"},codewhale:{baseUrl:"CODEWHALE_BASE_URL",apiKey:"OPENAI_API_KEY",model:"CODEWHALE_MODEL",fixed:{CODEWHALE_PROVIDER:"openai"}},hermes:{baseUrl:"GRIX_HERMES_BASE_URL",apiKey:"GRIX_PROVIDER_API_KEY",model:"HERMES_INFERENCE_MODEL",fixed:{HERMES_INFERENCE_PROVIDER:"grix"}},pi:{apiKey:"GRIX_PROVIDER_API_KEY"}},s=["qwen","reasonix","codewhale","hermes","pi","opencode"];function a(r){const E=r?.trim().toLowerCase();return!!E&&s.includes(E)}function R(r){return!!r&&r.trim().toLowerCase()in n}function I(r,E){if(!E)return{};const e=n[r?.trim().toLowerCase()??""];if(!e)return{};const i=E.baseUrl?.trim(),t=E.apiKey?.trim(),_=E.model?.trim();if(!i&&!t&&!_)return{};const o={...e.fixed??{}};return e.baseUrl&&i&&(o[e.baseUrl]=i),e.apiKey&&t&&(o[e.apiKey]=t),e.model&&_&&(o[e.model]=_),o}export{s as DIRECT_PROVIDER_CLIENT_TYPES,n as PROVIDER_ENV_SPECS,I as buildProviderEnv,a as supportsDirectProviderConfig,R as supportsProviderEnv};
1
+ const _={qwen:{baseUrl:"OPENAI_BASE_URL",apiKey:"OPENAI_API_KEY",model:"OPENAI_MODEL"},reasonix:{baseUrl:"DEEPSEEK_BASE_URL",apiKey:"DEEPSEEK_API_KEY"},codewhale:{apiKey:"OPENAI_API_KEY"},hermes:{baseUrl:"GRIX_HERMES_BASE_URL",apiKey:"GRIX_PROVIDER_API_KEY",model:"HERMES_INFERENCE_MODEL",fixed:{HERMES_INFERENCE_PROVIDER:"grix"}},pi:{apiKey:"GRIX_PROVIDER_API_KEY"}},s=["qwen","reasonix","codewhale","hermes","pi","opencode"];function a(r){const E=r?.trim().toLowerCase();return!!E&&s.includes(E)}function R(r){return!!r&&r.trim().toLowerCase()in _}function I(r,E){if(!E)return{};const e=_[r?.trim().toLowerCase()??""];if(!e)return{};const i=E.baseUrl?.trim(),t=E.apiKey?.trim(),n=E.model?.trim();if(!i&&!t&&!n)return{};const o={...e.fixed??{}};return e.baseUrl&&i&&(o[e.baseUrl]=i),e.apiKey&&t&&(o[e.apiKey]=t),e.model&&n&&(o[e.model]=n),o}export{s as DIRECT_PROVIDER_CLIENT_TYPES,_ as PROVIDER_ENV_SPECS,I as buildProviderEnv,a as supportsDirectProviderConfig,R as supportsProviderEnv};
@@ -1 +1 @@
1
- import{existsSync as h,mkdirSync as U,readFileSync as w,renameSync as C,writeFileSync as M}from"node:fs";import{join as g,dirname as $}from"node:path";import{spawn as R}from"node:child_process";import{log as o}from"../log/index.js";import{GRIX_PATHS as m}from"../log/index.js";import{resolveClientVersion as _}from"../util/client-version.js";import{UpgradeError as L,collectEnvInfo as O,getUpgradeLogTail as D,isVersionAvailable as N,npmInstall as F,pendingExists as v,preflightCheck as H,readPending as x,removePending as u,upgradeLog as s,verifyInstalledVersion as z,writePending as B}from"./npm-upgrader.js";import{isRetriableRegistryError as G}from"../installer/npm-registry.js";const V=360*60*1e3,j=300*1e3,T=1800*1e3,K=2,X=3,S="grix-connector",k=1e4,A=15,q=2e3,J=8e3,Y=1e3;function b(l){return new URL(l.replace(/^wss:/,"https:").replace(/^ws:/,"http:")).origin}function P(){return g(m.data,"upgrade-state.json")}function E(){const l=P();if(!h(l))return{daily_attempts:{},version_attempts:{}};try{return JSON.parse(w(l,"utf-8"))}catch{return{daily_attempts:{},version_attempts:{}}}}function Z(l){const t=P();U($(t),{recursive:!0});const e=t+".tmp";M(e,JSON.stringify(l),"utf-8"),C(e,t)}function I(){return new Date().toISOString().slice(0,10)}class ot{agentConfigs;isBusy;timer=null;initialTimer=null;running=!1;stopped=!1;constructor(t,e){this.agentConfigs=t,this.isBusy=e}async start(){await this.handlePendingOnStartup(),this.initialTimer=setTimeout(()=>{this.stopped||(this.runCheck(),!this.stopped&&(this.timer=setInterval(()=>this.runCheck(),V)))},j)}stop(){this.stopped=!0,this.initialTimer&&(clearTimeout(this.initialTimer),this.initialTimer=null),this.timer&&(clearInterval(this.timer),this.timer=null)}triggerCheck(){this.stopped||this.runCheck()}async checkForUpdate(){return this.agentConfigs.length===0?{available:!1}:(await Promise.all(this.agentConfigs.map(i=>this.queryUpgrade(i)))).find(i=>i.available&&i.release)??{available:!1}}async handlePendingOnStartup(){if(!v())return;const t=x();if(!t){u();return}const e=_(),i=e===t.target_version,r=i?{from_version:t.from_version,to_version:t.target_version,status:"success"}:{from_version:t.from_version,to_version:t.target_version,status:"rolled_back",error_code:"STARTUP_CRASH",crash_count:t.crash_count};if(s(i?`daemon startup: version matches target ${t.target_version}, upgrade succeeded`:`daemon startup: version ${e} != target ${t.target_version}, rolled back`),i){const a=await this.confirmHealthz(J);s(a?"healthz confirmed healthy, reporting success":"healthz not confirmed within window; reporting success on version-match (guardian still guards crashes)")}if(await this.reportUpgrade(r)){u();return}this.retryStartupReport(r)}async retryStartupReport(t){for(let e=2;e<=A;e++){if(this.stopped)return;const i=q*(e-1)+Math.floor(Math.random()*1e3);if(await new Promise(r=>setTimeout(r,i)),this.stopped)return;if(await this.reportUpgrade(t)){u();return}}s(`startup report (${t.status}) not delivered after ${A} attempts; clearing pending to unblock future checks`),u()}async confirmHealthz(t){let e="19579";try{const a=g(m.data,"health-port");h(a)&&(e=w(a,"utf-8").trim()||e)}catch{}const i=`http://127.0.0.1:${e}/healthz`,r=Date.now()+t;for(;Date.now()<r&&!this.stopped;){try{if((await fetch(i,{signal:AbortSignal.timeout(2e3)})).ok)return!0}catch{}await new Promise(a=>setTimeout(a,Y))}return!1}async runCheck(){if(!this.running){this.running=!0;try{await this.check()}catch(t){o.error("upgrade",`Check failed: ${t instanceof Error?t.message:t}`)}finally{this.running=!1}}}async check(){if(v()||this.agentConfigs.length===0)return;const i=(await Promise.all(this.agentConfigs.map(n=>this.queryUpgrade(n)))).find(n=>n.available&&n.release)?.release;if(!i)return;const r=i.version,a=_();if(!i.force&&!this.checkRateLimit(r))return;const p=H();if(!p.ok){await this.reportUpgrade({from_version:a,to_version:r,status:"failed",error_code:p.errorCode,error_msg:p.errorMsg});return}if(!await N(S,r)){s(`target ${r} not resolvable on any registry yet; skipping this cycle (no failure recorded)`);return}s(`upgrade start: ${a} -> ${r}`);const y=Date.now();try{if(B(a,r),await F(S,r),z(r),this.startGuardian(),s("npm install verified, reporting installed"),await this.reportUpgrade({from_version:a,to_version:r,status:"installed",duration_ms:Date.now()-y}),s("shutting down for restart"),!await this.waitUntilIdle()){s("upgrade aborted: checker stopped during wait");return}process.kill(process.pid,"SIGTERM")}catch(n){const d=n instanceof L?n.code:"NPM_INSTALL_FAILED",f=n instanceof Error?n.message:String(n);s(`upgrade failed: ${d} ${f}`),u(),G(f)||d==="NPM_TIMEOUT"?s(`failure is retriable (${d}); skipping cooldown, will retry next cycle`):this.recordFailure(r),await this.reportUpgrade({from_version:a,to_version:r,status:"failed",error_code:d,error_msg:f,duration_ms:Date.now()-y,upgrade_log:D()})}}async waitUntilIdle(){if(!this.isBusy?.())return!this.stopped;s("active tasks detected, waiting until idle before restart (no max wait)");const t=5e3,e=600*1e3;let i=Date.now();for(;this.isBusy()&&!this.stopped;)await new Promise(r=>setTimeout(r,t)),Date.now()-i>=e&&(i=Date.now(),s("still waiting for active tasks to finish before restart"));return this.stopped?!1:(s("all tasks completed, proceeding with restart"),!0)}checkRateLimit(t){const e=E();if(e.last_failure_at){const c=Date.now()-new Date(e.last_failure_at).getTime();if(c<T)return o.info("upgrade",`In cooldown, ${(T-c)/6e4}m remaining`),!1}const i=e.version_attempts[t]??0;if(i>=X)return o.info("upgrade",`Version ${t} already tried ${i} times, skipping`),!1;const r=I(),a=e.daily_attempts[r]??0;return a>=K?(o.info("upgrade",`Already ${a} attempts today, skipping`),!1):!0}recordFailure(t){const e=E(),i=I();e.last_failure_at=new Date().toISOString(),e.last_failure_version=t,e.daily_attempts[i]=(e.daily_attempts[i]??0)+1,e.version_attempts[t]=(e.version_attempts[t]??0)+1,Z(e)}async queryUpgrade(t){const i=`${b(t.wsUrl)}/v1/agent-api/upgrade/check?`+new URLSearchParams({client_type:"grix-connector",client_version:_(),channel:t.channel??"stable",platform:process.platform,arch:process.arch}).toString();try{const r=await fetch(i,{headers:{Authorization:`Bearer ${t.apiKey}`},signal:AbortSignal.timeout(k)});if(!r.ok)return o.warn("upgrade",`Check API returned ${r.status}`),{available:!1};const a=await r.json();return a.code!==0?{available:!1}:a.data}catch(r){return o.warn("upgrade",`Check API error: ${r instanceof Error?r.message:r}`),{available:!1}}}async reportUpgrade(t){if(this.agentConfigs.length===0)return!1;const e={client_type:"grix-connector",...t.npm_version?t:{...t,...O()}};return(await Promise.all(this.agentConfigs.map(async r=>{const c=`${b(r.wsUrl)}/v1/agent-api/upgrade/report`;try{return(await fetch(c,{method:"POST",headers:{Authorization:`Bearer ${r.apiKey}`,"Content-Type":"application/json"},body:JSON.stringify(e),signal:AbortSignal.timeout(k)})).ok}catch{return!1}}))).some(r=>r)}startGuardian(){const t=g(m.base,"bin","upgrade-guardian.sh");if(process.platform==="win32"||!h(t)){o.info("upgrade","Guardian not available on this platform, skipping");return}try{const e=R(t,[],{detached:!0,stdio:"ignore"});e.unref(),s(`guardian started (pid ${e.pid})`)}catch(e){o.warn("upgrade",`Failed to start guardian: ${e instanceof Error?e.message:e}`)}}}export{ot as UpgradeChecker};
1
+ import{existsSync as h,mkdirSync as U,readFileSync as w,renameSync as C,writeFileSync as M}from"node:fs";import{join as g,dirname as $}from"node:path";import{spawn as R}from"node:child_process";import{log as o}from"../log/index.js";import{GRIX_PATHS as m}from"../log/index.js";import{resolveClientVersion as _}from"../util/client-version.js";import{UpgradeError as L,collectEnvInfo as O,getUpgradeLogTail as D,isVersionAvailable as N,npmInstall as F,pendingExists as v,preflightCheck as H,readPending as x,removePending as u,upgradeLog as s,verifyInstalledVersion as z,writePending as B}from"./npm-upgrader.js";import{isRetriableRegistryError as G}from"../installer/npm-registry.js";const V=360*60*1e3,j=300*1e3,T=1800*1e3,K=2,X=3,S="grix-connector",k=1e4,A=15,q=2e3,J=8e3,Y=1e3;function b(l){return new URL(l.replace(/^wss:/,"https:").replace(/^ws:/,"http:")).origin}function P(){return g(m.data,"upgrade-state.json")}function E(){const l=P();if(!h(l))return{daily_attempts:{},version_attempts:{}};try{return JSON.parse(w(l,"utf-8"))}catch{return{daily_attempts:{},version_attempts:{}}}}function Z(l){const t=P();U($(t),{recursive:!0});const e=t+".tmp";M(e,JSON.stringify(l),"utf-8"),C(e,t)}function I(){return new Date().toISOString().slice(0,10)}class ot{agentConfigs;isBusy;timer=null;initialTimer=null;running=!1;stopped=!1;constructor(t,e){this.agentConfigs=t,this.isBusy=e}async start(){await this.handlePendingOnStartup(),this.initialTimer=setTimeout(()=>{this.stopped||(this.runCheck(),!this.stopped&&(this.timer=setInterval(()=>this.runCheck(),V)))},j)}stop(){this.stopped=!0,this.initialTimer&&(clearTimeout(this.initialTimer),this.initialTimer=null),this.timer&&(clearInterval(this.timer),this.timer=null)}triggerCheck(){this.stopped||this.runCheck()}async checkForUpdate(){return this.agentConfigs.length===0?{available:!1}:(await Promise.all(this.agentConfigs.map(i=>this.queryUpgrade(i)))).find(i=>i.available&&i.release)??{available:!1}}async handlePendingOnStartup(){if(!v())return;const t=x();if(!t){u();return}const e=_(),i=e===t.target_version,r=i?{from_version:t.from_version,to_version:t.target_version,status:"success"}:{from_version:t.from_version,to_version:t.target_version,status:"rolled_back",error_code:"STARTUP_CRASH",crash_count:t.crash_count};if(s(i?`daemon startup: version matches target ${t.target_version}, upgrade succeeded`:`daemon startup: version ${e} != target ${t.target_version}, rolled back`),i){const a=await this.confirmHealthz(J);s(a?"healthz confirmed healthy, reporting success":"healthz not confirmed within window; reporting success on version-match (guardian still guards crashes)")}if(await this.reportUpgrade(r)){u();return}this.retryStartupReport(r)}async retryStartupReport(t){for(let e=2;e<=A;e++){if(this.stopped)return;const i=q*(e-1)+Math.floor(Math.random()*1e3);if(await new Promise(r=>setTimeout(r,i)),this.stopped)return;if(await this.reportUpgrade(t)){u();return}}s(`startup report (${t.status}) not delivered after ${A} attempts; clearing pending to unblock future checks`),u()}async confirmHealthz(t){let e="19579";try{const a=g(m.data,"health-port");h(a)&&(e=w(a,"utf-8").trim()||e)}catch{}const i=`http://127.0.0.1:${e}/healthz`,r=Date.now()+t;for(;Date.now()<r&&!this.stopped;){try{if((await fetch(i,{signal:AbortSignal.timeout(2e3)})).ok)return!0}catch{}await new Promise(a=>setTimeout(a,Y))}return!1}async runCheck(){if(!this.running){this.running=!0;try{await this.check()}catch(t){o.error("upgrade",`Check failed: ${t instanceof Error?t.message:t}`)}finally{this.running=!1}}}async check(){if(v()||this.agentConfigs.length===0)return;const i=(await Promise.all(this.agentConfigs.map(n=>this.queryUpgrade(n)))).find(n=>n.available&&n.release)?.release;if(!i)return;const r=i.version,a=_();if(!i.force&&!this.checkRateLimit(r))return;const p=H();if(!p.ok){await this.reportUpgrade({from_version:a,to_version:r,status:"failed",error_code:p.errorCode,error_msg:p.errorMsg});return}if(!await N(S,r)){s(`target ${r} not resolvable on any registry yet; skipping this cycle (no failure recorded)`);return}s(`upgrade start: ${a} -> ${r}`);const y=Date.now();try{if(B(a,r),await F(S,r),z(r),s("npm install verified, reporting installed"),await this.reportUpgrade({from_version:a,to_version:r,status:"installed",duration_ms:Date.now()-y}),s("shutting down for restart"),!await this.waitUntilIdle()){s("upgrade aborted: checker stopped during wait");return}this.startGuardian(),process.kill(process.pid,"SIGTERM")}catch(n){const d=n instanceof L?n.code:"NPM_INSTALL_FAILED",f=n instanceof Error?n.message:String(n);s(`upgrade failed: ${d} ${f}`),u(),G(f)||d==="NPM_TIMEOUT"?s(`failure is retriable (${d}); skipping cooldown, will retry next cycle`):this.recordFailure(r),await this.reportUpgrade({from_version:a,to_version:r,status:"failed",error_code:d,error_msg:f,duration_ms:Date.now()-y,upgrade_log:D()})}}async waitUntilIdle(){if(!this.isBusy?.())return!this.stopped;s("active tasks detected, waiting until idle before restart (no max wait)");const t=5e3,e=600*1e3;let i=Date.now();for(;this.isBusy()&&!this.stopped;)await new Promise(r=>setTimeout(r,t)),Date.now()-i>=e&&(i=Date.now(),s("still waiting for active tasks to finish before restart"));return this.stopped?!1:(s("all tasks completed, proceeding with restart"),!0)}checkRateLimit(t){const e=E();if(e.last_failure_at){const c=Date.now()-new Date(e.last_failure_at).getTime();if(c<T)return o.info("upgrade",`In cooldown, ${(T-c)/6e4}m remaining`),!1}const i=e.version_attempts[t]??0;if(i>=X)return o.info("upgrade",`Version ${t} already tried ${i} times, skipping`),!1;const r=I(),a=e.daily_attempts[r]??0;return a>=K?(o.info("upgrade",`Already ${a} attempts today, skipping`),!1):!0}recordFailure(t){const e=E(),i=I();e.last_failure_at=new Date().toISOString(),e.last_failure_version=t,e.daily_attempts[i]=(e.daily_attempts[i]??0)+1,e.version_attempts[t]=(e.version_attempts[t]??0)+1,Z(e)}async queryUpgrade(t){const i=`${b(t.wsUrl)}/v1/agent-api/upgrade/check?`+new URLSearchParams({client_type:"grix-connector",client_version:_(),channel:t.channel??"stable",platform:process.platform,arch:process.arch}).toString();try{const r=await fetch(i,{headers:{Authorization:`Bearer ${t.apiKey}`},signal:AbortSignal.timeout(k)});if(!r.ok)return o.warn("upgrade",`Check API returned ${r.status}`),{available:!1};const a=await r.json();return a.code!==0?{available:!1}:a.data}catch(r){return o.warn("upgrade",`Check API error: ${r instanceof Error?r.message:r}`),{available:!1}}}async reportUpgrade(t){if(this.agentConfigs.length===0)return!1;const e={client_type:"grix-connector",...t.npm_version?t:{...t,...O()}};return(await Promise.all(this.agentConfigs.map(async r=>{const c=`${b(r.wsUrl)}/v1/agent-api/upgrade/report`;try{return(await fetch(c,{method:"POST",headers:{Authorization:`Bearer ${r.apiKey}`,"Content-Type":"application/json"},body:JSON.stringify(e),signal:AbortSignal.timeout(k)})).ok}catch{return!1}}))).some(r=>r)}startGuardian(){const t=g(m.base,"bin","upgrade-guardian.sh");if(process.platform==="win32"||!h(t)){o.info("upgrade","Guardian not available on this platform, skipping");return}try{const e=R(t,[],{detached:!0,stdio:"ignore"});e.unref(),s(`guardian started (pid ${e.pid})`)}catch(e){o.warn("upgrade",`Failed to start guardian: ${e instanceof Error?e.message:e}`)}}}export{ot as UpgradeChecker};
package/dist/manager.js CHANGED
@@ -1,2 +1,2 @@
1
- import{readFileSync as L,readdirSync as Q,writeFileSync as W}from"node:fs";import{join as S}from"node:path";import{AgentInstance as x}from"./bridge/bridge.js";import{buildSharedInstanceConfig as K,diffSharedOwners as z,sharedInstanceKey as G,SharedOwnersCache as J}from"./manager-share-config.js";import{isAgentDeletedError as M}from"./core/aibot/errors.js";import{GRIX_PATHS as A,log as l}from"./core/log/index.js";import{resolveClientVersion as V}from"./core/util/client-version.js";import{UpgradeChecker as X}from"./core/upgrade/upgrade-checker.js";import{AgentGlobalConfigStore as Y}from"./core/persistence/agent-global-config-store.js";import{resolveSkillScanMode as Z}from"./adapter/claude/skill-scanner.js";import{buildReportedSkills as ee,logDefaultSkillsCheck as te,cleanupProjectedSkills as j}from"./default-skills/index.js";import{supportsDirectProviderConfig as ne}from"./core/config/provider-env.js";import{resolveCopilotCommand as D}from"./core/runtime/copilot-resolve.js";import{getCliVersion as ae,resolveCliPath as re}from"./core/util/cli-probe.js";import{AgentInstaller as se}from"./core/installer/installer.js";import{reportInstallFailure as oe}from"./core/observability/sentry.js";const ie=8e3;function ce(){const n=D();return[{clientType:"openclaw",command:"openclaw"},{clientType:"claude",command:"claude"},{clientType:"codex",command:"codex"},{clientType:"gemini",command:"gemini"},{clientType:"qwen",command:"qwen"},{clientType:"hermes",command:"hermes"},{clientType:"reasonix",command:"reasonix"},{clientType:"codewhale",command:"codewhale"},{clientType:"opencode",command:"opencode"},{clientType:"cursor",command:"agent"},{clientType:"pi",command:"pi"},{clientType:"openhuman",command:"openhuman-core"},{clientType:"kiro",command:"kiro-cli"},{clientType:"copilot",command:n.command},{clientType:"agy",command:"agy"}]}async function le(){return Promise.all(ce().map(async n=>{const e=await re(n.command);if(!e)return{client_type:n.clientType,command:n.command,installed:!1,path:null,version:null};const t=await ae(e,n.versionArgs??["--version"]);return{client_type:n.clientType,command:n.command,installed:!0,path:e,version:t.version,error:t.error}}))}function de(n){switch(n){case"claude":return{adapterType:"claude",command:"claude"};case"codex":return{adapterType:"codex",command:"codex",options:{sandboxMode:"danger-full-access"}};case"gemini":return{adapterType:"acp",command:"gemini",autoInjectArgs:{acp:!0},enableSessionBinding:!0};case"qwen":return{adapterType:"acp",command:"qwen",adapterHint:"qwen/base",autoInjectArgs:{acp:!0},enableSessionBinding:!0};case"pi":return{adapterType:"pi",command:"pi"};case"cursor":return{adapterType:"cursor",command:"agent"};case"reasonix":return{adapterType:"acp",command:"reasonix",args:["acp"],enableSessionBinding:!0};case"codewhale":return{adapterType:"codewhale",command:"codewhale",enableSessionBinding:!0};case"openhuman":return{adapterType:"openhuman",command:"openhuman-core",enableSessionBinding:!0};case"kiro":return{adapterType:"acp",command:"kiro-cli",args:["acp"],enableSessionBinding:!0};case"opencode":return{adapterType:"opencode",command:"opencode",args:["serve"],enableSessionBinding:!0};case"copilot":{const e=D();return{adapterType:"acp",command:e.command,args:[...e.prefixArgs,"--acp"],enableSessionBinding:!0}}case"agy":return{adapterType:"agy",command:"agy",enableSessionBinding:!0};case"hermes":return{adapterType:"acp",command:"hermes",args:["acp"],enableSessionBinding:!0};default:throw new Error(`Unsupported client_type: ${n}`)}}function P(n,e){return String(n??"").trim().toLowerCase().replace(/[^a-z0-9._-]+/g,"-").replace(/-+/g,"-").replace(/^-|-$/g,"")||String(e??"").trim().toLowerCase().replace(/[^a-z0-9._-]+/g,"-").replace(/-+/g,"-").replace(/^-|-$/g,"")||"default"}function pe(n,e){return S(A.data,`session-bindings-${P(n,e)}.json`)}function me(n,e){return S(A.data,`active-events-${P(n,e)}.json`)}function ue(...n){const e=[],t=new Set;for(const r of n)for(const a of r??[]){const s=String(a??"").trim(),o=s.toLowerCase();!s||t.has(o)||(t.add(o),e.push(s))}return e.length>0?e:void 0}function ge(n,e){const t={claude:{maxConcurrent:1,maxQueued:5,queueTimeoutMs:0},codex:{maxConcurrent:1,maxQueued:5,queueTimeoutMs:0},cursor:{maxConcurrent:1,maxQueued:3,queueTimeoutMs:0},acp:{maxConcurrent:1,maxQueued:3,queueTimeoutMs:0},pi:{maxConcurrent:1,maxQueued:5,queueTimeoutMs:0},codewhale:{maxConcurrent:1,maxQueued:3,queueTimeoutMs:0},openhuman:{maxConcurrent:1,maxQueued:3,queueTimeoutMs:0},opencode:{maxConcurrent:1,maxQueued:3,queueTimeoutMs:0},agy:{maxConcurrent:1,maxQueued:3,queueTimeoutMs:0}},r=t[n]??t.acp;return{maxConcurrent:e?.max_concurrent??r.maxConcurrent??1,maxQueued:e?.max_queued??r.maxQueued??3,queueTimeoutMs:e?.queue_timeout_ms??r.queueTimeoutMs??0,cancelableQueued:!0,cancelableRunning:!0}}function T(n){const e=V(),t=String(n.client_type??"").trim().toLowerCase(),r=de(t),a=String(n.ws_url??"").trim(),s="get_session_usage",o="get_rate_limits",i="get_agent_global_config";if(!n.name?.trim())throw new Error("agent name is required");if(!a)throw new Error(`agent ${n.name}: ws_url is required`);if(!n.agent_id?.trim())throw new Error(`agent ${n.name}: agent_id is required`);if(!n.api_key?.trim())throw new Error(`agent ${n.name}: api_key is required`);const c=r.adapterType,p=c==="acp",g=t==="qwen",f={...r.options??{}},w=c==="codex"?{capabilities:["local_action_v1","agent_invoke"],localActions:["session_control","get_context","set_model","set_mode","set_reasoning_effort","set_service_tier","set_sandbox_mode","exec_approve","exec_reject","file_list","create_folder","turn_interrupt","permission_approve","permission_reject","thread_compact",s,o]}:null,d=c==="claude"?{localActions:["session_control","set_mode","set_model","claude_interaction_reply","exec_approve","exec_reject","file_list","create_folder","thread_compact",s,o]}:null,u=g?{capabilities:["stream_chunk","local_action_v1"],localActions:["exec_approve","exec_reject","permission_approve","permission_reject","session_control","set_model","set_mode","file_list","create_folder",s],adapterHint:"qwen/base"}:null,h=c==="pi"?{adapterHint:"pi/base",capabilities:["local_action_v1"],localActions:["session_control","set_model","get_context","file_list","create_folder",s]}:null,m=c==="openhuman"?{adapterHint:"openhuman/base",capabilities:["local_action_v1"],localActions:["session_control","set_model","file_list","create_folder",s]}:null,_=c==="cursor"?{adapterHint:"cursor/base",capabilities:["stream_chunk","local_action_v1"],localActions:["session_control","set_model","set_mode","get_context","file_list","create_folder",s,o]}:null,y=c==="codewhale"?{capabilities:["stream_chunk","local_action_v1"],localActions:["session_control","set_model","file_list","create_folder",s]}:null,b=c==="opencode"?{adapterHint:"opencode/base",capabilities:["stream_chunk","local_action_v1"],localActions:["exec_approve","exec_reject","permission_approve","permission_reject","session_control","set_model","set_mode","file_list","create_folder",s]}:null,C=c==="agy"?{adapterHint:"agy/base",capabilities:["stream_chunk","local_action_v1"],localActions:["session_control","set_model","file_list","create_folder",s]}:null,H=p&&!g?{localActions:["exec_approve","exec_reject","permission_approve","permission_reject","session_control","set_model","set_mode","file_list","create_folder",s]}:null,q=t==="kiro"?{localActions:["exec_approve","exec_reject","permission_approve","permission_reject","session_control","set_model","set_mode","file_list","create_folder","thread_compact",s,o]}:null,R=c==="codex"||c==="claude"||t==="gemini"?["session_control","set_model","set_mode"]:void 0,N=[s,o];p&&f.raw_transport===void 0&&(f.raw_transport=t==="gemini");const F=`${t}/base`,I=Z(c,t),U=I==="kiro"?void 0:process.cwd(),k=ee({mode:I,projectDir:U}),E=k.filter($=>$.source==="connector").map($=>$.name);E.length>0&&l.info("manager",`[${n.name}] injecting connector skills: [${E.join(", ")}]`);let B=k.length>0?k:void 0;return{name:n.name,adapterType:c,aibot:{url:a,agentId:n.agent_id,apiKey:n.api_key,clientType:t,clientVersion:e,adapterHint:r.adapterHint??u?.adapterHint??h?.adapterHint??m?.adapterHint??_?.adapterHint??b?.adapterHint??C?.adapterHint??F,capabilities:w?.capabilities??y?.capabilities??h?.capabilities??m?.capabilities??_?.capabilities??b?.capabilities??C?.capabilities??u?.capabilities??["stream_chunk","local_action_v1","connector_upgrade"],localActions:ue(w?.localActions??y?.localActions??d?.localActions??h?.localActions??m?.localActions??_?.localActions??b?.localActions??C?.localActions??u?.localActions??q?.localActions??H?.localActions??["exec_approve","exec_reject"],R,N,["connector_rollback","connector_upgrade_push",i,"configure_gateway_provider"]),skills:B},agent:{command:r.command,args:[...r.args??[],...n.args??[]],env:n.env,clientType:t,...n.provider?{provider:{...n.provider.base_url?.trim()?{baseUrl:n.provider.base_url.trim()}:{},...n.provider.api_key?.trim()?{apiKey:n.provider.api_key.trim()}:{},...n.provider.model?.trim()?{model:n.provider.model.trim()}:{}}}:{}},adapterOptions:f,acpAuthMethod:f.auth_method,acpInitialMode:f.initial_mode,acpMcpTools:f.acp_mcp_tools,promptTimeoutMs:n.prompt_timeout_ms,bindingsPath:pe(n.name,n.agent_id),activeEventStorePath:me(n.name,n.agent_id),...r.enableSessionBinding||p?{enableSessionBinding:!0}:{},...r.autoInjectArgs||t==="reasonix"&&n.provider?.model?.trim()?{autoInjectArgs:{...r.autoInjectArgs??{},...t==="reasonix"&&n.provider?.model?.trim()?{model:n.provider.model.trim()}:{}}}:{},poolMaxSize:n.pool?.maxSize,poolIdleTimeoutMs:n.pool?.idleTimeoutMs,eventQueue:ge(c,n.event_queue),logDir:A.log,providerBaseUrl:n.provider_base_url?.trim()||void 0,providerApiKey:n.provider_api_key?.trim()||void 0}}function he(){const n=process.env.GRIX_AGENT_STARTUP_WAIT_MS,e=Number(n);return Number.isFinite(e)&&e>=500?Math.floor(e):ie}function O(n){const e=[],t=[],r=Q(n).filter(a=>a.endsWith(".json")).sort();for(const a of r)try{const s=JSON.parse(L(S(n,a),"utf-8"));if(Array.isArray(s.agents)){if(s.agents.length===0){t.push({file:a,error:"agents array is empty"});continue}for(const o of s.agents)e.push({entry:o,file:a})}else t.push({file:a,error:"unrecognized config format"})}catch(s){t.push({file:a,error:s instanceof Error?s.message:String(s)})}return{entries:e,fileErrors:t}}function v(n){if(n===null||typeof n!="object")return JSON.stringify(n)??"null";if(Array.isArray(n))return`[${n.map(v).join(",")}]`;const e=n;return`{${Object.keys(e).sort().map(r=>`${JSON.stringify(r)}:${v(e[r])}`).join(",")}}`}function fe(n,e){return v(n)===v(e)}class Oe{instances=[];configMap=new Map;rawConfigMap=new Map;reloadChain=Promise.resolve();sharedInstances=new Map;shareSyncChains=new Map;stopping=!1;deletedAgentCleanups=new Set;upgradeChecker=null;globalConfigStore;configDir=A.config;sharedOwnersCache=new J(A.data);installer=new se;async start(e){const t=e??A.config;this.configDir=t,l.info("manager",`Loading configs from ${t}`),te(),j(),this.globalConfigStore=new Y(S(A.data,"agent-global-configs.json")),this.globalConfigStore.load();const{entries:r,fileErrors:a}=O(t);for(const c of a)l.error("manager",`Failed to load ${c.file}: ${c.error}`);if(r.length===0&&a.length===0)throw new Error(`No config files found in ${t}`);const s=[];let o=a.length;for(const{entry:c,file:p}of r)try{const g=T(c);s.push({config:g,entry:c,file:p}),l.info("manager",`Loaded ${g.name} (${g.adapterType??"acp"}) from ${p}`)}catch(g){const f=typeof c?.name=="string"?c.name:"<unknown>";l.error("manager",`Invalid agent config in ${p} (name=${f}): ${g}`),o++}let i=0;if(s.length>0){const c=he();l.info("manager",`Starting ${s.length} agent(s), startup wait=${c}ms`);const p=()=>this.upgradeChecker?.triggerCheck(),g=d=>{this.instances=this.instances.filter(u=>u!==d)},f=s.map(({config:d,entry:u,file:h})=>{const m=new x(d,this.globalConfigStore);return m.setUpgradeTrigger(p),m.setShareSetHandler(_=>this.onShareSet(d,_)),m.setAgentDeletedHandler(()=>this.onAgentDeletedByPlatform(d.name)),this.instances.push(m),this.configMap.set(d.name,d),this.rawConfigMap.set(d.name,{entry:u,file:h}),{config:d,instance:m,startPromise:m.start()}}),w=await Promise.all(f.map(async d=>{const u=await new Promise(h=>{let m=!1;const _=setTimeout(()=>{m||(m=!0,h({kind:"timeout"}))},c);d.startPromise.then(()=>{m||(m=!0,clearTimeout(_),h({kind:"started"}))}).catch(y=>{m||(m=!0,clearTimeout(_),h({kind:"failed",error:y}))})});return{task:d,outcome:u}}));for(const{task:d,outcome:u}of w){if(u.kind==="started"){this.restoreCachedSharedInstances(d.config);continue}if(u.kind==="failed"){if(M(u.error)){this.onAgentDeletedByPlatform(d.config.name);continue}g(d.instance),l.error("manager",`Failed to start ${d.config.name}: ${u.error}`);continue}i++,l.warn("manager",`Startup pending for ${d.config.name}, continue retrying in background`),d.startPromise.then(()=>{l.info("manager",`Delayed start succeeded: ${d.config.name}`),this.restoreCachedSharedInstances(d.config)}).catch(h=>{if(M(h)){this.onAgentDeletedByPlatform(d.config.name);return}g(d.instance),l.error("manager",`Delayed start failed: ${d.config.name}: ${h}`)})}if(this.instances.length>0){const d=Math.max(0,this.instances.length-i);l.info("manager",`${d}/${s.length} agent(s) running now`)}i>0&&l.warn("manager",`${i} agent(s) still connecting in background`)}if(this.instances.length===0&&s.length>0)throw new Error("All agent configurations failed to start");s.length>0&&(this.upgradeChecker=new X(s.map(({config:c})=>({apiKey:c.aibot.apiKey,wsUrl:c.aibot.url})),()=>this.instances.some(c=>c.hasPendingWork())),await this.upgradeChecker.start())}async stop(){l.info("manager","Stopping all agents..."),this.stopping=!0,this.upgradeChecker?.stop(),await Promise.allSettled([...this.shareSyncChains.values()]),this.shareSyncChains.clear();const e=[...this.sharedInstances.values()];this.sharedInstances.clear(),await Promise.allSettled([...this.instances.map(t=>t.stop()),...e.map(t=>t.stop())]),await this.globalConfigStore?.flush(),this.instances=[],j(),l.info("manager","All stopped")}onShareSet(e,t){this.sharedOwnersCache.save(e.name,t);const a=(this.shareSyncChains.get(e.name)??Promise.resolve()).catch(()=>{}).then(()=>this.syncSharedInstances(e,t));this.shareSyncChains.set(e.name,a)}async syncSharedInstances(e,t){if(this.stopping)return;const{toAdd:r,toRemove:a}=z(e.name,this.sharedInstances.keys(),t);for(const s of r){if(this.stopping)break;const o=G(e.name,s);try{const i=K(e,s),c=new x(i,this.globalConfigStore);this.sharedInstances.set(o,c),await c.start(),l.info("manager",`shared instance started: ${o}`)}catch(i){this.sharedInstances.delete(o);const c=i instanceof Error?i.message:String(i);/Auth failed/i.test(c)?(this.sharedOwnersCache.remove(e.name,s),l.warn("manager",`shared instance auth rejected, removed from cache: ${o}`)):l.error("manager",`start shared instance failed ${o}: ${i}`)}}for(const{key:s}of a){const o=this.sharedInstances.get(s);o&&(this.sharedInstances.delete(s),o.stop().catch(i=>l.error("manager",`stop shared instance failed ${s}: ${i}`)),l.info("manager",`shared instance stopped: ${s}`))}}restoreCachedSharedInstances(e){const t=this.sharedOwnersCache.load(e.name);t.length!==0&&(l.info("manager",`restoring ${t.length} cached shared instance(s) for ${e.name}`),this.onShareSet(e,t))}getAgentsStatus(){return this.instances.map(e=>e.getStatus())}async addAgent(e){await this.addAgentInternal(e,"agents.json"),this.persistAgentsConfig(),l.info("manager",`Added agent: ${e.name}`)}async addAgentInternal(e,t){const r=T(e);if(this.instances.some(a=>a.name===r.name))throw new Error(`Agent "${r.name}" already exists`);await this.startInstanceFromConfig(r,e,t)}async startInstanceFromConfig(e,t,r){const a=new x(e,this.globalConfigStore);a.setUpgradeTrigger(()=>this.upgradeChecker?.triggerCheck()),a.setShareSetHandler(s=>this.onShareSet(e,s)),a.setAgentDeletedHandler(()=>this.onAgentDeletedByPlatform(e.name)),a.setProviderConfigHandler(s=>this.setAgentProvider(e.name,s)),a.setRelayEnvSettledHandler(()=>this.relayEnvSettledHandler?.()),await a.start(),this.instances.push(a),this.configMap.set(e.name,e),this.rawConfigMap.set(e.name,{entry:t,file:r}),this.relayEnvSettledHandler?.()}async removeAgent(e){if(!this.instances.some(t=>t.name===e))throw Object.assign(new Error(`Agent "${e}" not found`),{code:"NOT_FOUND"});await this.removeAgentInternal(e),this.persistAgentsConfig(),l.info("manager",`Removed agent: ${e}`),this.relayEnvSettledHandler?.()}async removeAgentInternal(e,t){const r=this.instances.findIndex(i=>i.name===e),a=r===-1?void 0:this.instances[r];r!==-1&&this.instances.splice(r,1),this.configMap.delete(e),this.rawConfigMap.delete(e);const s=this.shareSyncChains.get(e);this.shareSyncChains.delete(e),s&&await s.catch(()=>{});const o=`${e}#shared:`;for(const[i,c]of[...this.sharedInstances.entries()])i.startsWith(o)&&(this.sharedInstances.delete(i),c.stop().catch(p=>l.error("manager",`stop shared instance failed ${i}: ${p}`)));t?.keepShareCache||this.sharedOwnersCache.delete(e),a&&await a.stop()}onAgentDeletedByPlatform(e){this.stopping||this.deletedAgentCleanups.has(e)||(this.deletedAgentCleanups.add(e),(async()=>{try{if(!(this.rawConfigMap.has(e)||this.instances.some(r=>r.name===e)))return;l.warn("manager",`Agent "${e}" \u5DF2\u5728\u5E73\u53F0\u5220\u9664\uFF0C\u505C\u6B62\u5B9E\u4F8B\u5E76\u6E05\u7406\u672C\u5730\u914D\u7F6E`),await this.removeAgentInternal(e),this.persistAgentsConfig(),l.info("manager",`Agent "${e}" \u5DF2\u5728\u5E73\u53F0\u5220\u9664\uFF0C\u672C\u5730\u914D\u7F6E\u5DF2\u6E05\u7406\uFF08agents.json \u5DF2\u79FB\u9664\u8BE5\u6761\u76EE\uFF09`)}catch(t){l.error("manager",`cleanup for deleted agent "${e}" failed: ${t}`)}finally{this.deletedAgentCleanups.delete(e)}})())}persistAgentsConfig(){const e=S(this.configDir,"agents.json");try{const t=[];for(const[,a]of this.configMap){const s=this.rawConfigMap.get(a.name)?.entry.provider;t.push({name:a.name,ws_url:a.aibot.url,agent_id:a.aibot.agentId,api_key:a.aibot.apiKey,client_type:a.aibot.clientType,...s?{provider:s}:{}})}W(e,JSON.stringify({agents:t},null,4)+`
2
- `,"utf-8")}catch(t){l.error("manager",`Failed to persist agents config: ${t}`)}}isAgentBusy(e){return this.instances.find(r=>r.name===e)?.hasPendingWork()??!1}getAgentsWithStaleRelayEnv(){return this.instances.filter(e=>e.hasStaleRelayEnv()).map(e=>e.name)}markAgentRelayEnvStale(e){const t=this.instances.find(r=>r.name===e);return t?(t.markRelayEnvStale(),!0):!1}relayEnvSettledHandler;setRelayEnvSettledHandler(e){this.relayEnvSettledHandler=e}async restartAgent(e){const t=this.rawConfigMap.get(e);if(!t)throw Object.assign(new Error(`Agent "${e}" not found`),{code:"NOT_FOUND"});await this.replaceInstance(e,t.entry,t.file),l.info("manager",`Restarted agent: ${e}`)}async setAgentProvider(e,t){const r=this.rawConfigMap.get(e);if(!r)throw Object.assign(new Error(`Agent "${e}" not found`),{code:"NOT_FOUND"});const a={...r.entry,provider:t};await this.replaceInstance(e,a,r.file),this.persistAgentsConfig(),l.info("manager",`Updated provider config for agent: ${e}`)}async clearDirectProviderConfigs(){const e=[...this.rawConfigMap.entries()].filter(([,r])=>!!r.entry.provider&&ne(r.entry.client_type)).map(([r])=>r),t=[];for(const r of e)try{await this.setAgentProvider(r,void 0),t.push(r)}catch(a){l.warn("manager",`Failed to clear provider config for "${r}": ${a instanceof Error?a.message:String(a)}`)}return t}async replaceInstance(e,t,r){const a=T(t);await this.removeAgentInternal(e,{keepShareCache:!0}),await this.startInstanceFromConfig(a,t,r),this.restoreCachedSharedInstances(a)}async reload(){const e=this.reloadChain.catch(()=>{}).then(()=>this.doReload());return this.reloadChain=e.catch(()=>{}),e}async doReload(){if(this.stopping)throw Object.assign(new Error("manager is stopping"),{code:"RELOAD_UNSAFE"});const{entries:e,fileErrors:t}=O(this.configDir);if(t.length>0){const o=t.map(i=>`${i.file}: ${i.error}`).join("; ");throw Object.assign(new Error(`reload aborted: ${t.length} config file(s) failed to parse [${o}]`),{code:"RELOAD_UNSAFE"})}if(e.length===0)throw Object.assign(new Error("reload aborted: no valid agent config found"),{code:"RELOAD_UNSAFE"});const r=new Map;for(const o of e){const i=String(o.entry?.name??"").trim();i&&(r.has(i)&&l.warn("manager",`reload: duplicate agent name "${i}", last one wins`),r.set(i,o))}const a={added:[],removed:[],restarted:[],unchanged:[],failed:[]},s=new Set(this.rawConfigMap.keys());for(const o of s)if(!r.has(o))try{await this.removeAgentInternal(o),a.removed.push(o)}catch(i){a.failed.push({name:o,error:i instanceof Error?i.message:String(i)})}for(const[o,i]of r){const c=this.rawConfigMap.get(o);if(c)if(fe(c.entry,i.entry))this.rawConfigMap.set(o,i),a.unchanged.push(o);else try{await this.replaceInstance(o,i.entry,i.file),a.restarted.push(o)}catch(p){a.failed.push({name:o,error:p instanceof Error?p.message:String(p)})}else try{await this.addAgentInternal(i.entry,i.file),a.added.push(o)}catch(p){a.failed.push({name:o,error:p instanceof Error?p.message:String(p)})}}return l.info("manager",`reload done: +${a.added.length} -${a.removed.length} ~${a.restarted.length} =${a.unchanged.length} !${a.failed.length}`),a}async checkUpgrade(){return this.upgradeChecker?this.upgradeChecker.checkForUpdate():{available:!1}}triggerUpgrade(){this.upgradeChecker?.triggerCheck()}async probeAll(e={}){return _e(this.instances,e)}async probeOne(e,t={}){return we(this.instances,e,t)}listInstallable(){return this.installer.listInstallable()}async installAgent(e){const t=await this.installer.install(e);return oe(t),t}getInstallProgress(e){return this.installer.getProgress(e)??null}}async function _e(n,e){const t=e.concurrency??4,r=Date.now(),a=new Array(n.length);await new Promise(p=>{let g=0,f=0;const w=n.length;if(w===0){p();return}function d(m){const _=n[m];_.probe(e).then(y=>{a[m]=y,u()},y=>{a[m]={agent_name:_.name,client_type:"unknown",adapter_type:"acp",ok:!1,status:"error",probed_at:Date.now(),duration_ms:0,cached:!1,cli:{command:"",installed:!1,path:null,version:null,error:{code:"internal",message:y?.message??String(y)}},conversation:{attempted:!1,ok:!1,latency_ms:null},config:{model:null,base_url:null,source:{model:"unknown",base_url:"unknown"}},process:{started:!1,alive:!1,busy:!1}},u()})}function u(){f++,g<w?d(g++):f===w&&p()}const h=Math.min(t,w);for(let m=0;m<h;m++)d(g++)});const s=a.filter(p=>p.status==="healthy").length,o=a.filter(p=>p.status==="degraded").length,i=a.filter(p=>p.status==="unavailable").length,c=await le();return{ok:s===a.length&&a.length>0,total:a.length,healthy:s,degraded:o,unavailable:i,installed_clients:c,agents:a,probed_at:r,duration_ms:Date.now()-r}}async function we(n,e,t){const r=n.find(a=>a.name===e);if(!r)throw Object.assign(new Error(`Agent "${e}" not found`),{code:"NOT_FOUND"});return r.probe(t)}export{Oe as Manager,le as probeInstalledClientCommands,_e as probeInstances,we as probeOneInstance};
1
+ import{readFileSync as L,readdirSync as Q,writeFileSync as W}from"node:fs";import{join as S}from"node:path";import{AgentInstance as T}from"./bridge/bridge.js";import{buildSharedInstanceConfig as K,diffSharedOwners as z,sharedInstanceKey as G,SharedOwnersCache as J}from"./manager-share-config.js";import{isAgentDeletedError as M}from"./core/aibot/errors.js";import{GRIX_PATHS as A,log as l}from"./core/log/index.js";import{resolveClientVersion as V}from"./core/util/client-version.js";import{UpgradeChecker as X}from"./core/upgrade/upgrade-checker.js";import{AgentGlobalConfigStore as Y}from"./core/persistence/agent-global-config-store.js";import{resolveSkillScanMode as Z}from"./adapter/claude/skill-scanner.js";import{buildReportedSkills as ee,logDefaultSkillsCheck as te,cleanupProjectedSkills as j}from"./default-skills/index.js";import{supportsDirectProviderConfig as ne}from"./core/config/provider-env.js";import{resolveCopilotCommand as D}from"./core/runtime/copilot-resolve.js";import{getCliVersion as ae,resolveCliPath as re}from"./core/util/cli-probe.js";import{AgentInstaller as se}from"./core/installer/installer.js";import{reportInstallFailure as oe}from"./core/observability/sentry.js";const ie=8e3;function ce(){const n=D();return[{clientType:"openclaw",command:"openclaw"},{clientType:"claude",command:"claude"},{clientType:"codex",command:"codex"},{clientType:"gemini",command:"gemini"},{clientType:"qwen",command:"qwen"},{clientType:"hermes",command:"hermes"},{clientType:"reasonix",command:"reasonix"},{clientType:"codewhale",command:"codewhale"},{clientType:"opencode",command:"opencode"},{clientType:"cursor",command:"agent"},{clientType:"pi",command:"pi"},{clientType:"openhuman",command:"openhuman-core"},{clientType:"kiro",command:"kiro-cli"},{clientType:"kimi",command:"kimi"},{clientType:"copilot",command:n.command},{clientType:"agy",command:"agy"}]}async function le(){return Promise.all(ce().map(async n=>{const e=await re(n.command);if(!e)return{client_type:n.clientType,command:n.command,installed:!1,path:null,version:null};const t=await ae(e,n.versionArgs??["--version"]);return{client_type:n.clientType,command:n.command,installed:!0,path:e,version:t.version,error:t.error}}))}function de(n){switch(n){case"claude":return{adapterType:"claude",command:"claude"};case"codex":return{adapterType:"codex",command:"codex",options:{sandboxMode:"danger-full-access"}};case"gemini":return{adapterType:"acp",command:"gemini",autoInjectArgs:{acp:!0},enableSessionBinding:!0};case"qwen":return{adapterType:"acp",command:"qwen",adapterHint:"qwen/base",autoInjectArgs:{acp:!0},enableSessionBinding:!0};case"pi":return{adapterType:"pi",command:"pi"};case"cursor":return{adapterType:"cursor",command:"agent"};case"reasonix":return{adapterType:"acp",command:"reasonix",args:["acp"],enableSessionBinding:!0};case"codewhale":return{adapterType:"codewhale",command:"codewhale",enableSessionBinding:!0};case"openhuman":return{adapterType:"openhuman",command:"openhuman-core",enableSessionBinding:!0};case"kiro":return{adapterType:"acp",command:"kiro-cli",args:["acp"],enableSessionBinding:!0};case"kimi":return{adapterType:"acp",command:"kimi",args:["acp"],enableSessionBinding:!0};case"opencode":return{adapterType:"opencode",command:"opencode",args:["serve"],enableSessionBinding:!0};case"copilot":{const e=D();return{adapterType:"acp",command:e.command,args:[...e.prefixArgs,"--acp"],enableSessionBinding:!0}}case"agy":return{adapterType:"agy",command:"agy",enableSessionBinding:!0};case"hermes":return{adapterType:"acp",command:"hermes",args:["acp"],enableSessionBinding:!0};default:throw new Error(`Unsupported client_type: ${n}`)}}function P(n,e){return String(n??"").trim().toLowerCase().replace(/[^a-z0-9._-]+/g,"-").replace(/-+/g,"-").replace(/^-|-$/g,"")||String(e??"").trim().toLowerCase().replace(/[^a-z0-9._-]+/g,"-").replace(/-+/g,"-").replace(/^-|-$/g,"")||"default"}function pe(n,e){return S(A.data,`session-bindings-${P(n,e)}.json`)}function me(n,e){return S(A.data,`active-events-${P(n,e)}.json`)}function ue(...n){const e=[],t=new Set;for(const r of n)for(const a of r??[]){const s=String(a??"").trim(),o=s.toLowerCase();!s||t.has(o)||(t.add(o),e.push(s))}return e.length>0?e:void 0}function ge(n,e){const t={claude:{maxConcurrent:1,maxQueued:5,queueTimeoutMs:0},codex:{maxConcurrent:1,maxQueued:5,queueTimeoutMs:0},cursor:{maxConcurrent:1,maxQueued:3,queueTimeoutMs:0},acp:{maxConcurrent:1,maxQueued:3,queueTimeoutMs:0},pi:{maxConcurrent:1,maxQueued:5,queueTimeoutMs:0},codewhale:{maxConcurrent:1,maxQueued:3,queueTimeoutMs:0},openhuman:{maxConcurrent:1,maxQueued:3,queueTimeoutMs:0},opencode:{maxConcurrent:1,maxQueued:3,queueTimeoutMs:0},agy:{maxConcurrent:1,maxQueued:3,queueTimeoutMs:0}},r=t[n]??t.acp;return{maxConcurrent:e?.max_concurrent??r.maxConcurrent??1,maxQueued:e?.max_queued??r.maxQueued??3,queueTimeoutMs:e?.queue_timeout_ms??r.queueTimeoutMs??0,cancelableQueued:!0,cancelableRunning:!0}}function x(n){const e=V(),t=String(n.client_type??"").trim().toLowerCase(),r=de(t),a=String(n.ws_url??"").trim(),s="get_session_usage",o="get_rate_limits",c="get_agent_global_config";if(!n.name?.trim())throw new Error("agent name is required");if(!a)throw new Error(`agent ${n.name}: ws_url is required`);if(!n.agent_id?.trim())throw new Error(`agent ${n.name}: agent_id is required`);if(!n.api_key?.trim())throw new Error(`agent ${n.name}: api_key is required`);const i=r.adapterType,p=i==="acp",g=t==="qwen",f={...r.options??{}},w=i==="codex"?{capabilities:["local_action_v1","agent_invoke"],localActions:["session_control","get_context","set_model","set_mode","set_reasoning_effort","set_service_tier","set_sandbox_mode","exec_approve","exec_reject","file_list","create_folder","turn_interrupt","permission_approve","permission_reject","thread_compact",s,o]}:null,d=i==="claude"?{localActions:["session_control","set_mode","set_model","claude_interaction_reply","exec_approve","exec_reject","file_list","create_folder","thread_compact",s,o]}:null,u=g?{capabilities:["stream_chunk","local_action_v1"],localActions:["exec_approve","exec_reject","permission_approve","permission_reject","session_control","set_model","set_mode","file_list","create_folder",s],adapterHint:"qwen/base"}:null,h=i==="pi"?{adapterHint:"pi/base",capabilities:["local_action_v1"],localActions:["session_control","set_model","get_context","file_list","create_folder",s]}:null,m=i==="openhuman"?{adapterHint:"openhuman/base",capabilities:["local_action_v1"],localActions:["session_control","set_model","file_list","create_folder",s]}:null,_=i==="cursor"?{adapterHint:"cursor/base",capabilities:["stream_chunk","local_action_v1"],localActions:["session_control","set_model","set_mode","get_context","file_list","create_folder",s,o]}:null,y=i==="codewhale"?{capabilities:["stream_chunk","local_action_v1"],localActions:["session_control","set_model","file_list","create_folder",s]}:null,b=i==="opencode"?{adapterHint:"opencode/base",capabilities:["stream_chunk","local_action_v1"],localActions:["exec_approve","exec_reject","permission_approve","permission_reject","session_control","set_model","set_mode","file_list","create_folder",s]}:null,C=i==="agy"?{adapterHint:"agy/base",capabilities:["stream_chunk","local_action_v1"],localActions:["session_control","set_model","file_list","create_folder",s]}:null,H=p&&!g?{localActions:["exec_approve","exec_reject","permission_approve","permission_reject","session_control","set_model","set_mode","file_list","create_folder",s]}:null,q=t==="kiro"?{localActions:["exec_approve","exec_reject","permission_approve","permission_reject","session_control","set_model","set_mode","file_list","create_folder","thread_compact",s,o]}:null,R=i==="codex"||i==="claude"||t==="gemini"?["session_control","set_model","set_mode"]:void 0,N=[s,o];p&&f.raw_transport===void 0&&(f.raw_transport=t==="gemini");const F=`${t}/base`,I=Z(i,t),B=I==="kiro"?void 0:process.cwd(),k=ee({mode:I,projectDir:B}),E=k.filter($=>$.source==="connector").map($=>$.name);E.length>0&&l.info("manager",`[${n.name}] injecting connector skills: [${E.join(", ")}]`);let U=k.length>0?k:void 0;return{name:n.name,adapterType:i,aibot:{url:a,agentId:n.agent_id,apiKey:n.api_key,clientType:t,clientVersion:e,adapterHint:r.adapterHint??u?.adapterHint??h?.adapterHint??m?.adapterHint??_?.adapterHint??b?.adapterHint??C?.adapterHint??F,capabilities:w?.capabilities??y?.capabilities??h?.capabilities??m?.capabilities??_?.capabilities??b?.capabilities??C?.capabilities??u?.capabilities??["stream_chunk","local_action_v1","connector_upgrade"],localActions:ue(w?.localActions??y?.localActions??d?.localActions??h?.localActions??m?.localActions??_?.localActions??b?.localActions??C?.localActions??u?.localActions??q?.localActions??H?.localActions??["exec_approve","exec_reject"],R,N,["connector_rollback","connector_upgrade_push",c,"configure_gateway_provider"]),skills:U},agent:{command:r.command,args:[...r.args??[],...n.args??[]],env:n.env,clientType:t,...n.provider?{provider:{...n.provider.base_url?.trim()?{baseUrl:n.provider.base_url.trim()}:{},...n.provider.api_key?.trim()?{apiKey:n.provider.api_key.trim()}:{},...n.provider.model?.trim()?{model:n.provider.model.trim()}:{}}}:{}},adapterOptions:f,acpAuthMethod:f.auth_method,acpInitialMode:f.initial_mode,acpMcpTools:f.acp_mcp_tools,promptTimeoutMs:n.prompt_timeout_ms,bindingsPath:pe(n.name,n.agent_id),activeEventStorePath:me(n.name,n.agent_id),...r.enableSessionBinding||p?{enableSessionBinding:!0}:{},...r.autoInjectArgs||t==="reasonix"&&n.provider?.model?.trim()?{autoInjectArgs:{...r.autoInjectArgs??{},...t==="reasonix"&&n.provider?.model?.trim()?{model:n.provider.model.trim()}:{}}}:{},poolMaxSize:n.pool?.maxSize,poolIdleTimeoutMs:n.pool?.idleTimeoutMs,eventQueue:ge(i,n.event_queue),logDir:A.log,providerBaseUrl:n.provider_base_url?.trim()||void 0,providerApiKey:n.provider_api_key?.trim()||void 0}}function he(){const n=process.env.GRIX_AGENT_STARTUP_WAIT_MS,e=Number(n);return Number.isFinite(e)&&e>=500?Math.floor(e):ie}function O(n){const e=[],t=[],r=Q(n).filter(a=>a.endsWith(".json")).sort();for(const a of r)try{const s=JSON.parse(L(S(n,a),"utf-8"));if(Array.isArray(s.agents)){if(s.agents.length===0){t.push({file:a,error:"agents array is empty"});continue}for(const o of s.agents)e.push({entry:o,file:a})}else t.push({file:a,error:"unrecognized config format"})}catch(s){t.push({file:a,error:s instanceof Error?s.message:String(s)})}return{entries:e,fileErrors:t}}function v(n){if(n===null||typeof n!="object")return JSON.stringify(n)??"null";if(Array.isArray(n))return`[${n.map(v).join(",")}]`;const e=n;return`{${Object.keys(e).sort().map(r=>`${JSON.stringify(r)}:${v(e[r])}`).join(",")}}`}function fe(n,e){return v(n)===v(e)}class Oe{instances=[];configMap=new Map;rawConfigMap=new Map;reloadChain=Promise.resolve();sharedInstances=new Map;shareSyncChains=new Map;stopping=!1;deletedAgentCleanups=new Set;upgradeChecker=null;globalConfigStore;configDir=A.config;sharedOwnersCache=new J(A.data);installer=new se;async start(e){const t=e??A.config;this.configDir=t,l.info("manager",`Loading configs from ${t}`),te(),j(),this.globalConfigStore=new Y(S(A.data,"agent-global-configs.json")),this.globalConfigStore.load();const{entries:r,fileErrors:a}=O(t);for(const i of a)l.error("manager",`Failed to load ${i.file}: ${i.error}`);if(r.length===0&&a.length===0)throw new Error(`No config files found in ${t}`);const s=[];let o=a.length;for(const{entry:i,file:p}of r)try{const g=x(i);s.push({config:g,entry:i,file:p}),l.info("manager",`Loaded ${g.name} (${g.adapterType??"acp"}) from ${p}`)}catch(g){const f=typeof i?.name=="string"?i.name:"<unknown>";l.error("manager",`Invalid agent config in ${p} (name=${f}): ${g}`),o++}let c=0;if(s.length>0){const i=he();l.info("manager",`Starting ${s.length} agent(s), startup wait=${i}ms`);const p=()=>this.upgradeChecker?.triggerCheck(),g=d=>{this.instances=this.instances.filter(u=>u!==d)},f=s.map(({config:d,entry:u,file:h})=>{const m=new T(d,this.globalConfigStore);return m.setUpgradeTrigger(p),m.setShareSetHandler(_=>this.onShareSet(d,_)),m.setAgentDeletedHandler(()=>this.onAgentDeletedByPlatform(d.name)),this.instances.push(m),this.configMap.set(d.name,d),this.rawConfigMap.set(d.name,{entry:u,file:h}),{config:d,instance:m,startPromise:m.start()}}),w=await Promise.all(f.map(async d=>{const u=await new Promise(h=>{let m=!1;const _=setTimeout(()=>{m||(m=!0,h({kind:"timeout"}))},i);d.startPromise.then(()=>{m||(m=!0,clearTimeout(_),h({kind:"started"}))}).catch(y=>{m||(m=!0,clearTimeout(_),h({kind:"failed",error:y}))})});return{task:d,outcome:u}}));for(const{task:d,outcome:u}of w){if(u.kind==="started"){this.restoreCachedSharedInstances(d.config);continue}if(u.kind==="failed"){if(M(u.error)){this.onAgentDeletedByPlatform(d.config.name);continue}g(d.instance),l.error("manager",`Failed to start ${d.config.name}: ${u.error}`);continue}c++,l.warn("manager",`Startup pending for ${d.config.name}, continue retrying in background`),d.startPromise.then(()=>{l.info("manager",`Delayed start succeeded: ${d.config.name}`),this.restoreCachedSharedInstances(d.config)}).catch(h=>{if(M(h)){this.onAgentDeletedByPlatform(d.config.name);return}g(d.instance),l.error("manager",`Delayed start failed: ${d.config.name}: ${h}`)})}if(this.instances.length>0){const d=Math.max(0,this.instances.length-c);l.info("manager",`${d}/${s.length} agent(s) running now`)}c>0&&l.warn("manager",`${c} agent(s) still connecting in background`)}if(this.instances.length===0&&s.length>0)throw new Error("All agent configurations failed to start");s.length>0&&(this.upgradeChecker=new X(s.map(({config:i})=>({apiKey:i.aibot.apiKey,wsUrl:i.aibot.url})),()=>this.instances.some(i=>i.hasPendingWork())||[...this.sharedInstances.values()].some(i=>i.hasPendingWork())),await this.upgradeChecker.start())}async stop(){l.info("manager","Stopping all agents..."),this.stopping=!0,this.upgradeChecker?.stop(),await Promise.allSettled([...this.shareSyncChains.values()]),this.shareSyncChains.clear();const e=[...this.sharedInstances.values()];this.sharedInstances.clear(),await Promise.allSettled([...this.instances.map(t=>t.stop()),...e.map(t=>t.stop())]),await this.globalConfigStore?.flush(),this.instances=[],j(),l.info("manager","All stopped")}onShareSet(e,t){this.sharedOwnersCache.save(e.name,t);const a=(this.shareSyncChains.get(e.name)??Promise.resolve()).catch(()=>{}).then(()=>this.syncSharedInstances(e,t));this.shareSyncChains.set(e.name,a)}async syncSharedInstances(e,t){if(this.stopping)return;const{toAdd:r,toRemove:a}=z(e.name,this.sharedInstances.keys(),t);for(const s of r){if(this.stopping)break;const o=G(e.name,s);try{const c=K(e,s),i=new T(c,this.globalConfigStore);this.sharedInstances.set(o,i),await i.start(),l.info("manager",`shared instance started: ${o}`)}catch(c){this.sharedInstances.delete(o);const i=c instanceof Error?c.message:String(c);/Auth failed/i.test(i)?(this.sharedOwnersCache.remove(e.name,s),l.warn("manager",`shared instance auth rejected, removed from cache: ${o}`)):l.error("manager",`start shared instance failed ${o}: ${c}`)}}for(const{key:s}of a){const o=this.sharedInstances.get(s);o&&(this.sharedInstances.delete(s),o.stop().catch(c=>l.error("manager",`stop shared instance failed ${s}: ${c}`)),l.info("manager",`shared instance stopped: ${s}`))}}restoreCachedSharedInstances(e){const t=this.sharedOwnersCache.load(e.name);t.length!==0&&(l.info("manager",`restoring ${t.length} cached shared instance(s) for ${e.name}`),this.onShareSet(e,t))}getAgentsStatus(){return this.instances.map(e=>e.getStatus())}async addAgent(e){await this.addAgentInternal(e,"agents.json"),this.persistAgentsConfig(),l.info("manager",`Added agent: ${e.name}`)}async addAgentInternal(e,t){const r=x(e);if(this.instances.some(a=>a.name===r.name))throw new Error(`Agent "${r.name}" already exists`);await this.startInstanceFromConfig(r,e,t)}async startInstanceFromConfig(e,t,r){const a=new T(e,this.globalConfigStore);a.setUpgradeTrigger(()=>this.upgradeChecker?.triggerCheck()),a.setShareSetHandler(s=>this.onShareSet(e,s)),a.setAgentDeletedHandler(()=>this.onAgentDeletedByPlatform(e.name)),a.setProviderConfigHandler(s=>this.setAgentProvider(e.name,s)),a.setRelayEnvSettledHandler(()=>this.relayEnvSettledHandler?.()),await a.start(),this.instances.push(a),this.configMap.set(e.name,e),this.rawConfigMap.set(e.name,{entry:t,file:r}),this.relayEnvSettledHandler?.()}async removeAgent(e){if(!this.instances.some(t=>t.name===e))throw Object.assign(new Error(`Agent "${e}" not found`),{code:"NOT_FOUND"});await this.removeAgentInternal(e),this.persistAgentsConfig(),l.info("manager",`Removed agent: ${e}`),this.relayEnvSettledHandler?.()}async removeAgentInternal(e,t){const r=this.instances.findIndex(c=>c.name===e),a=r===-1?void 0:this.instances[r];r!==-1&&this.instances.splice(r,1),this.configMap.delete(e),this.rawConfigMap.delete(e);const s=this.shareSyncChains.get(e);this.shareSyncChains.delete(e),s&&await s.catch(()=>{});const o=`${e}#shared:`;for(const[c,i]of[...this.sharedInstances.entries()])c.startsWith(o)&&(this.sharedInstances.delete(c),i.stop().catch(p=>l.error("manager",`stop shared instance failed ${c}: ${p}`)));t?.keepShareCache||this.sharedOwnersCache.delete(e),a&&await a.stop()}onAgentDeletedByPlatform(e){this.stopping||this.deletedAgentCleanups.has(e)||(this.deletedAgentCleanups.add(e),(async()=>{try{if(!(this.rawConfigMap.has(e)||this.instances.some(r=>r.name===e)))return;l.warn("manager",`Agent "${e}" \u5DF2\u5728\u5E73\u53F0\u5220\u9664\uFF0C\u505C\u6B62\u5B9E\u4F8B\u5E76\u6E05\u7406\u672C\u5730\u914D\u7F6E`),await this.removeAgentInternal(e),this.persistAgentsConfig(),l.info("manager",`Agent "${e}" \u5DF2\u5728\u5E73\u53F0\u5220\u9664\uFF0C\u672C\u5730\u914D\u7F6E\u5DF2\u6E05\u7406\uFF08agents.json \u5DF2\u79FB\u9664\u8BE5\u6761\u76EE\uFF09`)}catch(t){l.error("manager",`cleanup for deleted agent "${e}" failed: ${t}`)}finally{this.deletedAgentCleanups.delete(e)}})())}persistAgentsConfig(){const e=S(this.configDir,"agents.json");try{const t=[];for(const[,a]of this.configMap){const s=this.rawConfigMap.get(a.name)?.entry.provider;t.push({name:a.name,ws_url:a.aibot.url,agent_id:a.aibot.agentId,api_key:a.aibot.apiKey,client_type:a.aibot.clientType,...s?{provider:s}:{}})}W(e,JSON.stringify({agents:t},null,4)+`
2
+ `,"utf-8")}catch(t){l.error("manager",`Failed to persist agents config: ${t}`)}}isAgentBusy(e){return this.instances.find(r=>r.name===e)?.hasPendingWork()??!1}getAgentsWithStaleRelayEnv(){return this.instances.filter(e=>e.hasStaleRelayEnv()).map(e=>e.name)}markAgentRelayEnvStale(e){const t=this.instances.find(r=>r.name===e);return t?(t.markRelayEnvStale(),!0):!1}relayEnvSettledHandler;setRelayEnvSettledHandler(e){this.relayEnvSettledHandler=e}async restartAgent(e){const t=this.rawConfigMap.get(e);if(!t)throw Object.assign(new Error(`Agent "${e}" not found`),{code:"NOT_FOUND"});await this.replaceInstance(e,t.entry,t.file),l.info("manager",`Restarted agent: ${e}`)}async setAgentProvider(e,t){const r=this.rawConfigMap.get(e);if(!r)throw Object.assign(new Error(`Agent "${e}" not found`),{code:"NOT_FOUND"});const a={...r.entry,provider:t};await this.replaceInstance(e,a,r.file),this.persistAgentsConfig(),l.info("manager",`Updated provider config for agent: ${e}`)}async clearDirectProviderConfigs(){const e=[...this.rawConfigMap.entries()].filter(([,r])=>!!r.entry.provider&&ne(r.entry.client_type)).map(([r])=>r),t=[];for(const r of e)try{await this.setAgentProvider(r,void 0),t.push(r)}catch(a){l.warn("manager",`Failed to clear provider config for "${r}": ${a instanceof Error?a.message:String(a)}`)}return t}async replaceInstance(e,t,r){const a=x(t);await this.removeAgentInternal(e,{keepShareCache:!0}),await this.startInstanceFromConfig(a,t,r),this.restoreCachedSharedInstances(a)}async reload(){const e=this.reloadChain.catch(()=>{}).then(()=>this.doReload());return this.reloadChain=e.catch(()=>{}),e}async doReload(){if(this.stopping)throw Object.assign(new Error("manager is stopping"),{code:"RELOAD_UNSAFE"});const{entries:e,fileErrors:t}=O(this.configDir);if(t.length>0){const o=t.map(c=>`${c.file}: ${c.error}`).join("; ");throw Object.assign(new Error(`reload aborted: ${t.length} config file(s) failed to parse [${o}]`),{code:"RELOAD_UNSAFE"})}if(e.length===0)throw Object.assign(new Error("reload aborted: no valid agent config found"),{code:"RELOAD_UNSAFE"});const r=new Map;for(const o of e){const c=String(o.entry?.name??"").trim();c&&(r.has(c)&&l.warn("manager",`reload: duplicate agent name "${c}", last one wins`),r.set(c,o))}const a={added:[],removed:[],restarted:[],unchanged:[],failed:[]},s=new Set(this.rawConfigMap.keys());for(const o of s)if(!r.has(o))try{await this.removeAgentInternal(o),a.removed.push(o)}catch(c){a.failed.push({name:o,error:c instanceof Error?c.message:String(c)})}for(const[o,c]of r){const i=this.rawConfigMap.get(o);if(i)if(fe(i.entry,c.entry))this.rawConfigMap.set(o,c),a.unchanged.push(o);else try{await this.replaceInstance(o,c.entry,c.file),a.restarted.push(o)}catch(p){a.failed.push({name:o,error:p instanceof Error?p.message:String(p)})}else try{await this.addAgentInternal(c.entry,c.file),a.added.push(o)}catch(p){a.failed.push({name:o,error:p instanceof Error?p.message:String(p)})}}return l.info("manager",`reload done: +${a.added.length} -${a.removed.length} ~${a.restarted.length} =${a.unchanged.length} !${a.failed.length}`),a}async checkUpgrade(){return this.upgradeChecker?this.upgradeChecker.checkForUpdate():{available:!1}}triggerUpgrade(){this.upgradeChecker?.triggerCheck()}async probeAll(e={}){return _e(this.instances,e)}async probeOne(e,t={}){return we(this.instances,e,t)}listInstallable(){return this.installer.listInstallable()}async installAgent(e){const t=await this.installer.install(e);return oe(t),t}getInstallProgress(e){return this.installer.getProgress(e)??null}}async function _e(n,e){const t=e.concurrency??4,r=Date.now(),a=new Array(n.length);await new Promise(p=>{let g=0,f=0;const w=n.length;if(w===0){p();return}function d(m){const _=n[m];_.probe(e).then(y=>{a[m]=y,u()},y=>{a[m]={agent_name:_.name,client_type:"unknown",adapter_type:"acp",ok:!1,status:"error",probed_at:Date.now(),duration_ms:0,cached:!1,cli:{command:"",installed:!1,path:null,version:null,error:{code:"internal",message:y?.message??String(y)}},conversation:{attempted:!1,ok:!1,latency_ms:null},config:{model:null,base_url:null,source:{model:"unknown",base_url:"unknown"}},process:{started:!1,alive:!1,busy:!1}},u()})}function u(){f++,g<w?d(g++):f===w&&p()}const h=Math.min(t,w);for(let m=0;m<h;m++)d(g++)});const s=a.filter(p=>p.status==="healthy").length,o=a.filter(p=>p.status==="degraded").length,c=a.filter(p=>p.status==="unavailable").length,i=await le();return{ok:s===a.length&&a.length>0,total:a.length,healthy:s,degraded:o,unavailable:c,installed_clients:i,agents:a,probed_at:r,duration_ms:Date.now()-r}}async function we(n,e,t){const r=n.find(a=>a.name===e);if(!r)throw Object.assign(new Error(`Agent "${e}" not found`),{code:"NOT_FOUND"});return r.probe(t)}export{Oe as Manager,le as probeInstalledClientCommands,_e as probeInstances,we as probeOneInstance};
@@ -1 +1 @@
1
- function a(e){const t=new Set([`http://127.0.0.1:${e.serverPort}`,`http://localhost:${e.serverPort}`,...e.allowedOrigins]),o=new Set([`127.0.0.1:${e.serverPort}`,`localhost:${e.serverPort}`,...e.allowedHosts]);return{validateRequest(s){const r=i(s,t);if(!r.ok)return r;const n=l(s,o);return n.ok?{ok:!0}:n}}}function i(e,t){const o=e.headers.origin;return o?t.has(o)?{ok:!0}:{ok:!1,statusCode:403,message:`Origin not allowed: ${o}`}:{ok:!0}}function l(e,t){const o=e.headers.host;return o?t.has(o)?{ok:!0}:{ok:!1,statusCode:403,message:`Host not allowed: ${o}`}:{ok:!1,statusCode:403,message:"Missing Host header"}}export{a as createSecurityPolicy};
1
+ function a(t){const o=new Set([`http://127.0.0.1:${t.serverPort}`,`http://localhost:${t.serverPort}`,...t.allowedOrigins]),e=new Set([`127.0.0.1:${t.serverPort}`,`localhost:${t.serverPort}`,...t.allowedHosts]);return{validateRequest(s){const r=i(s,o);if(!r.ok)return r;const n=l(s,e);return n.ok?{ok:!0}:n}}}function i(t,o){const e=t.headers.origin;return e?o.has(e)?{ok:!0}:{ok:!1,statusCode:403,message:`Origin not allowed: ${e}`}:{ok:!0}}function l(t,o){const e=t.headers.host;return e?o.has(e)?{ok:!0}:{ok:!1,statusCode:403,message:`Host not allowed: ${e}`}:{ok:!1,statusCode:403,message:"Missing Host header"}}export{a as createSecurityPolicy};
@@ -1 +1 @@
1
- import{EventEmitter as S}from"events";import{log as l}from"../core/log/index.js";import{AgentEventType as c}from"../types/events.js";import{mapSessionUpdate as M}from"./event-mapper.js";class h extends Error{authMethods;constructor(e){super("ACP authentication required"),this.name="AcpAuthRequiredError",this.authMethods=e}}function p(a){if(!a||typeof a!="object")return!1;const e=a;return e.code===-32e3?!0:e.code===-32603&&e.data?.details?/\b(401|token\s*expired|access\s*token)\b/i.test(e.data.details):!1}function m(a){return!a||typeof a!="object"?[{id:"oauth"}]:a.data?.authMethods??[{id:"oauth"}]}class u extends S{acpSessionId="";alive=!1;pendingPermissions=new Map;availableModes=[];availableModels=[];currentMode="";currentModel="";listSupported=!1;loadSupported=!1;_supportsCommandsExecute=!1;_availableCommands=[];transport;settleTimer=null;static SETTLE_MS=3e3;pendingToolCallIds=new Set;settleSuppressedSince=0;static SETTLE_SUPPRESS_MAX_MS=900*1e3;constructor(){super(),this.transport=null}get sessionId(){return this.acpSessionId}get isAlive(){return this.alive}get modes(){return[...this.availableModes]}get mode(){return this.currentMode}get models(){return[...this.availableModels]}get model(){return this.currentModel}get sessionOptions(){return{modes:[...this.availableModes],currentModeId:this.currentMode,models:[...this.availableModels],currentModelId:this.currentModel}}get supportsCommandsExecute(){return this._supportsCommandsExecute}get availableCommands(){return[...this._availableCommands]}async connect(e){this.transport=e.transport,this.transport.on("close",()=>{this.alive&&(this.alive=!1,this.emit("session-lost"))}),this.transport.setHandlers((n,r)=>{n==="session/update"?this.handleSessionUpdate(r):n==="_kiro.dev/metadata"?this.handleKiroMetadata(r):n==="_kiro.dev/commands/available"?this.handleCommandsAvailable(r):n==="_kiro.dev/compaction/status"?this.emit("compactionStatus",r):n==="_kiro.dev/clear/status"&&this.emit("clearStatus",r)},(n,r,d)=>{n==="session/request_permission"?this.handlePermissionRequest(r,d):n.startsWith("cursor/")?this.transport.respondSuccess(r,{}).catch(()=>{}):this.transport.respondError(r,-32601,"method not implemented").catch(()=>{})});const t=await this.initialize();this.listSupported=!!t.agentCapabilities?.sessionCapabilities?.list,this.loadSupported=!!t.agentCapabilities?.loadSession;const i=t.agentCapabilities??{};if(this._supportsCommandsExecute=!!(i.extensions?.["_kiro.dev/commands"]||i.commands),e.authMethod)try{await this.transport.call("authenticate",{methodId:e.authMethod})}catch(n){throw p(n)?new h(m(n)):n}const s=e.sessionId&&e.sessionId!=="__continue__";let o=!1;if(s){if(!this.loadSupported)throw new Error(`session/load not supported by agent, cannot resume session ${e.sessionId}`);try{await this.loadSession(e.sessionId,e)&&(o=!0)}catch(n){throw p(n)?new h(m(n)):n}if(!o)throw new Error(`session/load failed for session ${e.sessionId}`)}else try{await this.newSession(e)}catch(n){throw p(n)?new h(m(n)):n}e.initialMode&&await this.setLiveMode(e.initialMode).catch(()=>{}),e.initialModel&&await this.setModel(e.initialModel).catch(()=>{}),this.alive=!0}async initialize(){return await this.transport.call("initialize",{protocolVersion:1,clientCapabilities:{fs:{readTextFile:!1,writeTextFile:!1},terminal:!1},clientInfo:{name:"grix-connector-acp",version:"0.2.0"}})}buildMcpEntries(e){return(e??[]).map(t=>{const i={name:t.name,command:t.command};return t.args&&(i.args=t.args),t.env&&Object.keys(t.env).length>0&&(i.env=Object.entries(t.env).map(([s,o])=>({name:s,value:o}))),i})}async newSession(e){const t=this.buildMcpEntries(e.mcpServers),i={cwd:e.cwd||process.cwd(),mcpServers:t};e.additionalDirectories&&e.additionalDirectories.length>0&&(i.additionalDirectories=e.additionalDirectories);const s=await this.transport.call("session/new",i);if(!s?.sessionId)throw new Error("session/new returned empty sessionId");this.acpSessionId=s.sessionId,this.absorbModes(s.modes),this.absorbModels(s.models)}async loadSession(e,t){const i=this.buildMcpEntries(t.mcpServers),s={sessionId:e,cwd:t.cwd||process.cwd(),mcpServers:i};t.additionalDirectories&&t.additionalDirectories.length>0&&(s.additionalDirectories=t.additionalDirectories);const o=await this.transport.call("session/load",s);return o?.sessionId?(this.acpSessionId=o.sessionId,this.absorbModes(o.modes),this.absorbModels(o.models),!0):o&&(o.modes||o.models)?(this.acpSessionId=e,this.absorbModes(o.modes),this.absorbModels(o.models),!0):!1}absorbModes(e){e?.availableModes?.length&&(this.availableModes=[...e.availableModes],e.currentModeId&&(this.currentMode=e.currentModeId))}absorbModels(e){e?.availableModels?.length&&(this.availableModels=[...e.availableModels],e.currentModelId&&(this.currentModel=e.currentModelId))}async send(e,t,i){if(!this.alive)throw new Error("session not active");if(!this.acpSessionId)throw new Error("no agent session id");const s=[{type:"text",text:e}];if(t)for(const o of t)s.push({type:"image",data:o.data.toString("base64"),mimeType:o.mimeType});this.pendingToolCallIds.clear(),this.settleSuppressedSince=0,await this.transport.call("session/prompt",{sessionId:this.acpSessionId,prompt:s,...this.currentModel?{modelId:this.currentModel}:{}}),this.scheduleSettledResult()}scheduleSettledResult(){this.settleTimer&&clearTimeout(this.settleTimer),this.settleTimer=setTimeout(()=>{if(this.settleTimer=null,!!this.alive){if(this.pendingToolCallIds.size>0){if(this.settleSuppressedSince===0&&(this.settleSuppressedSince=Date.now()),Date.now()-this.settleSuppressedSince<u.SETTLE_SUPPRESS_MAX_MS){l.info("acp-client",`settle suppressed: ${this.pendingToolCallIds.size} pending tool call(s) for session ${this.acpSessionId}`),this.scheduleSettledResult();return}l.warn("acp-client",`settle suppression exceeded ${u.SETTLE_SUPPRESS_MAX_MS/6e4}min with ${this.pendingToolCallIds.size} pending tool call(s), emitting Result anyway`)}this.settleSuppressedSince=0,l.info("acp-client",`settle timer fired, emitting Result for session ${this.acpSessionId}`),this.emit("event",{type:c.Result,sessionId:this.acpSessionId,done:!0})}},u.SETTLE_MS),this.settleTimer.unref()}clearSettleTimer(){this.settleTimer&&(clearTimeout(this.settleTimer),this.settleTimer=null),this.settleSuppressedSince=0}async cancel(){if(this.acpSessionId){this.pendingToolCallIds.clear(),this.settleSuppressedSince=0;try{await this.transport.notify("session/cancel",{sessionId:this.acpSessionId})}catch{}}}async authenticate(e){await this.transport.call("authenticate",{methodId:e})}async respondPermission(e,t){const i=this.pendingPermissions.get(e);if(!i)throw new Error(`unknown permission request: ${e}`);this.pendingPermissions.delete(e);const s=this.pickPermissionOptionId(t.behavior,i.options),o=this.buildPermissionResult(t.behavior,s);await this.transport.respondSuccess(i.rpcId,o)}async ping(e=1e4){if(!this.alive)return!1;try{const t=new AbortController,i=setTimeout(()=>t.abort(),e);return await this.transport.call("session/list",{},t.signal),clearTimeout(i),!0}catch(t){return t?.code===-32601}}async setLiveMode(e){if(!this.acpSessionId)return l.warn("acp-client",`setLiveMode("${e}") skipped: no active session`),!1;const t=this.matchAvailableMode(e);if(!t)return l.warn("acp-client",`setLiveMode("${e}") failed: mode not found in available [${this.availableModes.map(i=>i.id).join(",")}]`),!1;try{const i=new AbortController,s=setTimeout(()=>i.abort(),8e3);return await this.transport.call("session/set_mode",{sessionId:this.acpSessionId,modeId:t},i.signal),clearTimeout(s),this.currentMode=t,!0}catch(i){return l.warn("acp-client",`setLiveMode("${t}") RPC failed: ${i instanceof Error?i.message:i}`),!1}}async setModel(e){if(!this.acpSessionId)return l.warn("acp-client",`setModel("${e}") skipped: no active session`),!1;try{const t=new AbortController,i=setTimeout(()=>t.abort(),8e3);return await this.transport.call("session/set_model",{sessionId:this.acpSessionId,modelId:e},t.signal),clearTimeout(i),this.currentModel=e,!0}catch(t){return l.warn("acp-client",`setModel("${e}") RPC failed: ${t instanceof Error?t.message:t}`),!1}}handleSessionUpdate(e){this.emit("activity");const t=e?.update?.sessionUpdate,i=t==="usage_update";if(t==="tool_call"||t==="tool_call_update"){const o=e?.update??{},n=String(o.toolCallId??""),r=String(o.status??"").toLowerCase().trim();n&&(r==="completed"||r==="failed"?this.pendingToolCallIds.delete(n):t==="tool_call"&&this.pendingToolCallIds.add(n))}this.settleTimer&&!i&&this.scheduleSettledResult();const s=M(this.acpSessionId,e);for(const o of s)o.sessionId||(o.sessionId=this.acpSessionId),o.type===c.Result&&(this.pendingToolCallIds.clear(),this.settleTimer&&this.clearSettleTimer()),this.emit("event",o)}handleKiroMetadata(e){const i=e?.contextUsagePercentage;typeof i!="number"||!Number.isFinite(i)||this.emit("event",{type:c.ContextWindowUpdate,sessionId:this.acpSessionId,contextWindow:{usedPercentage:Math.min(100,Math.max(0,i))}})}handleCommandsAvailable(e){const t=e,i=Array.isArray(t?.commands)?t.commands:[];this._availableCommands=i.map(s=>({name:String(s.name??""),description:s.description?String(s.description):void 0,args:s.args?String(s.args):void 0})),this._supportsCommandsExecute=!0,this.emit("commandsAvailable",this._availableCommands)}async executeCommand(e,t){if(!this.alive)throw new Error("session not active");if(!this.acpSessionId)throw new Error("no agent session id");const i=new AbortController,s=setTimeout(()=>i.abort(),2e4);try{const o=await this.transport.call("_kiro.dev/commands/execute",{sessionId:this.acpSessionId,command:e,...t?{args:t}:{}},i.signal);return clearTimeout(s),{status:o?.status??"ok",message:o?.message,options:o?.options,data:o?.data}}catch(o){clearTimeout(s);const n=o instanceof Error?o.message:String(o);if(o?.code===-32601)throw this._supportsCommandsExecute=!1,o;return{status:"failed",message:n}}}handlePermissionRequest(e,t){const i=t,s=i?.toolCall??{},o=String(e),n=Array.isArray(i?.options)?i.options:[];this.pendingPermissions.set(o,{rpcId:e,options:n});const r=s.title||s.kind||"permission",d=s.toolCallId||o,f=s.title||"",w={type:c.PermissionRequest,requestId:o,toolName:r,toolInput:f||d,sessionId:this.acpSessionId,permissionRequest:{requestId:o,toolCallId:d,toolName:r,toolTitle:f,options:n,rawParams:t}};this.emit("event",w)}matchAvailableMode(e){const t=e.toLowerCase();for(const i of this.availableModes)if(i.id.toLowerCase()===t||i.name.toLowerCase()===t)return i.id;return""}pickPermissionOptionId(e,t){if(t.length===0)return"";if(e==="deny"){for(const s of t)if(s.kind==="reject_once"||s.kind==="reject_always")return s.optionId;for(const s of t)if(s.kind.toLowerCase().includes("reject")||s.kind.toLowerCase().includes("deny"))return s.optionId;for(const s of t)if(s.name.toLowerCase().includes("reject")||s.name.toLowerCase().includes("deny"))return s.optionId;return t[t.length-1].optionId}const i=e==="allow-always"?"allow_always":e==="allow-once"?"allow_once":null;if(i){for(const s of t)if(s.kind===i)return s.optionId}for(const s of t)if(s.kind.toLowerCase().includes("allow"))return s.optionId;for(const s of t)if(s.name.toLowerCase().includes("allow"))return s.optionId;return t[0].optionId}buildPermissionResult(e,t){return e==="deny"?t?{outcome:{outcome:"selected",optionId:t}}:{outcome:{outcome:"cancelled"}}:t?{outcome:{outcome:"selected",optionId:t}}:{outcome:{outcome:"cancelled"}}}}export{h as AcpAuthRequiredError,u as AcpClient,p as isAuthRequiredError};
1
+ import{EventEmitter as S}from"events";import{log as l}from"../core/log/index.js";import{AgentEventType as c}from"../types/events.js";import{mapSessionUpdate as M}from"./event-mapper.js";class h extends Error{authMethods;constructor(e){super("ACP authentication required"),this.name="AcpAuthRequiredError",this.authMethods=e}}function p(a){if(!a||typeof a!="object")return!1;const e=a;return e.code===-32e3?!0:e.code===-32603&&e.data?.details?/\b(401|token\s*expired|access\s*token)\b/i.test(e.data.details):!1}function m(a){return!a||typeof a!="object"?[{id:"oauth"}]:a.data?.authMethods??[{id:"oauth"}]}class u extends S{acpSessionId="";alive=!1;pendingPermissions=new Map;availableModes=[];availableModels=[];currentMode="";currentModel="";listSupported=!1;loadSupported=!1;_supportsCommandsExecute=!1;_availableCommands=[];transport;settleTimer=null;static SETTLE_MS=3e3;pendingToolCallIds=new Set;settleSuppressedSince=0;static SETTLE_SUPPRESS_MAX_MS=900*1e3;constructor(){super(),this.transport=null}get sessionId(){return this.acpSessionId}get isAlive(){return this.alive}get modes(){return[...this.availableModes]}get mode(){return this.currentMode}get models(){return[...this.availableModels]}get model(){return this.currentModel}get sessionOptions(){return{modes:[...this.availableModes],currentModeId:this.currentMode,models:[...this.availableModels],currentModelId:this.currentModel}}get supportsCommandsExecute(){return this._supportsCommandsExecute}get availableCommands(){return[...this._availableCommands]}async connect(e){this.transport=e.transport,this.transport.on("close",()=>{this.alive&&(this.alive=!1,this.emit("session-lost"))}),this.transport.setHandlers((n,r)=>{n==="session/update"?this.handleSessionUpdate(r):n==="_kiro.dev/metadata"?this.handleKiroMetadata(r):n==="_kiro.dev/commands/available"?this.handleCommandsAvailable(r):n==="_kiro.dev/compaction/status"?this.emit("compactionStatus",r):n==="_kiro.dev/clear/status"&&this.emit("clearStatus",r)},(n,r,d)=>{n==="session/request_permission"?this.handlePermissionRequest(r,d):n.startsWith("cursor/")?this.transport.respondSuccess(r,{}).catch(()=>{}):this.transport.respondError(r,-32601,"method not implemented").catch(()=>{})});const t=await this.initialize();this.listSupported=!!t.agentCapabilities?.sessionCapabilities?.list,this.loadSupported=!!t.agentCapabilities?.loadSession;const i=t.agentCapabilities??{};if(this._supportsCommandsExecute=!!(i.extensions?.["_kiro.dev/commands"]||i.commands),e.authMethod)try{await this.transport.call("authenticate",{methodId:e.authMethod})}catch(n){throw p(n)?new h(m(n)):n}const s=e.sessionId&&e.sessionId!=="__continue__";let o=!1;if(s){if(!this.loadSupported)throw new Error(`session/load not supported by agent, cannot resume session ${e.sessionId}`);try{await this.loadSession(e.sessionId,e)&&(o=!0)}catch(n){throw p(n)?new h(m(n)):n}if(!o)throw new Error(`session/load failed for session ${e.sessionId}`)}else try{await this.newSession(e)}catch(n){throw p(n)?new h(m(n)):n}e.initialMode&&await this.setLiveMode(e.initialMode).catch(()=>{}),e.initialModel&&await this.setModel(e.initialModel).catch(()=>{}),this.alive=!0}async initialize(){return await this.transport.call("initialize",{protocolVersion:1,clientCapabilities:{fs:{readTextFile:!1,writeTextFile:!1},terminal:!1},clientInfo:{name:"grix-connector-acp",version:"0.2.0"}})}buildMcpEntries(e){return(e??[]).map(t=>{const i={name:t.name,command:t.command};return t.args&&(i.args=t.args),t.env&&(i.env=Object.entries(t.env).map(([s,o])=>({name:s,value:o}))),i})}async newSession(e){const t=this.buildMcpEntries(e.mcpServers),i={cwd:e.cwd||process.cwd(),mcpServers:t};e.additionalDirectories&&e.additionalDirectories.length>0&&(i.additionalDirectories=e.additionalDirectories);const s=await this.transport.call("session/new",i);if(!s?.sessionId)throw new Error("session/new returned empty sessionId");this.acpSessionId=s.sessionId,this.absorbModes(s.modes),this.absorbModels(s.models)}async loadSession(e,t){const i=this.buildMcpEntries(t.mcpServers),s={sessionId:e,cwd:t.cwd||process.cwd(),mcpServers:i};t.additionalDirectories&&t.additionalDirectories.length>0&&(s.additionalDirectories=t.additionalDirectories);const o=await this.transport.call("session/load",s);return o?.sessionId?(this.acpSessionId=o.sessionId,this.absorbModes(o.modes),this.absorbModels(o.models),!0):o&&(o.modes||o.models)?(this.acpSessionId=e,this.absorbModes(o.modes),this.absorbModels(o.models),!0):(this.acpSessionId=e,!0)}absorbModes(e){e?.availableModes?.length&&(this.availableModes=[...e.availableModes],e.currentModeId&&(this.currentMode=e.currentModeId))}absorbModels(e){e?.availableModels?.length&&(this.availableModels=[...e.availableModels],e.currentModelId&&(this.currentModel=e.currentModelId))}async send(e,t,i){if(!this.alive)throw new Error("session not active");if(!this.acpSessionId)throw new Error("no agent session id");const s=[{type:"text",text:e}];if(t)for(const o of t)s.push({type:"image",data:o.data.toString("base64"),mimeType:o.mimeType});this.pendingToolCallIds.clear(),this.settleSuppressedSince=0,await this.transport.call("session/prompt",{sessionId:this.acpSessionId,prompt:s,...this.currentModel?{modelId:this.currentModel}:{}}),this.scheduleSettledResult()}scheduleSettledResult(){this.settleTimer&&clearTimeout(this.settleTimer),this.settleTimer=setTimeout(()=>{if(this.settleTimer=null,!!this.alive){if(this.pendingToolCallIds.size>0){if(this.settleSuppressedSince===0&&(this.settleSuppressedSince=Date.now()),Date.now()-this.settleSuppressedSince<u.SETTLE_SUPPRESS_MAX_MS){l.info("acp-client",`settle suppressed: ${this.pendingToolCallIds.size} pending tool call(s) for session ${this.acpSessionId}`),this.scheduleSettledResult();return}l.warn("acp-client",`settle suppression exceeded ${u.SETTLE_SUPPRESS_MAX_MS/6e4}min with ${this.pendingToolCallIds.size} pending tool call(s), emitting Result anyway`)}this.settleSuppressedSince=0,l.info("acp-client",`settle timer fired, emitting Result for session ${this.acpSessionId}`),this.emit("event",{type:c.Result,sessionId:this.acpSessionId,done:!0})}},u.SETTLE_MS),this.settleTimer.unref()}clearSettleTimer(){this.settleTimer&&(clearTimeout(this.settleTimer),this.settleTimer=null),this.settleSuppressedSince=0}async cancel(){if(this.acpSessionId){this.pendingToolCallIds.clear(),this.settleSuppressedSince=0;try{await this.transport.notify("session/cancel",{sessionId:this.acpSessionId})}catch{}}}async authenticate(e){await this.transport.call("authenticate",{methodId:e})}async respondPermission(e,t){const i=this.pendingPermissions.get(e);if(!i)throw new Error(`unknown permission request: ${e}`);this.pendingPermissions.delete(e);const s=this.pickPermissionOptionId(t.behavior,i.options),o=this.buildPermissionResult(t.behavior,s);await this.transport.respondSuccess(i.rpcId,o)}async ping(e=1e4){if(!this.alive)return!1;try{const t=new AbortController,i=setTimeout(()=>t.abort(),e);return await this.transport.call("session/list",{},t.signal),clearTimeout(i),!0}catch(t){return t?.code===-32601}}async setLiveMode(e){if(!this.acpSessionId)return l.warn("acp-client",`setLiveMode("${e}") skipped: no active session`),!1;const t=this.matchAvailableMode(e);if(!t)return l.warn("acp-client",`setLiveMode("${e}") failed: mode not found in available [${this.availableModes.map(i=>i.id).join(",")}]`),!1;try{const i=new AbortController,s=setTimeout(()=>i.abort(),8e3);return await this.transport.call("session/set_mode",{sessionId:this.acpSessionId,modeId:t},i.signal),clearTimeout(s),this.currentMode=t,!0}catch(i){return l.warn("acp-client",`setLiveMode("${t}") RPC failed: ${i instanceof Error?i.message:i}`),!1}}async setModel(e){if(!this.acpSessionId)return l.warn("acp-client",`setModel("${e}") skipped: no active session`),!1;try{const t=new AbortController,i=setTimeout(()=>t.abort(),8e3);return await this.transport.call("session/set_model",{sessionId:this.acpSessionId,modelId:e},t.signal),clearTimeout(i),this.currentModel=e,!0}catch(t){return l.warn("acp-client",`setModel("${e}") RPC failed: ${t instanceof Error?t.message:t}`),!1}}handleSessionUpdate(e){this.emit("activity");const t=e?.update?.sessionUpdate,i=t==="usage_update";if(t==="tool_call"||t==="tool_call_update"){const o=e?.update??{},n=String(o.toolCallId??""),r=String(o.status??"").toLowerCase().trim();n&&(r==="completed"||r==="failed"?this.pendingToolCallIds.delete(n):t==="tool_call"&&this.pendingToolCallIds.add(n))}this.settleTimer&&!i&&this.scheduleSettledResult();const s=M(this.acpSessionId,e);for(const o of s)o.sessionId||(o.sessionId=this.acpSessionId),o.type===c.Result&&(this.pendingToolCallIds.clear(),this.settleTimer&&this.clearSettleTimer()),this.emit("event",o)}handleKiroMetadata(e){const i=e?.contextUsagePercentage;typeof i!="number"||!Number.isFinite(i)||this.emit("event",{type:c.ContextWindowUpdate,sessionId:this.acpSessionId,contextWindow:{usedPercentage:Math.min(100,Math.max(0,i))}})}handleCommandsAvailable(e){const t=e,i=Array.isArray(t?.commands)?t.commands:[];this._availableCommands=i.map(s=>({name:String(s.name??""),description:s.description?String(s.description):void 0,args:s.args?String(s.args):void 0})),this._supportsCommandsExecute=!0,this.emit("commandsAvailable",this._availableCommands)}async executeCommand(e,t){if(!this.alive)throw new Error("session not active");if(!this.acpSessionId)throw new Error("no agent session id");const i=new AbortController,s=setTimeout(()=>i.abort(),2e4);try{const o=await this.transport.call("_kiro.dev/commands/execute",{sessionId:this.acpSessionId,command:e,...t?{args:t}:{}},i.signal);return clearTimeout(s),{status:o?.status??"ok",message:o?.message,options:o?.options,data:o?.data}}catch(o){clearTimeout(s);const n=o instanceof Error?o.message:String(o);if(o?.code===-32601)throw this._supportsCommandsExecute=!1,o;return{status:"failed",message:n}}}handlePermissionRequest(e,t){const i=t,s=i?.toolCall??{},o=String(e),n=Array.isArray(i?.options)?i.options:[];this.pendingPermissions.set(o,{rpcId:e,options:n});const r=s.title||s.kind||"permission",d=s.toolCallId||o,f=s.title||"",w={type:c.PermissionRequest,requestId:o,toolName:r,toolInput:f||d,sessionId:this.acpSessionId,permissionRequest:{requestId:o,toolCallId:d,toolName:r,toolTitle:f,options:n,rawParams:t}};this.emit("event",w)}matchAvailableMode(e){const t=e.toLowerCase();for(const i of this.availableModes)if(i.id.toLowerCase()===t||i.name.toLowerCase()===t)return i.id;return""}pickPermissionOptionId(e,t){if(t.length===0)return"";if(e==="deny"){for(const s of t)if(s.kind==="reject_once"||s.kind==="reject_always")return s.optionId;for(const s of t)if(s.kind.toLowerCase().includes("reject")||s.kind.toLowerCase().includes("deny"))return s.optionId;for(const s of t)if(s.name.toLowerCase().includes("reject")||s.name.toLowerCase().includes("deny"))return s.optionId;return t[t.length-1].optionId}const i=e==="allow-always"?"allow_always":e==="allow-once"?"allow_once":null;if(i){for(const s of t)if(s.kind===i)return s.optionId}for(const s of t)if(s.kind.toLowerCase().includes("allow"))return s.optionId;for(const s of t)if(s.name.toLowerCase().includes("allow"))return s.optionId;return t[0].optionId}buildPermissionResult(e,t){return e==="deny"?t?{outcome:{outcome:"selected",optionId:t}}:{outcome:{outcome:"cancelled"}}:t?{outcome:{outcome:"selected",optionId:t}}:{outcome:{outcome:"cancelled"}}}}export{h as AcpAuthRequiredError,u as AcpClient,p as isAuthRequiredError};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "grix-connector",
3
- "version": "3.6.0",
3
+ "version": "3.7.1",
4
4
  "description": "Connect local AI coding agents (Claude, Codex, Gemini, Qwen, DeepSeek, Cursor, OpenCode, Pi, OpenHuman, Reasonix) to the Grix scheduling platform. Also serves as an OpenClaw plugin for Grix channel transport.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",