grix-connector 3.15.1 → 3.15.3

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,5 +1,5 @@
1
- import{fileURLToPath as D}from"node:url";import m from"node:path";import{homedir as v}from"node:os";import{promises as C}from"node:fs";import{EventEmitter as w}from"node:events";import{spawn as k}from"node:child_process";import{AgentProcess as B}from"../../agent/process.js";import{hasChildProcesses as j}from"../../core/runtime/spawn.js";import{applyNativeProviderConfig as F}from"../shared/native-provider-config.js";import{syncDefaultSkillsToDir as L,resolveKimiCodeHome as K}from"../../default-skills/index.js";import{AcpClient as H,AcpAuthRequiredError as W,isAuthRequiredError as G}from"../../protocol/acp-client.js";import{AgentEventType as f}from"../../types/events.js";import{InternalApiServer as Y}from"../../core/mcp/internal-api-server.js";import{EventResultsStore as V}from"../../core/persistence/event-results-store.js";import{QuotedMessageStream as S}from"../../core/util/quoted-message-stream.js";import{SafeMarkdownStreamSegmenter as z}from"../../core/text-segmentation/index.js";import{extractAcpTurnInput as X}from"../../core/protocol/payload-parser.js";import{injectMessageMetadata as Q}from"../../core/protocol/message-metadata.js";import{log as o}from"../../core/log/index.js";function P(c,e=2e3){let t;try{const i=new WeakSet;t=JSON.stringify(c,(s,n)=>{if(n instanceof Error)return{name:n.name,message:n.message};if(typeof n=="function")return"[function]";if(typeof n=="bigint"||typeof n=="symbol")return n.toString();if(typeof n=="object"&&n!==null){if(i.has(n))return"[circular]";i.add(n)}return n})}catch(i){t=`[stringify failed: ${i instanceof Error?i.message:String(i)}]`}return t===void 0?"[undefined]":t.length<=e?t:`${t.slice(0,e)}...[+${t.length-e}b]`}const J=5e3;function $(c,e){const t=e.toolName??"";return e.toolCallId&&(!t||t===e.toolCallId)?c.toolNamesById.get(e.toolCallId)??t:t}function y(c){const e=String(c??"").trim();if(!e)return"";try{const t=JSON.parse(e);return JSON.stringify(T(t))}catch{return e}}function T(c){if(c===null||typeof c!="object")return c;if(Array.isArray(c))return c.map(T);const e=c,t=Object.keys(e).sort(),i={};for(const s of t)i[s]=T(e[s]);return i}import{scanSkills as E}from"../claude/skill-scanner.js";import{checkKiroSessionActivity as Z}from"./session-activity.js";import{resolveCliPath as ee,getCliVersion as te}from"../../core/util/cli-probe.js";const b=m.dirname(D(import.meta.url)),ie=[{modelId:"deepseek-v4-pro",name:"DeepSeek V4 Pro"},{modelId:"deepseek-v4-flash",name:"DeepSeek V4 Flash"}],M=[{modelId:"kimi-code/kimi-for-coding",name:"K2.7 Coding"},{modelId:"kimi-code/kimi-for-coding-highspeed",name:"K2.7 Coding Highspeed"},{modelId:"kimi-code/k3",name:"K3"}];function R(c,e){const t=Number(process.env[c]);return Number.isFinite(t)&&t>0?t:e}const se=200,ne=60*1e3,oe=600*1e3,_=R("GRIX_ACP_TURN_INACTIVITY_TIMEOUT_MS",900*1e3),x=80,N=1200*1e3,re=R("GRIX_ACP_BUSY_RETRY_INTERVAL_MS",5e3),U=R("GRIX_ACP_BUSY_WAIT_MAX_MS",1800*1e3),ae=6e4,le=2e3,ce=12e3,O=3e3,q=3e4,de=["[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 ue(c){return!(!(c instanceof Error)||c.code!==-32603||G(c))}function he(c){if(!(c instanceof Error))return!1;const e=c.data;return e&&typeof e=="object"&&e.code==="turn.agent_busy"?!0:/cannot launch a new turn/i.test(c.message)}function g(c){if(!(c instanceof Error))return String(c);let e=c.message;const t=c.data;return t&&(e+=` data=${JSON.stringify(t)}`),e}function pe(c){return c.replace(/\u001b\[[0-9;?]*[ -/]*[@-~]/g,"")}class qe extends w{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;orphanBuffer="";orphanQuotedStream=new S;orphanFlushTimer=null;orphanSessionId=null;orphanSuppressedUntil=0;constructor(e,t,i){if(super(),this.config=e,this.callbacks=t,this.bridgeLog=i?.bridgeLog??null,this.acpAuthMethod=i?.acpAuthMethod,this.acpInitialMode=i?.acpInitialMode,this.acpInitialModel=i?.acpInitialModel,this.acpMcpTools=i?.acpMcpTools??!0,this.rawTransport=i?.rawTransport??!1,this.approvalMode=i?.approvalMode??"default",this.autoInjectArgs=i?.autoInjectArgs,this.nativeProviderScope=i?.nativeProviderScope,this.bindingStore=i?.bindingStore??null,this.currentAibotSessionId=i?.aibotSessionId?String(i.aibotSessionId).trim():void 0,i?.eventResultsPath&&(this.eventResults=new V(i.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.clearRunTimers(this.activeRun),this.activeRun=null),this.flushOrphanBuffer(),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,t){}async destroySession(e){this.quoteProtocolTaughtSessions.delete(e),this.identityInjectedSessions.delete(e)}onAgentProfileChanged(){this.identityInjectedSessions.size>0&&(o.info("acp-adapter",`agent profile changed; clearing ${this.identityInjectedSessions.size} identity-injected session(s)`),this.identityInjectedSessions.clear())}sendPrompt(e){const t=new me(e.adapterSessionId);return this.acpClient?.isAlive&&this.acpClient.send(e.text).catch(i=>{t.emitError(i instanceof Error?i:new Error(String(i)))}),t}async cancel(e){this.activeRun&&this.acpClient&&(await this.acpClient.cancel(),this.flushStream(),this.finishRun("canceled","stopped by user"),this.suppressOrphanOutput(q))}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.clearRunTimers(this.activeRun),this.activeRun=null)}clearRunTimers(e){e.flushTimer&&(clearTimeout(e.flushTimer),e.flushTimer=null),e.idleTimer&&(clearTimeout(e.idleTimer),e.idleTimer=null),e.busyRetryTimer&&(clearTimeout(e.busyRetryTimer),e.busyRetryTimer=null)}getMcpConfig(){if(!this.internalApi)return null;this.internalApi.setMcpBridgeUpHandler(t=>this.onMcpBridgeUp(t));const e=m.resolve(b,"../../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?j(e,[e]):!1}async probe(e){const t=this.config.command||"gemini",i=await ee(t),s=i!==null;let n=null,r;if(s){const h=await te(t);n=h.version,h.error&&(r=h.error)}else r={code:"cli_not_found",message:`command not found: ${t}`};const a=this.acpClient?.model??null,l=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:t,installed:s,path:i,version:n,...r?{error:r}:{}},conversation:d,config:{model:a,base_url:null,source:{model:a?"runtime":"unknown",base_url:"unknown"}},process:{started:!!this.agentProcess,alive:l.alive,busy:l.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,t,i){return this.acpClient?.supportsCommandsExecute?this.execCommandViaAcp(e,t):this.execCommandFallback(e,t)}async execCommandViaAcp(e,t){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 i=await this.acpClient.executeCommand(e,t||void 0);return this.finishCompaction("acp-commands-execute"),{status:i.status==="failed"?"failed":"ok",message:i.message??"Compacted",data:i.data}}catch(i){return this.finishCompaction("error"),i?.code===-32601?this.execCommandFallback(e,t):{status:"failed",message:`compact failed: ${i instanceof Error?i.message:i}`}}}try{const i=await this.acpClient.executeCommand(e,t||void 0);return{status:i.status??"ok",message:i.message,data:i.options?{command:e,options:i.options}:i.data}}catch(i){return i?.code===-32601?this.execCommandFallback(e,t):{status:"failed",message:`Command failed: ${i instanceof Error?i.message:i}`}}}async execCommandFallback(e,t){try{switch(e){case"model":{const i=t.trim();if(i)return await this.setModel(i)?{status:"ok",message:`Model set to ${i}`}:{status:"failed",message:`Failed to set model: ${i}`};const s=this.buildToolbarContext("model_list");return{status:"ok",message:`Current: ${s.currentModelId||"unknown"}`,data:s}}case"mode":{const i=t.trim();if(i)return await this.setMode(i)?{status:"ok",message:`Mode set to ${i}`}:{status:"failed",message:`Failed to set mode: ${i}`};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 i=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 i,{status:"ok",message:"Compacted"}}case"status":{const i=this.getStatus(),s=this.acpClient?.sessionOptions;return{status:"ok",message:`Alive: ${i.alive}, Busy: ${i.busy}, Model: ${s?.currentModelId??"unknown"}, Mode: ${s?.currentModeId??"unknown"}`,data:{alive:i.alive,busy:i.busy,sessions:i.sessions,model:s?.currentModelId??"",mode:s?.currentModeId??""}}}case"skills":{const i=E({mode:this.config.command==="kiro-cli"?"kiro":"gemini",projectDir:process.cwd()}),s=i.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:i}}default:return{status:"unsupported",message:`Unknown command: ${e}`}}}catch(i){return{status:"failed",message:i instanceof Error?i.message:String(i)}}}get acpSessionOptions(){return this.acpClient?.sessionOptions??null}get isReasonix(){return(this.config.command??"").includes("reasonix")}get isKimi(){return(this.config.command??"").includes("kimi")}buildToolbarContext(e,t){const i=this.acpClient?.sessionOptions,s=i?.currentModeId??"";let n=i?.currentModelId??"";const r=i?.modes.map(p=>({id:p.id,name:p.name}))??[],a=i?.models&&i.models.length>0,l=a?i.models:this.isReasonix?ie:this.isKimi?M:[],d=a?"acp":this.isReasonix?"reasonix_fallback":this.isKimi?"kimi_fallback":"empty";this.isKimi&&!a&&o.warn("acp-adapter",`[toolbar-diag] kimi ACP session returned no models; using static fallback. command=${this.config.command} fallbackIds=${JSON.stringify(M.map(p=>p.modelId))}`),o.info("acp-adapter",`[toolbar-diag] buildToolbarContext outcome=${e} agent=${this.config.command} modelSource=${d} acpModelCount=${i?.models?.length??0} finalModelCount=${l.length}`);const h=l.map(p=>({modelId:p.modelId,name:p.name})),u=l.map(p=>({id:p.modelId,displayName:p.name})),A=i?.modes.map(p=>({id:p.id,displayName:p.name}))??[];u.length>0&&!u.some(p=>p.id===n)&&(n=u[0].id);const I={outcome:e,...t?{cwd:t}:{},model_id:n,mode_id:s,currentModelId:n,currentModeId:s,available_models:u,available_modes:A,availableModels:u,availableModes:A,models:h,modes:r};return o.info("acp-adapter",`[toolbar] buildToolbarContext outcome=${e} model_id="${n}" available_models=${JSON.stringify(u.map(p=>p.id))} currentModelId=${n}`),I}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,t){const i=this.pendingApprovals.get(e);return i?(o.info("acp-adapter","handleAcpApprovalAction resolving",{toolCallId:e,requestId:i,behavior:t}),this.pendingApprovals.delete(e),this.acpClient&&this.acpClient.respondPermission(i,{behavior:t}).catch(s=>{o.error("acp-adapter",`Failed to respond to permission: ${s}`)}),this.activeRun&&(this.activeRun.lastProgressAt=Date.now(),this.resetIdleTimer(this.activeRun)),!0):(o.warn("acp-adapter","handleAcpApprovalAction: no pending approval",{toolCallId:e,behavior:t,pendingCount:this.pendingApprovals.size}),!1)}respondToPermission(e,t){this.acpClient&&this.acpClient.respondPermission(e,t).catch(i=>{o.error("acp-adapter",`Failed to respond to permission: ${i}`)}),this.activeRun&&(this.activeRun.lastProgressAt=Date.now(),this.resetIdleTimer(this.activeRun))}async handleLocalAction(e){const t=e.action_type??"",i=e.params??{};if(t==="exec_approve"||t==="exec_reject"||t==="permission_approve"||t==="permission_reject"){const s=String(i.tool_call_id??i.approval_command_id??i.approval_id??i.exec_context_id??""),n=t==="exec_approve"||t==="permission_approve",r=String(i.decision??"");let a;return n&&(r==="allow-once"||r==="allow-always")?a=r: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,t){const i=this.sessionBindings.get(e);if(i)try{return await C.stat(i),!1}catch{o.info("acp-adapter",`Stale binding for session ${e}: ${i} no longer exists, allowing rebind to ${t}`),this.sessionBindings.delete(e)}return this.sessionBindings.set(e,t),this.bindingStore&&this.bindingStore.set(e,t),this.maybeRefreshKiroSkills(t),!this.sessionConnected&&this.agentProcess?.alive?this.connectSession(t).then(()=>!0).catch(s=>(o.error("acp-adapter",`Failed to create session on bind: ${g(s)}`),this.callbacks.sendUpdateBindingCard(e,"failed",t),!0)):(this.acpClient?.isAlive&&(this.bindingStore&&this.acpClient.sessionId&&this.bindingStore.setAcpSessionId(e,this.acpClient.sessionId),this.callbacks.sendUpdateBindingCard(e,"connected",t,this.buildToolbarContext("binding_ready",t))),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 t=String(e??"").trim()||void 0;let i=[];try{i=E({mode:"kiro",projectDir:t})}catch(s){o.warn("acp-adapter",`Kiro skills scan failed: ${s instanceof Error?s.message:String(s)}`);return}try{this.callbacks.onSkillsUpdate(i)}catch(s){o.warn("acp-adapter",`onSkillsUpdate failed: ${s instanceof Error?s.message:String(s)}`)}}replayDeferredEvents(e){const t=this.deferredEvents.get(e);if(!t||t.length===0)return;if(this.activeRun){o.info("acp-adapter",`Cannot replay deferred events for session ${e}: agent busy`);return}const i=t.shift();i&&(t.length===0?(this.deferredEvents.delete(e),this.cancelDeferredTimer(e)):this.deferredEvents.set(e,t),o.info("acp-adapter",`Replaying deferred event ${i.event.event_id} for session ${e} (remaining: ${t.length})`),this.startRun(i.event,!1))}finishCompaction(e){if(!this.compacting)return;this.compacting=!1,this.compactingTimer&&(clearTimeout(this.compactingTimer),this.compactingTimer=null),this.suppressOrphanOutput(q),o.info("acp-adapter",`Compaction finished (${e}); replaying deferred events`),this.replayNextDeferredEvent();const t=this.compactionDoneResolver;this.compactionDoneResolver=null,t?.()}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 t=this.eventResults.get(e.session_id,e.event_id);o.info("acp-adapter",`Deduplicating event ${e.event_id} (cached: ${t.status})`),this.callbacks.sendEventResult(e.event_id,t.status,t.msg);return}if(this.compacting){o.info("acp-adapter",`Event ${e.event_id} deferred: compaction in progress`),this.deferEvent(e);return}if(this.activeRun){o.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,t){this.acpClient?(this.acpClient.cancel().catch(()=>{}),this.flushStream()):this.flushStream(),this.activeRun?.eventId===e&&this.finishRun("canceled","stopped by user")}deferEvent(e){const t=this.deferredEvents.get(e.session_id)??[];t.some(i=>i.event.event_id===e.event_id)||(t.push({event:e,queuedAt:Date.now()}),this.deferredEvents.set(e.session_id,t),o.info("acp-adapter",`Deferred event ${e.event_id} for session ${e.session_id} (queue: ${t.length})`),this.scheduleDeferredTimeout(e.session_id))}deferredTimers=new Map;rejectDeferredEvents(e){for(const[t,i]of this.deferredEvents)for(const{event:s}of i)o.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 t=this.deferredTimers.get(e);t&&(clearTimeout(t),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 t=3e4,i=setTimeout(()=>{this.deferredTimers.delete(e);const s=this.deferredEvents.get(e);if(!(!s||s.length===0)&&!this.acpClient?.isAlive){o.error("acp-adapter",`Deferred events for session ${e} timed out after ${t/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")}},t);this.deferredTimers.set(e,i)}startRun(e,t){if(!this.acpClient?.isAlive){this.deferEvent(e);return}const i=`acp_${++this.clientMsgSeq}_${Date.now()}`;this.activeRun={eventId:e.event_id,sessionId:e.session_id,threadId:e.thread_id,clientMsgIdBase:i,currentClientMsgId:i,currentSegmentIndex:0,chunkSeq:0,buffer:"",quotedStream:new S,markdownSegmenter:new z,quotedMessageId:void 0,flushTimer:null,idleTimer:null,awaitingToolResult:!1,responded:!1,silent:t,lastProgressAt:Date.now(),lastIdleCheckAt:Date.now(),idleNoProgressCount:0,lastToolProgress:new Map,lastToolProgressSentAt:new Map,toolNamesById:new Map,sentToolUseIds:new Set,lastToolUse:null,lastToolResult:null,busyRetryTimer:null,busyWaitSince:0,busyRetryCount:0,busyWaiting:!1};const s=this.activeRun,n=X(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 r=Q(n.prompt,{messageId:e.msg_id,quotedMessageId:e.quoted_message_id}),a=this.injectQuoteProtocolOnce(s.sessionId,r),l=this.injectIdentityOnce(s.sessionId,a),d=this.injectRecoveryContext(s.sessionId,l);this.sendPromptWithRetry(s,d)}injectQuoteProtocolOnce(e,t){return!e||this.quoteProtocolTaughtSessions.has(e)?t:(this.quoteProtocolTaughtSessions.add(e),`${de}
1
+ import{fileURLToPath as D}from"node:url";import m from"node:path";import{homedir as v}from"node:os";import{promises as C}from"node:fs";import{EventEmitter as w}from"node:events";import{spawn as k}from"node:child_process";import{AgentProcess as B}from"../../agent/process.js";import{hasChildProcesses as j}from"../../core/runtime/spawn.js";import{applyNativeProviderConfig as F}from"../shared/native-provider-config.js";import{syncDefaultSkillsToDir as L,resolveKimiCodeHome as K}from"../../default-skills/index.js";import{AcpClient as H,AcpAuthRequiredError as W,isAuthRequiredError as G}from"../../protocol/acp-client.js";import{AgentEventType as f}from"../../types/events.js";import{InternalApiServer as Y}from"../../core/mcp/internal-api-server.js";import{EventResultsStore as V}from"../../core/persistence/event-results-store.js";import{QuotedMessageStream as S}from"../../core/util/quoted-message-stream.js";import{SafeMarkdownStreamSegmenter as z}from"../../core/text-segmentation/index.js";import{extractAcpTurnInput as X}from"../../core/protocol/payload-parser.js";import{injectMessageMetadata as Q}from"../../core/protocol/message-metadata.js";import{log as o}from"../../core/log/index.js";function P(c,e=2e3){let t;try{const i=new WeakSet;t=JSON.stringify(c,(s,n)=>{if(n instanceof Error)return{name:n.name,message:n.message};if(typeof n=="function")return"[function]";if(typeof n=="bigint"||typeof n=="symbol")return n.toString();if(typeof n=="object"&&n!==null){if(i.has(n))return"[circular]";i.add(n)}return n})}catch(i){t=`[stringify failed: ${i instanceof Error?i.message:String(i)}]`}return t===void 0?"[undefined]":t.length<=e?t:`${t.slice(0,e)}...[+${t.length-e}b]`}const J=5e3;function $(c,e){const t=e.toolName??"";return e.toolCallId&&(!t||t===e.toolCallId)?c.toolNamesById.get(e.toolCallId)??t:t}function y(c){const e=String(c??"").trim();if(!e)return"";try{const t=JSON.parse(e);return JSON.stringify(b(t))}catch{return e}}function b(c){if(c===null||typeof c!="object")return c;if(Array.isArray(c))return c.map(b);const e=c,t=Object.keys(e).sort(),i={};for(const s of t)i[s]=b(e[s]);return i}import{scanSkills as E}from"../claude/skill-scanner.js";import{checkKiroSessionActivity as Z}from"./session-activity.js";import{resolveCliPath as ee,getCliVersion as te}from"../../core/util/cli-probe.js";const R=m.dirname(D(import.meta.url)),ie=[{modelId:"deepseek-v4-pro",name:"DeepSeek V4 Pro"},{modelId:"deepseek-v4-flash",name:"DeepSeek V4 Flash"}],M=[{modelId:"kimi-code/kimi-for-coding",name:"K2.7 Coding"},{modelId:"kimi-code/kimi-for-coding-highspeed",name:"K2.7 Coding Highspeed"},{modelId:"kimi-code/k3",name:"K3"}];function _(c,e){const t=Number(process.env[c]);return Number.isFinite(t)&&t>0?t:e}const se=200,ne=60*1e3,oe=600*1e3,A=_("GRIX_ACP_TURN_INACTIVITY_TIMEOUT_MS",900*1e3),x=80,N=1200*1e3,re=_("GRIX_ACP_BUSY_RETRY_INTERVAL_MS",5e3),U=_("GRIX_ACP_BUSY_WAIT_MAX_MS",1800*1e3),ae=6e4,le=2e3,ce=12e3,O=3e3,q=3e4,de=["[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 ue(c){return!(!(c instanceof Error)||c.code!==-32603||G(c))}function he(c){if(!(c instanceof Error))return!1;const e=c.data;return e&&typeof e=="object"&&e.code==="turn.agent_busy"?!0:/cannot launch a new turn/i.test(c.message)}function g(c){if(!(c instanceof Error))return String(c);let e=c.message;const t=c.data;return t&&(e+=` data=${JSON.stringify(t)}`),e}function pe(c){return c.replace(/\u001b\[[0-9;?]*[ -/]*[@-~]/g,"")}class qe extends w{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;orphanBuffer="";orphanQuotedStream=new S;orphanFlushTimer=null;orphanSessionId=null;orphanSuppressedUntil=0;constructor(e,t,i){if(super(),this.config=e,this.callbacks=t,this.bridgeLog=i?.bridgeLog??null,this.acpAuthMethod=i?.acpAuthMethod,this.acpInitialMode=i?.acpInitialMode,this.acpInitialModel=i?.acpInitialModel,this.acpMcpTools=i?.acpMcpTools??!0,this.rawTransport=i?.rawTransport??!1,this.approvalMode=i?.approvalMode??"default",this.autoInjectArgs=i?.autoInjectArgs,this.nativeProviderScope=i?.nativeProviderScope,this.bindingStore=i?.bindingStore??null,this.currentAibotSessionId=i?.aibotSessionId?String(i.aibotSessionId).trim():void 0,i?.eventResultsPath&&(this.eventResults=new V(i.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.clearRunTimers(this.activeRun),this.activeRun=null),this.flushOrphanBuffer(),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,t){}async destroySession(e){this.quoteProtocolTaughtSessions.delete(e),this.identityInjectedSessions.delete(e)}onAgentProfileChanged(){this.identityInjectedSessions.size>0&&(o.info("acp-adapter",`agent profile changed; clearing ${this.identityInjectedSessions.size} identity-injected session(s)`),this.identityInjectedSessions.clear())}sendPrompt(e){const t=new me(e.adapterSessionId);return this.acpClient?.isAlive&&this.acpClient.send(e.text).catch(i=>{t.emitError(i instanceof Error?i:new Error(String(i)))}),t}async cancel(e){this.activeRun&&this.acpClient&&(await this.acpClient.cancel(),this.flushStream(),this.finishRun("canceled","stopped by user"),this.suppressOrphanOutput(q))}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.clearRunTimers(this.activeRun),this.activeRun=null)}clearRunTimers(e){e.flushTimer&&(clearTimeout(e.flushTimer),e.flushTimer=null),e.idleTimer&&(clearTimeout(e.idleTimer),e.idleTimer=null),e.busyRetryTimer&&(clearTimeout(e.busyRetryTimer),e.busyRetryTimer=null)}getMcpConfig(){if(!this.internalApi)return null;this.internalApi.setMcpBridgeUpHandler(t=>this.onMcpBridgeUp(t));const e=m.resolve(R,"../../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?j(e,[e]):!1}async probe(e){const t=this.config.command||"gemini",i=await ee(t),s=i!==null;let n=null,r;if(s){const h=await te(t);n=h.version,h.error&&(r=h.error)}else r={code:"cli_not_found",message:`command not found: ${t}`};const a=this.acpClient?.model??null,l=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:t,installed:s,path:i,version:n,...r?{error:r}:{}},conversation:d,config:{model:a,base_url:null,source:{model:a?"runtime":"unknown",base_url:"unknown"}},process:{started:!!this.agentProcess,alive:l.alive,busy:l.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,t,i){return this.acpClient?.supportsCommandsExecute?this.execCommandViaAcp(e,t):this.execCommandFallback(e,t)}async execCommandViaAcp(e,t){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 i=await this.acpClient.executeCommand(e,t||void 0);return this.finishCompaction("acp-commands-execute"),{status:i.status==="failed"?"failed":"ok",message:i.message??"Compacted",data:i.data}}catch(i){return this.finishCompaction("error"),i?.code===-32601?this.execCommandFallback(e,t):{status:"failed",message:`compact failed: ${i instanceof Error?i.message:i}`}}}try{const i=await this.acpClient.executeCommand(e,t||void 0);return{status:i.status??"ok",message:i.message,data:i.options?{command:e,options:i.options}:i.data}}catch(i){return i?.code===-32601?this.execCommandFallback(e,t):{status:"failed",message:`Command failed: ${i instanceof Error?i.message:i}`}}}async execCommandFallback(e,t){try{switch(e){case"model":{const i=t.trim();if(i)return await this.setModel(i)?{status:"ok",message:`Model set to ${i}`}:{status:"failed",message:`Failed to set model: ${i}`};const s=this.buildToolbarContext("model_list");return{status:"ok",message:`Current: ${s.currentModelId||"unknown"}`,data:s}}case"mode":{const i=t.trim();if(i)return await this.setMode(i)?{status:"ok",message:`Mode set to ${i}`}:{status:"failed",message:`Failed to set mode: ${i}`};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 i=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 i,{status:"ok",message:"Compacted"}}case"status":{const i=this.getStatus(),s=this.acpClient?.sessionOptions;return{status:"ok",message:`Alive: ${i.alive}, Busy: ${i.busy}, Model: ${s?.currentModelId??"unknown"}, Mode: ${s?.currentModeId??"unknown"}`,data:{alive:i.alive,busy:i.busy,sessions:i.sessions,model:s?.currentModelId??"",mode:s?.currentModeId??""}}}case"skills":{const i=E({mode:this.config.command==="kiro-cli"?"kiro":"gemini",projectDir:process.cwd()}),s=i.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:i}}default:return{status:"unsupported",message:`Unknown command: ${e}`}}}catch(i){return{status:"failed",message:i instanceof Error?i.message:String(i)}}}get acpSessionOptions(){return this.acpClient?.sessionOptions??null}get isReasonix(){return(this.config.command??"").includes("reasonix")}get isKimi(){return(this.config.command??"").includes("kimi")}buildToolbarContext(e,t){const i=this.acpClient?.sessionOptions,s=i?.currentModeId??"";let n=i?.currentModelId??"";const r=i?.modes.map(p=>({id:p.id,name:p.name}))??[],a=i?.models&&i.models.length>0,l=a?i.models:this.isReasonix?ie:this.isKimi?M:[],d=a?"acp":this.isReasonix?"reasonix_fallback":this.isKimi?"kimi_fallback":"empty";this.isKimi&&!a&&o.warn("acp-adapter",`[toolbar-diag] kimi ACP session returned no models; using static fallback. command=${this.config.command} fallbackIds=${JSON.stringify(M.map(p=>p.modelId))}`),o.info("acp-adapter",`[toolbar-diag] buildToolbarContext outcome=${e} agent=${this.config.command} modelSource=${d} acpModelCount=${i?.models?.length??0} finalModelCount=${l.length}`);const h=l.map(p=>({modelId:p.modelId,name:p.name})),u=l.map(p=>({id:p.modelId,displayName:p.name})),T=i?.modes.map(p=>({id:p.id,displayName:p.name}))??[];u.length>0&&!u.some(p=>p.id===n)&&(n=u[0].id);const I={outcome:e,...t?{cwd:t}:{},model_id:n,mode_id:s,currentModelId:n,currentModeId:s,available_models:u,available_modes:T,availableModels:u,availableModes:T,models:h,modes:r};return o.info("acp-adapter",`[toolbar] buildToolbarContext outcome=${e} model_id="${n}" available_models=${JSON.stringify(u.map(p=>p.id))} currentModelId=${n}`),I}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,t){const i=this.pendingApprovals.get(e);return i?(o.info("acp-adapter","handleAcpApprovalAction resolving",{toolCallId:e,requestId:i,behavior:t}),this.pendingApprovals.delete(e),this.acpClient&&this.acpClient.respondPermission(i,{behavior:t}).catch(s=>{o.error("acp-adapter",`Failed to respond to permission: ${s}`)}),this.activeRun&&(this.activeRun.lastProgressAt=Date.now(),this.resetIdleTimer(this.activeRun)),!0):(o.warn("acp-adapter","handleAcpApprovalAction: no pending approval",{toolCallId:e,behavior:t,pendingCount:this.pendingApprovals.size}),!1)}respondToPermission(e,t){this.acpClient&&this.acpClient.respondPermission(e,t).catch(i=>{o.error("acp-adapter",`Failed to respond to permission: ${i}`)}),this.activeRun&&(this.activeRun.lastProgressAt=Date.now(),this.resetIdleTimer(this.activeRun))}async handleLocalAction(e){const t=e.action_type??"",i=e.params??{};if(t==="exec_approve"||t==="exec_reject"||t==="permission_approve"||t==="permission_reject"){const s=String(i.tool_call_id??i.approval_command_id??i.approval_id??i.exec_context_id??""),n=t==="exec_approve"||t==="permission_approve",r=String(i.decision??"");let a;return n&&(r==="allow-once"||r==="allow-always")?a=r: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,t){const i=this.sessionBindings.get(e);if(i)try{return await C.stat(i),!1}catch{o.info("acp-adapter",`Stale binding for session ${e}: ${i} no longer exists, allowing rebind to ${t}`),this.sessionBindings.delete(e)}return this.sessionBindings.set(e,t),this.bindingStore&&this.bindingStore.set(e,t),this.maybeRefreshKiroSkills(t),!this.sessionConnected&&this.agentProcess?.alive?this.connectSession(t).then(()=>!0).catch(s=>(o.error("acp-adapter",`Failed to create session on bind: ${g(s)}`),this.callbacks.sendUpdateBindingCard(e,"failed",t),!0)):(this.acpClient?.isAlive&&(this.bindingStore&&this.acpClient.sessionId&&this.bindingStore.setAcpSessionId(e,this.acpClient.sessionId),this.callbacks.sendUpdateBindingCard(e,"connected",t,this.buildToolbarContext("binding_ready",t))),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 t=String(e??"").trim()||void 0;let i=[];try{i=E({mode:"kiro",projectDir:t})}catch(s){o.warn("acp-adapter",`Kiro skills scan failed: ${s instanceof Error?s.message:String(s)}`);return}try{this.callbacks.onSkillsUpdate(i)}catch(s){o.warn("acp-adapter",`onSkillsUpdate failed: ${s instanceof Error?s.message:String(s)}`)}}replayDeferredEvents(e){const t=this.deferredEvents.get(e);if(!t||t.length===0)return;if(this.activeRun){o.info("acp-adapter",`Cannot replay deferred events for session ${e}: agent busy`);return}const i=t.shift();i&&(t.length===0?(this.deferredEvents.delete(e),this.cancelDeferredTimer(e)):this.deferredEvents.set(e,t),o.info("acp-adapter",`Replaying deferred event ${i.event.event_id} for session ${e} (remaining: ${t.length})`),this.startRun(i.event,!1))}finishCompaction(e){if(!this.compacting)return;this.compacting=!1,this.compactingTimer&&(clearTimeout(this.compactingTimer),this.compactingTimer=null),this.suppressOrphanOutput(q),o.info("acp-adapter",`Compaction finished (${e}); replaying deferred events`),this.replayNextDeferredEvent();const t=this.compactionDoneResolver;this.compactionDoneResolver=null,t?.()}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 t=this.eventResults.get(e.session_id,e.event_id);o.info("acp-adapter",`Deduplicating event ${e.event_id} (cached: ${t.status})`),this.callbacks.sendEventResult(e.event_id,t.status,t.msg);return}if(this.compacting){o.info("acp-adapter",`Event ${e.event_id} deferred: compaction in progress`),this.deferEvent(e);return}if(this.activeRun){o.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,t){this.acpClient?(this.acpClient.cancel().catch(()=>{}),this.flushStream()):this.flushStream(),this.activeRun?.eventId===e&&this.finishRun("canceled","stopped by user")}deferEvent(e){const t=this.deferredEvents.get(e.session_id)??[];t.some(i=>i.event.event_id===e.event_id)||(t.push({event:e,queuedAt:Date.now()}),this.deferredEvents.set(e.session_id,t),o.info("acp-adapter",`Deferred event ${e.event_id} for session ${e.session_id} (queue: ${t.length})`),this.scheduleDeferredTimeout(e.session_id))}deferredTimers=new Map;rejectDeferredEvents(e){for(const[t,i]of this.deferredEvents)for(const{event:s}of i)o.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 t=this.deferredTimers.get(e);t&&(clearTimeout(t),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 t=3e4,i=setTimeout(()=>{this.deferredTimers.delete(e);const s=this.deferredEvents.get(e);if(!(!s||s.length===0)&&!this.acpClient?.isAlive){o.error("acp-adapter",`Deferred events for session ${e} timed out after ${t/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")}},t);this.deferredTimers.set(e,i)}startRun(e,t){if(!this.acpClient?.isAlive){this.deferEvent(e);return}const i=`acp_${++this.clientMsgSeq}_${Date.now()}`;this.activeRun={eventId:e.event_id,sessionId:e.session_id,threadId:e.thread_id,clientMsgIdBase:i,currentClientMsgId:i,currentSegmentIndex:0,chunkSeq:0,buffer:"",quotedStream:new S,markdownSegmenter:new z,quotedMessageId:void 0,flushTimer:null,idleTimer:null,awaitingToolResult:!1,responded:!1,silent:t,lastProgressAt:Date.now(),lastIdleCheckAt:Date.now(),idleNoProgressCount:0,lastToolProgress:new Map,lastToolProgressSentAt:new Map,toolNamesById:new Map,sentToolUseIds:new Set,lastToolUse:null,lastToolResult:null,busyRetryTimer:null,busyWaitSince:0,busyRetryCount:0,busyWaiting:!1};const s=this.activeRun,n=X(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 r=Q(n.prompt,{messageId:e.msg_id,quotedMessageId:e.quoted_message_id}),a=this.injectQuoteProtocolOnce(s.sessionId,r),l=this.injectIdentityOnce(s.sessionId,a),d=this.injectRecoveryContext(s.sessionId,l);this.sendPromptWithRetry(s,d)}injectQuoteProtocolOnce(e,t){return!e||this.quoteProtocolTaughtSessions.has(e)?t:(this.quoteProtocolTaughtSessions.add(e),`${de}
3
3
 
4
4
  ${t}`)}injectIdentityOnce(e,t){if(!e||this.identityInjectedSessions.has(e))return t;const i=this.callbacks.getAgentProfile?.(),s=i?.agentName?.trim()??"",n=i?.introduction?.trim()??"";if(!s&&!n)return t;this.identityInjectedSessions.add(e);const r=["[system-identity]"];return s&&r.push(`Your name is "${s}".`),n&&r.push(`Your self-introduction: ${n}`),r.push("Treat this as an out-of-band instruction; do not echo or repeat it in your replies."),r.push("[/system-identity]"),o.info("acp-adapter",`Injected agent identity for session=${e} name="${s}"`),`${r.join(`
5
5
  `)}
@@ -14,12 +14,12 @@ The agent's own background turn kept the session busy past the wait limit, so th
14
14
  `)}catch(s){o.warn("acp-adapter",`Failed to build kiro recovery context: ${g(s)}`);return}}async generateSummaryByKiroCli(e,t){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(`
15
15
  `)],n=await this.runCommandCapture("kiro-cli",s,t,45e3);if(!n)return;const r=pe(n).trim(),a=r.indexOf("> "),l=a>=0?r.slice(a+2).trim():r;if(l)return o.info("acp-adapter",`Kiro summary generated from ${e} (raw_chars=${r.length}, body_chars=${l.length})`),l.split(`
16
16
  `).filter(d=>!d.includes("Credits:")&&!d.includes("Time:")).join(`
17
- `).trim()}runCommandCapture(e,t,i,s){return new Promise((n,r)=>{const a=k(e,t,{cwd:i,stdio:["ignore","pipe","pipe"]});let l="",d="";const h=setTimeout(()=>{a.kill("SIGTERM"),r(new Error(`${e} timed out after ${s}ms`))},s);a.stdout.on("data",u=>{l+=String(u)}),a.stderr.on("data",u=>{d+=String(u)}),a.on("error",u=>{clearTimeout(h),r(u)}),a.on("close",u=>{if(clearTimeout(h),u===0){n(l||d);return}r(new Error(`${e} exited with code ${u}: ${d||l}`))})})}handleConnectFailure(e){o.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 Y,this.internalApi.setInvokeHandler(async(r,a,l,d)=>this.callbacks.agentInvoke(r,a,d)),await this.internalApi.start(0),o.info("acp-adapter",`Internal API started at ${this.internalApi.url}`)),this.internalApi.setMcpBridgeUpHandler(r=>this.onMcpBridgeUp(r));const e=this.config.command??"",t=e.includes("qwen")?m.join(v(),".qwen","skills"):e.includes("gemini")?m.join(v(),".gemini","skills"):e.includes("kiro")?m.join(v(),".kiro","skills"):this.isReasonix?m.join(v(),".reasonix","skills"):this.isKimi?m.join(K(),"skills"):void 0;if(t){const r=L(t);r.length>0&&o.info("acp-adapter",`Synced connector skills to ${t}: [${r.join(", ")}]`)}const i=m.resolve(b,"../../mcp/mcp-bridge-server.js"),s=this.internalApi.mcpBridgeWsUrl,n=m.resolve(b,"../../mcp/acp-mcp-server.js");return[{name:"grix-app-bridge",command:process.execPath,args:[i,"--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){o.error("acp-adapter",`Failed to start MCP tools: ${e}`);return}}onMcpBridgeUp(e){this.callbacks.sendMcpFrame?.(e)}deliverMcpFrameToAgent(e){this.internalApi?.sendMcpFrameToBridge(e)}cleanup(){this.flushOrphanBuffer(),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){o.info("acp-adapter",`Auth required, methods: ${e.authMethods.map(s=>s.id).join(", ")}`);const t=e.authMethods.find(s=>/oauth|browser/i.test(s.id))??e.authMethods[0];if(!t)throw e;const i=this.currentAibotSessionId??"";this.callbacks.sendAuthNotification(i,`Authentication required (${t.id}). Initiating auth flow...`);for(const[s,n]of this.sessionBindings)this.callbacks.sendUpdateBindingCard(s,"failed",n);if(t.type==="terminal")await this.runTerminalAuth(t,i);else{const s=await this.captureAuthUrl();s&&this.callbacks.sendAuthNotification(i,`Please open this URL to authenticate:
17
+ `).trim()}runCommandCapture(e,t,i,s){return new Promise((n,r)=>{const a=k(e,t,{cwd:i,stdio:["ignore","pipe","pipe"]});let l="",d="";const h=setTimeout(()=>{a.kill("SIGTERM"),r(new Error(`${e} timed out after ${s}ms`))},s);a.stdout.on("data",u=>{l+=String(u)}),a.stderr.on("data",u=>{d+=String(u)}),a.on("error",u=>{clearTimeout(h),r(u)}),a.on("close",u=>{if(clearTimeout(h),u===0){n(l||d);return}r(new Error(`${e} exited with code ${u}: ${d||l}`))})})}handleConnectFailure(e){o.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 Y,this.internalApi.setInvokeHandler(async(r,a,l,d)=>this.callbacks.agentInvoke(r,a,d)),await this.internalApi.start(0),o.info("acp-adapter",`Internal API started at ${this.internalApi.url}`)),this.internalApi.setMcpBridgeUpHandler(r=>this.onMcpBridgeUp(r));const e=this.config.command??"",t=e.includes("qwen")?m.join(v(),".qwen","skills"):e.includes("gemini")?m.join(v(),".gemini","skills"):e.includes("kiro")?m.join(v(),".kiro","skills"):this.isReasonix?m.join(v(),".reasonix","skills"):this.isKimi?m.join(K(),"skills"):void 0;if(t){const r=L(t);r.length>0&&o.info("acp-adapter",`Synced connector skills to ${t}: [${r.join(", ")}]`)}const i=m.resolve(R,"../../mcp/mcp-bridge-server.js"),s=this.internalApi.mcpBridgeWsUrl,n=m.resolve(R,"../../mcp/acp-mcp-server.js");return[{name:"grix-app-bridge",command:process.execPath,args:[i,"--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){o.error("acp-adapter",`Failed to start MCP tools: ${e}`);return}}onMcpBridgeUp(e){this.callbacks.sendMcpFrame?.(e)}deliverMcpFrameToAgent(e){this.internalApi?.sendMcpFrameToBridge(e)}cleanup(){this.flushOrphanBuffer(),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){o.info("acp-adapter",`Auth required, methods: ${e.authMethods.map(s=>s.id).join(", ")}`);const t=e.authMethods.find(s=>/oauth|browser/i.test(s.id))??e.authMethods[0];if(!t)throw e;const i=this.currentAibotSessionId??"";this.callbacks.sendAuthNotification(i,`Authentication required (${t.id}). Initiating auth flow...`);for(const[s,n]of this.sessionBindings)this.callbacks.sendUpdateBindingCard(s,"failed",n);if(t.type==="terminal")await this.runTerminalAuth(t,i);else{const s=await this.captureAuthUrl();s&&this.callbacks.sendAuthNotification(i,`Please open this URL to authenticate:
18
18
  ${s}
19
19
 
20
20
  Waiting for authentication to complete...`)}try{await this.acpClient.authenticate(t.id),o.info("acp-adapter","Authentication successful"),this.callbacks.sendAuthNotification(i,"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}),o.info("acp-adapter",`ACP session ready after auth: ${this.acpClient.sessionId}`),this.emit("acpSessionReady",this.acpClient.sessionId);for(const[n,r]of this.sessionBindings)this.callbacks.sendUpdateBindingCard(n,"ready",r,this.buildToolbarContext("binding_ready",r))}catch(s){throw o.error("acp-adapter",`Auth retry failed: ${s}`),s}}runTerminalAuth(e,t){return new Promise((i,s)=>{const n=k(this.config.command,e.args??[],{env:{...process.env,...this.config.env??{}},stdio:["ignore","pipe","pipe"]});this.terminalAuthChild=n;const r=/https?:\/\/[^\s"')\]]+/;let a=!1,l=!1;const d=u=>{if(a)return;const I=u.toString().replace(/\x1b\[[0-9;]*m/g,"").match(r);I&&(a=!0,o.info("acp-adapter",`Terminal auth URL captured: ${I[0]}`),this.callbacks.sendAuthNotification(t,`Please open this URL on any device to authenticate:
21
21
  ${I[0]}
22
22
 
23
- Waiting for authentication to complete...`))};n.stdout?.on("data",d),n.stderr?.on("data",d);const h=setTimeout(()=>{l||(l=!0,n.kill("SIGKILL"),this.terminalAuthChild=null,s(new Error(`terminal auth timed out after ${N}ms`)))},N);n.on("error",u=>{l||(l=!0,clearTimeout(h),this.terminalAuthChild=null,s(u))}),n.on("exit",u=>{l||(l=!0,clearTimeout(h),this.terminalAuthChild=null,u===0?i():s(new Error(`terminal auth process exited with code ${u}`)))})})}captureAuthUrl(){return new Promise(e=>{if(!this.agentProcess){e(null);return}const t=/https?:\/\/[^\s"')\]]+/;let i=!1;const s=n=>{if(i)return;const a=n.toString().replace(/\x1b\[[0-9;]*m/g,"").match(t);a&&(i=!0,this.agentProcess.removeListener("stderr",s),e(a[0]))};this.agentProcess.on("stderr",s),setTimeout(()=>{i||(i=!0,this.agentProcess.removeListener("stderr",s),e(null))},3e4)})}handleAcpEvent(e){try{o.info("acp-adapter","incoming acp event",{eventType:e.type,sessionId:this.activeRun?.sessionId??null,eventKeys:Object.keys(e),rawEvent:P(e,2e3)})}catch{}if(e.type===f.PermissionRequest){this.activeRun&&this.resetIdleTimer(this.activeRun),this.handlePermissionRequest(e);return}if(e.type===f.ContextWindowUpdate){e.contextWindow&&this.callbacks.onContextWindowUpdated?.(e.contextWindow),this.compacting?this.finishCompaction("context-window-update"):e.contextWindow&&this.maybeScheduleAutoCompact(e.contextWindow);return}const t=this.activeRun;if(!t){this.handleOrphanAcpEvent(e);return}if(t.busyWaiting){this.handleOrphanAcpEvent(e);return}t.responded||(t.responded=!0);let i=!1;switch(e.type){case f.Text:{if(t.lastToolUse=null,t.lastToolResult=null,e.content){i=!0;const s=t.quotedStream.consume(e.content);s.quotedMessageId&&(t.quotedMessageId=s.quotedMessageId),s.deltaContent&&this.appendToStream(t,s.deltaContent)}break}case f.ToolUse:{if(i=!0,t.awaitingToolResult=!0,t.lastToolResult=null,e.toolName){if(e.toolCallId&&(t.toolNamesById.set(e.toolCallId,e.toolName),t.sentToolUseIds.has(e.toolCallId)))break;const s=e.toolInput??"",n=y(s);if(t.lastToolUse?.toolName===e.toolName&&y(t.lastToolUse.toolInput)===n)break;t.lastToolUse={toolName:e.toolName,toolInput:s},e.toolCallId&&t.sentToolUseIds.add(e.toolCallId),this.emitRawEventEnvelope(t,{type:"tool_use",payload:{tool_name:e.toolName,tool_input:s}})||this.callbacks.sendToolUse(t.eventId,t.sessionId,e.toolName,s)}break}case f.ToolResult:{t.awaitingToolResult=!1;{const s=e.toolCallId||e.toolName||"";t.lastToolProgress.delete(s),t.lastToolProgressSentAt.delete(s)}if(t.lastToolUse=null,e.content){i=!0;const s=$(t,e),n=e.content,r=y(n);if(t.lastToolResult?.toolName===s&&y(t.lastToolResult.content)===r)break;t.lastToolResult={toolName:s,content:n},this.emitRawEventEnvelope(t,{type:"tool_result",payload:{tool_name:s,content:n}})||this.callbacks.sendToolResult(t.eventId,t.sessionId,s,n)}break}case f.ToolProgress:{if(i=!0,e.content){const s=e.toolCallId||e.toolName||"";if(t.lastToolProgress.get(s)===e.content)break;t.lastToolProgress.set(s,e.content);const n=Date.now(),r=t.lastToolProgressSentAt.get(s);if(r!==void 0&&n-r<J)break;t.lastToolProgressSentAt.set(s,n);const a=$(t,e);this.emitRawEventEnvelope(t,{type:"tool_progress",payload:{tool_name:a,content:e.content}})||this.callbacks.sendToolResult(t.eventId,t.sessionId,a,e.content)}break}case f.Thinking:{t.lastToolUse=null,t.lastToolResult=null,e.content&&(i=!0,this.emitRawEventEnvelope(t,{type:"thinking",payload:{content:e.content}})||this.callbacks.sendThinking(t.eventId,t.sessionId,e.content));break}case f.Error:{t.lastToolUse=null,t.lastToolResult=null;const s=String(e.error??"unknown error");this.emitRawEventEnvelope(t,{type:"error",payload:{message:s}}),o.error("acp-adapter",`ACP error: ${s}`),this.callbacks.sendRunError(t.eventId,t.sessionId,s);break}case f.Result:{this.emitRawEventEnvelope(t,{type:"result",payload:{done:e.done??!0}});const s=t.quotedStream.flush();s.deltaContent&&this.appendToStream(t,s.deltaContent),s.quotedMessageId&&(t.quotedMessageId=s.quotedMessageId),this.flushStream(),this.finishRun("responded");break}}i&&(t.lastProgressAt=Date.now(),t.idleNoProgressCount=0),this.resetIdleTimer(t)}handlePermissionRequest(e){const t=e.permissionRequest;if(o.info("acp-adapter","permission_request received",{sessionId:this.activeRun?.sessionId,eventId:this.activeRun?.eventId,requestId:e.requestId??null,toolCallId:t?.toolCallId??null,toolName:t?.toolName??null,toolTitle:t?.toolTitle??null,optionsCount:Array.isArray(t?.options)?t.options.length:0,hasActiveRun:this.activeRun!==null,approvalMode:this.approvalMode,rawTransport:this.rawTransport,hasRawEventEnvelopeCb:typeof this.callbacks.sendRawEventEnvelope=="function",hasSendPermissionCardCb:typeof this.callbacks.sendPermissionCard=="function",rawPr:P(t,3e3)}),!t||!e.requestId||!this.acpClient){o.warn("acp-adapter","permission_request dropped: missing prerequisite",{hasPr:t!=null,hasRequestId:!!e.requestId,hasAcpClient:!!this.acpClient});return}if(this.approvalMode==="yolo"||this.approvalMode==="autoEdit"){o.info("acp-adapter",`Auto-approving (${this.approvalMode}): ${t.toolName}`),this.acpClient.respondPermission(t.requestId,{behavior:"allow"}).catch(()=>{});return}const i=t.toolCallId;this.pendingApprovals.set(i,e.requestId);const s=this.activeRun;s?(s.idleTimer&&(clearTimeout(s.idleTimer),s.idleTimer=null),this.emitRawEventEnvelope(s,{type:"permission_request",payload:{request_id:t.requestId,tool_call_id:i,tool_name:t.toolName,tool_title:t.toolTitle,options:t.options}})?o.info("acp-adapter","permission_request forwarded via rawEventEnvelope",{sessionId:s.sessionId,eventId:s.eventId,toolCallId:i,toolName:t.toolName}):(o.info("acp-adapter","permission_request falling back to sendPermissionCard",{sessionId:s.sessionId,eventId:s.eventId,toolCallId:i,toolName:t.toolName}),this.callbacks.sendPermissionCard({eventId:s.eventId,sessionId:s.sessionId,toolCallId:i,toolName:t.toolName,toolTitle:t.toolTitle,options:t.options}))):(o.info("acp-adapter",`Permission request without active run, auto-approving: ${t.toolName}`),this.acpClient.respondPermission(e.requestId,{behavior:"allow"}),this.pendingApprovals.delete(i))}emitRawEventEnvelope(e,t){return!e||!this.rawTransport||!this.callbacks.sendRawEventEnvelope?!1:(this.callbacks.sendRawEventEnvelope(e.eventId,e.sessionId,{type:t.type,payload:t.payload,seq:++this.rawEventSeq,at:new Date().toISOString()}),!0)}resetIdleTimer(e){e.idleTimer&&clearTimeout(e.idleTimer);const t=e.awaitingToolResult?oe:ne,i=e.lastProgressAt+_-Date.now(),s=Math.max(0,Math.min(t,i));e.idleTimer=setTimeout(()=>{if(this.activeRun?.eventId!==e.eventId)return;const n=Date.now()-e.lastProgressAt;if(n>=_){o.error("acp-adapter",`Turn inactive ${Math.round(n/1e3)}s \u2265 cap ${Math.round(_/1e3)}s, forcing timeout: ${e.eventId}`),this.finishRun("failed",`turn inactivity timeout after ${Math.round(n/1e3)}s with no ACP activity`),this.declareStuck();return}const r=this.acpClient;r?.isAlive?r.ping(5e3).then(async a=>{if(this.activeRun?.eventId!==e.eventId)return;if(!a){o.error("acp-adapter",`Ping failed, declaring stuck: ${e.eventId}`),this.finishRun("failed","agent unreachable"),this.declareStuck();return}if(e.awaitingToolResult){o.info("acp-adapter",`Idle timer: tool running, ping ok, continuing: ${e.eventId}`),e.idleNoProgressCount=0,this.resetIdleTimer(e);return}const l=e.lastIdleCheckAt,d=e.lastProgressAt>l,h=d?!1:await Z(r.sessionId,l),u=d||h;e.lastIdleCheckAt=Date.now(),u?(h&&o.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?(o.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()):(o.warn("acp-adapter",`Idle timer fired, ping ok, no progress (${e.idleNoProgressCount}/2): ${e.eventId}`),this.resetIdleTimer(e)))}).catch(()=>{this.activeRun?.eventId===e.eventId&&(o.error("acp-adapter",`Ping error, declaring stuck: ${e.eventId}`),this.finishRun("failed","agent unreachable"),this.declareStuck())}):(o.error("acp-adapter",`Agent idle (no client), declaring stuck: ${e.eventId}`),this.finishRun("failed","agent unreachable"),this.declareStuck())},s)}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,t){e.buffer+=t,e.flushTimer||(e.flushTimer=setTimeout(()=>this.flushStream(),se))}emitSegmentedStream(e,t){if(!t)return;const i=e.markdownSegmenter.push(t);for(const s of i)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,o.info("acp-adapter",`stream segment rollover event=${e.eventId} segment=${e.currentSegmentIndex} reason=${s.reason??"threshold"}`)))}handleOrphanAcpEvent(e){if(!(!this.sessionConnected||this.compacting||Date.now()<this.orphanSuppressedUntil)){if(e.type===f.Text&&e.content){this.orphanSessionId===null&&(this.orphanSessionId=this.currentAibotSessionId??"");const t=this.orphanQuotedStream.consume(e.content);t.deltaContent&&(this.orphanBuffer+=t.deltaContent),this.orphanBuffer.length>=ce?this.flushOrphanBuffer():this.scheduleOrphanFlush();return}e.type===f.Result&&this.flushOrphanBuffer()}}scheduleOrphanFlush(){this.orphanFlushTimer&&clearTimeout(this.orphanFlushTimer),this.orphanFlushTimer=setTimeout(()=>{this.orphanFlushTimer=null,this.flushOrphanBuffer()},le),this.orphanFlushTimer.unref?.()}flushOrphanBuffer(){if(this.orphanFlushTimer&&(clearTimeout(this.orphanFlushTimer),this.orphanFlushTimer=null),this.orphanSessionId===null&&!this.orphanBuffer)return;const e=this.orphanQuotedStream.flush();e.deltaContent&&(this.orphanBuffer+=e.deltaContent),this.orphanQuotedStream=new S;const t=this.orphanSessionId||this.currentAibotSessionId;this.orphanSessionId=null;const i=this.orphanBuffer.trim();if(this.orphanBuffer="",!!i){if(!t||!this.callbacks.sendAgentMessage){o.warn("acp-adapter",`orphan agent output dropped (${t?"no sendAgentMessage callback":"no session id"}) chars=${i.length}`);return}o.info("acp-adapter",`delivering orphan agent output session=${t} chars=${i.length}`);for(let s=0;s<i.length;s+=O)this.callbacks.sendAgentMessage(t,i.slice(s,s+O))}}suppressOrphanOutput(e){this.orphanSuppressedUntil=Date.now()+e,this.orphanFlushTimer&&(clearTimeout(this.orphanFlushTimer),this.orphanFlushTimer=null),this.orphanBuffer="",this.orphanQuotedStream=new S,this.orphanSessionId=null}flushStream(){const e=this.activeRun;if(!e||!e.buffer)return;e.flushTimer&&(clearTimeout(e.flushTimer),e.flushTimer=null);const t=e.buffer;e.buffer="",this.emitSegmentedStream(e,t)}finishRun(e,t){const i=this.activeRun;if(!i)return;this.activeRun=null,this.acpClient?.clearSettleTimer(),this.emit("eventDone",i.eventId),this.clearRunTimers(i);const s=i.quotedStream.flush();s.deltaContent&&(i.buffer+=s.deltaContent),s.quotedMessageId&&(i.quotedMessageId=s.quotedMessageId),t&&(e==="failed"?(o.error("acp-adapter",`finishRun failed: ${t} event=${i.eventId}`),i.buffer+=`
23
+ Waiting for authentication to complete...`))};n.stdout?.on("data",d),n.stderr?.on("data",d);const h=setTimeout(()=>{l||(l=!0,n.kill("SIGKILL"),this.terminalAuthChild=null,s(new Error(`terminal auth timed out after ${N}ms`)))},N);n.on("error",u=>{l||(l=!0,clearTimeout(h),this.terminalAuthChild=null,s(u))}),n.on("exit",u=>{l||(l=!0,clearTimeout(h),this.terminalAuthChild=null,u===0?i():s(new Error(`terminal auth process exited with code ${u}`)))})})}captureAuthUrl(){return new Promise(e=>{if(!this.agentProcess){e(null);return}const t=/https?:\/\/[^\s"')\]]+/;let i=!1;const s=n=>{if(i)return;const a=n.toString().replace(/\x1b\[[0-9;]*m/g,"").match(t);a&&(i=!0,this.agentProcess.removeListener("stderr",s),e(a[0]))};this.agentProcess.on("stderr",s),setTimeout(()=>{i||(i=!0,this.agentProcess.removeListener("stderr",s),e(null))},3e4)})}handleAcpEvent(e){try{o.info("acp-adapter","incoming acp event",{eventType:e.type,sessionId:this.activeRun?.sessionId??null,eventKeys:Object.keys(e),rawEvent:P(e,2e3)})}catch{}if(e.type===f.PermissionRequest){this.activeRun&&this.resetIdleTimer(this.activeRun),this.handlePermissionRequest(e);return}if(e.type===f.ContextWindowUpdate){e.contextWindow&&this.callbacks.onContextWindowUpdated?.(e.contextWindow),this.compacting?this.finishCompaction("context-window-update"):e.contextWindow&&this.maybeScheduleAutoCompact(e.contextWindow);return}const t=this.activeRun;if(!t){this.handleOrphanAcpEvent(e);return}if(t.busyWaiting){this.handleOrphanAcpEvent(e);return}t.responded||(t.responded=!0);let i=!1;switch(e.type){case f.Text:{if(t.lastToolUse=null,t.lastToolResult=null,e.content){i=!0;const s=t.quotedStream.consume(e.content);s.quotedMessageId&&(t.quotedMessageId=s.quotedMessageId),s.deltaContent&&this.appendToStream(t,s.deltaContent)}break}case f.ToolUse:{if(i=!0,t.awaitingToolResult=!0,t.lastToolResult=null,e.toolName){if(e.toolCallId&&(t.toolNamesById.set(e.toolCallId,e.toolName),t.sentToolUseIds.has(e.toolCallId)))break;const s=e.toolInput??"",n=y(s);if(t.lastToolUse?.toolName===e.toolName&&y(t.lastToolUse.toolInput)===n)break;t.lastToolUse={toolName:e.toolName,toolInput:s},e.toolCallId&&t.sentToolUseIds.add(e.toolCallId),this.emitRawEventEnvelope(t,{type:"tool_use",payload:{tool_name:e.toolName,tool_input:s}})||this.callbacks.sendToolUse(t.eventId,t.sessionId,e.toolName,s)}break}case f.ToolResult:{t.awaitingToolResult=!1;{const s=e.toolCallId||e.toolName||"";t.lastToolProgress.delete(s),t.lastToolProgressSentAt.delete(s)}if(t.lastToolUse=null,e.content){i=!0;const s=$(t,e),n=e.content,r=y(n);if(t.lastToolResult?.toolName===s&&y(t.lastToolResult.content)===r)break;t.lastToolResult={toolName:s,content:n},this.emitRawEventEnvelope(t,{type:"tool_result",payload:{tool_name:s,content:n}})||this.callbacks.sendToolResult(t.eventId,t.sessionId,s,n)}break}case f.ToolProgress:{if(i=!0,e.content){const s=e.toolCallId||e.toolName||"";if(t.lastToolProgress.get(s)===e.content)break;t.lastToolProgress.set(s,e.content);const n=Date.now(),r=t.lastToolProgressSentAt.get(s);if(r!==void 0&&n-r<J)break;t.lastToolProgressSentAt.set(s,n);const a=$(t,e);this.emitRawEventEnvelope(t,{type:"tool_progress",payload:{tool_name:a,content:e.content}})||this.callbacks.sendToolResult(t.eventId,t.sessionId,a,e.content)}break}case f.Thinking:{t.lastToolUse=null,t.lastToolResult=null,e.content&&(i=!0,this.emitRawEventEnvelope(t,{type:"thinking",payload:{content:e.content}})||this.callbacks.sendThinking(t.eventId,t.sessionId,e.content));break}case f.Error:{t.lastToolUse=null,t.lastToolResult=null;const s=String(e.error??"unknown error");this.emitRawEventEnvelope(t,{type:"error",payload:{message:s}}),o.error("acp-adapter",`ACP error: ${s}`),this.callbacks.sendRunError(t.eventId,t.sessionId,s);break}case f.Result:{this.emitRawEventEnvelope(t,{type:"result",payload:{done:e.done??!0}});const s=t.quotedStream.flush();s.deltaContent&&this.appendToStream(t,s.deltaContent),s.quotedMessageId&&(t.quotedMessageId=s.quotedMessageId),this.flushStream(),this.finishRun("responded");break}}i&&(t.lastProgressAt=Date.now(),t.idleNoProgressCount=0),this.resetIdleTimer(t)}handlePermissionRequest(e){const t=e.permissionRequest;if(o.info("acp-adapter","permission_request received",{sessionId:this.activeRun?.sessionId,eventId:this.activeRun?.eventId,requestId:e.requestId??null,toolCallId:t?.toolCallId??null,toolName:t?.toolName??null,toolTitle:t?.toolTitle??null,optionsCount:Array.isArray(t?.options)?t.options.length:0,hasActiveRun:this.activeRun!==null,approvalMode:this.approvalMode,rawTransport:this.rawTransport,hasRawEventEnvelopeCb:typeof this.callbacks.sendRawEventEnvelope=="function",hasSendPermissionCardCb:typeof this.callbacks.sendPermissionCard=="function",rawPr:P(t,3e3)}),!t||!e.requestId||!this.acpClient){o.warn("acp-adapter","permission_request dropped: missing prerequisite",{hasPr:t!=null,hasRequestId:!!e.requestId,hasAcpClient:!!this.acpClient});return}if(this.approvalMode==="yolo"||this.approvalMode==="autoEdit"){o.info("acp-adapter",`Auto-approving (${this.approvalMode}): ${t.toolName}`),this.acpClient.respondPermission(t.requestId,{behavior:"allow"}).catch(()=>{});return}const i=t.toolCallId;this.pendingApprovals.set(i,e.requestId);const s=this.activeRun;s?(s.idleTimer&&(clearTimeout(s.idleTimer),s.idleTimer=null),this.emitRawEventEnvelope(s,{type:"permission_request",payload:{request_id:t.requestId,tool_call_id:i,tool_name:t.toolName,tool_title:t.toolTitle,options:t.options}})?o.info("acp-adapter","permission_request forwarded via rawEventEnvelope",{sessionId:s.sessionId,eventId:s.eventId,toolCallId:i,toolName:t.toolName}):(o.info("acp-adapter","permission_request falling back to sendPermissionCard",{sessionId:s.sessionId,eventId:s.eventId,toolCallId:i,toolName:t.toolName}),this.callbacks.sendPermissionCard({eventId:s.eventId,sessionId:s.sessionId,toolCallId:i,toolName:t.toolName,toolTitle:t.toolTitle,options:t.options}))):(o.info("acp-adapter",`Permission request without active run, auto-approving: ${t.toolName}`),this.acpClient.respondPermission(e.requestId,{behavior:"allow"}),this.pendingApprovals.delete(i))}emitRawEventEnvelope(e,t){return!e||!this.rawTransport||!this.callbacks.sendRawEventEnvelope?!1:(this.callbacks.sendRawEventEnvelope(e.eventId,e.sessionId,{type:t.type,payload:t.payload,seq:++this.rawEventSeq,at:new Date().toISOString()}),!0)}resetIdleTimer(e){e.idleTimer&&clearTimeout(e.idleTimer);const t=e.awaitingToolResult?oe:ne,i=e.lastProgressAt+A-Date.now(),s=Math.max(0,Math.min(t,i));e.idleTimer=setTimeout(()=>{if(this.activeRun?.eventId!==e.eventId)return;const n=Date.now()-e.lastProgressAt;if(n>=A){o.error("acp-adapter",`Turn inactive ${Math.round(n/1e3)}s \u2265 cap ${Math.round(A/1e3)}s, forcing timeout: ${e.eventId}`),this.finishRun("failed",`turn inactivity timeout after ${Math.round(n/1e3)}s with no ACP activity`),this.declareStuck();return}const r=this.acpClient;r?.isAlive?r.ping(5e3).then(async a=>{if(this.activeRun?.eventId!==e.eventId)return;if(!a){o.error("acp-adapter",`Ping failed, declaring stuck: ${e.eventId}`),this.finishRun("failed","agent unreachable"),this.declareStuck();return}if(e.awaitingToolResult){o.info("acp-adapter",`Idle timer: tool running, ping ok, continuing: ${e.eventId}`),e.idleNoProgressCount=0,this.resetIdleTimer(e);return}const l=e.lastIdleCheckAt,d=e.lastProgressAt>l,h=d?!1:await Z(r.sessionId,l),u=d||h;e.lastIdleCheckAt=Date.now(),u?(h&&o.info("acp-adapter",`Idle timer: no stream progress but kiro session active, continuing: ${e.eventId}`),e.idleNoProgressCount=0,this.resetIdleTimer(e)):(e.idleNoProgressCount++,o.info("acp-adapter",`Idle timer: ping ok, no stream progress (cycle=${e.idleNoProgressCount}), extending until inactivity cap (${Math.round(A/6e4)}min): ${e.eventId}`),this.resetIdleTimer(e))}).catch(()=>{this.activeRun?.eventId===e.eventId&&(o.error("acp-adapter",`Ping error, declaring stuck: ${e.eventId}`),this.finishRun("failed","agent unreachable"),this.declareStuck())}):(o.error("acp-adapter",`Agent idle (no client), declaring stuck: ${e.eventId}`),this.finishRun("failed","agent unreachable"),this.declareStuck())},s)}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,t){e.buffer+=t,e.flushTimer||(e.flushTimer=setTimeout(()=>this.flushStream(),se))}emitSegmentedStream(e,t){if(!t)return;const i=e.markdownSegmenter.push(t);for(const s of i)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,o.info("acp-adapter",`stream segment rollover event=${e.eventId} segment=${e.currentSegmentIndex} reason=${s.reason??"threshold"}`)))}handleOrphanAcpEvent(e){if(!(!this.sessionConnected||this.compacting||Date.now()<this.orphanSuppressedUntil)){if(e.type===f.Text&&e.content){this.orphanSessionId===null&&(this.orphanSessionId=this.currentAibotSessionId??"");const t=this.orphanQuotedStream.consume(e.content);t.deltaContent&&(this.orphanBuffer+=t.deltaContent),this.orphanBuffer.length>=ce?this.flushOrphanBuffer():this.scheduleOrphanFlush();return}e.type===f.Result&&this.flushOrphanBuffer()}}scheduleOrphanFlush(){this.orphanFlushTimer&&clearTimeout(this.orphanFlushTimer),this.orphanFlushTimer=setTimeout(()=>{this.orphanFlushTimer=null,this.flushOrphanBuffer()},le),this.orphanFlushTimer.unref?.()}flushOrphanBuffer(){if(this.orphanFlushTimer&&(clearTimeout(this.orphanFlushTimer),this.orphanFlushTimer=null),this.orphanSessionId===null&&!this.orphanBuffer)return;const e=this.orphanQuotedStream.flush();e.deltaContent&&(this.orphanBuffer+=e.deltaContent),this.orphanQuotedStream=new S;const t=this.orphanSessionId||this.currentAibotSessionId;this.orphanSessionId=null;const i=this.orphanBuffer.trim();if(this.orphanBuffer="",!!i){if(!t||!this.callbacks.sendAgentMessage){o.warn("acp-adapter",`orphan agent output dropped (${t?"no sendAgentMessage callback":"no session id"}) chars=${i.length}`);return}o.info("acp-adapter",`delivering orphan agent output session=${t} chars=${i.length}`);for(let s=0;s<i.length;s+=O)this.callbacks.sendAgentMessage(t,i.slice(s,s+O))}}suppressOrphanOutput(e){this.orphanSuppressedUntil=Date.now()+e,this.orphanFlushTimer&&(clearTimeout(this.orphanFlushTimer),this.orphanFlushTimer=null),this.orphanBuffer="",this.orphanQuotedStream=new S,this.orphanSessionId=null}flushStream(){const e=this.activeRun;if(!e||!e.buffer)return;e.flushTimer&&(clearTimeout(e.flushTimer),e.flushTimer=null);const t=e.buffer;e.buffer="",this.emitSegmentedStream(e,t)}finishRun(e,t){const i=this.activeRun;if(!i)return;this.activeRun=null,this.acpClient?.clearSettleTimer(),this.emit("eventDone",i.eventId),this.clearRunTimers(i);const s=i.quotedStream.flush();s.deltaContent&&(i.buffer+=s.deltaContent),s.quotedMessageId&&(i.quotedMessageId=s.quotedMessageId),t&&(e==="failed"?(o.error("acp-adapter",`finishRun failed: ${t} event=${i.eventId}`),i.buffer+=`
24
24
 
25
25
  The task was interrupted. Please resend your instruction.`):o.info("acp-adapter",`finishRun ${e}: ${t} event=${i.eventId}`)),i.buffer&&(this.emitSegmentedStream(i,i.buffer),i.buffer="");const n=++i.chunkSeq;if(this.callbacks.sendFinalStreamChunkReliable){let a;const l=this.callbacks.sendFinalStreamChunkReliable(i.eventId,i.sessionId,i.currentClientMsgId);l.catch(()=>{});const d=new Promise((h,u)=>{a=setTimeout(()=>u(new Error("final chunk ACK timeout")),5e3),a.unref()});Promise.race([l,d]).then(()=>{clearTimeout(a),i.silent||this.callbacks.sendEventResult(i.eventId,e,t),this.persistEventResult(i,e,t),this.replayNextDeferredEvent(),this.tryRunPendingAutoCompact()}).catch(h=>{clearTimeout(a),o.warn("acp-adapter",`finalStreamChunk ACK timeout/failed event=${i.eventId}: ${h instanceof Error?h.message:h}`),this.callbacks.sendStreamChunk(i.eventId,i.sessionId,"",n,!0,i.currentClientMsgId),i.silent||this.callbacks.sendEventResult(i.eventId,e,t),this.persistEventResult(i,e,t),this.replayNextDeferredEvent(),this.tryRunPendingAutoCompact()})}else this.callbacks.sendStreamChunk(i.eventId,i.sessionId,"",n,!0,i.currentClientMsgId),i.silent||this.callbacks.sendEventResult(i.eventId,e,t),this.persistEventResult(i,e,t),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 t=this.getContextWindowUsedPercentage(e);t===null||t<x||this.pendingAutoCompact||this.compacting||(this.pendingAutoCompact=!0,o.info("acp-adapter",`[auto-compact] context_window usedPercentage=${t.toFixed(1)}% >= ${x}%, 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=>{o.info("acp-adapter",`[auto-compact] compact done status=${e.status} msg=${e.message??""}`)}).catch(e=>{o.warn("acp-adapter",`[auto-compact] compact error: ${e instanceof Error?e.message:String(e)}`)})))}persistEventResult(e,t,i){this.eventResults&&!e.silent&&this.eventResults.set({sessionId:e.sessionId,eventId:e.eventId,status:t,msg:i,updatedAt:Date.now()})}}class me extends w{adapterSessionId;constructor(e){super(),this.adapterSessionId=e}emitDone(e){this.emit("done",e)}emitError(e){if(this.listenerCount("error")===0){o.warn("acp-adapter",`Prompt handle error (no listeners): ${e.message}`);return}this.emit("error",e)}async cancel(){}}export{qe as AcpAdapter};
@@ -1,6 +1,6 @@
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:
1
+ import{execFileSync as I}from"node:child_process";import{stat as C}from"node:fs/promises";import{resolveCommandPath as w,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 N}from"../../default-skills/index.js";import{buildSimpleProbeReport as R}from"../shared/probe-util.js";import{getCachedCodeWhaleModels as U,primeCodeWhaleModels as B}from"./model-list.js";import{writeCodeWhaleGrixConfig as D}from"./codewhale-provider-config.js";import{log as n}from"../../core/log/index.js";import{SessionBindingStore as L}from"../../core/persistence/session-binding-store.js";const v=120*1e3;function O(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;toolNamesById=new Map;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 L?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(),B(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 D(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&&!O(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 R(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(a,u,h,c)=>this.callbacks.agentInvoke(a,u,c)),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 a of e.args)s.push("--arg",a);I(o,s,{env:t,timeout:1e4,stdio:"ignore"}),n.info("codewhale-adapter",`Registered MCP server: ${e.name}`);const d=x(E(),".codewhale","skills"),r=N(d);r.length>0&&n.info("codewhale-adapter",`Synced connector skills to ${d}: [${r.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(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};
6
+ ${s}`),s=this.identity.injectOnce(o??e.adapterSessionId,s);const d=this.config.command||"codewhale",r=this.buildExecArgs(s),a={...process.env,...this.config.env},u=w(d,typeof a.PATH=="string"?a.PATH:void 0);n.info("codewhale-adapter",`Spawning: ${u} ${r.slice(0,5).join(" ")}...`);try{if(!(await C(this.cwd)).isDirectory())throw new Error(`Bound path is not a directory: ${this.cwd}`)}catch(c){throw String(c?.code??"")==="ENOENT"?new Error(`Bound directory does not exist: ${this.cwd}. Please rebind with /grix open <valid-directory>.`):c}const h=_(u,r,{cwd:this.cwd,env:a}).process;return this.activeProcess=h,h.stderr?.on("data",c=>{const l=c.toString().trim();l&&n.info("codewhale-adapter",`[codewhale stderr] ${l}`)}),new Promise((c,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"}),c()}),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 d=Number(s.input_tokens??0),r=Number(s.output_tokens??0);if(d>0||r>0){const a=this.lastUsage;this.lastUsage={sampledAt:new Date().toISOString(),turns:(a?.turns??0)+1,total:{input:(a?.total.input??0)+d,output:(a?.total.output??0)+r}}}}break}case"tool_use":{const s=typeof i.name=="string"?i.name.trim():"",d=typeof i.id=="string"?i.id.trim():"",r=typeof i.input=="string"?i.input:JSON.stringify(i.input??{});s&&t&&this.activeEventId===t&&this.activeSessionId&&(d&&this.toolNamesById.set(d,s),n.info("codewhale-adapter",`Tool use: ${s}`),this.callbacks.sendToolUse(t,this.activeSessionId,s,r),this.resetIdleTimer(t));break}case"tool_result":{const s=typeof i.id=="string"?i.id.trim():"",r=(typeof i.name=="string"?i.name.trim():"")||(s?this.toolNamesById.get(s):void 0)||"tool";s&&this.toolNamesById.delete(s);const a=typeof i.output=="string"?i.output:"";t&&this.activeEventId===t&&this.activeSessionId&&(this.callbacks.sendToolResult(t,this.activeSessionId,r,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.toolNamesById.clear(),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};
@@ -1,9 +1,9 @@
1
- import{createInterface as x}from"node:readline";import{EventEmitter as B}from"node:events";import{randomUUID as z}from"node:crypto";import{mkdirSync as $,readFileSync as q,writeFileSync as L,readdirSync as J,statSync as G}from"node:fs";import{join as b,resolve as X}from"node:path";import{fileURLToPath as K}from"node:url";import{homedir as E}from"node:os";import{GRIX_PATHS as Y,log as u}from"../../core/log/index.js";import{IdentityInjector as V}from"../shared/identity-injector.js";import{buildSimpleProbeReport as Z}from"../shared/probe-util.js";import{InternalApiServer as ee}from"../../core/mcp/internal-api-server.js";import{syncDefaultSkillsToDir as te}from"../../default-skills/index.js";import{resolveCommandPath as R,spawnCommand as T,killProcessGroup as w}from"../../core/runtime/spawn.js";import{SessionBindingStore as se}from"../../core/persistence/session-binding-store.js";import{fetchCursorParameterizedModels as ne}from"./fetch-models.js";import{fetchCursorAccountUsage as ie}from"./fetch-usage.js";import{buildCursorContextWindow as re,buildCursorContextWindowFromUsage as oe,resolveCursorContextWindowSize as ae}from"./context-window.js";const le=12e4,P=300*1e3,de=6e5,ce=8192,ue="Summarize this conversation for handoff to a fresh chat. Include: goals, key decisions, important file paths, current status, and next steps. Be concise but complete. Output ONLY the summary text.",D=12e4,N=new Set(["completed","failed","error","errored","canceled","cancelled","rejected"]);function me(l){const e=l.replace(/ToolCall$/i,"").trim();return e?e.charAt(0).toUpperCase()+e.slice(1):l||"tool"}function O(l){const e=String(l.call_id??l.callId??"").trim(),t=l.tool_call??l.toolCall;if(t&&typeof t=="object"&&!Array.isArray(t)){const n=Object.keys(t);if(n.length>0){const s=n[0],i=t[s],r=i&&typeof i=="object"&&!Array.isArray(i)?i:null;return{toolName:me(s),toolInput:r?.args??r?.arguments??{},callId:e}}}return{toolName:String(l.name??l.tool_name??l.toolName??"tool").trim()||"tool",toolInput:l.args??l.tool_input??l.toolInput??{},callId:e}}function j(l){return String(l??"").trim().toLowerCase().replace(/[^a-z0-9]/g,"")}function U(l){const e=j(l);return e==="askquestion"||e==="askuserquestion"}function Q(l){const e=j(l);return e==="createplan"||e==="exitplanmode"}function he(l){if(!Array.isArray(l))return;const e=l.map(t=>{if(typeof t=="string")return t.trim();if(!t||typeof t!="object")return"";const n=t,s=String(n.label??"").trim();if(s)return s;const i=String(n.value??"").trim();if(i)return i;const r=String(n.text??"").trim();return r||""}).filter(t=>t.length>0);return e.length>0?e:void 0}function fe(l){if(!l||typeof l!="object"||Array.isArray(l))return null;const e=l,t=String(e.title??"").trim(),n=e.questions;if(!Array.isArray(n)||n.length===0)return null;const s=[];for(let i=0;i<n.length;i++){const r=n[i];if(!r||typeof r!="object")continue;const a=r,o=t||`Question ${i+1}`,d=String(a.header??a.id??a.question??o).trim()||o,c=String(a.prompt??a.question??"").trim();if(!c)continue;const h=he(a.options),S=a.multiSelect===!0||a.multi_select===!0;s.push({header:d,prompt:c,...h?{options:h}:{},...S?{multi_select:!0}:{}})}return s.length>0?s:null}function pe(l){if(!l||typeof l!="object"||Array.isArray(l))return null;const e=l,t=String(e.name??e.title??"").trim(),n=String(e.overview??"").trim(),s=String(e.plan??"").trim();if(!s&&!n&&!t)return null;const i=[];return t&&i.push(`# ${t}`),n&&i.push(n),s&&i.push(s),i.join(`
1
+ import{createInterface as $}from"node:readline";import{EventEmitter as q}from"node:events";import{randomUUID as V}from"node:crypto";import{mkdirSync as E,readFileSync as L,writeFileSync as P,readdirSync as Y,statSync as D}from"node:fs";import{join as A,resolve as Z}from"node:path";import{fileURLToPath as ee}from"node:url";import{homedir as N}from"node:os";import{GRIX_PATHS as te,log as u}from"../../core/log/index.js";import{IdentityInjector as se}from"../shared/identity-injector.js";import{buildSimpleProbeReport as ne}from"../shared/probe-util.js";import{InternalApiServer as ie}from"../../core/mcp/internal-api-server.js";import{syncDefaultSkillsToDir as re}from"../../default-skills/index.js";import{resolveCommandPath as C,spawnCommand as T,killProcessGroup as I}from"../../core/runtime/spawn.js";import{SessionBindingStore as oe}from"../../core/persistence/session-binding-store.js";import{fetchCursorParameterizedModels as ae}from"./fetch-models.js";import{fetchCursorAccountUsage as le}from"./fetch-usage.js";import{buildCursorContextWindow as de,buildCursorContextWindowFromUsage as ce,resolveCursorContextWindowSize as ue}from"./context-window.js";const he=12e4,x=300*1e3,F=90*1e3,j=1800*1e3,me=8192,fe="Summarize this conversation for handoff to a fresh chat. Include: goals, key decisions, important file paths, current status, and next steps. Be concise but complete. Output ONLY the summary text.",Q=12e4,O=new Set(["completed","failed","error","errored","canceled","cancelled","rejected"]);function pe(l){const e=l.replace(/ToolCall$/i,"").trim();return e?e.charAt(0).toUpperCase()+e.slice(1):l||"tool"}function U(l){const e=String(l.call_id??l.callId??"").trim(),t=l.tool_call??l.toolCall;if(t&&typeof t=="object"&&!Array.isArray(t)){const n=Object.keys(t);if(n.length>0){const s=n[0],i=t[s],r=i&&typeof i=="object"&&!Array.isArray(i)?i:null;return{toolName:pe(s),toolInput:r?.args??r?.arguments??{},callId:e}}}return{toolName:String(l.name??l.tool_name??l.toolName??"tool").trim()||"tool",toolInput:l.args??l.tool_input??l.toolInput??{},callId:e}}function W(l){return String(l??"").trim().toLowerCase().replace(/[^a-z0-9]/g,"")}function H(l){const e=W(l);return e==="askquestion"||e==="askuserquestion"}function z(l){const e=W(l);return e==="createplan"||e==="exitplanmode"}function ge(l){if(!Array.isArray(l))return;const e=l.map(t=>{if(typeof t=="string")return t.trim();if(!t||typeof t!="object")return"";const n=t,s=String(n.label??"").trim();if(s)return s;const i=String(n.value??"").trim();if(i)return i;const r=String(n.text??"").trim();return r||""}).filter(t=>t.length>0);return e.length>0?e:void 0}function Se(l){if(!l||typeof l!="object"||Array.isArray(l))return null;const e=l,t=String(e.title??"").trim(),n=e.questions;if(!Array.isArray(n)||n.length===0)return null;const s=[];for(let i=0;i<n.length;i++){const r=n[i];if(!r||typeof r!="object")continue;const a=r,o=t||`Question ${i+1}`,d=String(a.header??a.id??a.question??o).trim()||o,c=String(a.prompt??a.question??"").trim();if(!c)continue;const m=ge(a.options),y=a.multiSelect===!0||a.multi_select===!0;s.push({header:d,prompt:c,...m?{options:m}:{},...y?{multi_select:!0}:{}})}return s.length>0?s:null}function ye(l){if(!l||typeof l!="object"||Array.isArray(l))return null;const e=l,t=String(e.name??e.title??"").trim(),n=String(e.overview??"").trim(),s=String(e.plan??"").trim();if(!s&&!n&&!t)return null;const i=[];return t&&i.push(`# ${t}`),n&&i.push(n),s&&i.push(s),i.join(`
2
2
 
3
- `)}function ge(l){if(!l||typeof l!="object"||Array.isArray(l))return null;const e=l,t=String(e.type??"").trim().toLowerCase();if(!t)return null;switch(t){case"tool_call":{const n=String(e.subtype??"").trim().toLowerCase(),s=O(e);return U(s.toolName)||Q(s.toolName)?null:n==="started"||n==="start"?{type:"tool_call",payload:{tool_name:s.toolName,...s.callId?{tool_call_id:s.callId}:{}}}:(N.has(n),null)}case"error":return{type:"error",payload:{message:String(e.message??e.error??e.result??"cursor error").trim()||"cursor error"}};case"result":{const n=String(e.subtype??"").trim().toLowerCase();return e.is_error===!0||n==="error"?{type:"error",payload:{message:String(e.result??e.message??"cursor run failed").trim()||"cursor run failed"}}:null}default:return null}}function ye(l,e){if(!l||typeof l!="object"||Array.isArray(l))return"";const t=l;if(String(t.type??"")!=="assistant")return"";const n=t.model_call_id??t.modelCallId;if(n!=null&&String(n).trim()!=="")return"";const s=t.message,i=s&&typeof s=="object"&&!Array.isArray(s)?s.content:void 0;if(!Array.isArray(i))return"";const r=i.map(a=>{if(!a||typeof a!="object"||Array.isArray(a))return"";const o=a;return o.type==="text"?String(o.text??""):""}).filter(Boolean).join("");if(!r)return"";if(e){if(r===e||e.startsWith(r)&&r.length<e.length)return"";if(r.startsWith(e))return r.slice(e.length)}return r}const F=[{id:"approval",displayName:"\u4EBA\u5DE5\u786E\u8BA4"},{id:"full_auto",displayName:"\u81EA\u7531\u6A21\u5F0F"},{id:"plan",displayName:"\u8BA1\u5212\u6A21\u5F0F"}];function C(l){const e=String(l??"").trim().toLowerCase();return F.some(t=>t.id===e)?e:null}function Se(l){try{return process.kill(l,0),!0}catch(e){return e.code==="EPERM"}}function W(l,e,t){if(t<0)return null;let n;try{n=J(l,{withFileTypes:!0})}catch{return null}for(const s of n){const i=b(l,s.name);if(s.isDirectory()){const r=W(i,e,t-1);if(r)return r}else if(s.isFile()&&s.name.endsWith(e))return i}return null}function _e(l,e){if(!l)return null;const t=e||b(E(),".cursor","projects");return W(t,`${l}.jsonl`,6)}function ve(l){const e=l.context_messages_json;if(e)try{const t=JSON.parse(e);return!Array.isArray(t)||t.length===0?void 0:t.map(n=>({senderId:String(n.sender_id??"unknown"),content:String(n.content??"")}))}catch{return}}function be(l,e){const t=[String(l??"")],n=String(e.quoted_message_id??"").trim();n&&t.push(`[quoted_message_id] ${n}`);const s=String(e.attachments_json??"").trim();return s&&t.push(`[attachments_json]
3
+ `)}function _e(l){if(!l||typeof l!="object"||Array.isArray(l))return null;const e=l,t=String(e.type??"").trim().toLowerCase();if(!t)return null;switch(t){case"tool_call":{const n=String(e.subtype??"").trim().toLowerCase(),s=U(e);return H(s.toolName)||z(s.toolName)?null:n==="started"||n==="start"?{type:"tool_call",payload:{tool_name:s.toolName,...s.callId?{tool_call_id:s.callId}:{}}}:(O.has(n),null)}case"error":return{type:"error",payload:{message:String(e.message??e.error??e.result??"cursor error").trim()||"cursor error"}};case"result":{const n=String(e.subtype??"").trim().toLowerCase();return e.is_error===!0||n==="error"?{type:"error",payload:{message:String(e.result??e.message??"cursor run failed").trim()||"cursor run failed"}}:null}default:return null}}function ve(l,e){if(!l||typeof l!="object"||Array.isArray(l))return"";const t=l;if(String(t.type??"")!=="assistant")return"";const n=t.model_call_id??t.modelCallId;if(n!=null&&String(n).trim()!=="")return"";const s=t.message,i=s&&typeof s=="object"&&!Array.isArray(s)?s.content:void 0;if(!Array.isArray(i))return"";const r=i.map(a=>{if(!a||typeof a!="object"||Array.isArray(a))return"";const o=a;return o.type==="text"?String(o.text??""):""}).filter(Boolean).join("");if(!r)return"";if(e){if(r===e||e.startsWith(r)&&r.length<e.length)return"";if(r.startsWith(e))return r.slice(e.length)}return r}const J=[{id:"approval",displayName:"\u4EBA\u5DE5\u786E\u8BA4"},{id:"full_auto",displayName:"\u81EA\u7531\u6A21\u5F0F"},{id:"plan",displayName:"\u8BA1\u5212\u6A21\u5F0F"}];function R(l){const e=String(l??"").trim().toLowerCase();return J.some(t=>t.id===e)?e:null}function M(l){try{return process.kill(l,0),!0}catch(e){return e.code==="EPERM"}}function G(l,e,t){if(t<0)return null;let n;try{n=Y(l,{withFileTypes:!0})}catch{return null}for(const s of n){const i=A(l,s.name);if(s.isDirectory()){const r=G(i,e,t-1);if(r)return r}else if(s.isFile()&&s.name.endsWith(e))return i}return null}function X(l,e){if(!l)return null;const t=e||A(N(),".cursor","projects");return G(t,`${l}.jsonl`,6)}function Ae(l){const e=l.context_messages_json;if(e)try{const t=JSON.parse(e);return!Array.isArray(t)||t.length===0?void 0:t.map(n=>({senderId:String(n.sender_id??"unknown"),content:String(n.content??"")}))}catch{return}}function be(l,e){const t=[String(l??"")],n=String(e.quoted_message_id??"").trim();n&&t.push(`[quoted_message_id] ${n}`);const s=String(e.attachments_json??"").trim();return s&&t.push(`[attachments_json]
4
4
  ${s}`),t.filter(i=>i.length>0).join(`
5
5
 
6
- `)}class H extends B{adapterSessionId;constructor(e){super(),this.adapterSessionId=e}emitDone(e){this.emit("done",e)}emitError(e){if(this.listenerCount("error")===0){u.warn("cursor-adapter",`Prompt handle error (no listeners): ${e.message}`);return}this.emit("error",e)}async cancel(){}}class M extends B{type="cursor";static STDERR_MAX_CHARS=8192;config;callbacks;alive=!1;stopped=!1;permissionHandler=null;internalApi=null;bindingStore=null;identity;inboundQueue=[];pendingBySession=new Map;sessions=new Set;sessionRuntime=new Map;sessionSeq=0;lastUsageBySession=new Map;lastContextBySession=new Map;pendingHandoffBySession=new Map;pendingQuestionByRequestId=new Map;pendingQuestionAnswerBySession=new Map;cachedAccountRateLimits=null;rateLimitsInflight=null;_availableModels=[];activeBySession=new Map;constructor(e,t){super(),this.config=e,this.callbacks=t;const n=e.options??{};this.bindingStore=n.bindingStore instanceof se?n.bindingStore:null,this.identity=new V("cursor-adapter",t.getAgentProfile)}async start(){this.alive=!0,this.stopped=!1,(this.config.options??{}).mcp_tools!==!1&&(this.internalApi=new ee,this.internalApi.setInvokeHandler(async(t,n,s,i)=>this.callbacks.agentInvoke(t,n,i)),await this.internalApi.start()),this.notifyBindingReady(),this.refreshModels().then(()=>this.notifyBindingReady()).catch(()=>{}),this.refreshAccountRateLimits().then(()=>this.notifyBindingReady()).catch(()=>{})}get availableModels(){return this._availableModels}get availableModes(){return F.map(e=>({...e}))}async refreshModels(){try{const e={...process.env,...this.config.env??{}},t=await ne({command:this.config.command,env:e,cwd:process.cwd()});t.length>0&&(this._availableModels=t,u.info("cursor-adapter",`Loaded ${t.length} available models`))}catch(e){u.warn("cursor-adapter",`Failed to refresh models: ${e instanceof Error?e.message:String(e)}`)}}async stop(){this.stopped=!0,this.alive=!1;for(const e of this.activeBySession.values())w(e.child,"SIGTERM");this.activeBySession.clear(),this.internalApi&&(await this.internalApi.stop(),this.internalApi=null)}isAlive(){return this.alive}async createSession(e){const t=`cursor-${Date.now()}-${++this.sessionSeq}`;return this.sessions.add(t),this.sessionRuntime.set(t,{cwd:typeof e.cwd=="string"?e.cwd:void 0,modelId:typeof e.modelId=="string"?e.modelId:void 0,modeId:typeof e.modeId=="string"?e.modeId:void 0}),t}async resumeSession(e,t){this.sessions.add(e);const n=this.sessionRuntime.get(e)??{};this.sessionRuntime.set(e,{...n,cwd:typeof t?.cwd=="string"?t.cwd:n.cwd,modelId:typeof t?.modelId=="string"?t.modelId:n.modelId,modeId:typeof t?.modeId=="string"?t.modeId:n.modeId})}onAgentProfileChanged(){this.identity.onProfileChanged()}async destroySession(e){this.sessions.delete(e),this.sessionRuntime.delete(e),this.identity.forgetSession(e),this.pendingQuestionAnswerBySession.delete(e),this.pendingHandoffBySession.delete(e);for(const[t,n]of this.pendingQuestionByRequestId)n.sessionId===e&&this.pendingQuestionByRequestId.delete(t)}sendPrompt(e){const t=new H(e.adapterSessionId);if(!this.alive||this.stopped)return queueMicrotask(()=>t.emitError(new Error("adapter not running"))),t;const n=this.inboundQueue.shift()??{event_id:`cursor-evt-${Date.now()}`,session_id:e.adapterSessionId,content:e.text},s=e.adapterSessionId,i=this.pendingBySession.get(s)??[];return i.push({event:n,request:e,handle:t}),this.pendingBySession.set(s,i),this.tryStartNext(s),t}tryStartNext(e){if(this.activeBySession.has(e)||!this.alive||this.stopped)return;const t=this.pendingBySession.get(e);if(!t||t.length===0)return;const n=t.shift();t.length===0?this.pendingBySession.delete(e):this.pendingBySession.set(e,t),n&&this.startJob(n,!1,0)}startJob(e,t,n){const{event:s,request:i,handle:r}=e,a=i.adapterSessionId,o=this.sessionRuntime.get(i.adapterSessionId)??{},d=this.config.options??{},c=[...this.config.args??[]];c.push("-p","--output-format","stream-json"),d.trust!==!1&&c.push("--trust");const h=o.cwd||d.workspace;this.internalApi&&h&&(this.ensureWorkspaceMcpAndSkills(h),c.push("--approve-mcps")),h&&c.push("--workspace",h);const S=o.modelId||d.model;S&&c.push("--model",S);const v=C(o.modeId||d.mode)??"full_auto";v==="full_auto"?c.push("--yolo"):v==="plan"&&c.push("--plan");const _=o.cursorSessionId??this.bindingStore?.getAcpSessionId(i.adapterSessionId),A=!!(_&&d.use_continue!==!1&&!t);A&&c.push("--resume",_),c.push(this.buildPromptText(i));const m={...process.env,...this.config.env??{}},y=R(this.config.command,typeof m.PATH=="string"?m.PATH:void 0);u.info("cursor-adapter",`job start: event=${s.event_id} session=${s.session_id} retry=${n}`);let p;try{p=T(y,c,{cwd:h||process.cwd(),env:m,stdio:["ignore","pipe","pipe"]}).process}catch(g){this.finishActive(a,"failed",`spawn failed: ${g instanceof Error?g.message:String(g)}`);return}this.callbacks.sendEventAck(s.event_id,s.session_id),this.emit("eventStarted",s.event_id,s.session_id);const f={event:s,request:i,handle:r,child:p,seq:0,done:!1,stderr:"",timer:null,idleTimer:null,toolCallsInFlight:new Set,toolCallsStarted:0,assistantTextEmitted:"",streamBuffer:"",retryCount:n,usedContinue:A,workspace:h||process.cwd(),args:c};this.activeBySession.set(a,f),this.notifyBindingReady(a),this.armActiveIdleTimer(a);const k=i.timeoutMs&&i.timeoutMs>0?i.timeoutMs:0;k>0&&(f.timer=setTimeout(()=>{f.done||(w(p,"SIGTERM"),this.finishActive(a,"failed",`cursor agent timeout after ${k}ms`))},k)),x({input:p.stdout}).on("line",g=>this.handleStdoutLineForActive(f,g)),p.stderr?.on("data",g=>{const I=f.stderr+String(g??"");f.stderr=I.length>M.STDERR_MAX_CHARS?I.slice(-M.STDERR_MAX_CHARS):I}),p.once("error",g=>{this.finishActive(a,"failed",`spawn failed: ${String(g?.message??g)}`)}),p.once("close",g=>{if(!f.done)if((g??0)===0)this.finishActive(a,"responded");else{if(this.shouldRetryWithoutContinue(f)){f.timer&&clearTimeout(f.timer),f.idleTimer&&clearTimeout(f.idleTimer),this.activeBySession.delete(a),this.startJob(e,!0,f.retryCount+1);return}const I=f.stderr.trim()||`cursor agent exited with code ${g??-1}`;this.finishActive(a,"failed",I)}})}async cancel(e){const t=this.activeBySession.get(e);t&&(w(t.child,"SIGTERM"),this.finishActive(e,"canceled","canceled"))}deliverInboundEvent(e){const t=String(e.session_id??"").trim();if(!t){u.warn("cursor-adapter",`Dropping event ${e.event_id}: missing session_id`),this.callbacks.sendEventResult(e.event_id,"failed","missing session_id");return}if(!this.alive||this.stopped){u.warn("cursor-adapter",`Dropping event ${e.event_id}: adapter not running`),this.callbacks.sendEventAck(e.event_id,t),this.callbacks.sendEventResult(e.event_id,"failed","adapter not running");return}const n={adapterSessionId:t,text:be(String(e.content??""),e),contextMessages:ve(e)},s=new H(t),i=this.pendingBySession.get(t)??[];i.push({event:e,request:n,handle:s}),this.pendingBySession.set(t,i),u.info("cursor-adapter",`inbound queued: event=${e.event_id} session=${t} depth=${i.length}`),this.tryStartNext(t)}cancelSessionWork(e,t){const n=this.pendingBySession.get(e)??[];this.pendingBySession.delete(e);const s=this.activeBySession.get(e);s&&(this.killChildWithFallback(s.child),this.finishActive(e,"canceled",t));for(const i of n)this.callbacks.sendEventAck(i.event.event_id,i.event.session_id),this.callbacks.sendEventResult(i.event.event_id,"canceled",t),i.handle.emitDone({status:"canceled",error:t})}killChildWithFallback(e){w(e,"SIGTERM"),setTimeout(()=>{if(e.exitCode===null&&e.signalCode===null)try{w(e,"SIGKILL")}catch{}},5e3).unref()}deliverStopEvent(e,t){if(t){const n=this.activeBySession.get(t);if(n?.event.event_id===e){this.killChildWithFallback(n.child),this.finishActive(t,"canceled","stopped");return}const s=this.pendingBySession.get(t)??[],i=s.findIndex(r=>r.event.event_id===e);if(i>=0){const[r]=s.splice(i,1);s.length===0?this.pendingBySession.delete(t):this.pendingBySession.set(t,s),this.callbacks.sendEventAck(r.event.event_id,r.event.session_id),this.callbacks.sendEventResult(r.event.event_id,"canceled","stopped"),r.handle.emitDone({status:"canceled",error:"stopped"})}return}for(const[n,s]of this.activeBySession.entries())if(s.event.event_id===e){this.killChildWithFallback(s.child),this.finishActive(n,"canceled","stopped");return}for(const[n,s]of this.pendingBySession.entries()){const i=s.findIndex(a=>a.event.event_id===e);if(i<0)continue;const[r]=s.splice(i,1);s.length===0?this.pendingBySession.delete(n):this.pendingBySession.set(n,s),this.callbacks.sendEventAck(r.event.event_id,r.event.session_id),this.callbacks.sendEventResult(r.event.event_id,"canceled","stopped"),r.handle.emitDone({status:"canceled",error:"stopped"});return}}async handleLocalAction(e){const t=String(e.action_type??"").trim().toLowerCase(),n=e.params??{},s=String(n.session_id??"").trim(),i=e.action_id;if(t==="claude_interaction_reply")return String(n.kind??"").trim()==="permission"?(this.callbacks.sendLocalActionResult(i,"failed",void 0,"unsupported","Cursor adapter does not support interactive permission approvals"),{handled:!0,kind:"permission_unsupported"}):this.handleQuestionReply(e);switch(t){case"set_model":{const r=String(n.model_id??"").trim();if(!r||!s)return this.callbacks.sendLocalActionResult(i,"failed",void 0,"invalid_params","model_id and session_id are required"),{handled:!0,kind:"set_model"};const a=this.sessionRuntime.get(s)??{};this.sessionRuntime.set(s,{...a,modelId:r}),this.notifyBindingReady(s);const o=this.getToolbarMeta(s);return this.callbacks.sendLocalActionResult(i,"ok",{outcome:"model_set",session_context:{model_id:r,mode_id:o.mode_id,modelId:r,modeId:o.mode_id},model_id:r,mode_id:o.mode_id,available_models:this.availableModels,available_modes:this.availableModes}),{handled:!0,kind:"set_model"}}case"set_mode":{const r=C(n.mode_id);if(!r||!s)return this.callbacks.sendLocalActionResult(i,"failed",void 0,"invalid_params","mode_id must be approval, full_auto, or plan and session_id is required"),{handled:!0,kind:"set_mode"};const a=this.sessionRuntime.get(s)??{};this.sessionRuntime.set(s,{...a,modeId:r}),this.notifyBindingReady(s);const o=this.getToolbarMeta(s);return this.callbacks.sendLocalActionResult(i,"ok",{outcome:"mode_set",session_context:{model_id:o.model_id,mode_id:r,modelId:o.model_id,modeId:r},model_id:o.model_id,mode_id:r,available_models:this.availableModels,available_modes:this.availableModes}),{handled:!0,kind:"set_mode"}}case"get_context":{const r=s?this.sessionRuntime.get(s):void 0,a=this.config.options??{},o=this.getToolbarMeta(s);return this.callbacks.sendLocalActionResult(i,"ok",{session_context:{model_id:o.model_id,mode_id:o.mode_id,cwd:r?.cwd??a.workspace??null},model_id:o.model_id,mode_id:o.mode_id,modelId:o.model_id,modeId:o.mode_id,cwd:r?.cwd??a.workspace??null,available_models:this.availableModels,available_modes:this.availableModes}),{handled:!0,kind:"get_context"}}case"get_rate_limits":{await this.refreshAccountRateLimits({force:!0});const r=this.getRateLimitsSnapshot(s||void 0);return this.callbacks.sendLocalActionResult(i,"ok",r),s&&this.notifyBindingReady(s),{handled:!0,kind:"get_rate_limits"}}case"get_session_usage":{const r=s?this.getUsageSnapshot(s):null;return r?this.callbacks.sendLocalActionResult(i,"ok",{adapterType:"cursor",available:!0,sampledAt:r.sampledAt,turns:r.turns,tokenUsage:r.total}):this.callbacks.sendLocalActionResult(i,"ok",{adapterType:"cursor",available:!1,sampledAt:null,turns:0,tokenUsage:null}),{handled:!0,kind:"get_session_usage"}}case"session_control":{const r=String(n.verb??"").trim().toLowerCase();if(r==="restart"&&s)this.cancelSessionWork(s,"restarted"),this.callbacks.sendLocalActionResult(i,"ok",{outcome:"restarted"});else if(r==="status"){const a=this.sessionRuntime.get(s),o=this.config.options??{},d=this.getToolbarMeta(s),c=this.activeBySession.has(s);this.callbacks.sendLocalActionResult(i,"ok",{verb:"status",status:c?"running":"idle",session_context:{model_id:d.model_id,mode_id:d.mode_id,cwd:a?.cwd??o.workspace??null},model_id:d.model_id,mode_id:d.mode_id,modelId:d.model_id,modeId:d.mode_id,cwd:a?.cwd??o.workspace??null,available_models:this.availableModels,available_modes:this.availableModes})}else this.callbacks.sendLocalActionResult(i,"failed",void 0,"invalid_verb",`Unsupported verb: ${r}`);return{handled:!0,kind:"session_control"}}default:return{handled:!1,kind:"unsupported"}}}async execCommand(e,t,n){return e!=="compact"?{status:"unsupported",message:`Unsupported command: ${e}`}:this.runCompactHandoff(n)}async runCompactHandoff(e){if(!e)return{status:"failed",message:"session_id is required"};if(this.activeBySession.has(e))return{status:"failed",message:"Cannot compact while a Cursor turn is active"};const t=this.sessionRuntime.get(e)??{},n=this.config.options??{},s=t.cursorSessionId??this.bindingStore?.getAcpSessionId(e)??null;if(!s)return{status:"failed",message:"No Cursor chat to compact (send a message first)"};const i=String(t.cwd||n.workspace||process.cwd()),r=String(t.modelId||n.model||""),a=C(t.modeId||n.mode)??"full_auto";u.info("cursor-adapter",`[compact] summarize resume=${s} session=${e}`);const o=await this.runPrintOnce({prompt:ue,resumeChatId:s,cwd:i,modelId:r||void 0,modeId:a,timeoutMs:D});if(!o.ok||!o.text.trim())return{status:"failed",message:o.error??"Summarize failed (empty result)"};const d=await this.createEmptyChat();return d?(this.sessionRuntime.set(e,{...t,cwd:i,modelId:r||t.modelId,modeId:a,cursorSessionId:d}),this.bindingStore?.setAcpSessionId(e,d),this.pendingHandoffBySession.set(e,o.text.trim()),this.lastContextBySession.set(e,re(0,ae(r))),this.notifyBindingReady(e),u.info("cursor-adapter",`[compact] handoff ready: old=${s} new=${d} summaryChars=${o.text.trim().length}`),{status:"ok",message:"Context compacted via summarize + new chat",data:{previousChatId:s,chatId:d}}):{status:"failed",message:"agent create-chat failed"}}runPrintOnce(e){const t={...process.env,...this.config.env??{}},n=R(this.config.command,typeof t.PATH=="string"?t.PATH:void 0),s=[...this.config.args??[]];s.push("-p","--output-format","stream-json"),this.config.options?.trust!==!1&&s.push("--trust"),e.cwd&&s.push("--workspace",e.cwd),e.modelId&&s.push("--model",e.modelId);const i=C(e.modeId)??"full_auto";return i==="full_auto"?s.push("--yolo"):i==="plan"&&s.push("--plan"),e.resumeChatId&&s.push("--resume",e.resumeChatId),s.push(e.prompt),new Promise(r=>{let a;try{a=T(n,s,{cwd:e.cwd||process.cwd(),env:t,stdio:["ignore","pipe","pipe"]}).process}catch(m){r({ok:!1,text:"",error:`spawn failed: ${m instanceof Error?m.message:String(m)}`});return}let o="",d="",c="",h=!1;const S=e.timeoutMs&&e.timeoutMs>0?e.timeoutMs:D,v=setTimeout(()=>{h||(h=!0,this.killChildWithFallback(a),r({ok:!1,text:o,sessionId:d||void 0,error:`compact timeout after ${S}ms`}))},S);v.unref?.();const _=(m,y)=>{h||(h=!0,clearTimeout(v),r({ok:m,text:o,sessionId:d||void 0,error:y}))};x({input:a.stdout}).on("line",m=>{const y=m.trim();if(!y)return;let p;try{p=JSON.parse(y)}catch{return}if(p?.type!=="result")return;const f=typeof p?.result=="string"?p.result:"";f&&(o=f);const k=String(p?.session_id??"").trim();k&&(d=k)}),a.stderr?.on("data",m=>{c+=typeof m=="string"?m:m.toString("utf8")}),a.on("error",m=>_(!1,m.message)),a.on("close",m=>{if(!h){if(o.trim()){_(!0);return}_(!1,c.trim()||`cursor agent exited with code ${m??"unknown"}`)}})})}createEmptyChat(){const e={...process.env,...this.config.env??{}},t=R(this.config.command,typeof e.PATH=="string"?e.PATH:void 0);return new Promise(n=>{let s;try{s=T(t,["create-chat"],{cwd:process.cwd(),env:e,stdio:["ignore","pipe","pipe"]}).process}catch{n(null);return}let i="",r=!1;const a=setTimeout(()=>{r||(r=!0,this.killChildWithFallback(s),n(null))},3e4);a.unref?.(),s.stdout?.on("data",o=>{i+=typeof o=="string"?o:o.toString("utf8")}),s.on("error",()=>{r||(r=!0,clearTimeout(a),n(null))}),s.on("close",o=>{if(r)return;r=!0,clearTimeout(a);const d=i.trim().split(/\s+/)[0]??"";n(o===0&&/^[0-9a-f-]{16,}$/i.test(d)?d:null)})})}setPermissionHandler(e){this.permissionHandler=e}async ping(e){if(!this.alive||this.stopped||(this.config.options??{}).mcp_tools!==!1&&this.internalApi===null)return!1;for(const n of this.activeBySession.values()){const s=n.child?.pid;if(s&&!Se(s))return!1}return!0}getStatus(){let e=0;for(const t of this.pendingBySession.values())e+=t.length;return{alive:this.alive,busy:this.activeBySession.size>0,sessions:this.sessions.size,details:{queueDepth:e+this.inboundQueue.length,activeSessions:this.activeBySession.size}}}getActiveEventIds(){const e=[];for(const t of this.activeBySession.values())t.event.event_id&&e.push(t.event.event_id);return e}clearActiveEventForShutdown(){this.activeBySession.clear()}getMcpConfig(){return null}async probe(e){const t=this.getStatus();return{...await Z(this.config.command||"cursor-agent",{alive:t.alive,busy:t.busy,started:this.alive},e),session:this.probeSessionRecord()}}probeSessionRecord(){const e=this.firstKnownCursorSessionId(),t=e?_e(e):null;if(!t)return{recordPath:null,lastActivityMs:null,freshMs:null};try{const n=G(t);return{recordPath:t,lastActivityMs:n.mtimeMs,freshMs:Date.now()-n.mtimeMs}}catch{return{recordPath:t,lastActivityMs:null,freshMs:null}}}firstKnownCursorSessionId(){for(const e of this.activeBySession.keys()){const t=this.sessionRuntime.get(e);if(t?.cursorSessionId)return t.cursorSessionId}for(const e of this.sessionRuntime.values())if(e.cursorSessionId)return e.cursorSessionId;return null}getUsageSnapshot(e){return this.lastUsageBySession.get(e)??null}getRateLimitsSnapshot(e){const t=this.cachedAccountRateLimits,n=e?this.lastContextBySession.get(e)??null:null;if(!t)return{adapterType:"cursor",available:!1,cached:!1,sampledAt:null,rateLimits:null,contextWindow:n,tokenUsage:null};const s=Date.parse(t.sampledAt);return{adapterType:"cursor",available:t.available&&!!t.rateLimits,cached:!0,sampledAt:Number.isFinite(s)?s:Date.now(),rateLimits:t.rateLimits,contextWindow:n,tokenUsage:null,...t.displayMessage?{displayMessage:t.displayMessage}:{},...t.error?{error:t.error}:{}}}async refreshAccountRateLimits(e){const t=e?.force===!0,n=this.cachedAccountRateLimits,s=n?Date.now()-Date.parse(n.sampledAt):Number.POSITIVE_INFINITY;if(!t&&n&&Number.isFinite(s)&&s<6e4||this.rateLimitsInflight&&(await this.rateLimitsInflight,!t))return;const i=(async()=>{const r=await ie();this.cachedAccountRateLimits={available:r.available,sampledAt:r.sampledAt,rateLimits:r.rateLimits,displayMessage:r.displayMessage,error:r.error},r.available&&r.rateLimits?u.info("cursor-adapter",`[rate-limits] monthly=${r.rateLimits.fiveHour.usedPercentage.toFixed(1)}% api=${r.rateLimits.sevenDay.usedPercentage.toFixed(1)}% resetsAt=${r.rateLimits.fiveHour.resetsAt}`+(r.displayMessage?` msg=${r.displayMessage}`:"")):u.warn("cursor-adapter",`[rate-limits] unavailable: ${r.error??"unknown"}`)})();this.rateLimitsInflight=i.finally(()=>{this.rateLimitsInflight=null}),await this.rateLimitsInflight}getToolbarMeta(e){const t=this.config.options??{},n=e||String(t.aibotSessionId??""),s=n?this.sessionRuntime.get(n):void 0,i=String(s?.modelId||t.model||"auto"),r=C(s?.modeId||t.mode)??"full_auto",a={model_id:i,mode_id:r,currentModelId:i,currentModeId:r,available_models:this.availableModels,available_modes:this.availableModes},o=this.cachedAccountRateLimits?.rateLimits;if(o?.fiveHour||o?.sevenDay){const c=Date.parse(this.cachedAccountRateLimits?.sampledAt??"")||Date.now();a.rate_limits={...o.fiveHour?{fiveHour:o.fiveHour}:{},...o.sevenDay?{sevenDay:o.sevenDay}:{},sampledAt:c}}const d=n?this.lastContextBySession.get(n):void 0;return d&&(a.context_window={usedPercentage:d.usedPercentage,remainingPercentage:d.remainingPercentage,usedTokens:d.usedTokens,sizeTokens:d.sizeTokens}),a}notifyBindingReady(e){if(!this.callbacks.sendUpdateBindingCard)return;const t=this.config.options??{},n=e||String(t.aibotSessionId??"");if(!n)return;const s=this.sessionRuntime.get(n),i=String(s?.cwd||t.workspace||"");i&&this.callbacks.sendUpdateBindingCard(n,this.activeBySession.has(n)?"busy":"ready",i,this.getToolbarMeta(n))}buildPromptText(e){let t;!e.contextMessages||e.contextMessages.length===0?t=e.text:t=`${e.contextMessages.map(r=>`[${r.senderId}] ${r.content}`).join(`
6
+ `)}class K extends q{adapterSessionId;constructor(e){super(),this.adapterSessionId=e}emitDone(e){this.emit("done",e)}emitError(e){if(this.listenerCount("error")===0){u.warn("cursor-adapter",`Prompt handle error (no listeners): ${e.message}`);return}this.emit("error",e)}async cancel(){}}class B extends q{type="cursor";static STDERR_MAX_CHARS=8192;config;callbacks;alive=!1;stopped=!1;permissionHandler=null;internalApi=null;bindingStore=null;identity;inboundQueue=[];pendingBySession=new Map;sessions=new Set;sessionRuntime=new Map;sessionSeq=0;lastUsageBySession=new Map;lastContextBySession=new Map;pendingHandoffBySession=new Map;pendingQuestionByRequestId=new Map;pendingQuestionAnswerBySession=new Map;cachedAccountRateLimits=null;rateLimitsInflight=null;_availableModels=[];transcriptPathBySession=new Map;activeBySession=new Map;constructor(e,t){super(),this.config=e,this.callbacks=t;const n=e.options??{};this.bindingStore=n.bindingStore instanceof oe?n.bindingStore:null,this.identity=new se("cursor-adapter",t.getAgentProfile)}async start(){this.alive=!0,this.stopped=!1,(this.config.options??{}).mcp_tools!==!1&&(this.internalApi=new ie,this.internalApi.setInvokeHandler(async(t,n,s,i)=>this.callbacks.agentInvoke(t,n,i)),await this.internalApi.start()),this.notifyBindingReady(),this.refreshModels().then(()=>this.notifyBindingReady()).catch(()=>{}),this.refreshAccountRateLimits().then(()=>this.notifyBindingReady()).catch(()=>{})}get availableModels(){return this._availableModels}get availableModes(){return J.map(e=>({...e}))}async refreshModels(){try{const e={...process.env,...this.config.env??{}},t=await ae({command:this.config.command,env:e,cwd:process.cwd()});t.length>0&&(this._availableModels=t,u.info("cursor-adapter",`Loaded ${t.length} available models`))}catch(e){u.warn("cursor-adapter",`Failed to refresh models: ${e instanceof Error?e.message:String(e)}`)}}async stop(){this.stopped=!0,this.alive=!1;for(const e of this.activeBySession.values())I(e.child,"SIGTERM");this.activeBySession.clear(),this.transcriptPathBySession.clear(),this.internalApi&&(await this.internalApi.stop(),this.internalApi=null)}isAlive(){return this.alive}async createSession(e){const t=`cursor-${Date.now()}-${++this.sessionSeq}`;return this.sessions.add(t),this.sessionRuntime.set(t,{cwd:typeof e.cwd=="string"?e.cwd:void 0,modelId:typeof e.modelId=="string"?e.modelId:void 0,modeId:typeof e.modeId=="string"?e.modeId:void 0}),t}async resumeSession(e,t){this.sessions.add(e);const n=this.sessionRuntime.get(e)??{};this.sessionRuntime.set(e,{...n,cwd:typeof t?.cwd=="string"?t.cwd:n.cwd,modelId:typeof t?.modelId=="string"?t.modelId:n.modelId,modeId:typeof t?.modeId=="string"?t.modeId:n.modeId})}onAgentProfileChanged(){this.identity.onProfileChanged()}async destroySession(e){this.sessions.delete(e),this.sessionRuntime.delete(e),this.transcriptPathBySession.delete(e),this.identity.forgetSession(e),this.pendingQuestionAnswerBySession.delete(e),this.pendingHandoffBySession.delete(e);for(const[t,n]of this.pendingQuestionByRequestId)n.sessionId===e&&this.pendingQuestionByRequestId.delete(t)}sendPrompt(e){const t=new K(e.adapterSessionId);if(!this.alive||this.stopped)return queueMicrotask(()=>t.emitError(new Error("adapter not running"))),t;const n=this.inboundQueue.shift()??{event_id:`cursor-evt-${Date.now()}`,session_id:e.adapterSessionId,content:e.text},s=e.adapterSessionId,i=this.pendingBySession.get(s)??[];return i.push({event:n,request:e,handle:t}),this.pendingBySession.set(s,i),this.tryStartNext(s),t}tryStartNext(e){if(this.activeBySession.has(e)||!this.alive||this.stopped)return;const t=this.pendingBySession.get(e);if(!t||t.length===0)return;const n=t.shift();t.length===0?this.pendingBySession.delete(e):this.pendingBySession.set(e,t),n&&this.startJob(n,!1,0)}startJob(e,t,n){const{event:s,request:i,handle:r}=e,a=i.adapterSessionId,o=this.sessionRuntime.get(i.adapterSessionId)??{},d=this.config.options??{},c=[...this.config.args??[]];c.push("-p","--output-format","stream-json"),d.trust!==!1&&c.push("--trust");const m=o.cwd||d.workspace;this.internalApi&&m&&(this.ensureWorkspaceMcpAndSkills(m),c.push("--approve-mcps")),m&&c.push("--workspace",m);const y=o.modelId||d.model;y&&c.push("--model",y);const v=R(o.modeId||d.mode)??"full_auto";v==="full_auto"?c.push("--yolo"):v==="plan"&&c.push("--plan");const _=o.cursorSessionId??this.bindingStore?.getAcpSessionId(i.adapterSessionId),b=!!(_&&d.use_continue!==!1&&!t);b&&c.push("--resume",_),c.push(this.buildPromptText(i));const h={...process.env,...this.config.env??{}},S=C(this.config.command,typeof h.PATH=="string"?h.PATH:void 0);u.info("cursor-adapter",`job start: event=${s.event_id} session=${s.session_id} retry=${n}`);let p;try{p=T(S,c,{cwd:m||process.cwd(),env:h,stdio:["ignore","pipe","pipe"]}).process}catch(g){this.finishActive(a,"failed",`spawn failed: ${g instanceof Error?g.message:String(g)}`);return}this.callbacks.sendEventAck(s.event_id,s.session_id),this.emit("eventStarted",s.event_id,s.session_id);const f={event:s,request:i,handle:r,child:p,seq:0,done:!1,stderr:"",timer:null,idleTimer:null,toolCallsInFlight:new Set,toolCallsStarted:0,assistantTextEmitted:"",streamBuffer:"",lastIoAt:Date.now(),sawResult:!1,retryCount:n,usedContinue:b,workspace:m||process.cwd(),args:c};this.activeBySession.set(a,f),this.notifyBindingReady(a),this.armActiveIdleTimer(a);const k=i.timeoutMs&&i.timeoutMs>0?i.timeoutMs:0;k>0&&(f.timer=setTimeout(()=>{f.done||(I(p,"SIGTERM"),this.finishActive(a,"failed",`cursor agent timeout after ${k}ms`))},k)),$({input:p.stdout}).on("line",g=>this.handleStdoutLineForActive(f,g)),p.stderr?.on("data",g=>{if(f.done)return;const w=f.stderr+String(g??"");f.stderr=w.length>B.STDERR_MAX_CHARS?w.slice(-B.STDERR_MAX_CHARS):w,f.lastIoAt=Date.now(),this.armActiveIdleTimer(a)}),p.once("error",g=>{this.finishActive(a,"failed",`spawn failed: ${String(g?.message??g)}`)}),p.once("close",g=>{if(!f.done)if((g??0)===0)this.finishActive(a,"responded");else{if(this.shouldRetryWithoutContinue(f)){f.timer&&clearTimeout(f.timer),f.idleTimer&&clearTimeout(f.idleTimer),this.activeBySession.delete(a),this.startJob(e,!0,f.retryCount+1);return}const w=f.stderr.trim()||`cursor agent exited with code ${g??-1}`;this.finishActive(a,"failed",w)}})}async cancel(e){const t=this.activeBySession.get(e);t&&(I(t.child,"SIGTERM"),this.finishActive(e,"canceled","canceled"))}deliverInboundEvent(e){const t=String(e.session_id??"").trim();if(!t){u.warn("cursor-adapter",`Dropping event ${e.event_id}: missing session_id`),this.callbacks.sendEventResult(e.event_id,"failed","missing session_id");return}if(!this.alive||this.stopped){u.warn("cursor-adapter",`Dropping event ${e.event_id}: adapter not running`),this.callbacks.sendEventAck(e.event_id,t),this.callbacks.sendEventResult(e.event_id,"failed","adapter not running");return}const n={adapterSessionId:t,text:be(String(e.content??""),e),contextMessages:Ae(e)},s=new K(t),i=this.pendingBySession.get(t)??[];i.push({event:e,request:n,handle:s}),this.pendingBySession.set(t,i),u.info("cursor-adapter",`inbound queued: event=${e.event_id} session=${t} depth=${i.length}`),this.tryStartNext(t)}cancelSessionWork(e,t){const n=this.pendingBySession.get(e)??[];this.pendingBySession.delete(e);const s=this.activeBySession.get(e);s&&(this.killChildWithFallback(s.child),this.finishActive(e,"canceled",t));for(const i of n)this.callbacks.sendEventAck(i.event.event_id,i.event.session_id),this.callbacks.sendEventResult(i.event.event_id,"canceled",t),i.handle.emitDone({status:"canceled",error:t})}killChildWithFallback(e){I(e,"SIGTERM"),setTimeout(()=>{if(e.exitCode===null&&e.signalCode===null)try{I(e,"SIGKILL")}catch{}},5e3).unref()}deliverStopEvent(e,t){if(t){const n=this.activeBySession.get(t);if(n?.event.event_id===e){this.killChildWithFallback(n.child),this.finishActive(t,"canceled","stopped");return}const s=this.pendingBySession.get(t)??[],i=s.findIndex(r=>r.event.event_id===e);if(i>=0){const[r]=s.splice(i,1);s.length===0?this.pendingBySession.delete(t):this.pendingBySession.set(t,s),this.callbacks.sendEventAck(r.event.event_id,r.event.session_id),this.callbacks.sendEventResult(r.event.event_id,"canceled","stopped"),r.handle.emitDone({status:"canceled",error:"stopped"})}return}for(const[n,s]of this.activeBySession.entries())if(s.event.event_id===e){this.killChildWithFallback(s.child),this.finishActive(n,"canceled","stopped");return}for(const[n,s]of this.pendingBySession.entries()){const i=s.findIndex(a=>a.event.event_id===e);if(i<0)continue;const[r]=s.splice(i,1);s.length===0?this.pendingBySession.delete(n):this.pendingBySession.set(n,s),this.callbacks.sendEventAck(r.event.event_id,r.event.session_id),this.callbacks.sendEventResult(r.event.event_id,"canceled","stopped"),r.handle.emitDone({status:"canceled",error:"stopped"});return}}async handleLocalAction(e){const t=String(e.action_type??"").trim().toLowerCase(),n=e.params??{},s=String(n.session_id??"").trim(),i=e.action_id;if(t==="claude_interaction_reply")return String(n.kind??"").trim()==="permission"?(this.callbacks.sendLocalActionResult(i,"failed",void 0,"unsupported","Cursor adapter does not support interactive permission approvals"),{handled:!0,kind:"permission_unsupported"}):this.handleQuestionReply(e);switch(t){case"set_model":{const r=String(n.model_id??"").trim();if(!r||!s)return this.callbacks.sendLocalActionResult(i,"failed",void 0,"invalid_params","model_id and session_id are required"),{handled:!0,kind:"set_model"};const a=this.sessionRuntime.get(s)??{};this.sessionRuntime.set(s,{...a,modelId:r}),this.notifyBindingReady(s);const o=this.getToolbarMeta(s);return this.callbacks.sendLocalActionResult(i,"ok",{outcome:"model_set",session_context:{model_id:r,mode_id:o.mode_id,modelId:r,modeId:o.mode_id},model_id:r,mode_id:o.mode_id,available_models:this.availableModels,available_modes:this.availableModes}),{handled:!0,kind:"set_model"}}case"set_mode":{const r=R(n.mode_id);if(!r||!s)return this.callbacks.sendLocalActionResult(i,"failed",void 0,"invalid_params","mode_id must be approval, full_auto, or plan and session_id is required"),{handled:!0,kind:"set_mode"};const a=this.sessionRuntime.get(s)??{};this.sessionRuntime.set(s,{...a,modeId:r}),this.notifyBindingReady(s);const o=this.getToolbarMeta(s);return this.callbacks.sendLocalActionResult(i,"ok",{outcome:"mode_set",session_context:{model_id:o.model_id,mode_id:r,modelId:o.model_id,modeId:r},model_id:o.model_id,mode_id:r,available_models:this.availableModels,available_modes:this.availableModes}),{handled:!0,kind:"set_mode"}}case"get_context":{const r=s?this.sessionRuntime.get(s):void 0,a=this.config.options??{},o=this.getToolbarMeta(s);return this.callbacks.sendLocalActionResult(i,"ok",{session_context:{model_id:o.model_id,mode_id:o.mode_id,cwd:r?.cwd??a.workspace??null},model_id:o.model_id,mode_id:o.mode_id,modelId:o.model_id,modeId:o.mode_id,cwd:r?.cwd??a.workspace??null,available_models:this.availableModels,available_modes:this.availableModes}),{handled:!0,kind:"get_context"}}case"get_rate_limits":{await this.refreshAccountRateLimits({force:!0});const r=this.getRateLimitsSnapshot(s||void 0);return this.callbacks.sendLocalActionResult(i,"ok",r),s&&this.notifyBindingReady(s),{handled:!0,kind:"get_rate_limits"}}case"get_session_usage":{const r=s?this.getUsageSnapshot(s):null;if(r){const a={inputTokens:r.total.input,outputTokens:r.total.output,cacheReadInputTokens:r.total.cacheRead,cacheCreationInputTokens:r.total.cacheWrite};this.callbacks.sendLocalActionResult(i,"ok",{sessionId:s,adapterType:"cursor",sampledAt:r.sampledAt,turns:r.turns,total:a,models:[{model:this.getToolbarMeta(s).model_id??"auto",turns:r.turns,total:a}]})}else this.callbacks.sendLocalActionResult(i,"failed",void 0,"usage_not_found","No usage data found for this session");return{handled:!0,kind:"get_session_usage"}}case"session_control":{const r=String(n.verb??"").trim().toLowerCase();if(r==="restart"&&s)this.cancelSessionWork(s,"restarted"),this.callbacks.sendLocalActionResult(i,"ok",{outcome:"restarted"});else if(r==="status"){const a=this.sessionRuntime.get(s),o=this.config.options??{},d=this.getToolbarMeta(s),c=this.activeBySession.has(s);this.callbacks.sendLocalActionResult(i,"ok",{verb:"status",status:c?"running":"idle",session_context:{model_id:d.model_id,mode_id:d.mode_id,cwd:a?.cwd??o.workspace??null},model_id:d.model_id,mode_id:d.mode_id,modelId:d.model_id,modeId:d.mode_id,cwd:a?.cwd??o.workspace??null,available_models:this.availableModels,available_modes:this.availableModes})}else this.callbacks.sendLocalActionResult(i,"failed",void 0,"invalid_verb",`Unsupported verb: ${r}`);return{handled:!0,kind:"session_control"}}default:return{handled:!1,kind:"unsupported"}}}async execCommand(e,t,n){return e!=="compact"?{status:"unsupported",message:`Unsupported command: ${e}`}:this.runCompactHandoff(n)}async runCompactHandoff(e){if(!e)return{status:"failed",message:"session_id is required"};if(this.activeBySession.has(e))return{status:"failed",message:"Cannot compact while a Cursor turn is active"};const t=this.sessionRuntime.get(e)??{},n=this.config.options??{},s=t.cursorSessionId??this.bindingStore?.getAcpSessionId(e)??null;if(!s)return{status:"failed",message:"No Cursor chat to compact (send a message first)"};const i=String(t.cwd||n.workspace||process.cwd()),r=String(t.modelId||n.model||""),a=R(t.modeId||n.mode)??"full_auto";u.info("cursor-adapter",`[compact] summarize resume=${s} session=${e}`);const o=await this.runPrintOnce({prompt:fe,resumeChatId:s,cwd:i,modelId:r||void 0,modeId:a,timeoutMs:Q});if(!o.ok||!o.text.trim())return{status:"failed",message:o.error??"Summarize failed (empty result)"};const d=await this.createEmptyChat();return d?(this.sessionRuntime.set(e,{...t,cwd:i,modelId:r||t.modelId,modeId:a,cursorSessionId:d}),this.transcriptPathBySession.delete(e),this.bindingStore?.setAcpSessionId(e,d),this.pendingHandoffBySession.set(e,o.text.trim()),this.lastContextBySession.set(e,de(0,ue(r))),this.notifyBindingReady(e),u.info("cursor-adapter",`[compact] handoff ready: old=${s} new=${d} summaryChars=${o.text.trim().length}`),{status:"ok",message:"Context compacted via summarize + new chat",data:{previousChatId:s,chatId:d}}):{status:"failed",message:"agent create-chat failed"}}runPrintOnce(e){const t={...process.env,...this.config.env??{}},n=C(this.config.command,typeof t.PATH=="string"?t.PATH:void 0),s=[...this.config.args??[]];s.push("-p","--output-format","stream-json"),this.config.options?.trust!==!1&&s.push("--trust"),e.cwd&&s.push("--workspace",e.cwd),e.modelId&&s.push("--model",e.modelId);const i=R(e.modeId)??"full_auto";return i==="full_auto"?s.push("--yolo"):i==="plan"&&s.push("--plan"),e.resumeChatId&&s.push("--resume",e.resumeChatId),s.push(e.prompt),new Promise(r=>{let a;try{a=T(n,s,{cwd:e.cwd||process.cwd(),env:t,stdio:["ignore","pipe","pipe"]}).process}catch(h){r({ok:!1,text:"",error:`spawn failed: ${h instanceof Error?h.message:String(h)}`});return}let o="",d="",c="",m=!1;const y=e.timeoutMs&&e.timeoutMs>0?e.timeoutMs:Q,v=setTimeout(()=>{m||(m=!0,this.killChildWithFallback(a),r({ok:!1,text:o,sessionId:d||void 0,error:`compact timeout after ${y}ms`}))},y);v.unref?.();const _=(h,S)=>{m||(m=!0,clearTimeout(v),r({ok:h,text:o,sessionId:d||void 0,error:S}))};$({input:a.stdout}).on("line",h=>{const S=h.trim();if(!S)return;let p;try{p=JSON.parse(S)}catch{return}if(p?.type!=="result")return;const f=typeof p?.result=="string"?p.result:"";f&&(o=f);const k=String(p?.session_id??"").trim();k&&(d=k)}),a.stderr?.on("data",h=>{c+=typeof h=="string"?h:h.toString("utf8")}),a.on("error",h=>_(!1,h.message)),a.on("close",h=>{if(!m){if(o.trim()){_(!0);return}_(!1,c.trim()||`cursor agent exited with code ${h??"unknown"}`)}})})}createEmptyChat(){const e={...process.env,...this.config.env??{}},t=C(this.config.command,typeof e.PATH=="string"?e.PATH:void 0);return new Promise(n=>{let s;try{s=T(t,["create-chat"],{cwd:process.cwd(),env:e,stdio:["ignore","pipe","pipe"]}).process}catch{n(null);return}let i="",r=!1;const a=setTimeout(()=>{r||(r=!0,this.killChildWithFallback(s),n(null))},3e4);a.unref?.(),s.stdout?.on("data",o=>{i+=typeof o=="string"?o:o.toString("utf8")}),s.on("error",()=>{r||(r=!0,clearTimeout(a),n(null))}),s.on("close",o=>{if(r)return;r=!0,clearTimeout(a);const d=i.trim().split(/\s+/)[0]??"";n(o===0&&/^[0-9a-f-]{16,}$/i.test(d)?d:null)})})}setPermissionHandler(e){this.permissionHandler=e}async ping(e){if(!this.alive||this.stopped||(this.config.options??{}).mcp_tools!==!1&&this.internalApi===null)return!1;for(const n of this.activeBySession.values()){const s=n.child?.pid;if(s&&!M(s))return!1}return!0}getStatus(){let e=0;for(const t of this.pendingBySession.values())e+=t.length;return{alive:this.alive,busy:this.activeBySession.size>0,sessions:this.sessions.size,details:{queueDepth:e+this.inboundQueue.length,activeSessions:this.activeBySession.size}}}getActiveEventIds(){const e=[];for(const t of this.activeBySession.values())t.event.event_id&&e.push(t.event.event_id);return e}clearActiveEventForShutdown(){this.activeBySession.clear()}getMcpConfig(){return null}async probe(e){const t=this.getStatus();return{...await ne(this.config.command||"cursor-agent",{alive:t.alive,busy:t.busy,started:this.alive},e),session:this.probeSessionRecord()}}probeSessionRecord(){const e=this.firstKnownCursorSessionId(),t=e?X(e):null;if(!t)return{recordPath:null,lastActivityMs:null,freshMs:null};try{const n=D(t);return{recordPath:t,lastActivityMs:n.mtimeMs,freshMs:Date.now()-n.mtimeMs}}catch{return{recordPath:t,lastActivityMs:null,freshMs:null}}}firstKnownCursorSessionId(){for(const e of this.activeBySession.keys()){const t=this.sessionRuntime.get(e);if(t?.cursorSessionId)return t.cursorSessionId}for(const e of this.sessionRuntime.values())if(e.cursorSessionId)return e.cursorSessionId;return null}getUsageSnapshot(e){return this.lastUsageBySession.get(e)??null}getRateLimitsSnapshot(e){const t=this.cachedAccountRateLimits,n=e?this.lastContextBySession.get(e)??null:null;if(!t)return{adapterType:"cursor",available:!1,cached:!1,sampledAt:null,rateLimits:null,contextWindow:n,tokenUsage:null};const s=Date.parse(t.sampledAt);return{adapterType:"cursor",available:t.available&&!!t.rateLimits,cached:!0,sampledAt:Number.isFinite(s)?s:Date.now(),rateLimits:t.rateLimits,contextWindow:n,tokenUsage:null,...t.displayMessage?{displayMessage:t.displayMessage}:{},...t.error?{error:t.error}:{}}}async refreshAccountRateLimits(e){const t=e?.force===!0,n=this.cachedAccountRateLimits,s=n?Date.now()-Date.parse(n.sampledAt):Number.POSITIVE_INFINITY;if(!t&&n&&Number.isFinite(s)&&s<6e4||this.rateLimitsInflight&&(await this.rateLimitsInflight,!t))return;const i=(async()=>{const r=await le();this.cachedAccountRateLimits={available:r.available,sampledAt:r.sampledAt,rateLimits:r.rateLimits,displayMessage:r.displayMessage,error:r.error},r.available&&r.rateLimits?u.info("cursor-adapter",`[rate-limits] monthly=${r.rateLimits.fiveHour.usedPercentage.toFixed(1)}% api=${r.rateLimits.sevenDay.usedPercentage.toFixed(1)}% resetsAt=${r.rateLimits.fiveHour.resetsAt}`+(r.displayMessage?` msg=${r.displayMessage}`:"")):u.warn("cursor-adapter",`[rate-limits] unavailable: ${r.error??"unknown"}`)})();this.rateLimitsInflight=i.finally(()=>{this.rateLimitsInflight=null}),await this.rateLimitsInflight}getToolbarMeta(e){const t=this.config.options??{},n=e||String(t.aibotSessionId??""),s=n?this.sessionRuntime.get(n):void 0,i=String(s?.modelId||t.model||"auto"),r=R(s?.modeId||t.mode)??"full_auto",a={model_id:i,mode_id:r,currentModelId:i,currentModeId:r,available_models:this.availableModels,available_modes:this.availableModes},o=this.cachedAccountRateLimits?.rateLimits;if(o?.fiveHour||o?.sevenDay){const c=Date.parse(this.cachedAccountRateLimits?.sampledAt??"")||Date.now();a.rate_limits={...o.fiveHour?{fiveHour:o.fiveHour}:{},...o.sevenDay?{sevenDay:o.sevenDay}:{},sampledAt:c}}const d=n?this.lastContextBySession.get(n):void 0;return d&&(a.context_window={usedPercentage:d.usedPercentage,remainingPercentage:d.remainingPercentage,usedTokens:d.usedTokens,sizeTokens:d.sizeTokens}),a}notifyBindingReady(e){if(!this.callbacks.sendUpdateBindingCard)return;const t=this.config.options??{},n=e||String(t.aibotSessionId??"");if(!n)return;const s=this.sessionRuntime.get(n),i=String(s?.cwd||t.workspace||"");i&&this.callbacks.sendUpdateBindingCard(n,this.activeBySession.has(n)?"busy":"ready",i,this.getToolbarMeta(n))}buildPromptText(e){let t;!e.contextMessages||e.contextMessages.length===0?t=e.text:t=`${e.contextMessages.map(r=>`[${r.senderId}] ${r.content}`).join(`
7
7
  `)}
8
8
 
9
9
  [Current user message]
@@ -15,6 +15,6 @@ ${t}`);const s=this.pendingHandoffBySession.get(e.adapterSessionId);return s&&(t
15
15
  ${s}
16
16
 
17
17
  [Current user message]
18
- ${t}`),this.identity.injectOnce(e.adapterSessionId,t)}handleStdoutLineForActive(e,t){if(e.done)return;const n=t.trim();if(!n)return;const{event:s}=e;let i;try{i=JSON.parse(n)}catch{this.armActiveIdleTimer(e.request.adapterSessionId);return}if(i?.type==="tool_call"){const o=String(i?.call_id??""),d=String(i?.subtype??"");o&&(d==="started"||d==="start"?(this.flushAssistantStreamBuffer(e),e.toolCallsInFlight.add(o),e.toolCallsStarted+=1,this.handleSpecialToolCallStarted(e,i)):N.has(d)&&e.toolCallsInFlight.delete(o))}this.armActiveIdleTimer(e.request.adapterSessionId);const r=ge(i);r&&this.callbacks.sendRawEventEnvelope?.(s.event_id,s.session_id,r);const a=ye(i,e.assistantTextEmitted);if(a&&this.enqueueAssistantStreamDelta(e,a),i?.type==="result"){const o=typeof i?.result=="string"?i.result.trim():"";o&&(e.assistantTextEmitted?o.startsWith(e.assistantTextEmitted)&&o.length>e.assistantTextEmitted.length&&this.enqueueAssistantStreamDelta(e,o.slice(e.assistantTextEmitted.length)):this.enqueueAssistantStreamDelta(e,o)),this.flushAssistantStreamBuffer(e);const d=String(i?.session_id??"").trim();if(d){const p=this.sessionRuntime.get(e.request.adapterSessionId)??{};this.sessionRuntime.set(e.request.adapterSessionId,{...p,cursorSessionId:d}),this.bindingStore?.setAcpSessionId(e.request.adapterSessionId,d)}const c=i?.usage??{},h=this.lastUsageBySession.get(e.request.adapterSessionId);this.lastUsageBySession.set(e.request.adapterSessionId,{sampledAt:new Date().toISOString(),turns:(h?.turns??0)+1,total:{input:(h?.total.input??0)+Number(c.inputTokens??0),output:(h?.total.output??0)+Number(c.outputTokens??0),cacheRead:(h?.total.cacheRead??0)+Number(c.cacheReadTokens??0),cacheWrite:(h?.total.cacheWrite??0)+Number(c.cacheWriteTokens??0)}});const S=String((this.sessionRuntime.get(e.request.adapterSessionId)?.modelId??this.config.options?.model??"")||""),v=Math.max(1,(e.toolCallsStarted??0)+1),_=Number(c.inputTokens??0),A=Number(c.cacheReadTokens??0),m=Number(c.cacheWriteTokens??0),y=oe({inputTokens:_,cacheReadTokens:A,cacheWriteTokens:m},S,{apiRounds:v});y&&(this.lastContextBySession.set(e.request.adapterSessionId,y),u.info("cursor-adapter",`[context_window] used=${y.usedTokens} size=${y.sizeTokens} pct=${y.usedPercentage.toFixed(1)}% model=${S||"auto"} rawIn=${_} rawCache=${A} rawWrite=${m} apiRounds=${v}`))}}handleSpecialToolCallStarted(e,t){const n=O(t);if(Q(n.toolName)){const s=pe(n.toolInput);s?(e.seq+=1,this.callbacks.sendStreamChunk(e.event.event_id,e.event.session_id,s,e.seq,!1),u.info("cursor-adapter",`CreatePlan forwarded as stream text: event=${e.event.event_id} chars=${s.length}`)):(e.seq+=1,this.callbacks.sendStreamChunk(e.event.event_id,e.event.session_id,"[CreatePlan] \u65B9\u6848\u5185\u5BB9\u89E3\u6790\u5931\u8D25\uFF0C\u672A\u4E0B\u53D1\u6B63\u6587\u3002",e.seq,!1),u.warn("cursor-adapter",`CreatePlan missing plan body: event=${e.event.event_id}`));return}U(n.toolName)&&this.emitAskQuestionCard(e,n.toolInput)}emitAskQuestionCard(e,t){const n=e.event,s=fe(t);if(!s){e.seq+=1,this.callbacks.sendStreamChunk(n.event_id,n.session_id,"[AskQuestion] \u95EE\u9898\u5185\u5BB9\u89E3\u6790\u5931\u8D25\uFF0C\u672A\u4E0B\u53D1\u95EE\u9898\u5361\u3002",e.seq,!1),u.warn("cursor-adapter",`AskQuestion parse failed: event=${n.event_id}`);return}if(!this.callbacks.sendAgentQuestionCard){u.warn("cursor-adapter","AskQuestion skipped: sendAgentQuestionCard callback not wired");return}const i=z(),r=Date.now();this.pendingQuestionByRequestId.set(i,{eventId:n.event_id,sessionId:n.session_id,createdAt:r}),this.callbacks.sendAgentQuestionCard(n.event_id,n.session_id,{request_id:i,mode:"form",questions:s,expires_at:r+P}),u.info("cursor-adapter",`Sent agent_question card: request_id=${i} questions=${s.length} event=${n.event_id}`)}async handleQuestionReply(e){const t=e.params??{},n=String(t.request_id??"").trim();if(!n)return this.callbacks.sendLocalActionResult(e.action_id,"failed",void 0,"request_id_required","request_id is required"),{handled:!0,kind:"question_reply_no_id"};const s=this.pendingQuestionByRequestId.get(n),i=s?Date.now()-s.createdAt>P+3e4:!0;if(!s||i)return this.pendingQuestionByRequestId.delete(n),this.callbacks.sendLocalActionResult(e.action_id,"failed",void 0,"interaction_request_not_pending","The question is no longer pending; the reply was not delivered."),{handled:!0,kind:"question_reply_not_pending"};const r=t.resolution??{},a=String(r.type??"");if(a==="action"&&String(r.value??"")==="cancel")return this.pendingQuestionByRequestId.delete(n),this.callbacks.sendLocalActionResult(e.action_id,"ok",{request_id:n,resolution:"cancel"}),{handled:!0,kind:"question_reply_cancel"};let o="";return a==="text"?o=String(r.value??""):a==="map"&&(o=(Array.isArray(r.entries)?r.entries:[]).map(c=>c.value).join(", ")),o?(this.pendingQuestionByRequestId.delete(n),this.pendingQuestionAnswerBySession.set(s.sessionId,o),this.callbacks.sendLocalActionResult(e.action_id,"ok",{request_id:n,resolution:"answer"}),u.info("cursor-adapter",`Question reply cached for next prompt: request_id=${n} session=${s.sessionId}`),{handled:!0,kind:"question_reply"}):(this.pendingQuestionByRequestId.delete(n),this.callbacks.sendLocalActionResult(e.action_id,"ok",{request_id:n,resolution:"cancel"}),{handled:!0,kind:"question_reply_empty"})}enqueueAssistantStreamDelta(e,t){t&&(e.assistantTextEmitted+=t,e.streamBuffer+=t,e.streamBuffer.length>=ce&&this.flushAssistantStreamBuffer(e))}flushAssistantStreamBuffer(e){if(!e.streamBuffer)return;const t=e.streamBuffer;e.streamBuffer="",e.seq+=1,this.callbacks.sendStreamChunk(e.event.event_id,e.event.session_id,t,e.seq,!1,void 0,void 0)}armActiveIdleTimer(e){const t=this.activeBySession.get(e);if(!t||t.done)return;t.idleTimer&&clearTimeout(t.idleTimer);const n=t.toolCallsInFlight.size>0?de:le;t.idleTimer=setTimeout(()=>{t.done||(u.error("cursor-adapter",`Idle timeout (${n/1e3}s, toolsInFlight=${t.toolCallsInFlight.size}) \u2014 killing cursor child: event=${t.event.event_id} session=${e}`),this.killChildWithFallback(t.child),this.finishActive(e,"failed",`idle timeout after ${n/1e3}s`))},n),t.idleTimer.unref?.()}finishActive(e,t,n){const s=this.activeBySession.get(e);if(!s)return;s.done=!0,s.timer&&clearTimeout(s.timer),s.idleTimer&&clearTimeout(s.idleTimer),this.flushAssistantStreamBuffer(s),s.seq+=1;const i=String(s.event.quoted_message_id??"").trim()||void 0;this.callbacks.sendStreamChunk(s.event.event_id,s.event.session_id,"",s.seq,!0,void 0,i),u.info("cursor-adapter",`job finish: event=${s.event.event_id} session=${s.event.session_id} status=${t}${n?` msg=${n}`:""}`),this.emit("eventDone",s.event.event_id),this.callbacks.sendEventResult(s.event.event_id,t,n),t==="responded"?s.handle.emitDone({status:"completed"}):t==="canceled"?s.handle.emitDone({status:"canceled",error:n}):s.handle.emitDone({status:"failed",error:n}),this.activeBySession.delete(e),this.notifyBindingReady(e),this.tryStartNext(e)}shouldRetryWithoutContinue(e){if(!e.usedContinue||e.retryCount>0)return!1;const t=e.stderr.toLowerCase(),n=t.includes("resume")||t.includes("continue")||t.includes("chat")||t.includes("session"),s=t.includes("not found")||t.includes("no previous session")||t.includes("does not exist")||t.includes("invalid");return n&&s}ensureWorkspaceMcpAndSkills(e){if(!this.internalApi)return;this.ensureCursorMcpConfig(e,this.internalApi.url);const t=b(E(),".cursor","skills"),n=te(t);n.length>0&&u.info("cursor-adapter",`Synced connector skills to ${t}: [${n.join(", ")}]`)}ensureCursorMcpConfig(e,t){try{const n=b(e,".cursor"),s=b(n,"mcp.json");$(n,{recursive:!0});let i={};try{i=JSON.parse(q(s,"utf8"))}catch{i={}}const r={command:process.execPath,args:[X(K(import.meta.url),"../../../mcp/acp-mcp-server.js"),"--api-url",t]};(!i.mcpServers||typeof i.mcpServers!="object")&&(i.mcpServers={}),i.mcpServers.grix=r,L(s,`${JSON.stringify(i,null,2)}
19
- `,"utf8"),this.recordCursorMcpRegistry(e,s,r),u.info("cursor-adapter",`MCP config synced: workspace=${e} file=${s}`)}catch(n){u.warn("cursor-adapter",`Failed to ensure .cursor/mcp.json: ${String(n)}`)}}recordCursorMcpRegistry(e,t,n){try{const s=b(Y.data,"cursor"),i=b(s,"mcp-registry.json");$(s,{recursive:!0});let r={};try{r=JSON.parse(q(i,"utf8"))}catch{r={}}r[e]={mcp_path:t,server:n,updated_at:new Date().toISOString()},L(i,`${JSON.stringify(r,null,2)}
20
- `,"utf8")}catch(s){u.warn("cursor-adapter",`Failed to record MCP registry: ${String(s)}`)}}}export{F as CURSOR_MODE_OPTIONS,M as CursorAdapter,ye as extractCursorAssistantStreamDelta,_e as findCursorTranscript,pe as formatCursorCreatePlanText,U as isCursorAskQuestionTool,Q as isCursorCreatePlanTool,ge as mapCursorStreamJsonToRawEnvelope,fe as parseCursorAskQuestionPayload};
18
+ ${t}`),this.identity.injectOnce(e.adapterSessionId,t)}handleStdoutLineForActive(e,t){if(e.done)return;const n=t.trim();if(!n)return;e.lastIoAt=Date.now();const{event:s}=e;let i;try{i=JSON.parse(n)}catch{this.armActiveIdleTimer(e.request.adapterSessionId);return}if(i?.type==="tool_call"){const o=String(i?.call_id??""),d=String(i?.subtype??"");o&&(d==="started"||d==="start"?(this.flushAssistantStreamBuffer(e),e.toolCallsInFlight.add(o),e.toolCallsStarted+=1,this.handleSpecialToolCallStarted(e,i)):O.has(d)&&e.toolCallsInFlight.delete(o))}this.armActiveIdleTimer(e.request.adapterSessionId);const r=_e(i);r&&this.callbacks.sendRawEventEnvelope?.(s.event_id,s.session_id,r);const a=ve(i,e.assistantTextEmitted);if(a&&this.enqueueAssistantStreamDelta(e,a),i?.type==="result"){e.sawResult=!0;const o=typeof i?.result=="string"?i.result.trim():"";o&&(e.assistantTextEmitted?o.startsWith(e.assistantTextEmitted)&&o.length>e.assistantTextEmitted.length&&this.enqueueAssistantStreamDelta(e,o.slice(e.assistantTextEmitted.length)):this.enqueueAssistantStreamDelta(e,o)),this.flushAssistantStreamBuffer(e);const d=String(i?.session_id??"").trim();if(d){const p=this.sessionRuntime.get(e.request.adapterSessionId)??{};p.cursorSessionId!==d&&this.transcriptPathBySession.delete(e.request.adapterSessionId),this.sessionRuntime.set(e.request.adapterSessionId,{...p,cursorSessionId:d}),this.bindingStore?.setAcpSessionId(e.request.adapterSessionId,d)}const c=i?.usage??{},m=this.lastUsageBySession.get(e.request.adapterSessionId);this.lastUsageBySession.set(e.request.adapterSessionId,{sampledAt:new Date().toISOString(),turns:(m?.turns??0)+1,total:{input:(m?.total.input??0)+Number(c.inputTokens??0),output:(m?.total.output??0)+Number(c.outputTokens??0),cacheRead:(m?.total.cacheRead??0)+Number(c.cacheReadTokens??0),cacheWrite:(m?.total.cacheWrite??0)+Number(c.cacheWriteTokens??0)}});const y=String((this.sessionRuntime.get(e.request.adapterSessionId)?.modelId??this.config.options?.model??"")||""),v=Math.max(1,(e.toolCallsStarted??0)+1),_=Number(c.inputTokens??0),b=Number(c.cacheReadTokens??0),h=Number(c.cacheWriteTokens??0),S=ce({inputTokens:_,cacheReadTokens:b,cacheWriteTokens:h},y,{apiRounds:v});S&&(this.lastContextBySession.set(e.request.adapterSessionId,S),u.info("cursor-adapter",`[context_window] used=${S.usedTokens} size=${S.sizeTokens} pct=${S.usedPercentage.toFixed(1)}% model=${y||"auto"} rawIn=${_} rawCache=${b} rawWrite=${h} apiRounds=${v}`))}}handleSpecialToolCallStarted(e,t){const n=U(t);if(z(n.toolName)){const s=ye(n.toolInput);s?(e.seq+=1,this.callbacks.sendStreamChunk(e.event.event_id,e.event.session_id,s,e.seq,!1),u.info("cursor-adapter",`CreatePlan forwarded as stream text: event=${e.event.event_id} chars=${s.length}`)):(e.seq+=1,this.callbacks.sendStreamChunk(e.event.event_id,e.event.session_id,"[CreatePlan] \u65B9\u6848\u5185\u5BB9\u89E3\u6790\u5931\u8D25\uFF0C\u672A\u4E0B\u53D1\u6B63\u6587\u3002",e.seq,!1),u.warn("cursor-adapter",`CreatePlan missing plan body: event=${e.event.event_id}`));return}H(n.toolName)&&this.emitAskQuestionCard(e,n.toolInput)}emitAskQuestionCard(e,t){const n=e.event,s=Se(t);if(!s){e.seq+=1,this.callbacks.sendStreamChunk(n.event_id,n.session_id,"[AskQuestion] \u95EE\u9898\u5185\u5BB9\u89E3\u6790\u5931\u8D25\uFF0C\u672A\u4E0B\u53D1\u95EE\u9898\u5361\u3002",e.seq,!1),u.warn("cursor-adapter",`AskQuestion parse failed: event=${n.event_id}`);return}if(!this.callbacks.sendAgentQuestionCard){u.warn("cursor-adapter","AskQuestion skipped: sendAgentQuestionCard callback not wired");return}const i=V(),r=Date.now();this.pendingQuestionByRequestId.set(i,{eventId:n.event_id,sessionId:n.session_id,createdAt:r}),this.callbacks.sendAgentQuestionCard(n.event_id,n.session_id,{request_id:i,mode:"form",questions:s,expires_at:r+x}),u.info("cursor-adapter",`Sent agent_question card: request_id=${i} questions=${s.length} event=${n.event_id}`)}async handleQuestionReply(e){const t=e.params??{},n=String(t.request_id??"").trim();if(!n)return this.callbacks.sendLocalActionResult(e.action_id,"failed",void 0,"request_id_required","request_id is required"),{handled:!0,kind:"question_reply_no_id"};const s=this.pendingQuestionByRequestId.get(n),i=s?Date.now()-s.createdAt>x+3e4:!0;if(!s||i)return this.pendingQuestionByRequestId.delete(n),this.callbacks.sendLocalActionResult(e.action_id,"failed",void 0,"interaction_request_not_pending","The question is no longer pending; the reply was not delivered."),{handled:!0,kind:"question_reply_not_pending"};const r=t.resolution??{},a=String(r.type??"");if(a==="action"&&String(r.value??"")==="cancel")return this.pendingQuestionByRequestId.delete(n),this.callbacks.sendLocalActionResult(e.action_id,"ok",{request_id:n,resolution:"cancel"}),{handled:!0,kind:"question_reply_cancel"};let o="";return a==="text"?o=String(r.value??""):a==="map"&&(o=(Array.isArray(r.entries)?r.entries:[]).map(c=>c.value).join(", ")),o?(this.pendingQuestionByRequestId.delete(n),this.pendingQuestionAnswerBySession.set(s.sessionId,o),this.callbacks.sendLocalActionResult(e.action_id,"ok",{request_id:n,resolution:"answer"}),u.info("cursor-adapter",`Question reply cached for next prompt: request_id=${n} session=${s.sessionId}`),{handled:!0,kind:"question_reply"}):(this.pendingQuestionByRequestId.delete(n),this.callbacks.sendLocalActionResult(e.action_id,"ok",{request_id:n,resolution:"cancel"}),{handled:!0,kind:"question_reply_empty"})}enqueueAssistantStreamDelta(e,t){t&&(e.assistantTextEmitted+=t,e.streamBuffer+=t,e.streamBuffer.length>=me&&this.flushAssistantStreamBuffer(e))}flushAssistantStreamBuffer(e){if(!e.streamBuffer)return;const t=e.streamBuffer;e.streamBuffer="",e.seq+=1,this.callbacks.sendStreamChunk(e.event.event_id,e.event.session_id,t,e.seq,!1,void 0,void 0)}shouldExtendByLiveness(e){const t=this.activeBySession.get(e);if(!t||t.done)return!1;const n=t.child?.pid;if(!n||!M(n)||t.sawResult)return!1;const s=Date.now(),i=s-t.lastIoAt,r=this.transcriptFreshMsForSession(e);if(i<F||r!==null&&r<F)return t.livenessExtendStartAt=s,u.info("cursor-adapter",`Liveness check: fresh progress for ${t.event.event_id} (ioAge=${i}ms, transcriptAge=${r??"n/a"}, toolsInFlight=${t.toolCallsInFlight.size}), extending`),!0;const o=t.livenessExtendStartAt??s;return t.livenessExtendStartAt===void 0&&(t.livenessExtendStartAt=o),s-o>j?(u.warn("cursor-adapter",`Liveness extension budget exhausted for ${t.event.event_id} (no progress for ${Math.round((s-o)/6e4)}min), allowing idle kill`),!1):(u.info("cursor-adapter",`Liveness check: turn in progress for ${t.event.event_id} (toolsInFlight=${t.toolCallsInFlight.size}, ioAge=${i}ms), extending`),!0)}transcriptFreshMsForSession(e){const t=this.sessionRuntime.get(e)?.cursorSessionId;if(!t)return null;let n=this.transcriptPathBySession.get(e);if(!n){const s=X(t);if(!s)return null;this.transcriptPathBySession.set(e,s),n=s}try{return Date.now()-D(n).mtimeMs}catch{return this.transcriptPathBySession.delete(e),null}}hasPendingQuestionForSession(e){const t=Date.now();for(const[n,s]of this.pendingQuestionByRequestId)if(s.sessionId===e){if(t-s.createdAt>x){this.pendingQuestionByRequestId.delete(n);continue}return!0}return!1}armActiveIdleTimer(e){const t=this.activeBySession.get(e);!t||t.done||(t.idleTimer&&clearTimeout(t.idleTimer),t.idleTimer=setTimeout(()=>{if(t.done)return;if(this.hasPendingQuestionForSession(e)){u.info("cursor-adapter",`Idle timeout skipped: awaiting AskQuestion for ${t.event.event_id}, resetting timer`),this.armActiveIdleTimer(e);return}if(this.shouldExtendByLiveness(e)){this.armActiveIdleTimer(e);return}if(t.sawResult){u.warn("cursor-adapter",`Idle after result with no close \u2014 finishing as responded: event=${t.event.event_id} session=${e}`),this.killChildWithFallback(t.child),this.finishActive(e,"responded");return}const n=t.child?.pid,i=!!(n&&M(n))?`liveness budget exhausted after ${j/6e4}min`:"child process dead";u.error("cursor-adapter",`Idle timeout (${i}, toolsInFlight=${t.toolCallsInFlight.size}) \u2014 killing cursor child: event=${t.event.event_id} session=${e}`),this.killChildWithFallback(t.child),this.finishActive(e,"failed",`idle timeout: ${i}`)},he),t.idleTimer.unref?.())}finishActive(e,t,n){const s=this.activeBySession.get(e);if(!s)return;s.done=!0,s.timer&&clearTimeout(s.timer),s.idleTimer&&clearTimeout(s.idleTimer),this.flushAssistantStreamBuffer(s),s.seq+=1;const i=String(s.event.quoted_message_id??"").trim()||void 0;this.callbacks.sendStreamChunk(s.event.event_id,s.event.session_id,"",s.seq,!0,void 0,i),u.info("cursor-adapter",`job finish: event=${s.event.event_id} session=${s.event.session_id} status=${t}${n?` msg=${n}`:""}`),this.emit("eventDone",s.event.event_id),this.callbacks.sendEventResult(s.event.event_id,t,n),t==="responded"?s.handle.emitDone({status:"completed"}):t==="canceled"?s.handle.emitDone({status:"canceled",error:n}):s.handle.emitDone({status:"failed",error:n}),this.activeBySession.delete(e),this.notifyBindingReady(e),this.tryStartNext(e)}shouldRetryWithoutContinue(e){if(!e.usedContinue||e.retryCount>0)return!1;const t=e.stderr.toLowerCase(),n=t.includes("resume")||t.includes("continue")||t.includes("chat")||t.includes("session"),s=t.includes("not found")||t.includes("no previous session")||t.includes("does not exist")||t.includes("invalid");return n&&s}ensureWorkspaceMcpAndSkills(e){if(!this.internalApi)return;this.ensureCursorMcpConfig(e,this.internalApi.url);const t=A(N(),".cursor","skills"),n=re(t);n.length>0&&u.info("cursor-adapter",`Synced connector skills to ${t}: [${n.join(", ")}]`)}ensureCursorMcpConfig(e,t){try{const n=A(e,".cursor"),s=A(n,"mcp.json");E(n,{recursive:!0});let i={};try{i=JSON.parse(L(s,"utf8"))}catch{i={}}const r={command:process.execPath,args:[Z(ee(import.meta.url),"../../../mcp/acp-mcp-server.js"),"--api-url",t]};(!i.mcpServers||typeof i.mcpServers!="object")&&(i.mcpServers={}),i.mcpServers.grix=r,P(s,`${JSON.stringify(i,null,2)}
19
+ `,"utf8"),this.recordCursorMcpRegistry(e,s,r),u.info("cursor-adapter",`MCP config synced: workspace=${e} file=${s}`)}catch(n){u.warn("cursor-adapter",`Failed to ensure .cursor/mcp.json: ${String(n)}`)}}recordCursorMcpRegistry(e,t,n){try{const s=A(te.data,"cursor"),i=A(s,"mcp-registry.json");E(s,{recursive:!0});let r={};try{r=JSON.parse(L(i,"utf8"))}catch{r={}}r[e]={mcp_path:t,server:n,updated_at:new Date().toISOString()},P(i,`${JSON.stringify(r,null,2)}
20
+ `,"utf8")}catch(s){u.warn("cursor-adapter",`Failed to record MCP registry: ${String(s)}`)}}}export{J as CURSOR_MODE_OPTIONS,B as CursorAdapter,ve as extractCursorAssistantStreamDelta,X as findCursorTranscript,ye as formatCursorCreatePlanText,H as isCursorAskQuestionTool,z as isCursorCreatePlanTool,_e as mapCursorStreamJsonToRawEnvelope,Se as parseCursorAskQuestionPayload};