grix-connector 3.9.1 → 3.9.2

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 f from"node:path";import{homedir as A}from"node:os";import{promises as I}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 F}from"../../core/runtime/spawn.js";import{applyNativeProviderConfig as L}from"../shared/native-provider-config.js";import{syncDefaultSkillsToDir as j}from"../../default-skills/index.js";import{AcpClient as K,AcpAuthRequiredError as H,isAuthRequiredError as W}from"../../protocol/acp-client.js";import{AgentEventType as m}from"../../types/events.js";import{InternalApiServer as G}from"../../core/mcp/internal-api-server.js";import{EventResultsStore as Y}from"../../core/persistence/event-results-store.js";import{QuotedMessageStream as C}from"../../core/util/quoted-message-stream.js";import{SafeMarkdownStreamSegmenter as V}from"../../core/text-segmentation/index.js";import{extractAcpTurnInput as z}from"../../core/protocol/payload-parser.js";import{injectMessageMetadata as X}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 Q=5e3;function $(c,e){const t=e.toolName??"";return e.toolCallId&&(!t||t===e.toolCallId)?c.toolNamesById.get(e.toolCallId)??t:t}function S(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 J}from"./session-activity.js";import{resolveCliPath as Z,getCliVersion as ee}from"../../core/util/cli-probe.js";const b=f.dirname(D(import.meta.url)),te=[{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 ie=200,se=60*1e3,ne=600*1e3,_=R("GRIX_ACP_TURN_INACTIVITY_TIMEOUT_MS",900*1e3),x=80,N=1200*1e3,oe=R("GRIX_ACP_BUSY_RETRY_INTERVAL_MS",5e3),U=R("GRIX_ACP_BUSY_WAIT_MAX_MS",1800*1e3),re=6e4,ae=2e3,le=12e3,O=3e3,q=3e4,ce=["[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 de(c){return!(!(c instanceof Error)||c.code!==-32603||W(c))}function ue(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 he(c){return c.replace(/\u001b\[[0-9;?]*[ -/]*[@-~]/g,"")}class Oe 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 C;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 Y(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 pe(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=f.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?F(e,[e]):!1}async probe(e){const t=this.config.command||"gemini",i=await Z(t),s=i!==null;let n=null,r;if(s){const h=await ee(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?te: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})),y=i?.modes.map(p=>({id:p.id,displayName:p.name}))??[];u.length>0&&!u.some(p=>p.id===n)&&(n=u[0].id);const v={outcome:e,...t?{cwd:t}:{},model_id:n,mode_id:s,currentModelId:n,currentModeId:s,available_models:u,available_modes:y,availableModels:u,availableModes:y,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}`),v}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 I.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 C,markdownSegmenter:new V,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=z(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=X(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),`${ce}
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}
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
  `)}
@@ -7,19 +7,19 @@ ${t}`)}injectIdentityOnce(e,t){if(!e||this.identityInjectedSessions.has(e))retur
7
7
  ${t}`}injectRecoveryContext(e,t){const i=this.recoveryContextBySessionId.get(e);return i?(this.recoveryContextBySessionId.delete(e),o.info("acp-adapter",`Injecting recovery context for session ${e} (chars=${i.length})`),`${i}
8
8
 
9
9
  [\u5F53\u524D\u7528\u6237\u6D88\u606F]
10
- ${t}`):t}sendPromptWithRetry(e,t){this.acpClient.send(t).then(()=>{this.activeRun===e&&(e.busyWaiting=!1)}).catch(i=>{if(ue(i)){this.scheduleAgentBusyRetry(e,t,g(i));return}if(o.error("acp-adapter",`Prompt failed: ${g(i)}`),de(i)){const n=g(i),r=e.eventId,a=e.sessionId;o.info("acp-adapter",`Internal error escalated to bridge: event=${r} session=${a} err=${n}`),this.silentlyDiscardActiveRun(e),this.emit("internalError",{eventId:r,sessionId:a,errorMsg:n});return}const s=i instanceof Error?i.message:String(i);this.finishRun("failed",s)})}scheduleAgentBusyRetry(e,t,i){if(this.activeRun!==e)return;e.busyWaiting=!0;const s=Date.now();e.busyWaitSince||(e.busyWaitSince=s);const n=s-e.busyWaitSince;if(n>=U){o.error("acp-adapter",`agent busy wait exceeded ${Math.round(U/6e4)}min, giving up event=${e.eventId}: ${i}`),e.buffer+=`
10
+ ${t}`):t}sendPromptWithRetry(e,t){this.acpClient.send(t).then(()=>{this.activeRun===e&&(e.busyWaiting=!1)}).catch(i=>{if(he(i)){this.scheduleAgentBusyRetry(e,t,g(i));return}if(o.error("acp-adapter",`Prompt failed: ${g(i)}`),ue(i)){const n=g(i),r=e.eventId,a=e.sessionId;o.info("acp-adapter",`Internal error escalated to bridge: event=${r} session=${a} err=${n}`),this.silentlyDiscardActiveRun(e),this.emit("internalError",{eventId:r,sessionId:a,errorMsg:n});return}const s=i instanceof Error?i.message:String(i);this.finishRun("failed",s)})}scheduleAgentBusyRetry(e,t,i){if(this.activeRun!==e)return;e.busyWaiting=!0;const s=Date.now();e.busyWaitSince||(e.busyWaitSince=s);const n=s-e.busyWaitSince;if(n>=U){o.error("acp-adapter",`agent busy wait exceeded ${Math.round(U/6e4)}min, giving up event=${e.eventId}: ${i}`),e.buffer+=`
11
11
 
12
- The agent's own background turn kept the session busy past the wait limit, so this message was not delivered. Please resend it later.`,this.finishRun("failed");return}const r=Math.min(oe*2**e.busyRetryCount,re);e.busyRetryCount+=1,o.info("acp-adapter",`agent busy, keeping event queued event=${e.eventId} waited=${Math.round(n/1e3)}s retry_in=${r}ms`),e.idleTimer&&(clearTimeout(e.idleTimer),e.idleTimer=null),e.lastProgressAt=s,e.busyRetryTimer=setTimeout(()=>{if(e.busyRetryTimer=null,this.activeRun===e){if(!this.acpClient?.isAlive){this.finishRun("failed","agent unreachable");return}this.resetIdleTimer(e),this.sendPromptWithRetry(e,t)}},r),e.busyRetryTimer.unref?.()}silentlyDiscardActiveRun(e){this.activeRun===e&&(this.activeRun=null,this.acpClient?.clearSettleTimer(),this.clearRunTimers(e),this.emit("eventDone",e.eventId))}async spawnProcess(){const e=[...this.config.args??[]];this.autoInjectArgs?.acp&&e.push("--acp"),this.autoInjectArgs?.model&&e.push("--model",this.autoInjectArgs.model),this.config.command==="gemini"&&!e.includes("--skip-trust")&&e.push("--skip-trust"),this.config.command.includes("qwen")&&!e.includes("--experimental-skills")&&e.push("--experimental-skills"),this.config.command==="kiro-cli"&&!e.includes("--trust-all-tools")&&e.push("--trust-all-tools"),(this.config.command==="copilot"||this.config.command.endsWith("/copilot")||this.config.command==="gh"&&e.includes("copilot"))&&(e.includes("--allow-all-tools")||e.push("--allow-all-tools"),e.includes("--allow-all-paths")||e.push("--allow-all-paths")),this.agentProcess=new B(this.bridgeLog);const i=this.resolveCwd();try{if(!(await I.stat(i)).isDirectory())throw new Error(`Bound path is not a directory: ${i}`)}catch(s){throw String(s?.code??"")==="ENOENT"?new Error(`Bound directory does not exist: ${i}. Please rebind with /grix open <valid-directory>.`):s}await L(this.config.command,i,this.config.env,this.nativeProviderScope).catch(s=>{o.warn("acp-adapter",`native provider config skipped: ${s instanceof Error?s.message:String(s)}`)});try{await this.agentProcess.start({command:this.config.command,args:e.length>0?e:void 0,cwd:i,env:this.config.env})}catch(s){throw o.error("acp-adapter",`Failed to spawn agent process: ${s}`),this.rejectDeferredEvents(`agent spawn failed: ${s instanceof Error?s.message:String(s)}`),this.emit("exit",null),s}o.info("acp-adapter","ACP agent process started"),this.agentProcess.on("error",s=>{this.stopped||(o.error("acp-adapter",`Agent process error: ${s.message}`),this.activeRun&&this.finishRun("failed",`agent process error: ${s.message}`),this.rejectDeferredEvents(`agent process error: ${s.message}`),this.emit("exit",null))}),this.agentProcess.on("exit",s=>{this.stopped||(o.error("acp-adapter",`Agent process exited unexpectedly (code=${s})`),this.activeRun&&this.finishRun("failed",`agent process exited (code=${s})`),this.rejectDeferredEvents(`agent process exited (code=${s})`),this.emit("exit",s))}),this.agentProcess.on("stderr",()=>{this.activeRun&&this.resetIdleTimer(this.activeRun)})}async connectSession(e){if(this.sessionConnected||!this.agentProcess?.transport)return;let t;this.acpMcpTools&&(t=await this.startInternalApiAndMcp()),this.acpClient=new K,this.acpClient.on("event",a=>this.handleAcpEvent(a)),this.acpClient.on("activity",()=>{this.activeRun&&this.resetIdleTimer(this.activeRun)}),this.acpClient.on("session-lost",()=>{this.stopped||this.agentProcess?.alive&&(o.error("acp-adapter","ACP transport closed while agent process still alive, triggering respawn"),this.activeRun&&(this.callbacks.sendRunError(this.activeRun.eventId,this.activeRun.sessionId,"ACP transport closed"),this.finishRun("failed","ACP transport closed")),this.emit("exit",null))});const i=this.currentAibotSessionId&&this.bindingStore?this.bindingStore.getAcpSessionId(this.currentAibotSessionId):void 0,s=async a=>{await this.acpClient.connect({transport:this.agentProcess.transport,authMethod:this.acpAuthMethod,initialMode:this.acpInitialMode,initialModel:this.acpInitialModel,cwd:e||this.resolveCwd(),mcpServers:t,sessionId:a})};let n=!1,r;try{await s(i)}catch(a){if(a instanceof H){await this.handleAuthRequired(a);return}if(!i)throw this.handleConnectFailure(a),a;if(o.warn("acp-adapter",`Failed to load persisted session ${i}, fallback to session/new: ${g(a)}`),n=!0,r=i,this.config.command==="kiro-cli"&&this.currentAibotSessionId){o.info("acp-adapter",`Building kiro recovery summary: aibotSession=${this.currentAibotSessionId} acpSession=${i}`);const l=await this.buildKiroRecoveryContext(this.currentAibotSessionId,i,e||this.resolveCwd());l?(this.recoveryContextBySessionId.set(this.currentAibotSessionId,l),o.info("acp-adapter",`Recovery context prepared for session ${this.currentAibotSessionId} (chars=${l.length})`)):o.warn("acp-adapter",`Recovery context skipped: no summary generated for acpSession=${i}`)}await s(void 0)}this.sessionConnected=!0,o.info("acp-adapter",`ACP session ready: ${this.acpClient.sessionId}`),this.emit("acpSessionReady",this.acpClient.sessionId),this.currentAibotSessionId&&this.bindingStore&&this.bindingStore.setAcpSessionId(this.currentAibotSessionId,this.acpClient.sessionId);for(const[a,l]of this.sessionBindings){this.bindingStore&&this.acpClient.sessionId&&this.bindingStore.setAcpSessionId(a,this.acpClient.sessionId);const d=n?"binding_recreated":"binding_ready",h=this.buildToolbarContext(d,l);n&&r&&(h.previous_session_id=r,h.recreated_session_id=this.acpClient.sessionId),this.callbacks.sendUpdateBindingCard(a,"ready",l,h)}}async buildKiroRecoveryContext(e,t,i){try{const s=f.join(A(),".kiro","sessions","cli",`${t}.json`);await I.access(s),o.info("acp-adapter",`Reading kiro source session file: ${s}`);const n=await this.generateSummaryByKiroCli(s,i);if(!n)return;const r=f.join(A(),".grix","data","session-summaries");await I.mkdir(r,{recursive:!0});const a=f.join(r,`${e}.md`),l=[`SOURCE_KIRO_SESSION_FILE: ${s}`,"",n.trim(),""].join(`
13
- `);return await I.writeFile(a,l,"utf8"),o.info("acp-adapter",`Recovery summary saved: ${a} (chars=${n.length})`),["[\u5386\u53F2\u4E0A\u4E0B\u6587\u56DE\u704C\u8BF4\u660E]","\u4EE5\u4E0B\u5185\u5BB9\u6765\u81EA\u65E7\u4F1A\u8BDD\u81EA\u52A8\u6458\u8981\uFF0C\u8BF7\u5728\u540E\u7EED\u56DE\u7B54\u4E2D\u5EF6\u7EED\u5176\u4E2D\u7684\u76EE\u6807\u3001\u7EA6\u675F\u548C\u7ED3\u8BBA\u3002",`SOURCE_KIRO_SESSION_FILE: ${s}`,`SUMMARY_FILE: ${a}`,"",n.trim()].join(`
12
+ The agent's own background turn kept the session busy past the wait limit, so this message was not delivered. Please resend it later.`,this.finishRun("failed");return}const r=Math.min(re*2**e.busyRetryCount,ae);e.busyRetryCount+=1,o.info("acp-adapter",`agent busy, keeping event queued event=${e.eventId} waited=${Math.round(n/1e3)}s retry_in=${r}ms`),e.idleTimer&&(clearTimeout(e.idleTimer),e.idleTimer=null),e.lastProgressAt=s,e.busyRetryTimer=setTimeout(()=>{if(e.busyRetryTimer=null,this.activeRun===e){if(!this.acpClient?.isAlive){this.finishRun("failed","agent unreachable");return}this.resetIdleTimer(e),this.sendPromptWithRetry(e,t)}},r),e.busyRetryTimer.unref?.()}silentlyDiscardActiveRun(e){this.activeRun===e&&(this.activeRun=null,this.acpClient?.clearSettleTimer(),this.clearRunTimers(e),this.emit("eventDone",e.eventId))}async spawnProcess(){const e=[...this.config.args??[]];this.autoInjectArgs?.acp&&e.push("--acp"),this.autoInjectArgs?.model&&e.push("--model",this.autoInjectArgs.model),this.config.command==="gemini"&&!e.includes("--skip-trust")&&e.push("--skip-trust"),this.config.command.includes("qwen")&&!e.includes("--experimental-skills")&&e.push("--experimental-skills"),this.config.command==="kiro-cli"&&!e.includes("--trust-all-tools")&&e.push("--trust-all-tools"),(this.config.command==="copilot"||this.config.command.endsWith("/copilot")||this.config.command==="gh"&&e.includes("copilot"))&&(e.includes("--allow-all-tools")||e.push("--allow-all-tools"),e.includes("--allow-all-paths")||e.push("--allow-all-paths")),this.agentProcess=new B(this.bridgeLog);const i=this.resolveCwd();try{if(!(await C.stat(i)).isDirectory())throw new Error(`Bound path is not a directory: ${i}`)}catch(s){throw String(s?.code??"")==="ENOENT"?new Error(`Bound directory does not exist: ${i}. Please rebind with /grix open <valid-directory>.`):s}await F(this.config.command,i,this.config.env,this.nativeProviderScope).catch(s=>{o.warn("acp-adapter",`native provider config skipped: ${s instanceof Error?s.message:String(s)}`)});try{await this.agentProcess.start({command:this.config.command,args:e.length>0?e:void 0,cwd:i,env:this.config.env})}catch(s){throw o.error("acp-adapter",`Failed to spawn agent process: ${s}`),this.rejectDeferredEvents(`agent spawn failed: ${s instanceof Error?s.message:String(s)}`),this.emit("exit",null),s}o.info("acp-adapter","ACP agent process started"),this.agentProcess.on("error",s=>{this.stopped||(o.error("acp-adapter",`Agent process error: ${s.message}`),this.activeRun&&this.finishRun("failed",`agent process error: ${s.message}`),this.rejectDeferredEvents(`agent process error: ${s.message}`),this.emit("exit",null))}),this.agentProcess.on("exit",s=>{this.stopped||(o.error("acp-adapter",`Agent process exited unexpectedly (code=${s})`),this.activeRun&&this.finishRun("failed",`agent process exited (code=${s})`),this.rejectDeferredEvents(`agent process exited (code=${s})`),this.emit("exit",s))}),this.agentProcess.on("stderr",()=>{this.activeRun&&this.resetIdleTimer(this.activeRun)})}async connectSession(e){if(this.sessionConnected||!this.agentProcess?.transport)return;let t;this.acpMcpTools&&(t=await this.startInternalApiAndMcp()),this.acpClient=new H,this.acpClient.on("event",a=>this.handleAcpEvent(a)),this.acpClient.on("activity",()=>{this.activeRun&&this.resetIdleTimer(this.activeRun)}),this.acpClient.on("session-lost",()=>{this.stopped||this.agentProcess?.alive&&(o.error("acp-adapter","ACP transport closed while agent process still alive, triggering respawn"),this.activeRun&&(this.callbacks.sendRunError(this.activeRun.eventId,this.activeRun.sessionId,"ACP transport closed"),this.finishRun("failed","ACP transport closed")),this.emit("exit",null))});const i=this.currentAibotSessionId&&this.bindingStore?this.bindingStore.getAcpSessionId(this.currentAibotSessionId):void 0,s=async a=>{await this.acpClient.connect({transport:this.agentProcess.transport,authMethod:this.acpAuthMethod,initialMode:this.acpInitialMode,initialModel:this.acpInitialModel,cwd:e||this.resolveCwd(),mcpServers:t,sessionId:a})};let n=!1,r;try{await s(i)}catch(a){if(a instanceof W){await this.handleAuthRequired(a);return}if(!i)throw this.handleConnectFailure(a),a;if(o.warn("acp-adapter",`Failed to load persisted session ${i}, fallback to session/new: ${g(a)}`),n=!0,r=i,this.config.command==="kiro-cli"&&this.currentAibotSessionId){o.info("acp-adapter",`Building kiro recovery summary: aibotSession=${this.currentAibotSessionId} acpSession=${i}`);const l=await this.buildKiroRecoveryContext(this.currentAibotSessionId,i,e||this.resolveCwd());l?(this.recoveryContextBySessionId.set(this.currentAibotSessionId,l),o.info("acp-adapter",`Recovery context prepared for session ${this.currentAibotSessionId} (chars=${l.length})`)):o.warn("acp-adapter",`Recovery context skipped: no summary generated for acpSession=${i}`)}await s(void 0)}this.sessionConnected=!0,o.info("acp-adapter",`ACP session ready: ${this.acpClient.sessionId}`),this.emit("acpSessionReady",this.acpClient.sessionId),this.currentAibotSessionId&&this.bindingStore&&this.bindingStore.setAcpSessionId(this.currentAibotSessionId,this.acpClient.sessionId);for(const[a,l]of this.sessionBindings){this.bindingStore&&this.acpClient.sessionId&&this.bindingStore.setAcpSessionId(a,this.acpClient.sessionId);const d=n?"binding_recreated":"binding_ready",h=this.buildToolbarContext(d,l);n&&r&&(h.previous_session_id=r,h.recreated_session_id=this.acpClient.sessionId),this.callbacks.sendUpdateBindingCard(a,"ready",l,h)}}async buildKiroRecoveryContext(e,t,i){try{const s=m.join(v(),".kiro","sessions","cli",`${t}.json`);await C.access(s),o.info("acp-adapter",`Reading kiro source session file: ${s}`);const n=await this.generateSummaryByKiroCli(s,i);if(!n)return;const r=m.join(v(),".grix","data","session-summaries");await C.mkdir(r,{recursive:!0});const a=m.join(r,`${e}.md`),l=[`SOURCE_KIRO_SESSION_FILE: ${s}`,"",n.trim(),""].join(`
13
+ `);return await C.writeFile(a,l,"utf8"),o.info("acp-adapter",`Recovery summary saved: ${a} (chars=${n.length})`),["[\u5386\u53F2\u4E0A\u4E0B\u6587\u56DE\u704C\u8BF4\u660E]","\u4EE5\u4E0B\u5185\u5BB9\u6765\u81EA\u65E7\u4F1A\u8BDD\u81EA\u52A8\u6458\u8981\uFF0C\u8BF7\u5728\u540E\u7EED\u56DE\u7B54\u4E2D\u5EF6\u7EED\u5176\u4E2D\u7684\u76EE\u6807\u3001\u7EA6\u675F\u548C\u7ED3\u8BBA\u3002",`SOURCE_KIRO_SESSION_FILE: ${s}`,`SUMMARY_FILE: ${a}`,"",n.trim()].join(`
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
- `)],n=await this.runCommandCapture("kiro-cli",s,t,45e3);if(!n)return;const r=he(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(`
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 G,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")?".qwen":e.includes("gemini")?".gemini":e.includes("kiro")?".kiro":this.isReasonix?".reasonix":void 0;if(t){const r=f.join(A(),t,"skills"),a=j(r);a.length>0&&o.info("acp-adapter",`Synced connector skills to ${r}: [${a.join(", ")}]`)}const i=f.resolve(b,"../../mcp/mcp-bridge-server.js"),s=this.internalApi.mcpBridgeWsUrl,n=f.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(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:
18
18
  ${s}
19
19
 
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 v=u.toString().replace(/\x1b\[[0-9;]*m/g,"").match(r);v&&(a=!0,o.info("acp-adapter",`Terminal auth URL captured: ${v[0]}`),this.callbacks.sendAuthNotification(t,`Please open this URL on any device to authenticate:
21
- ${v[0]}
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
+ ${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===m.PermissionRequest){this.activeRun&&this.resetIdleTimer(this.activeRun),this.handlePermissionRequest(e);return}if(e.type===m.ContextWindowUpdate){e.contextWindow&&this.callbacks.onContextWindowUpdated?.(e.contextWindow),this.compacting?this.finishCompaction("context-window-update"):e.contextWindow&&this.maybeScheduleAutoCompact(e.contextWindow);return}const 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 m.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 m.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=S(s);if(t.lastToolUse?.toolName===e.toolName&&S(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 m.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=S(n);if(t.lastToolResult?.toolName===s&&S(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 m.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<Q)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 m.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 m.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 m.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?ne:se,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 J(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(),ie))}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===m.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>=le?this.flushOrphanBuffer():this.scheduleOrphanFlush();return}e.type===m.Result&&this.flushOrphanBuffer()}}scheduleOrphanFlush(){this.orphanFlushTimer&&clearTimeout(this.orphanFlushTimer),this.orphanFlushTimer=setTimeout(()=>{this.orphanFlushTimer=null,this.flushOrphanBuffer()},ae),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 C;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 C,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+_-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+=`
24
24
 
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,n,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 pe 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{Oe as AcpAdapter};
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,n,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 +1 @@
1
- import c from"node:http";import{randomUUID as d}from"node:crypto";import{log as o}from"../../core/log/index.js";function l(t){t.writeHead(401,{"content-type":"application/json"}),t.end(JSON.stringify({error:"unauthorized"}))}function u(t){t.writeHead(404,{"content-type":"application/json"}),t.end(JSON.stringify({error:"not_found"}))}function h(t,e){t.writeHead(400,{"content-type":"application/json"}),t.end(JSON.stringify({error:e}))}function p(t,e={ok:!0}){t.writeHead(200,{"content-type":"application/json"}),t.end(JSON.stringify(e))}async function v(t){const e=[];for await(const n of t)e.push(n);const r=Buffer.concat(e).toString("utf8").trim();return r?JSON.parse(r):{}}function k(t){const e=(t.headers.authorization??"").trim();return e.toLowerCase().startsWith("bearer ")?e.slice(7).trim():""}class w{host="127.0.0.1";port=0;token;callbacks;server=null;address=null;constructor(e){this.token=d(),this.callbacks=e}getToken(){return this.token}getURL(){return this.address?`http://${this.address.address}:${this.address.port}`:""}async start(){this.server||(this.server=c.createServer(async(e,r)=>{try{await this.handleRequest(e,r)}catch(n){h(r,n instanceof Error?n.message:String(n))}}),await new Promise((e,r)=>{this.server.once("error",r),this.server.listen(this.port,this.host,()=>{this.server.off("error",r),e()})}),this.address=this.server.address(),o.info("claude-bridge",`Bridge server listening on ${this.getURL()}`))}async stop(){if(!this.server)return;const e=this.server;this.server=null,this.address=null,e.closeIdleConnections?.(),e.closeAllConnections?.(),await new Promise((r,n)=>{e.close(s=>s?n(s):r())})}async handleRequest(e,r){if(k(e)!==this.token){l(r);return}if(e.method!=="POST"){r.writeHead(405,{"content-type":"application/json"}),r.end(JSON.stringify({error:"method_not_allowed"}));return}const n=new URL(e.url,"http://localhost").pathname,s=await v(e),i=f.get(n);if(!i){u(r);return}const a=await i(this.callbacks,s);p(r,a??{ok:!0})}}const f=new Map([["/v1/worker/register",async(t,e)=>(o.info("claude-bridge",`Worker registered: ${e.worker_id} (pid=${e.pid})`),t.onRegisterWorker(e))],["/v1/worker/status",async(t,e)=>(o.info("claude-bridge",`Worker status: ${e.status}`),t.onStatusUpdate(e))],["/v1/worker/send-text",async(t,e)=>t.onSendText(e)],["/v1/worker/send-stream-chunk",async(t,e)=>t.onSendStreamChunk(e)],["/v1/worker/send-media",async(t,e)=>t.onSendMedia(e)],["/v1/worker/delete-message",async(t,e)=>t.onDeleteMessage(e)],["/v1/worker/ack-event",async(t,e)=>t.onAckEvent(e)],["/v1/worker/event-result",async(t,e)=>t.onSendEventResult(e)],["/v1/worker/event-stop-ack",async(t,e)=>t.onSendEventStopAck(e)],["/v1/worker/event-stop-result",async(t,e)=>t.onSendEventStopResult(e)],["/v1/worker/session-composing",async(t,e)=>t.onSetSessionComposing(e)],["/v1/worker/agent-invoke",async(t,e)=>t.onAgentInvoke(e)],["/v1/worker/local-action-result",async(t,e)=>t.onLocalActionResult(e)]]);export{w as ClaudeBridgeServer};
1
+ import c from"node:http";import{randomUUID as d}from"node:crypto";import{log as o}from"../../core/log/index.js";function l(t){t.writeHead(401,{"content-type":"application/json"}),t.end(JSON.stringify({error:"unauthorized"}))}function u(t){t.writeHead(404,{"content-type":"application/json"}),t.end(JSON.stringify({error:"not_found"}))}function h(t,e){t.writeHead(400,{"content-type":"application/json"}),t.end(JSON.stringify({error:e}))}function p(t,e={ok:!0}){t.writeHead(200,{"content-type":"application/json"}),t.end(JSON.stringify(e))}async function v(t){const e=[];for await(const r of t)e.push(r);const n=Buffer.concat(e).toString("utf8").trim();return n?JSON.parse(n):{}}function k(t){const e=(t.headers.authorization??"").trim();return e.toLowerCase().startsWith("bearer ")?e.slice(7).trim():""}class w{host="127.0.0.1";port=0;token;callbacks;server=null;address=null;constructor(e){this.token=d(),this.callbacks=e}getToken(){return this.token}getURL(){return this.address?`http://${this.address.address}:${this.address.port}`:""}async start(){this.server||(this.server=c.createServer(async(e,n)=>{try{await this.handleRequest(e,n)}catch(r){h(n,r instanceof Error?r.message:String(r))}}),await new Promise((e,n)=>{this.server.once("error",n),this.server.listen(this.port,this.host,()=>{this.server.off("error",n),e()})}),this.address=this.server.address(),o.info("claude-bridge",`Bridge server listening on ${this.getURL()}`))}async stop(){if(!this.server)return;const e=this.server;this.server=null,this.address=null,e.closeIdleConnections?.(),e.closeAllConnections?.(),await new Promise((n,r)=>{e.close(s=>s?r(s):n())})}async handleRequest(e,n){if(k(e)!==this.token){l(n);return}if(e.method!=="POST"){n.writeHead(405,{"content-type":"application/json"}),n.end(JSON.stringify({error:"method_not_allowed"}));return}const r=new URL(e.url,"http://localhost").pathname,s=await v(e),i=f.get(r);if(!i){u(n);return}const a=await i(this.callbacks,s);p(n,a??{ok:!0})}}const f=new Map([["/v1/worker/register",async(t,e)=>(o.info("claude-bridge",`Worker registered: ${e.worker_id} (pid=${e.pid})`),t.onRegisterWorker(e))],["/v1/worker/status",async(t,e)=>(o.info("claude-bridge",`Worker status: ${e.status}`),t.onStatusUpdate(e))],["/v1/worker/send-text",async(t,e)=>t.onSendText(e)],["/v1/worker/send-stream-chunk",async(t,e)=>t.onSendStreamChunk(e)],["/v1/worker/send-media",async(t,e)=>t.onSendMedia(e)],["/v1/worker/delete-message",async(t,e)=>t.onDeleteMessage(e)],["/v1/worker/ack-event",async(t,e)=>t.onAckEvent(e)],["/v1/worker/event-result",async(t,e)=>t.onSendEventResult(e)],["/v1/worker/event-stop-ack",async(t,e)=>t.onSendEventStopAck(e)],["/v1/worker/event-stop-result",async(t,e)=>t.onSendEventStopResult(e)],["/v1/worker/session-composing",async(t,e)=>t.onSetSessionComposing(e)],["/v1/worker/agent-invoke",async(t,e)=>t.onAgentInvoke(e)],["/v1/worker/local-action-result",async(t,e)=>t.onLocalActionResult(e)]]);export{w as ClaudeBridgeServer};
@@ -1 +1 @@
1
- import{randomUUID as x}from"node:crypto";import{CallToolRequestSchema as S,ListToolsRequestSchema as w}from"@modelcontextprotocol/sdk/types.js";import{log as f}from"../../core/log/index.js";import{toolCallToInvoke as k}from"../../core/mcp/tools.js";const E=new Set(["contact_search","session_search","message_history","message_search","group_create","group_detail_read","group_leave_self","group_member_add","group_member_remove","group_member_role_update","group_all_members_muted_update","group_member_speaking_update","group_dissolve","send_msg","delete_msg","agent_api_create","agent_category_list","agent_category_create","agent_category_update","agent_category_assign","agent_api_key_rotate"]),y=3e4,I=[{name:"reply",description:"Send a visible message back to the chat for this grix-claude event.",inputSchema:{type:"object",properties:{text:{type:"string",description:"The visible reply text to send."},chat_id:{type:"string",description:"The target chat/session id from the <channel> tag."},event_id:{type:"string",description:"The Aibot event_id from the <channel> tag."},reply_to:{type:"string",description:"Optional message_id to quote instead of the inbound trigger message."},files:{type:"array",items:{type:"string"},description:"Optional absolute local file paths. Each file is uploaded through Agent API OSS presign before sending."},final:{type:"boolean",description:"Whether this is the final reply for the event. Defaults to false \u2014 the event stays open while Claude continues working, and auto-completes after inactivity. Set true only when this is definitively the last message for the event."}},required:["chat_id","event_id"]}},{name:"complete",description:"Finish an event without sending a visible reply so the backend does not time out.",inputSchema:{type:"object",properties:{event_id:{type:"string",description:"The Aibot event_id from the <channel> tag."},status:{type:"string",enum:["responded","canceled","failed"]},code:{type:"string"},msg:{type:"string"}},required:["event_id","status"]}},{name:"delete_message",description:"Delete a previously sent message in the same grix-claude chat.",inputSchema:{type:"object",properties:{chat_id:{type:"string"},message_id:{type:"string"}},required:["chat_id","message_id"]}},{name:"status",description:"Show grix-claude runtime status, upstream access state, bridge health, and startup hints.",inputSchema:{type:"object",properties:{}}},{name:"send",description:"Send a message to a chat session proactively, without requiring an inbound event. Use for notifications or scheduled reports.",inputSchema:{type:"object",properties:{chat_id:{type:"string",description:"The target chat/session id."},text:{type:"string",description:"The message text to send."}},required:["chat_id","text"]}},{name:"access_pair",description:"Forward a sender pairing approval code to upstream access control.",inputSchema:{type:"object",properties:{code:{type:"string"}},required:["code"]}},{name:"access_deny",description:"Forward a sender pairing denial code to upstream access control.",inputSchema:{type:"object",properties:{code:{type:"string"}},required:["code"]}},{name:"allow_sender",description:"Ask upstream access control to allow a sender_id.",inputSchema:{type:"object",properties:{sender_id:{type:"string"}},required:["sender_id"]}},{name:"remove_sender",description:"Ask upstream access control to remove a sender_id.",inputSchema:{type:"object",properties:{sender_id:{type:"string"}},required:["sender_id"]}},{name:"access_policy",description:"Ask upstream access control to update the sender access policy.",inputSchema:{type:"object",properties:{policy:{type:"string",enum:["allowlist","open","disabled"]}},required:["policy"]}},{name:"grix_query",description:"Search contacts, sessions, message history, or messages by keyword in the Grix/AIBot platform.",inputSchema:{type:"object",properties:{action:{type:"string",enum:["contact_search","session_search","message_history","message_search"]},keyword:{type:"string"},id:{type:"string"},sessionId:{type:"string"},limit:{type:"number"},offset:{type:"number"},beforeId:{type:"string"}},required:["action"]}},{name:"grix_group",description:"Manage groups in the Grix/AIBot platform: create, get details, leave, dissolve, manage members and permissions.",inputSchema:{type:"object",properties:{action:{type:"string",enum:["create","detail","leave","add_members","remove_members","update_member_role","update_all_members_muted","update_member_speaking","dissolve"]},sessionId:{type:"string"},name:{type:"string"},memberIds:{type:"array",items:{type:"string"}},memberTypes:{type:"array",items:{type:"integer",enum:[1,2]}},memberId:{type:"string"},role:{type:"integer",enum:[1,2]},memberType:{type:"integer",description:"Member type (for update_member_role / update_member_speaking)."},allMembersMuted:{type:"boolean"},isSpeakMuted:{type:"boolean"},canSpeakWhenAllMuted:{type:"boolean",description:"Allow speaking when all muted (for update_member_speaking)."}},required:["action"]}},{name:"grix_message_send",description:"Send a message to a session in the Grix/AIBot platform.",inputSchema:{type:"object",properties:{sessionId:{type:"string"},content:{type:"string"},msgType:{type:"number"},quotedMessageId:{type:"string"},threadId:{type:"string"}},required:["sessionId","content"]}},{name:"grix_message_unsend",description:"Recall/unsend a message in the Grix/AIBot platform.",inputSchema:{type:"object",properties:{sessionId:{type:"string"},msgId:{type:"string"}},required:["sessionId","msgId"]}},{name:"grix_admin",description:"Agent and category management in the Grix/AIBot platform: create agents, manage categories, rotate API keys.",inputSchema:{type:"object",properties:{action:{type:"string",enum:["create_agent","list_categories","create_category","update_category","assign_category","rotate_api_key"]},agentId:{type:"string"},agentName:{type:"string"},introduction:{type:"string"},isMain:{type:"boolean"},categoryId:{type:"string"},name:{type:"string"},parentId:{type:"string"},sortOrder:{type:"number"}},required:["action"]}}];function q(n,e){n.setRequestHandler(w,async()=>({tools:I})),n.setRequestHandler(S,async i=>{const{name:r,arguments:t}=i.params,s=t??{};try{switch(r){case"reply":return await A(s,e);case"complete":return await $(s,e);case"delete_message":return await T(s,e);case"status":return j(e);case"send":return await M(s,e);case"access_pair":case"access_deny":case"allow_sender":case"remove_sender":case"access_policy":return await R(r,s,e);case"grix_query":case"grix_group":case"grix_message_send":case"grix_message_unsend":case"grix_admin":return await O(r,s,e);default:return{content:[{type:"text",text:`Unknown tool: ${r}`}],isError:!0}}}catch(a){return f.error("claude-tools",`Tool ${r} error: ${a}`),{content:[{type:"text",text:`Error: ${a instanceof Error?a.message:String(a)}`}],isError:!0}}})}async function A(n,e){const i=e.getActiveEvent();if(!i)return{content:[{type:"text",text:"No active event to reply to"}],isError:!0};const r=String(n.text??""),t=String(n.chat_id??""),s=String(n.event_id??i.eventId),a=n.reply_to,d=n.files,_=n.final===!0;if(!t||!s)return{content:[{type:"text",text:"reply requires chat_id and event_id"}],isError:!0};if(!r.trim()&&(!d||d.length===0))return{content:[{type:"text",text:"reply requires at least one of text or files"}],isError:!0};const{text:g,quotedMessageId:v}=e.resolveQuotedMessageId(a,r),b=[];let m=0;const h=`reply_${s}_${Date.now()}`;try{if(g){const p=e.splitText(g);for(let o=0;o<p.length;o++){if(!e.isEventActive(s))return c("ignored: event no longer active");m++,e.bridge.sendStreamChunk(s,t,p[o],++i.chunkSeq,!1,h)}}if(d&&d.length>0)for(const p of d){if(!e.isEventActive(s))return c("ignored: event no longer active");m++;const o=await e.uploadFile(p,t),l=`${x()}_${m}`;e.bridge.sendMedia(s,t,o.access_url,o.file_name,v,l,o.extra),f.info("claude-tools",`File sent: ${o.file_name}`)}e.bridge.sendStreamChunk(s,t,"",++i.chunkSeq,!0,h)}catch(p){if(g&&b.length===0)try{const o=`fallback_${s}_${Date.now()}`,l=e.splitText(g);for(let u=0;u<l.length;u++)e.bridge.sendStreamChunk("",t,l[u],u+1,!1,o);return e.bridge.sendStreamChunk("",t,"",l.length+1,!0,o),e.markReplySent(s),_&&e.finalizeEvent(s,"responded"),c("sent via fallback")}catch{}if(!e.isEventActive(s))return c("ignored: event no longer active");throw e.bridge.sendEventResult(s,"failed",String(p),"send_msg_failed"),p}return e.markReplySent(s),_?e.finalizeEvent(s,"responded"):(i.responded=!0,e.clearActiveEvent("completed")),c("Reply sent")}async function $(n,e){const i=e.getActiveEvent(),r=String(n.event_id??""),t=n.status??"",s=n.code,a=n.msg;if(!r||!t)return{content:[{type:"text",text:"complete requires event_id and status"}],isError:!0};const d=["responded","canceled","failed"];return d.includes(t)?e.isEventActive(r)?(e.bridge.sendEventResult(r,t,a,s),e.clearActiveEvent(t),c("Event completed")):c("ignored: event no longer active"):{content:[{type:"text",text:`status must be one of: ${d.join(", ")}`}],isError:!0}}async function T(n,e){const i=String(n.chat_id??""),r=String(n.message_id??"");if(!i||!r)return{content:[{type:"text",text:"chat_id and message_id are required"}],isError:!0};try{return await e.bridge.agentInvoke("grix_message_unsend",{sessionId:i,msgId:r},y),c(`deleted (${r})`)}catch(t){return{content:[{type:"text",text:`Delete failed: ${t}`}],isError:!0}}}function j(n){const e=n.getStatusInfo();return{content:[{type:"text",text:JSON.stringify({alive:e.alive,active_event:e.activeEvent,pending_approvals:e.pendingPermissions,pending_questions:e.pendingElicitations})}]}}async function M(n,e){const i=String(n.chat_id??""),r=String(n.text??"");if(!i||!r)return{content:[{type:"text",text:"chat_id and text are required"}],isError:!0};try{const t=e.splitText(r),s=`send_${i}_${Date.now()}`;for(let a=0;a<t.length;a++)e.bridge.sendStreamChunk("",i,t[a],a+1,!1,s);return e.bridge.sendStreamChunk("",i,"",t.length+1,!0,s),c("sent")}catch(t){return{content:[{type:"text",text:`Send failed: ${t}`}],isError:!0}}}const C={access_pair:{verb:"pair_approve",payloadKey:"code"},access_deny:{verb:"pair_deny",payloadKey:"code"},allow_sender:{verb:"sender_allow",payloadKey:"sender_id"},remove_sender:{verb:"sender_remove",payloadKey:"sender_id"},access_policy:{verb:"policy_set",payloadKey:"policy"}};async function R(n,e,i){try{const r=C[n];if(!r)throw new Error(`Unknown access control tool: ${n}`);const t={};e.code!=null&&(t.code=e.code),e.sender_id!=null&&(t.sender_id=e.sender_id),e.policy!=null&&(t.policy=e.policy);const s=await i.bridge.agentInvoke("claude_access_control",{verb:r.verb,payload:t},y);return{content:[{type:"text",text:typeof s=="string"?s:JSON.stringify(s)}]}}catch(r){return{content:[{type:"text",text:`${n} failed: ${r}`}],isError:!0}}}async function O(n,e,i){try{const r=k(n,e);if(!E.has(r.action))throw new Error(`Action not allowed: ${r.action}`);const t=await i.bridge.agentInvoke(r.action,r.params,y);if(t&&Number(t.code??0)!==0)throw new Error(String(t.msg??"invoke failed"));return{content:[{type:"text",text:t?.data!=null?typeof t.data=="string"?t.data:JSON.stringify(t.data):JSON.stringify(t)}]}}catch(r){return{content:[{type:"text",text:`${n} failed: ${r}`}],isError:!0}}}function c(n){return{content:[{type:"text",text:n}]}}export{q as registerClaudeTools};
1
+ import{randomUUID as x}from"node:crypto";import{CallToolRequestSchema as S,ListToolsRequestSchema as w}from"@modelcontextprotocol/sdk/types.js";import{log as f}from"../../core/log/index.js";import{toolCallToInvoke as k}from"../../core/mcp/tools.js";const E=new Set(["contact_search","session_search","message_history","message_search","group_create","group_detail_read","group_leave_self","group_member_add","group_member_remove","group_member_role_update","group_all_members_muted_update","group_member_speaking_update","group_dissolve","send_msg","delete_msg","agent_api_create","agent_category_list","agent_category_create","agent_category_update","agent_category_assign","agent_api_key_rotate"]),y=3e4,I=[{name:"reply",description:"Send a visible message back to the chat for this grix-claude event.",inputSchema:{type:"object",properties:{text:{type:"string",description:"The visible reply text to send."},chat_id:{type:"string",description:"The target chat/session id from the <channel> tag."},event_id:{type:"string",description:"The Aibot event_id from the <channel> tag."},reply_to:{type:"string",description:"Optional message_id to quote instead of the inbound trigger message."},files:{type:"array",items:{type:"string"},description:"Optional absolute local file paths. Each file is uploaded through Agent API OSS presign before sending."},final:{type:"boolean",description:"Whether this is the final reply for the event. Defaults to false \u2014 the event stays open while Claude continues working, and auto-completes after inactivity. Set true only when this is definitively the last message for the event."}},required:["chat_id","event_id"]}},{name:"complete",description:"Finish an event without sending a visible reply so the backend does not time out.",inputSchema:{type:"object",properties:{event_id:{type:"string",description:"The Aibot event_id from the <channel> tag."},status:{type:"string",enum:["responded","canceled","failed"]},code:{type:"string"},msg:{type:"string"}},required:["event_id","status"]}},{name:"delete_message",description:"Delete a previously sent message in the same grix-claude chat.",inputSchema:{type:"object",properties:{chat_id:{type:"string"},message_id:{type:"string"}},required:["chat_id","message_id"]}},{name:"status",description:"Show grix-claude runtime status, upstream access state, bridge health, and startup hints.",inputSchema:{type:"object",properties:{}}},{name:"send",description:"Send a message to a chat session proactively, without requiring an inbound event. Use for notifications or scheduled reports.",inputSchema:{type:"object",properties:{chat_id:{type:"string",description:"The target chat/session id."},text:{type:"string",description:"The message text to send."}},required:["chat_id","text"]}},{name:"access_pair",description:"Forward a sender pairing approval code to upstream access control.",inputSchema:{type:"object",properties:{code:{type:"string"}},required:["code"]}},{name:"access_deny",description:"Forward a sender pairing denial code to upstream access control.",inputSchema:{type:"object",properties:{code:{type:"string"}},required:["code"]}},{name:"allow_sender",description:"Ask upstream access control to allow a sender_id.",inputSchema:{type:"object",properties:{sender_id:{type:"string"}},required:["sender_id"]}},{name:"remove_sender",description:"Ask upstream access control to remove a sender_id.",inputSchema:{type:"object",properties:{sender_id:{type:"string"}},required:["sender_id"]}},{name:"access_policy",description:"Ask upstream access control to update the sender access policy.",inputSchema:{type:"object",properties:{policy:{type:"string",enum:["allowlist","open","disabled"]}},required:["policy"]}},{name:"grix_query",description:"Search contacts, sessions, message history, or messages by keyword in the Grix/AIBot platform.",inputSchema:{type:"object",properties:{action:{type:"string",enum:["contact_search","session_search","message_history","message_search"]},keyword:{type:"string"},id:{type:"string"},sessionId:{type:"string"},limit:{type:"number"},offset:{type:"number"},beforeId:{type:"string"}},required:["action"]}},{name:"grix_group",description:"Manage groups in the Grix/AIBot platform: create, get details, leave, dissolve, manage members and permissions.",inputSchema:{type:"object",properties:{action:{type:"string",enum:["create","detail","leave","add_members","remove_members","update_member_role","update_all_members_muted","update_member_speaking","dissolve"]},sessionId:{type:"string"},name:{type:"string"},memberIds:{type:"array",items:{type:"string"}},memberTypes:{type:"array",items:{type:"integer",enum:[1,2]}},memberId:{type:"string"},role:{type:"integer",enum:[1,2]},memberType:{type:"integer",description:"Member type (for update_member_role / update_member_speaking)."},allMembersMuted:{type:"boolean"},isSpeakMuted:{type:"boolean"},canSpeakWhenAllMuted:{type:"boolean",description:"Allow speaking when all muted (for update_member_speaking)."}},required:["action"]}},{name:"grix_message_send",description:"Send a message to a session in the Grix/AIBot platform.",inputSchema:{type:"object",properties:{sessionId:{type:"string"},content:{type:"string"},msgType:{type:"number"},quotedMessageId:{type:"string"},threadId:{type:"string"}},required:["sessionId","content"]}},{name:"grix_message_unsend",description:"Recall/unsend a message in the Grix/AIBot platform.",inputSchema:{type:"object",properties:{sessionId:{type:"string"},msgId:{type:"string"}},required:["sessionId","msgId"]}},{name:"grix_admin",description:"Agent and category management in the Grix/AIBot platform: create agents, manage categories, rotate API keys.",inputSchema:{type:"object",properties:{action:{type:"string",enum:["create_agent","list_categories","create_category","update_category","assign_category","rotate_api_key"]},agentId:{type:"string"},agentName:{type:"string"},introduction:{type:"string"},isMain:{type:"boolean"},categoryId:{type:"string"},name:{type:"string"},parentId:{type:"string"},sortOrder:{type:"number"}},required:["action"]}}];function q(r,e){r.setRequestHandler(w,async()=>({tools:I})),r.setRequestHandler(S,async i=>{const{name:n,arguments:t}=i.params,s=t??{};try{switch(n){case"reply":return await A(s,e);case"complete":return await $(s,e);case"delete_message":return await T(s,e);case"status":return j(e);case"send":return await M(s,e);case"access_pair":case"access_deny":case"allow_sender":case"remove_sender":case"access_policy":return await R(n,s,e);case"grix_query":case"grix_group":case"grix_message_send":case"grix_message_unsend":case"grix_admin":return await O(n,s,e);default:return{content:[{type:"text",text:`Unknown tool: ${n}`}],isError:!0}}}catch(a){return f.error("claude-tools",`Tool ${n} error: ${a}`),{content:[{type:"text",text:`Error: ${a instanceof Error?a.message:String(a)}`}],isError:!0}}})}async function A(r,e){const i=e.getActiveEvent();if(!i)return{content:[{type:"text",text:"No active event to reply to"}],isError:!0};const n=String(r.text??""),t=String(r.chat_id??""),s=String(r.event_id??i.eventId),a=r.reply_to,d=r.files,_=r.final===!0;if(!t||!s)return{content:[{type:"text",text:"reply requires chat_id and event_id"}],isError:!0};if(!n.trim()&&(!d||d.length===0))return{content:[{type:"text",text:"reply requires at least one of text or files"}],isError:!0};const{text:g,quotedMessageId:v}=e.resolveQuotedMessageId(a,n),b=[];let m=0;const h=`reply_${s}_${Date.now()}`;try{if(g){const p=e.splitText(g);for(let o=0;o<p.length;o++){if(!e.isEventActive(s))return c("ignored: event no longer active");m++,e.bridge.sendStreamChunk(s,t,p[o],++i.chunkSeq,!1,h)}}if(d&&d.length>0)for(const p of d){if(!e.isEventActive(s))return c("ignored: event no longer active");m++;const o=await e.uploadFile(p,t),l=`${x()}_${m}`;e.bridge.sendMedia(s,t,o.access_url,o.file_name,v,l,o.extra),f.info("claude-tools",`File sent: ${o.file_name}`)}e.bridge.sendStreamChunk(s,t,"",++i.chunkSeq,!0,h)}catch(p){if(g&&b.length===0)try{const o=`fallback_${s}_${Date.now()}`,l=e.splitText(g);for(let u=0;u<l.length;u++)e.bridge.sendStreamChunk("",t,l[u],u+1,!1,o);return e.bridge.sendStreamChunk("",t,"",l.length+1,!0,o),e.markReplySent(s),_&&e.finalizeEvent(s,"responded"),c("sent via fallback")}catch{}if(!e.isEventActive(s))return c("ignored: event no longer active");throw e.bridge.sendEventResult(s,"failed",String(p),"send_msg_failed"),p}return e.markReplySent(s),_?e.finalizeEvent(s,"responded"):(i.responded=!0,e.clearActiveEvent("completed")),c("Reply sent")}async function $(r,e){const i=e.getActiveEvent(),n=String(r.event_id??""),t=r.status??"",s=r.code,a=r.msg;if(!n||!t)return{content:[{type:"text",text:"complete requires event_id and status"}],isError:!0};const d=["responded","canceled","failed"];return d.includes(t)?e.isEventActive(n)?(e.bridge.sendEventResult(n,t,a,s),e.clearActiveEvent(t),c("Event completed")):c("ignored: event no longer active"):{content:[{type:"text",text:`status must be one of: ${d.join(", ")}`}],isError:!0}}async function T(r,e){const i=String(r.chat_id??""),n=String(r.message_id??"");if(!i||!n)return{content:[{type:"text",text:"chat_id and message_id are required"}],isError:!0};try{return await e.bridge.agentInvoke("grix_message_unsend",{sessionId:i,msgId:n},y),c(`deleted (${n})`)}catch(t){return{content:[{type:"text",text:`Delete failed: ${t}`}],isError:!0}}}function j(r){const e=r.getStatusInfo();return{content:[{type:"text",text:JSON.stringify({alive:e.alive,active_event:e.activeEvent,pending_approvals:e.pendingPermissions,pending_questions:e.pendingElicitations})}]}}async function M(r,e){const i=String(r.chat_id??""),n=String(r.text??"");if(!i||!n)return{content:[{type:"text",text:"chat_id and text are required"}],isError:!0};try{const t=e.splitText(n),s=`send_${i}_${Date.now()}`;for(let a=0;a<t.length;a++)e.bridge.sendStreamChunk("",i,t[a],a+1,!1,s);return e.bridge.sendStreamChunk("",i,"",t.length+1,!0,s),c("sent")}catch(t){return{content:[{type:"text",text:`Send failed: ${t}`}],isError:!0}}}const C={access_pair:{verb:"pair_approve",payloadKey:"code"},access_deny:{verb:"pair_deny",payloadKey:"code"},allow_sender:{verb:"sender_allow",payloadKey:"sender_id"},remove_sender:{verb:"sender_remove",payloadKey:"sender_id"},access_policy:{verb:"policy_set",payloadKey:"policy"}};async function R(r,e,i){try{const n=C[r];if(!n)throw new Error(`Unknown access control tool: ${r}`);const t={};e.code!=null&&(t.code=e.code),e.sender_id!=null&&(t.sender_id=e.sender_id),e.policy!=null&&(t.policy=e.policy);const s=await i.bridge.agentInvoke("claude_access_control",{verb:n.verb,payload:t},y);return{content:[{type:"text",text:typeof s=="string"?s:JSON.stringify(s)}]}}catch(n){return{content:[{type:"text",text:`${r} failed: ${n}`}],isError:!0}}}async function O(r,e,i){try{const n=k(r,e);if(!E.has(n.action))throw new Error(`Action not allowed: ${n.action}`);const t=await i.bridge.agentInvoke(n.action,n.params,y);if(t&&Number(t.code??0)!==0)throw new Error(String(t.msg??"invoke failed"));return{content:[{type:"text",text:t?.data!=null?typeof t.data=="string"?t.data:JSON.stringify(t.data):JSON.stringify(t)}]}}catch(n){return{content:[{type:"text",text:`${r} failed: ${n}`}],isError:!0}}}function c(r){return{content:[{type:"text",text:r}]}}export{q as registerClaudeTools};
@@ -1 +1 @@
1
- import{log as l}from"../../core/log/index.js";class c{controlURL="";token="";isConfigured(){return!!(this.controlURL&&this.token)}configure(t,e){this.controlURL=t.replace(/\/+$/,""),this.token=e.trim(),l.info("claude-worker-client",`Configured with control URL: ${this.controlURL}`)}async post(t,e,s){if(!this.isConfigured())throw new Error("worker control not configured");const i=new AbortController,o=setTimeout(()=>i.abort(),s);try{const r=await fetch(`${this.controlURL}${t}`,{method:"POST",headers:{"content-type":"application/json",authorization:`Bearer ${this.token}`},body:JSON.stringify(e),signal:i.signal}),n=await r.text(),a=n.trim()?JSON.parse(n):{};if(!r.ok)throw new Error(a.error||`worker control failed ${r.status}`);return a}finally{clearTimeout(o)}}isRetryableError(t){const e=t instanceof Error?t.message:String(t);return/fetch failed|network|ECONNRESET|ETIMEDOUT|EAI_AGAIN|socket hang up|aborted/i.test(e)}async postWithRetry(t,e,s,i=1){let o;for(let r=0;r<=i;r++)try{return r>0&&l.info("claude-worker-client",`Retrying ${t} attempt=${r+1}`),await this.post(t,e,s)}catch(n){if(o=n,r>=i||!this.isRetryableError(n))break;await new Promise(a=>setTimeout(a,150))}throw o instanceof Error?o:new Error(String(o))}async deliverEvent(t){return this.postWithRetry("/v1/worker/deliver-event",{payload:t},1e4,1)}async deliverStop(t){return this.postWithRetry("/v1/worker/deliver-stop",{payload:t},1e4,1)}async deliverLocalAction(t){return this.postWithRetry("/v1/worker/deliver-local-action",{payload:t},1e4,1)}async ping(){try{return await this.postWithRetry("/v1/worker/ping",{},5e3,1),!0}catch{return!1}}}export{c as ClaudeWorkerClient};
1
+ import{log as l}from"../../core/log/index.js";class c{controlURL="";token="";isConfigured(){return!!(this.controlURL&&this.token)}configure(r,e){this.controlURL=r.replace(/\/+$/,""),this.token=e.trim(),l.info("claude-worker-client",`Configured with control URL: ${this.controlURL}`)}async post(r,e,s){if(!this.isConfigured())throw new Error("worker control not configured");const i=new AbortController,o=setTimeout(()=>i.abort(),s);try{const t=await fetch(`${this.controlURL}${r}`,{method:"POST",headers:{"content-type":"application/json",authorization:`Bearer ${this.token}`},body:JSON.stringify(e),signal:i.signal}),n=await t.text(),a=n.trim()?JSON.parse(n):{};if(!t.ok)throw new Error(a.error||`worker control failed ${t.status}`);return a}finally{clearTimeout(o)}}isRetryableError(r){const e=r instanceof Error?r.message:String(r);return/fetch failed|network|ECONNRESET|ETIMEDOUT|EAI_AGAIN|socket hang up|aborted/i.test(e)}async postWithRetry(r,e,s,i=1){let o;for(let t=0;t<=i;t++)try{return t>0&&l.info("claude-worker-client",`Retrying ${r} attempt=${t+1}`),await this.post(r,e,s)}catch(n){if(o=n,t>=i||!this.isRetryableError(n))break;await new Promise(a=>setTimeout(a,150))}throw o instanceof Error?o:new Error(String(o))}async deliverEvent(r){return this.postWithRetry("/v1/worker/deliver-event",{payload:r},1e4,1)}async deliverStop(r){return this.postWithRetry("/v1/worker/deliver-stop",{payload:r},1e4,1)}async deliverLocalAction(r){return this.postWithRetry("/v1/worker/deliver-local-action",{payload:r},1e4,1)}async ping(){try{return await this.postWithRetry("/v1/worker/ping",{},5e3,1),!0}catch{return!1}}}export{c as ClaudeWorkerClient};
@@ -1,2 +1,2 @@
1
- import{spawn as x,execSync as y}from"node:child_process";import{randomUUID as v}from"node:crypto";import{mkdir as S}from"node:fs/promises";import{readFileSync as C}from"node:fs";import{join as d}from"node:path";import{homedir as T,tmpdir as I}from"node:os";import{log as o}from"../../core/log/index.js";import{MCP_HTTP_CHANNEL_NAME as l}from"./protocol-contract.js";function P(e){let t=null,r=0,n=!1,i=!1;const a=v(),s=e.gatewayUrl??"http://127.0.0.1:19580/mcp";return{async start(){await F(e.command,s,e.env);const c=E(e.grix),u=[...e.args??[],"--name",`grix-mcp-${e.name}`,"--session-id",a];e.fullAuto&&u.push("--dangerously-skip-permissions"),u.push("--dangerously-load-development-channels",`server:${l}`,"--append-system-prompt",c);const f=d(I(),`grix-mcp-claude-${e.name}`);await S(f,{recursive:!0});const{expectPath:$,pidPath:g}=await M(f,e.command,u),_={...process.env,...e.env??{}};t=x("/usr/bin/expect",[$],{cwd:e.cwd,env:_,stdio:["ignore","pipe","pipe"],detached:!0}),o.info("mcp-http-launcher",`\u542F\u52A8 Claude: name=${e.name} cwd=${e.cwd} pid=${t.pid}`),r=await k(g),n=!0,o.info("mcp-http-launcher",`Claude \u5B50\u8FDB\u7A0B PID: ${r}`),t.on("exit",(m,p)=>{o.info("mcp-http-launcher",`Claude \u9000\u51FA: code=${m} signal=${p}`),n=!1,t=null,r=0,i||(o.info("mcp-http-launcher","3 \u79D2\u540E\u81EA\u52A8\u91CD\u542F..."),setTimeout(()=>{i||this.start().catch(w=>{o.error("mcp-http-launcher",`\u91CD\u542F\u5931\u8D25: ${w}`)})},3e3))}),t.stdout?.on("data",m=>{const p=m.toString().trim();p&&o.info("mcp-http-launcher",`[stdout] ${p.slice(0,300)}`)}),t.stderr?.on("data",m=>{const p=m.toString().trim();p&&o.info("mcp-http-launcher",`[stderr] ${p.slice(0,300)}`)})},async stop(){if(i=!0,n=!1,r>0)try{process.kill(r,"SIGTERM")}catch{}if(t?.pid){try{process.kill(-t.pid,"SIGTERM")}catch{}await new Promise(c=>{const u=setTimeout(()=>{if(r>0)try{process.kill(r,"SIGKILL")}catch{}if(t?.pid)try{process.kill(-t.pid,"SIGKILL")}catch{}c()},5e3);t?.once("exit",()=>{clearTimeout(u),c()})})}t=null,r=0},getStatus(){return{name:e.name,alive:n,pid:r}}}}function E(e){return["You are connected to a chat via the grix MCP server.",`On startup, immediately call grix_authorize with: agentId="${e.agentId}", apiKey="${e.apiKey}", wsUrl="${e.wsUrl}", clientType="${e.clientType}".`,"When you receive a <channel> message, you MUST respond by calling the grix_reply tool (or the grix_complete tool if no response is needed).","Never write your reply as plain text \u2014 it will NOT reach the user. Only the grix_reply tool delivers your response to the chat.","The <channel> message contains event_id and session_id \u2014 pass them to grix_reply."].join(" ")}async function F(e,t,r){const n=d(T(),".claude.json");let i=null;try{const s=C(n,"utf8");i=JSON.parse(s)?.mcpServers?.[l]??null}catch{}if(i&&String(i.type??"").trim()==="http"&&String(i.url??"").trim()===t)return;o.info("mcp-http-launcher",`\u6CE8\u518C MCP Server: ${l} -> ${t}`);const a={...process.env,...r??{}};try{y(`${e} mcp remove -s user ${l}`,{encoding:"utf8",timeout:1e4,env:a,stdio:"pipe"})}catch{}y(`${e} mcp add --scope user --transport http ${l} ${t}`,{encoding:"utf8",timeout:1e4,env:a,stdio:"pipe"})}async function M(e,t,r){const{writeFile:n}=await import("node:fs/promises"),i=d(e,"claude.pid"),a=d(e,"claude.expect"),s=["log_user 1","set timeout -1","set startup_prompt_armed 1",`set claude_command [list {${h(t)}}${r.map(c=>` {${h(c)}}`).join("")}]`,"spawn -noecho {*}$claude_command",`set pid_file [open {${h(i)}} w]`,"puts $pid_file [exp_pid -i $spawn_id]","close $pid_file","expect {"," -re {(?i)(Quick.*safety.*check|trust.*folder)} {",' if {$startup_prompt_armed} { send -- "1\\r"; after 300 }; exp_continue'," }"," -re {(?i)I am using this for local development} {",' if {$startup_prompt_armed} { send -- "1\\r"; after 300 }; exp_continue'," }"," -re {(?i)(Enter.*confirm|Press.*Enter|Hit.*Enter)} {",' if {$startup_prompt_armed} { send -- "\\r"; after 300 }; exp_continue'," }"," -re {Listening for channel} {"," set startup_prompt_armed 0"," after 1000",' send -- "Call grix_authorize now as instructed in your system prompt.\\r"'," }"," -re {bypass permissions} {"," set startup_prompt_armed 0"," after 1000",' send -- "Call grix_authorize now as instructed in your system prompt.\\r"'," }"," eof {}","}","expect eof",""];return await n(i,"","utf8"),await n(a,s.join(`
2
- `),"utf8"),{expectPath:a,pidPath:i}}function h(e){return e.replace(/[\\{}$\[\]"]/g,"\\$&")}async function k(e,t=1e4){const{readFile:r}=await import("node:fs/promises"),n=Math.ceil(t/100);for(let i=0;i<n;i++){try{const a=await r(e,"utf8"),s=parseInt(String(a).trim(),10);if(Number.isFinite(s)&&s>0)return s}catch{}await new Promise(a=>setTimeout(a,100))}return 0}export{P as createMcpHttpLauncher};
1
+ import{spawn as x,execSync as y}from"node:child_process";import{randomUUID as v}from"node:crypto";import{mkdir as S}from"node:fs/promises";import{readFileSync as C}from"node:fs";import{join as d}from"node:path";import{homedir as T,tmpdir as I}from"node:os";import{log as o}from"../../core/log/index.js";import{MCP_HTTP_CHANNEL_NAME as m}from"./protocol-contract.js";function P(t){let e=null,r=0,n=!1,i=!1;const a=v(),s=t.gatewayUrl??"http://127.0.0.1:19580/mcp";return{async start(){await F(t.command,s,t.env);const c=E(t.grix),u=[...t.args??[],"--name",`grix-mcp-${t.name}`,"--session-id",a];t.fullAuto&&u.push("--dangerously-skip-permissions"),u.push("--dangerously-load-development-channels",`server:${m}`,"--append-system-prompt",c);const f=d(I(),`grix-mcp-claude-${t.name}`);await S(f,{recursive:!0});const{expectPath:$,pidPath:g}=await M(f,t.command,u),_={...process.env,...t.env??{}};e=x("/usr/bin/expect",[$],{cwd:t.cwd,env:_,stdio:["ignore","pipe","pipe"],detached:!0}),o.info("mcp-http-launcher",`\u542F\u52A8 Claude: name=${t.name} cwd=${t.cwd} pid=${e.pid}`),r=await k(g),n=!0,o.info("mcp-http-launcher",`Claude \u5B50\u8FDB\u7A0B PID: ${r}`),e.on("exit",(l,p)=>{o.info("mcp-http-launcher",`Claude \u9000\u51FA: code=${l} signal=${p}`),n=!1,e=null,r=0,i||(o.info("mcp-http-launcher","3 \u79D2\u540E\u81EA\u52A8\u91CD\u542F..."),setTimeout(()=>{i||this.start().catch(w=>{o.error("mcp-http-launcher",`\u91CD\u542F\u5931\u8D25: ${w}`)})},3e3))}),e.stdout?.on("data",l=>{const p=l.toString().trim();p&&o.info("mcp-http-launcher",`[stdout] ${p.slice(0,300)}`)}),e.stderr?.on("data",l=>{const p=l.toString().trim();p&&o.info("mcp-http-launcher",`[stderr] ${p.slice(0,300)}`)})},async stop(){if(i=!0,n=!1,r>0)try{process.kill(r,"SIGTERM")}catch{}if(e?.pid){try{process.kill(-e.pid,"SIGTERM")}catch{}await new Promise(c=>{const u=setTimeout(()=>{if(r>0)try{process.kill(r,"SIGKILL")}catch{}if(e?.pid)try{process.kill(-e.pid,"SIGKILL")}catch{}c()},5e3);e?.once("exit",()=>{clearTimeout(u),c()})})}e=null,r=0},getStatus(){return{name:t.name,alive:n,pid:r}}}}function E(t){return["You are connected to a chat via the grix MCP server.",`On startup, immediately call grix_authorize with: agentId="${t.agentId}", apiKey="${t.apiKey}", wsUrl="${t.wsUrl}", clientType="${t.clientType}".`,"When you receive a <channel> message, you MUST respond by calling the grix_reply tool (or the grix_complete tool if no response is needed).","Never write your reply as plain text \u2014 it will NOT reach the user. Only the grix_reply tool delivers your response to the chat.","The <channel> message contains event_id and session_id \u2014 pass them to grix_reply."].join(" ")}async function F(t,e,r){const n=d(T(),".claude.json");let i=null;try{const s=C(n,"utf8");i=JSON.parse(s)?.mcpServers?.[m]??null}catch{}if(i&&String(i.type??"").trim()==="http"&&String(i.url??"").trim()===e)return;o.info("mcp-http-launcher",`\u6CE8\u518C MCP Server: ${m} -> ${e}`);const a={...process.env,...r??{}};try{y(`${t} mcp remove -s user ${m}`,{encoding:"utf8",timeout:1e4,env:a,stdio:"pipe"})}catch{}y(`${t} mcp add --scope user --transport http ${m} ${e}`,{encoding:"utf8",timeout:1e4,env:a,stdio:"pipe"})}async function M(t,e,r){const{writeFile:n}=await import("node:fs/promises"),i=d(t,"claude.pid"),a=d(t,"claude.expect"),s=["log_user 1","set timeout -1","set startup_prompt_armed 1",`set claude_command [list {${h(e)}}${r.map(c=>` {${h(c)}}`).join("")}]`,"spawn -noecho {*}$claude_command",`set pid_file [open {${h(i)}} w]`,"puts $pid_file [exp_pid -i $spawn_id]","close $pid_file","expect {"," -re {(?i)(Quick.*safety.*check|trust.*folder)} {",' if {$startup_prompt_armed} { send -- "1\\r"; after 300 }; exp_continue'," }"," -re {(?i)I am using this for local development} {",' if {$startup_prompt_armed} { send -- "1\\r"; after 300 }; exp_continue'," }"," -re {(?i)(Enter.*confirm|Press.*Enter|Hit.*Enter)} {",' if {$startup_prompt_armed} { send -- "\\r"; after 300 }; exp_continue'," }"," -re {Listening for channel} {"," set startup_prompt_armed 0"," after 1000",' send -- "Call grix_authorize now as instructed in your system prompt.\\r"'," }"," -re {bypass permissions} {"," set startup_prompt_armed 0"," after 1000",' send -- "Call grix_authorize now as instructed in your system prompt.\\r"'," }"," eof {}","}","expect eof",""];return await n(i,"","utf8"),await n(a,s.join(`
2
+ `),"utf8"),{expectPath:a,pidPath:i}}function h(t){return t.replace(/[\\{}$\[\]"]/g,"\\$&")}async function k(t,e=1e4){const{readFile:r}=await import("node:fs/promises"),n=Math.ceil(e/100);for(let i=0;i<n;i++){try{const a=await r(t,"utf8"),s=parseInt(String(a).trim(),10);if(Number.isFinite(s)&&s>0)return s}catch{}await new Promise(a=>setTimeout(a,100))}return 0}export{P as createMcpHttpLauncher};
@@ -1 +1 @@
1
- class m{defaultTimeoutMs;onTimeout;timers=new Map;constructor(t){this.defaultTimeoutMs=t.defaultTimeoutMs??9e4,this.onTimeout=t.onTimeout}arm(t,e){this.cancel(t);const s=e?.timeoutMs??this.defaultTimeoutMs,i=Date.now()+s,o=setTimeout(()=>{this.timers.delete(t),this.onTimeout(t).catch(()=>{})},s);return this.timers.set(t,o),i}cancel(t){const e=this.timers.get(t);e&&(clearTimeout(e),this.timers.delete(t))}has(t){return this.timers.has(t)}close(){for(const t of this.timers.values())clearTimeout(t);this.timers.clear()}}export{m as ResultTimeoutManager};
1
+ class m{defaultTimeoutMs;onTimeout;timers=new Map;constructor(e){this.defaultTimeoutMs=e.defaultTimeoutMs??9e4,this.onTimeout=e.onTimeout}arm(e,t){this.cancel(e);const s=t?.timeoutMs??this.defaultTimeoutMs,i=Date.now()+s,o=setTimeout(()=>{this.timers.delete(e),this.onTimeout(e).catch(()=>{})},s);return this.timers.set(e,o),i}cancel(e){const t=this.timers.get(e);t&&(clearTimeout(t),this.timers.delete(e))}has(e){return this.timers.has(e)}close(){for(const e of this.timers.values())clearTimeout(e);this.timers.clear()}}export{m as ResultTimeoutManager};
@@ -1 +1 @@
1
- import{log as o}from"../core/log/index.js";import{RespawnManager as d}from"./respawn-manager.js";import{EventQueue as c}from"./event-queue.js";class p extends Error{constructor(e){super(`adapter pool full (maxSize=${e})`),this.name="PoolFullError"}}const v=6e4;class S{slots=new Map;config;factory;sendAck;onEventState=null;onQueueComposing=null;onInternalError=null;onEventStarted=null;onEventDone=null;onSessionActivity=null;stopped=!1;sweepTimer=null;constructor(e,t,s){this.config=e,this.factory=t,this.sendAck=s}setEventStateHandler(e){this.onEventState=e}setQueueComposingHandler(e){this.onQueueComposing=e}setInternalErrorHandler(e){this.onInternalError=e}setEventStartedHandler(e){this.onEventStarted=e}setEventDoneHandler(e){this.onEventDone=e}setSessionActivityHandler(e){this.onSessionActivity=e}async deliverInboundEvent(e){const t=e.session_id,s=this.slots.get(t);if(s){s.startPromise&&await s.startPromise,s.lastActivityAt=Date.now(),this.sendAck(e.event_id,e.session_id),s.state==="stopped"&&o.info("adapter-pool",`Holding event ${e.event_id} in paused queue for restarting slot ${t}`),this.submitToSlotQueue(s,e);return}if(this.slots.size>=this.config.maxPoolSize&&!this.evictDeadSlot()&&!this.evictIdleSlot())throw new p(this.config.maxPoolSize);this.sendAck(e.event_id,e.session_id);const r=this.createSlot(t);try{await r.startPromise}catch(n){throw n}this.submitToSlotQueue(r,e)}submitToSlotQueue(e,t){e.eventQueue.submit(t)==="rejected"&&o.info("adapter-pool",`Event ${t.event_id} rejected by EventQueue (queue full)`)}eventComplete(e,t){const s=this.slots.get(t);s&&s.eventQueue.complete(e)}cancelEvent(e,t){const s=this.slots.get(t);return s?s.eventQueue.cancel(e):!1}removeQueuedEvent(e,t){const s=this.slots.get(e);return s?s.eventQueue.removeQueued(t):!1}clearQueue(e){const t=this.slots.get(e);return t?t.eventQueue.clear(e):[]}drainAllQueuedEvents(){const e=[];for(const t of this.slots.values())e.push(...t.eventQueue.drainQueuedForSession(t.sessionId));return e}drainQueuedForSession(e){if(!this.config.eventQueue)return[];const t=this.slots.get(e);return t?t.eventQueue.drainQueuedForSession(e):[]}getQueueSnapshot(e){if(!this.config.eventQueue)return null;const t=this.slots.get(e);return t?t.eventQueue.snapshot(e):null}deliverStopEvent(e,t){if(t){const s=this.slots.get(t);o.info("adapter-pool",`[stop-trace] pool.deliverStopEvent session=${t} event=${e} slotExists=${!!s} -> adapter.deliverStopEvent`),s?.adapter.deliverStopEvent(e,t);return}o.info("adapter-pool",`[stop-trace] pool.deliverStopEvent (broadcast, no session) event=${e} slots=${this.slots.size}`);for(const s of this.slots.values())s.adapter.deliverStopEvent(e)}collectActiveEventIds(){const e=[];for(const t of this.slots.values()){const s=t.adapter.getActiveEventIds;typeof s=="function"&&e.push(...s.call(t.adapter))}return e}clearActiveEventsForShutdown(){for(const e of this.slots.values()){const t=e.adapter.clearActiveEventForShutdown;typeof t=="function"&&t.call(e.adapter)}}async deliverLocalAction(e,t){const s=String((e.params??{}).session_id??"");let r=s?this.slots.get(s):void 0;if(!r&&s&&t?.autoCreateSlot&&!this.stopped&&(this.slots.size>=this.config.maxPoolSize&&(this.evictDeadSlot()||this.evictIdleSlotSync()),this.slots.size<this.config.maxPoolSize)){o.info("adapter-pool",`Auto-creating slot for session ${s} (local_action trigger)`);const a=this.createSlot(s);try{await a.startPromise,r=a}catch(u){o.error("adapter-pool",`Failed to auto-create slot for ${s}: ${u}`)}}return r?.adapter?.handleLocalAction?(r.lastActivityAt=Date.now(),r.adapter.handleLocalAction(e)):(await Promise.allSettled([...this.slots.values()].map(a=>a.adapter.handleLocalAction?.(e)??Promise.resolve({handled:!1,kind:""})))).find(a=>a.status==="fulfilled"&&a.value.handled)?.value??{handled:!1,kind:""}}getOrCreateSlot(e){const t=this.slots.get(e);if(t)return t;if(!this.stopped)return this.createSlot(e)}getSlot(e){return this.slots.get(e)}getAllSlots(){return[...this.slots.values()]}async removeSlot(e){const t=this.slots.get(e);t&&(this.onSessionActivity?.(e,!1),t.eventQueue.clear(e,"session slot removed"),t.eventQueue.destroy(),t.respawn.stopAll(),this.detachSlotEventListenersExceptDone(t),await t.adapter.stop().catch(()=>{}),t.adapter.removeAllListeners("eventDone"),this.slots.delete(e))}async stop(){this.stopped=!0,this.stopIdleSweep();const e=[...this.slots.values()];this.slots.clear(),await Promise.allSettled(e.map(async t=>{t.eventQueue.destroy(),t.respawn.stopAll(),this.detachAllSlotEventListeners(t),await t.adapter.stop().catch(()=>{})}))}startIdleSweep(){this.stopIdleSweep(),this.sweepTimer=setInterval(()=>this.sweepIdle(),v)}stopIdleSweep(){this.sweepTimer&&(clearInterval(this.sweepTimer),this.sweepTimer=null)}sweepIdle(){const e=Date.now();for(const[t,s]of this.slots){if(s.state==="stopped"&&s.respawn.exhausted){o.info("adapter-pool",`Removing dead slot for session ${t} (sweep)`),this.removeSlot(t).catch(()=>{});continue}if(s.state!=="ready")continue;const r=s.adapter.getStatus();if(r.busy||r.backgroundBusy){s.lastActivityAt=e;continue}e-s.lastActivityAt<=this.config.idleTimeoutMs||(s.adapter.hasBackgroundWork?s.adapter.hasBackgroundWork().then(n=>{n?(o.info("adapter-pool",`Deferred eviction for session ${t}: background grandchild processes detected`),s.lastActivityAt=Date.now()):(o.info("adapter-pool",`Evicting idle slot for session ${t}`),this.removeSlot(t).catch(i=>{o.error("adapter-pool",`Failed to evict slot ${t}: ${i}`)}))}).catch(()=>{o.info("adapter-pool",`Evicting idle slot for session ${t} (background probe failed)`),this.removeSlot(t).catch(n=>{o.error("adapter-pool",`Failed to evict slot ${t}: ${n}`)})}):(o.info("adapter-pool",`Evicting idle slot for session ${t}`),this.removeSlot(t).catch(n=>{o.error("adapter-pool",`Failed to evict slot ${t}: ${n}`)})))}}evictDeadSlot(){for(const[e,t]of this.slots)if(t.state==="stopped"&&t.respawn.exhausted)return o.info("adapter-pool",`Removing dead slot for session ${e} (exhausted respawn)`),this.removeSlot(e).catch(()=>{}),!0;return!1}evictIdleSlot(){let e=null;for(const t of this.slots.values()){if(t.state!=="ready")continue;const s=t.adapter.getStatus();s.busy||s.backgroundBusy||(!e||t.lastActivityAt<e.lastActivityAt)&&(e=t)}return e?(o.info("adapter-pool",`Evicting idle slot for session ${e.sessionId} (pool full)`),this.removeSlot(e.sessionId).catch(()=>{}),!0):!1}evictIdleSlotSync(){let e=null,t="";for(const[s,r]of this.slots){if(r.state!=="ready")continue;const n=r.adapter.getStatus();n.busy||n.backgroundBusy||(!e||r.lastActivityAt<e.lastActivityAt)&&(e=r,t=s)}return!e||!t?!1:(o.info("adapter-pool",`Evicting idle slot for session ${t} (pool full, sync)`),this.slots.delete(t),e.eventQueue.clear(t,"session slot removed"),e.eventQueue.destroy(),e.respawn.stopAll(),this.detachAllSlotEventListeners(e),e.adapter.stop().catch(()=>{}),!0)}getStatus(){let e=0,t=0;for(const s of this.slots.values())s.state==="ready"&&(e++,s.adapter.getStatus().busy&&t++);return{total:this.slots.size,ready:e,busy:t}}hasPendingWork(){for(const e of this.slots.values())if(e.eventQueue.runningCount>0||e.eventQueue.queuedCount>0||e.state==="ready"&&e.adapter.getStatus().busy)return!0;return!1}hasPendingOrBackgroundWork(){for(const e of this.slots.values()){if(e.eventQueue.runningCount>0||e.eventQueue.queuedCount>0)return!0;if(e.state==="ready"){const t=e.adapter.getStatus();if(t.busy||t.backgroundBusy)return!0}}return!1}createSlot(e){const t=this.factory(e),s=new d,r=this.createEventQueue(e),n={sessionId:e,adapter:t,respawn:s,state:"starting",lastActivityAt:Date.now(),startPromise:null,eventQueue:r};this.slots.set(e,n);const i=(async()=>{try{this.wireSlotEvents(n),await t.start(),n.state="ready"}catch(a){throw this.detachAllSlotEventListeners(n),this.slots.delete(e),a}finally{n.startPromise=null}})();return n.startPromise=i,s.startHealthCheck(this.slotRespawnCtx(n)),n}wireSlotEvents(e){e.adapter.on("exit",t=>{this.stopped||(o.error("adapter-pool",`Slot ${e.sessionId} adapter exited (code=${t})`),e.state="stopped",e.eventQueue.pause("restart"),e.respawn.scheduleRespawn(this.slotRespawnCtx(e)))}),e.adapter.on("stuck",()=>{this.stopped||this.slots.get(e.sessionId)===e&&(o.error("adapter-pool",`Slot ${e.sessionId} adapter stuck, triggering respawn`),e.state="stopped",e.eventQueue.pause("restart"),e.respawn.scheduleRespawn(this.slotRespawnCtx(e)))}),e.adapter.on?.("error",t=>{if(this.stopped)return;const s=t instanceof Error?t.message:String(t);o.error("adapter-pool",`Slot ${e.sessionId} adapter error: ${s}`)}),e.adapter.on("internalError",t=>{if(!this.stopped){if(this.slots.get(e.sessionId)!==e){o.warn("adapter-pool",`Ignored internalError from detached slot session=${e.sessionId} event=${t.eventId}`);return}if(!this.onInternalError){o.warn("adapter-pool",`Slot ${e.sessionId} emitted internalError but no handler registered: event=${t.eventId} msg=${t.errorMsg}`);return}this.onInternalError(t)}}),e.adapter.on("eventStarted",(t,s)=>{this.stopped||this.slots.get(e.sessionId)===e&&this.onEventStarted?.(t,s)}),e.adapter.on("eventDone",t=>{this.stopped||this.slots.get(e.sessionId)===e&&(this.onEventDone?.(t,e.sessionId),e.eventQueue.markAdapterDone(t))}),e.adapter.on("sessionActivity",(t,s,r)=>{this.stopped||this.slots.get(e.sessionId)===e&&this.onSessionActivity?.(t||e.sessionId,s,r)}),e.adapter.on("pauseIntake",t=>{this.stopped||this.slots.get(e.sessionId)===e&&e.eventQueue.pause(t)}),e.adapter.on("resumeIntake",t=>{this.stopped||this.slots.get(e.sessionId)===e&&e.eventQueue.resume(t)})}detachAllSlotEventListeners(e){e.adapter.removeAllListeners("exit"),e.adapter.removeAllListeners("error"),e.adapter.removeAllListeners("stuck"),e.adapter.removeAllListeners("internalError"),e.adapter.removeAllListeners("eventStarted"),e.adapter.removeAllListeners("eventDone"),e.adapter.removeAllListeners("sessionActivity"),e.adapter.removeAllListeners("pauseIntake"),e.adapter.removeAllListeners("resumeIntake")}detachSlotEventListenersExceptDone(e){e.adapter.removeAllListeners("exit"),e.adapter.removeAllListeners("error"),e.adapter.removeAllListeners("stuck"),e.adapter.removeAllListeners("internalError"),e.adapter.removeAllListeners("eventStarted"),e.adapter.removeAllListeners("sessionActivity"),e.adapter.removeAllListeners("pauseIntake"),e.adapter.removeAllListeners("resumeIntake")}slotRespawnCtx(e){const t=this,s=e.sessionId;return{name:`slot-${s}`,get stopped(){return t.stopped||!t.slots.has(s)},get agentAlive(){return e.adapter.isAlive()},pingAgent:r=>e.adapter.ping(r),onRespawnNeeded:async()=>{e.eventQueue.pause("restart"),this.detachAllSlotEventListeners(e),await e.adapter.stop().catch(()=>{}),e.adapter=t.factory(s),e.state="starting",t.wireSlotEvents(e),await e.adapter.start(),e.state="ready",e.respawn.resetAttempts(),e.respawn.resetHealthFailures(),e.respawn.stopSlowRetry(),e.respawn.startHealthCheck(t.slotRespawnCtx(e)),e.eventQueue.resume("compaction"),e.eventQueue.resume("barrier"),e.eventQueue.resume("restart")},onCleanupAgent:()=>{"cleanup"in e.adapter&&e.adapter.cleanup()},onAbortActiveRun:r=>{}}}createEventQueue(e){const t=this.config.eventQueue??{maxConcurrent:1,maxQueued:5,queueTimeoutMs:3e5,cancelableQueued:!0,cancelableRunning:!0},s={onDeliver:r=>{const n=this.slots.get(e);n&&n.adapter.deliverInboundEvent(r)},onStateChange:(r,n,i,a)=>{this.onEventState?.(r,n,i,a)},onCancelRunning:r=>{this.deliverStopEvent(r,e)},onRejected:(r,n)=>{this.onEventState?.(r.event_id,r.session_id,"failed",{reason:n})},onComposing:(r,n,i)=>{this.onQueueComposing?.(r,n,i)}};return new c(t,s)}}export{S as AdapterPool,p as PoolFullError};
1
+ import{log as o}from"../core/log/index.js";import{RespawnManager as d}from"./respawn-manager.js";import{EventQueue as c}from"./event-queue.js";class p extends Error{constructor(e){super(`adapter pool full (maxSize=${e})`),this.name="PoolFullError"}}const v=6e4;class S{slots=new Map;config;factory;sendAck;onEventState=null;onQueueComposing=null;onInternalError=null;onEventStarted=null;onEventDone=null;onSessionActivity=null;stopped=!1;sweepTimer=null;constructor(e,t,s){this.config=e,this.factory=t,this.sendAck=s}setEventStateHandler(e){this.onEventState=e}setQueueComposingHandler(e){this.onQueueComposing=e}setInternalErrorHandler(e){this.onInternalError=e}setEventStartedHandler(e){this.onEventStarted=e}setEventDoneHandler(e){this.onEventDone=e}setSessionActivityHandler(e){this.onSessionActivity=e}async deliverInboundEvent(e){const t=e.session_id,s=this.slots.get(t);if(s){s.startPromise&&await s.startPromise,s.lastActivityAt=Date.now(),this.sendAck(e.event_id,e.session_id),s.state==="stopped"&&o.info("adapter-pool",`Holding event ${e.event_id} in paused queue for restarting slot ${t}`),this.submitToSlotQueue(s,e);return}if(this.slots.size>=this.config.maxPoolSize&&!this.evictDeadSlot()&&!this.evictIdleSlot())throw new p(this.config.maxPoolSize);this.sendAck(e.event_id,e.session_id);const r=this.createSlot(t);try{await r.startPromise}catch(n){throw n}this.submitToSlotQueue(r,e)}submitToSlotQueue(e,t){e.eventQueue.submit(t)==="rejected"&&o.info("adapter-pool",`Event ${t.event_id} rejected by EventQueue (queue full)`)}eventComplete(e,t){const s=this.slots.get(t);s&&s.eventQueue.complete(e)}cancelEvent(e,t){const s=this.slots.get(t);return s?s.eventQueue.cancel(e):!1}removeQueuedEvent(e,t){const s=this.slots.get(e);return s?s.eventQueue.removeQueued(t):!1}clearQueue(e){const t=this.slots.get(e);return t?t.eventQueue.clear(e):[]}reorderQueue(e,t){const s=this.slots.get(e);return s?s.eventQueue.reorder(e,t):[]}drainAllQueuedEvents(){const e=[];for(const t of this.slots.values())e.push(...t.eventQueue.drainQueuedForSession(t.sessionId));return e}drainQueuedForSession(e){if(!this.config.eventQueue)return[];const t=this.slots.get(e);return t?t.eventQueue.drainQueuedForSession(e):[]}getQueueSnapshot(e){if(!this.config.eventQueue)return null;const t=this.slots.get(e);return t?t.eventQueue.snapshot(e):null}deliverStopEvent(e,t){if(t){const s=this.slots.get(t);o.info("adapter-pool",`[stop-trace] pool.deliverStopEvent session=${t} event=${e} slotExists=${!!s} -> adapter.deliverStopEvent`),s?.adapter.deliverStopEvent(e,t);return}o.info("adapter-pool",`[stop-trace] pool.deliverStopEvent (broadcast, no session) event=${e} slots=${this.slots.size}`);for(const s of this.slots.values())s.adapter.deliverStopEvent(e)}collectActiveEventIds(){const e=[];for(const t of this.slots.values()){const s=t.adapter.getActiveEventIds;typeof s=="function"&&e.push(...s.call(t.adapter))}return e}clearActiveEventsForShutdown(){for(const e of this.slots.values()){const t=e.adapter.clearActiveEventForShutdown;typeof t=="function"&&t.call(e.adapter)}}async deliverLocalAction(e,t){const s=String((e.params??{}).session_id??"");let r=s?this.slots.get(s):void 0;if(!r&&s&&t?.autoCreateSlot&&!this.stopped&&(this.slots.size>=this.config.maxPoolSize&&(this.evictDeadSlot()||this.evictIdleSlotSync()),this.slots.size<this.config.maxPoolSize)){o.info("adapter-pool",`Auto-creating slot for session ${s} (local_action trigger)`);const a=this.createSlot(s);try{await a.startPromise,r=a}catch(u){o.error("adapter-pool",`Failed to auto-create slot for ${s}: ${u}`)}}return r?.adapter?.handleLocalAction?(r.lastActivityAt=Date.now(),r.adapter.handleLocalAction(e)):(await Promise.allSettled([...this.slots.values()].map(a=>a.adapter.handleLocalAction?.(e)??Promise.resolve({handled:!1,kind:""})))).find(a=>a.status==="fulfilled"&&a.value.handled)?.value??{handled:!1,kind:""}}getOrCreateSlot(e){const t=this.slots.get(e);if(t)return t;if(!this.stopped)return this.createSlot(e)}getSlot(e){return this.slots.get(e)}getAllSlots(){return[...this.slots.values()]}async removeSlot(e){const t=this.slots.get(e);t&&(this.onSessionActivity?.(e,!1),t.eventQueue.clear(e,"session slot removed"),t.eventQueue.destroy(),t.respawn.stopAll(),this.detachSlotEventListenersExceptDone(t),await t.adapter.stop().catch(()=>{}),t.adapter.removeAllListeners("eventDone"),this.slots.delete(e))}async stop(){this.stopped=!0,this.stopIdleSweep();const e=[...this.slots.values()];this.slots.clear(),await Promise.allSettled(e.map(async t=>{t.eventQueue.destroy(),t.respawn.stopAll(),this.detachAllSlotEventListeners(t),await t.adapter.stop().catch(()=>{})}))}startIdleSweep(){this.stopIdleSweep(),this.sweepTimer=setInterval(()=>this.sweepIdle(),v)}stopIdleSweep(){this.sweepTimer&&(clearInterval(this.sweepTimer),this.sweepTimer=null)}sweepIdle(){const e=Date.now();for(const[t,s]of this.slots){if(s.state==="stopped"&&s.respawn.exhausted){o.info("adapter-pool",`Removing dead slot for session ${t} (sweep)`),this.removeSlot(t).catch(()=>{});continue}if(s.state!=="ready")continue;const r=s.adapter.getStatus();if(r.busy||r.backgroundBusy){s.lastActivityAt=e;continue}e-s.lastActivityAt<=this.config.idleTimeoutMs||(s.adapter.hasBackgroundWork?s.adapter.hasBackgroundWork().then(n=>{n?(o.info("adapter-pool",`Deferred eviction for session ${t}: background grandchild processes detected`),s.lastActivityAt=Date.now()):(o.info("adapter-pool",`Evicting idle slot for session ${t}`),this.removeSlot(t).catch(i=>{o.error("adapter-pool",`Failed to evict slot ${t}: ${i}`)}))}).catch(()=>{o.info("adapter-pool",`Evicting idle slot for session ${t} (background probe failed)`),this.removeSlot(t).catch(n=>{o.error("adapter-pool",`Failed to evict slot ${t}: ${n}`)})}):(o.info("adapter-pool",`Evicting idle slot for session ${t}`),this.removeSlot(t).catch(n=>{o.error("adapter-pool",`Failed to evict slot ${t}: ${n}`)})))}}evictDeadSlot(){for(const[e,t]of this.slots)if(t.state==="stopped"&&t.respawn.exhausted)return o.info("adapter-pool",`Removing dead slot for session ${e} (exhausted respawn)`),this.removeSlot(e).catch(()=>{}),!0;return!1}evictIdleSlot(){let e=null;for(const t of this.slots.values()){if(t.state!=="ready")continue;const s=t.adapter.getStatus();s.busy||s.backgroundBusy||(!e||t.lastActivityAt<e.lastActivityAt)&&(e=t)}return e?(o.info("adapter-pool",`Evicting idle slot for session ${e.sessionId} (pool full)`),this.removeSlot(e.sessionId).catch(()=>{}),!0):!1}evictIdleSlotSync(){let e=null,t="";for(const[s,r]of this.slots){if(r.state!=="ready")continue;const n=r.adapter.getStatus();n.busy||n.backgroundBusy||(!e||r.lastActivityAt<e.lastActivityAt)&&(e=r,t=s)}return!e||!t?!1:(o.info("adapter-pool",`Evicting idle slot for session ${t} (pool full, sync)`),this.slots.delete(t),e.eventQueue.clear(t,"session slot removed"),e.eventQueue.destroy(),e.respawn.stopAll(),this.detachAllSlotEventListeners(e),e.adapter.stop().catch(()=>{}),!0)}getStatus(){let e=0,t=0;for(const s of this.slots.values())s.state==="ready"&&(e++,s.adapter.getStatus().busy&&t++);return{total:this.slots.size,ready:e,busy:t}}hasPendingWork(){for(const e of this.slots.values())if(e.eventQueue.runningCount>0||e.eventQueue.queuedCount>0||e.state==="ready"&&e.adapter.getStatus().busy)return!0;return!1}hasPendingOrBackgroundWork(){for(const e of this.slots.values()){if(e.eventQueue.runningCount>0||e.eventQueue.queuedCount>0)return!0;if(e.state==="ready"){const t=e.adapter.getStatus();if(t.busy||t.backgroundBusy)return!0}}return!1}createSlot(e){const t=this.factory(e),s=new d,r=this.createEventQueue(e),n={sessionId:e,adapter:t,respawn:s,state:"starting",lastActivityAt:Date.now(),startPromise:null,eventQueue:r};this.slots.set(e,n);const i=(async()=>{try{this.wireSlotEvents(n),await t.start(),n.state="ready"}catch(a){throw this.detachAllSlotEventListeners(n),this.slots.delete(e),a}finally{n.startPromise=null}})();return n.startPromise=i,s.startHealthCheck(this.slotRespawnCtx(n)),n}wireSlotEvents(e){e.adapter.on("exit",t=>{this.stopped||(o.error("adapter-pool",`Slot ${e.sessionId} adapter exited (code=${t})`),e.state="stopped",e.eventQueue.pause("restart"),e.respawn.scheduleRespawn(this.slotRespawnCtx(e)))}),e.adapter.on("stuck",()=>{this.stopped||this.slots.get(e.sessionId)===e&&(o.error("adapter-pool",`Slot ${e.sessionId} adapter stuck, triggering respawn`),e.state="stopped",e.eventQueue.pause("restart"),e.respawn.scheduleRespawn(this.slotRespawnCtx(e)))}),e.adapter.on?.("error",t=>{if(this.stopped)return;const s=t instanceof Error?t.message:String(t);o.error("adapter-pool",`Slot ${e.sessionId} adapter error: ${s}`)}),e.adapter.on("internalError",t=>{if(!this.stopped){if(this.slots.get(e.sessionId)!==e){o.warn("adapter-pool",`Ignored internalError from detached slot session=${e.sessionId} event=${t.eventId}`);return}if(!this.onInternalError){o.warn("adapter-pool",`Slot ${e.sessionId} emitted internalError but no handler registered: event=${t.eventId} msg=${t.errorMsg}`);return}this.onInternalError(t)}}),e.adapter.on("eventStarted",(t,s)=>{this.stopped||this.slots.get(e.sessionId)===e&&this.onEventStarted?.(t,s)}),e.adapter.on("eventDone",t=>{this.stopped||this.slots.get(e.sessionId)===e&&(this.onEventDone?.(t,e.sessionId),e.eventQueue.markAdapterDone(t))}),e.adapter.on("sessionActivity",(t,s,r)=>{this.stopped||this.slots.get(e.sessionId)===e&&this.onSessionActivity?.(t||e.sessionId,s,r)}),e.adapter.on("pauseIntake",t=>{this.stopped||this.slots.get(e.sessionId)===e&&e.eventQueue.pause(t)}),e.adapter.on("resumeIntake",t=>{this.stopped||this.slots.get(e.sessionId)===e&&e.eventQueue.resume(t)})}detachAllSlotEventListeners(e){e.adapter.removeAllListeners("exit"),e.adapter.removeAllListeners("error"),e.adapter.removeAllListeners("stuck"),e.adapter.removeAllListeners("internalError"),e.adapter.removeAllListeners("eventStarted"),e.adapter.removeAllListeners("eventDone"),e.adapter.removeAllListeners("sessionActivity"),e.adapter.removeAllListeners("pauseIntake"),e.adapter.removeAllListeners("resumeIntake")}detachSlotEventListenersExceptDone(e){e.adapter.removeAllListeners("exit"),e.adapter.removeAllListeners("error"),e.adapter.removeAllListeners("stuck"),e.adapter.removeAllListeners("internalError"),e.adapter.removeAllListeners("eventStarted"),e.adapter.removeAllListeners("sessionActivity"),e.adapter.removeAllListeners("pauseIntake"),e.adapter.removeAllListeners("resumeIntake")}slotRespawnCtx(e){const t=this,s=e.sessionId;return{name:`slot-${s}`,get stopped(){return t.stopped||!t.slots.has(s)},get agentAlive(){return e.adapter.isAlive()},pingAgent:r=>e.adapter.ping(r),onRespawnNeeded:async()=>{e.eventQueue.pause("restart"),this.detachAllSlotEventListeners(e),await e.adapter.stop().catch(()=>{}),e.adapter=t.factory(s),e.state="starting",t.wireSlotEvents(e),await e.adapter.start(),e.state="ready",e.respawn.resetAttempts(),e.respawn.resetHealthFailures(),e.respawn.stopSlowRetry(),e.respawn.startHealthCheck(t.slotRespawnCtx(e)),e.eventQueue.resume("compaction"),e.eventQueue.resume("barrier"),e.eventQueue.resume("restart")},onCleanupAgent:()=>{"cleanup"in e.adapter&&e.adapter.cleanup()},onAbortActiveRun:r=>{}}}createEventQueue(e){const t=this.config.eventQueue??{maxConcurrent:1,maxQueued:5,queueTimeoutMs:3e5,cancelableQueued:!0,cancelableRunning:!0},s={onDeliver:r=>{const n=this.slots.get(e);n&&n.adapter.deliverInboundEvent(r)},onStateChange:(r,n,i,a)=>{this.onEventState?.(r,n,i,a)},onCancelRunning:r=>{this.deliverStopEvent(r,e)},onRejected:(r,n)=>{this.onEventState?.(r.event_id,r.session_id,"failed",{reason:n})},onComposing:(r,n,i)=>{this.onQueueComposing?.(r,n,i)}};return new c(t,s)}}export{S as AdapterPool,p as PoolFullError};
@@ -8,7 +8,7 @@ Error: ${n}`,0,!1)},sendUpdateBindingCard:(s,i,n)=>this.aibotHandle.sendUpdateBi
8
8
 
9
9
  Error: ${n}`,0,!1)},sendUpdateBindingCard:(s,i,n)=>this.aibotHandle.sendUpdateBindingCard({session_id:s,worker_status:i,cwd:n}),agentInvoke:async(s,i,n)=>this.platformInvoke(s,i,n),sendLocalActionResult:(s,i,n,a,r)=>{this.aibotHandle.sendLocalActionResult({action_id:s,status:i,...n!==void 0?{result:n}:{},...a?{error_code:a}:{},...r?{error_msg:r}:{}})},getAgentProfile:()=>this.agentProfile},t=this.config.adapterOptions??{};return new Z({command:this.config.agent.command,args:this.config.agent.args,env:this.resolveSpawnEnv(),options:t},o,{port:t.port,hostname:t.hostname,model:t.model,agent:t.agent,permissionPolicy:t.permission_policy,enableSessionBinding:!0,aibotSessionId:e,bindingStore:this.bindingStore,...this.config.agent.provider?{provider:this.config.agent.provider}:{}})}createAgyAdapter(e){const o={sendStreamChunk:(s,i,n,a,r)=>{this.sendStreamChunkByRuntimeConfig(s,i,n,a,r)},sendEventResult:(s,i,n)=>{this.sendEventResultWithCleanup(s,i,n)},sendEventAck:(s,i)=>{this.aibotHandle.sendEventAck({event_id:s,session_id:i,received_at:Date.now()})},agentInvoke:async(s,i,n)=>this.platformInvoke(s,i,n),forceCompleteInternalEvent:(s,i)=>{this.pool.eventComplete(s,i),this.pushQueueSnapshotForSession(i)},persistConversationId:(s,i)=>{this.bindingStore.setAgyConversationId(s,i)},getAgentProfile:()=>this.agentProfile},t=s=>{const i=this.bindingStore.get(s);return{cwd:i?.cwd,modelId:this.resolveSessionModelId(s),conversationId:i?.agyConversationId}};return new U({command:this.config.agent.command,args:this.config.agent.args,env:this.resolveSpawnEnv(),options:this.config.adapterOptions??{}},o,t)}createAcpAdapter(e){const o=this.isAcpRawTransportEnabled(),t={sendStreamChunk:(r,d,c,l,h,m,v)=>{this.finalizeThinking(r,d),this.sendStreamChunkByRuntimeConfig(r,d,c,l,h,m,v)},sendFinalStreamChunkReliable:async(r,d,c,l)=>{if(this.finalizeThinking(r,d),this.resolveEventRuntimeConfig(r).responseDelivery==="single_message"&&r){this.bufferStreamChunk(r,d,"",!0);return}try{await this.aibotHandle.sendStreamChunkRequest({event_id:r,session_id:d,delta_content:"",chunk_seq:c,is_finish:!0,...l?{client_msg_id:l}:{}})}catch(m){u.warn("bridge",`[acp] sendFinalStreamChunkReliable ACK failed event=${r}: ${m}`)}u.info("bridge",`[acp] sendFinalStreamChunkReliable done event=${r} seq=${c}`)},sendEventResult:(r,d,c)=>{this.sendEventResultWithCleanup(r,d,c)},sendEventAck:(r,d)=>{this.aibotHandle.sendEventAck({event_id:r,session_id:d,received_at:Date.now()})},agentInvoke:async(r,d,c)=>this.platformInvoke(r,d,c),sendLocalActionResult:(r,d,c,l,h)=>{this.aibotHandle.sendLocalActionResult({action_id:r,status:d,...c!==void 0?{result:c}:{},...l?{error_code:l}:{},...h?{error_msg:h}:{}})},sendRawEventEnvelope:(r,d,c)=>{this.sendAcpRawEventEnvelope(r,d,c)},sendToolUse:(r,d,c,l)=>{if(o){this.sendAcpRawEventEnvelope(r,d,{type:"tool_use",payload:{tool_name:c,tool_input:l??""}});return}this.sendToolExecutionCard(r,d,$(c,l))},sendToolResult:(r,d,c,l)=>{this.sendToolExecutionCard(r,d,y(c,l))},sendThinking:(r,d,c)=>{this.sendThinkingByRuntimeConfig(r,d,c)},sendRunError:(r,d,c)=>{this.sendStreamChunkByRuntimeConfig(r,d,`
10
10
 
11
- Error: ${c}`,1,!1)},sendPermissionCard:r=>{if(u.info("bridge","sendPermissionCard callback entered",{rawTransport:o,eventId:r.eventId,sessionId:r.sessionId,toolCallId:r.toolCallId,toolName:r.toolName,toolTitle:r.toolTitle}),o){this.sendAcpRawEventEnvelope(r.eventId,r.sessionId,{type:"permission_request",payload:{tool_call_id:r.toolCallId,tool_name:r.toolName,tool_title:r.toolTitle,options:r.options}}),u.info("bridge","sendPermissionCard: sent via rawEventEnvelope",{eventId:r.eventId,toolCallId:r.toolCallId});return}const d=`perm_${H()}`,c={event_id:r.eventId,session_id:r.sessionId,client_msg_id:d,msg_type:1,content:r.toolTitle?`Permission required: ${r.toolTitle}`:"Permission request",extra:{channel_data:{execApproval:{approvalId:r.toolCallId,approvalSlug:r.toolName},grix:{execApproval:{approval_command_id:r.toolCallId,command:r.toolTitle||r.toolName,host:"acp"}}},agent_api_origin:!0}};u.info("bridge","sendPermissionCard: about to invoke aibotHandle.sendMsg",{eventId:r.eventId,sessionId:r.sessionId,clientMsgId:d,toolCallId:r.toolCallId,contentLength:c.content.length});try{this.aibotHandle.sendMsg(c),u.info("bridge","sendPermissionCard: aibotHandle.sendMsg returned",{eventId:r.eventId,clientMsgId:d})}catch(l){u.error("bridge","sendPermissionCard: aibotHandle.sendMsg threw",{eventId:r.eventId,clientMsgId:d,error:l instanceof Error?l.message:String(l)})}},sendAuthNotification:(r,d)=>{r&&this.aibotHandle.sendMsg({session_id:r,msg_type:1,content:d,extra:{biz_card:{version:1,type:"agent_error",payload:{error:{name:"AuthRequired",message:d}}}}})},sendAgentMessage:(r,d)=>{r&&d&&this.aibotHandle.sendMsg({session_id:r,msg_type:1,content:d})},sendUpdateBindingCard:(r,d,c,l)=>{const h={...l??{}};if(!h.rate_limits&&this.cachedProviderQuota?.success){this.isRateLimitsCacheFresh(this.cachedProviderQuotaSampledAtMs)||this.maybeQueryProviderQuota().catch(()=>{});const m=this.providerQuotaToRateLimits(this.cachedProviderQuota);m&&(h.rate_limits=m)}!h.provider_quota&&this.cachedProviderQuota?.success&&(h.provider_quota=this.cachedProviderQuota),this.aibotHandle.sendUpdateBindingCard({session_id:r,worker_status:d,cwd:c,...Object.keys(h).length>0?{meta:h}:{}})},onSkillsUpdate:r=>{try{this.aibotHandle.sendSkillsUpdate({skills:r})}catch(d){}},onContextWindowUpdated:r=>{this.cachedAcpContextWindow=r,this.cachedAcpContextWindowSampledAtMs=Date.now();const d="usedPercentage"in r?r.usedPercentage.toFixed(1):(r.used/r.size*100).toFixed(1);u.info(this.name,`[acp] context_window updated: ${d}%`);const c=this.bindingStore.get(e);if(c?.cwd){const l={context_window:"usedPercentage"in r?{usedPercentage:r.usedPercentage,remainingPercentage:100-r.usedPercentage}:r};if((this.config.aibot.clientType==="kiro"||this.config.aibot.clientType==="kimi")&&this.cachedProviderQuota?.success){this.isRateLimitsCacheFresh(this.cachedProviderQuotaSampledAtMs)||this.maybeQueryProviderQuota().catch(()=>{}),l.provider_quota=this.cachedProviderQuota;const h=this.providerQuotaToRateLimits(this.cachedProviderQuota);h&&(l.rate_limits=h)}this.aibotHandle.sendUpdateBindingCard({session_id:e,worker_status:"ready",cwd:c.cwd,meta:l})}},sendMcpFrame:r=>{this.aibotHandle.sendMcpFrame(e,r)},getAgentProfile:()=>this.agentProfile},s=e?this.bindingStore.get(e):void 0,i=this.globalConfigStore?.get(this.name),{initialModel:n,initialMode:a}=Ne({sessionBinding:s,globalDefaults:i,configInitialMode:this.config.acpInitialMode});return e&&n&&!s?.acpModelId&&this.bindingStore.setAcpModelId(e,n),(n||a)&&u.info(this.name,`[toolbar] hydrate from binding: session=${e} model=${n??"<none>"} mode=${a??"<none>"}`),new A({command:this.config.agent.command,args:this.config.agent.args,env:this.resolveSpawnEnv()},t,{acpAuthMethod:this.config.acpAuthMethod,acpInitialMode:a,acpInitialModel:n,acpMcpTools:this.config.acpMcpTools,rawTransport:o,eventResultsPath:this.config.eventResultsPath,approvalMode:this.config.approvalMode,bindingStore:this.config.enableSessionBinding?this.bindingStore:void 0,aibotSessionId:e,autoInjectArgs:this.config.autoInjectArgs,nativeProviderScope:this.name,bridgeLog:this.config.logDir?new Be(this.config.logDir,`${this.name}-${e}`):null})}async connectAibot(){const e=new V;this.aibotHandle=await e.connect(this.aibotConfig,{aborted:()=>this.stopped,label:this.name,packetLog:this.packetLog,maxRetries:this.config.connectMaxRetries});const o=this.aibotHandle.authAck;if(this.applyAgentProfile(o?.agent_name,o?.introduction,{source:"auth_ack",respawnOnChange:!1}),this.aibotHandle.onEvent(t=>{this.handleAibotEvent(t).catch(s=>{u.error(this.name,`handleAibotEvent failed: ${s}`),this.aibotHandle.sendEventAck({event_id:t.event_id,session_id:t.session_id,received_at:Date.now()});const i=s instanceof Error?s.message:String(s);if(/CWD must be|Bound directory does not exist|Bound path is not a directory/i.test(i)&&t.session_id){this.bindingStore.delete(t.session_id),this.sessionBindings.delete(t.session_id);const a=this.pool.getSlot(t.session_id);a?.adapter instanceof A&&a.adapter.getSessionBindings().delete(t.session_id);const r=this.config.adapterType??"acp",d=this.resolveBindingChannelKey(r);this.aibotHandle.sendMsg({event_id:t.event_id,session_id:t.session_id,msg_type:1,content:i,extra:{channel_data:{[d]:{sessionBinding:{status:"missing",reason:"binding_stale",error_code:g.invalidCwd}}}},quoted_message_id:t.msg_id});return}this.aibotHandle.sendEventResult({event_id:t.event_id,status:"failed",msg:i,updated_at:Date.now()})})}),this.aibotHandle.onStop(t=>{try{this.handleAibotStop(t)}catch(s){u.error(this.name,`handleAibotStop failed: ${s}`)}}),this.aibotHandle.onAgentDeleted(t=>{u.warn(this.name,`agent deleted on platform (source=${t?.source??"unknown"}${t?.reason?` reason=${t.reason}`:""}), notifying manager to clean up`);try{this.agentDeletedHandler?.()}catch(s){u.error(this.name,`agentDeletedHandler failed: ${s}`)}}),this.aibotHandle.onShareSet(t=>{try{this.shareSetHandler?.(Array.isArray(t?.shared_to)?t.shared_to:[])}catch(s){u.error(this.name,`onShareSet failed: ${s}`)}}),this.aibotHandle.onProfilePush(t=>{try{this.applyAgentProfile(t?.agent_name,t?.introduction,{source:"profile_push",respawnOnChange:!0})}catch(s){u.error(this.name,`onProfilePush failed: ${s}`)}}),this.aibotHandle.onRevoke(t=>{try{this.handleAibotRevoke(t)}catch(s){u.error(this.name,`handleAibotRevoke failed: ${s}`)}}),this.aibotHandle.onLocalAction(t=>{this.handleAibotLocalAction(t).catch(s=>{u.error(this.name,`handleAibotLocalAction failed: ${s}`)})}),this.aibotHandle.onEventCancel(t=>{u.info(this.name,`recv event_cancel event_id=${t.event_id} session_id=${t.session_id}`),this.handleEventCancel(t).catch(s=>{u.error(this.name,`handleEventCancel failed: ${s}`)})}),this.aibotHandle.onMcpFrame((t,s)=>{const i=this.pool.getSlot(t)?.adapter;i?.deliverMcpFrameToAgent?i.deliverMcpFrameToAgent(s):u.warn(this.name,`mcp_frame: no adapter for session=${t}`)}),this.aibotHandle.onQueueClear(t=>{const s=this.pool.clearQueue(t.session_id);this.aibotHandle.sendQueueClearResult({session_id:t.session_id,canceled_event_ids:s}),this.pushQueueSnapshotForSession(t.session_id)}),this.aibotHandle.onQueueSnapshotQuery(t=>{this.replyQueueSnapshotForSession(t.session_id)}),u.info(this.name,"Connected to aibot"),this.activeEventStore){const t=await this.activeEventStore.drain();if(t.length>0){u.warn(this.name,`Recovering ${t.length} stale event(s) from previous run`);for(const s of t)u.info(this.name,`Failing stale event on startup: ${s}`),this.aibotHandle.sendEventResult({event_id:s,status:"failed",msg:"process restarted, event lost",updated_at:Date.now()})}}this.pushQueueSnapshots(),this.aibotHandle.onReconnected(()=>{this.pushQueueSnapshots();const t=this.aibotHandle.authAck;this.applyAgentProfile(t?.agent_name,t?.introduction,{source:"reconnect",respawnOnChange:!0})}),this.aibotHandle.onStreamRejected((t,s)=>{this.sendCtrl.markEventRejected(t)})}applyAgentProfile(e,o,t){const s=String(e??"").trim(),i=String(o??"").trim(),n=s!==this.agentProfile.agentName||i!==this.agentProfile.introduction;this.agentProfile={agentName:s,introduction:i},s||i?u.info(this.name,`agent profile (${t.source}): GOT name="${s}" intro_len=${i.length} changed=${n}`):u.warn(this.name,`agent profile (${t.source}): EMPTY \u2014 \u670D\u52A1\u7AEF\u672A\u4E0B\u53D1 agent_name/introduction\uFF08auth_ack \u5B57\u6BB5\u7F3A\u5931\u6216\u503C\u4E3A\u7A7A\uFF09`),n&&t.respawnOnChange&&this.applyProfileChangeToAdapters("agent_profile_changed")}applyProfileChangeToAdapters(e){if(!this.pool)return;const o=this.pool.getAllSlots();let t=0;for(const a of o)if(a.adapter.onAgentProfileChanged)try{a.adapter.onAgentProfileChanged(),t++}catch(r){u.warn(this.name,`onAgentProfileChanged failed for session=${a.sessionId}: ${r}`)}const s=o.filter(a=>a.adapter instanceof T),i=s.filter(a=>{if(a.state!=="ready")return!1;const r=a.adapter.getStatus();return!r.busy&&!r.backgroundBusy}),n=s.length-i.length;u.info(this.name,`${e}: notified ${t} adapter(s) via hook; respawning ${i.length} idle Claude slot(s), skipping ${n} busy`);for(const a of i)this.pool.removeSlot(a.sessionId).catch(r=>{u.warn(this.name,`removeSlot failed during ${e}: ${r}`)})}pushQueueSnapshots(){if(!(!this.config.eventQueue||!this.pool))for(const e of this.pool.getAllSlots())this.pushQueueSnapshotForSession(e.sessionId)}buildQueueSnapshotPayload(e){const o=this.pool?.getQueueSnapshot(e)??null,t=o?[...o.running]:[],s=o?o.running_items.map(n=>({event_id:n.event_id,...n.content_preview?{content_preview:n.content_preview}:{},...n.title?{title:n.title}:{},...n.summary?{summary:n.summary}:{},actions:[{type:"stop"}]})):[],i=o?o.queued.map(n=>({event_id:n.event_id,position:n.position,...n.content_preview?{content_preview:n.content_preview}:{},...n.title?{title:n.title}:{},...n.summary?{summary:n.summary}:{},actions:[{type:"cancel"}]})):[];if(t.length===0&&this.selfDrivenSessions.has(e)&&this.pool?.getSlot(e)){const n=`selfdrive_${e}`,a=this.selfDrivenLabels.get(e)??"Background task in progress";t.push(n),s.push({event_id:n,content_preview:a,title:a,summary:a,actions:[]})}return{session_id:e,running:t,running_items:s,queued:i}}pushQueueSnapshotForSession(e){if(!this.config.eventQueue||!this.pool)return;const o=this.buildQueueSnapshotPayload(e);u.info(this.name,`[queue-debug] push snapshot session=${e} running=${o.running.length} queued=${o.queued.length} running_ids=[${o.running.join(",")}]`),this.aibotHandle.sendQueueSnapshot(o)}replyQueueSnapshotForSession(e){!this.config.eventQueue||!this.pool||this.aibotHandle.sendQueueSnapshot(this.buildQueueSnapshotPayload(e))}async platformInvoke(e,o,t){return e==="file_link"?je(o):e==="file_upload"?this.uploadFileAndSendMedia(o):this.aibotHandle.agentInvoke(e,o,t)}async uploadFileAndSendMedia(e){const o=String(e.file_path??"").trim(),t=String(e.session_id??"").trim(),s=String(e.caption??"").trim(),i=String(e.reply_to_message_id??"").trim();if(!o)throw new Error("file_path is required");if(!t)throw new Error("session_id is required");const n=await Je({wsURL:this.config.aibot.url,apiKey:this.config.aibot.apiKey,sessionID:t,filePath:o}),a=await this.aibotHandle.sendMedia({session_id:t,msg_type:2,content:s||`[${n.attachment_type}]`,client_msg_id:`file_upload_${H()}`,...i?{quoted_message_id:i}:{},extra:n.extra});if(a.cmd!=="send_ack"){const d=a.payload??{},c=String(d.msg??a.cmd);throw new Error(`media message send failed: ${c}`)}const r=a.payload??{};return{ok:!0,file_name:n.file_name,attachment_type:n.attachment_type,access_url:n.access_url,message_id:r.msg_id!=null?String(r.msg_id):null}}sendReplyByRuntimeConfig(e,o,t,s,i){this.indexEventSession(e,o),this.sendCtrl.sendReply(e,o,t,s,i),t&&this.conversationLog?.logOutbound?.(o,e,"reply",t)}sendStreamChunkByRuntimeConfig(e,o,t,s,i,n,a){this.indexEventSession(e,o),this.sendCtrl.sendStreamChunk(e,o,t,s,i,n,a),(t||i)&&this.conversationLog?.logOutbound?.(o,e,i?"stream_chunk_finish":"stream_chunk",t)}sendRunErrorAsChunk(e,o,t){this.sendStreamChunkByRuntimeConfig(e,o,`
11
+ Error: ${c}`,1,!1)},sendPermissionCard:r=>{if(u.info("bridge","sendPermissionCard callback entered",{rawTransport:o,eventId:r.eventId,sessionId:r.sessionId,toolCallId:r.toolCallId,toolName:r.toolName,toolTitle:r.toolTitle}),o){this.sendAcpRawEventEnvelope(r.eventId,r.sessionId,{type:"permission_request",payload:{tool_call_id:r.toolCallId,tool_name:r.toolName,tool_title:r.toolTitle,options:r.options}}),u.info("bridge","sendPermissionCard: sent via rawEventEnvelope",{eventId:r.eventId,toolCallId:r.toolCallId});return}const d=`perm_${H()}`,c={event_id:r.eventId,session_id:r.sessionId,client_msg_id:d,msg_type:1,content:r.toolTitle?`Permission required: ${r.toolTitle}`:"Permission request",extra:{channel_data:{execApproval:{approvalId:r.toolCallId,approvalSlug:r.toolName},grix:{execApproval:{approval_command_id:r.toolCallId,command:r.toolTitle||r.toolName,host:"acp"}}},agent_api_origin:!0}};u.info("bridge","sendPermissionCard: about to invoke aibotHandle.sendMsg",{eventId:r.eventId,sessionId:r.sessionId,clientMsgId:d,toolCallId:r.toolCallId,contentLength:c.content.length});try{this.aibotHandle.sendMsg(c),u.info("bridge","sendPermissionCard: aibotHandle.sendMsg returned",{eventId:r.eventId,clientMsgId:d})}catch(l){u.error("bridge","sendPermissionCard: aibotHandle.sendMsg threw",{eventId:r.eventId,clientMsgId:d,error:l instanceof Error?l.message:String(l)})}},sendAuthNotification:(r,d)=>{r&&this.aibotHandle.sendMsg({session_id:r,msg_type:1,content:d,extra:{biz_card:{version:1,type:"agent_error",payload:{error:{name:"AuthRequired",message:d}}}}})},sendAgentMessage:(r,d)=>{r&&d&&this.aibotHandle.sendMsg({session_id:r,msg_type:1,content:d})},sendUpdateBindingCard:(r,d,c,l)=>{const h={...l??{}};if(!h.rate_limits&&this.cachedProviderQuota?.success){this.isRateLimitsCacheFresh(this.cachedProviderQuotaSampledAtMs)||this.maybeQueryProviderQuota().catch(()=>{});const m=this.providerQuotaToRateLimits(this.cachedProviderQuota);m&&(h.rate_limits=m)}!h.provider_quota&&this.cachedProviderQuota?.success&&(h.provider_quota=this.cachedProviderQuota),this.aibotHandle.sendUpdateBindingCard({session_id:r,worker_status:d,cwd:c,...Object.keys(h).length>0?{meta:h}:{}})},onSkillsUpdate:r=>{try{this.aibotHandle.sendSkillsUpdate({skills:r})}catch(d){}},onContextWindowUpdated:r=>{this.cachedAcpContextWindow=r,this.cachedAcpContextWindowSampledAtMs=Date.now();const d="usedPercentage"in r?r.usedPercentage.toFixed(1):(r.used/r.size*100).toFixed(1);u.info(this.name,`[acp] context_window updated: ${d}%`);const c=this.bindingStore.get(e);if(c?.cwd){const l={context_window:"usedPercentage"in r?{usedPercentage:r.usedPercentage,remainingPercentage:100-r.usedPercentage}:r};if((this.config.aibot.clientType==="kiro"||this.config.aibot.clientType==="kimi")&&this.cachedProviderQuota?.success){this.isRateLimitsCacheFresh(this.cachedProviderQuotaSampledAtMs)||this.maybeQueryProviderQuota().catch(()=>{}),l.provider_quota=this.cachedProviderQuota;const h=this.providerQuotaToRateLimits(this.cachedProviderQuota);h&&(l.rate_limits=h)}this.aibotHandle.sendUpdateBindingCard({session_id:e,worker_status:"ready",cwd:c.cwd,meta:l})}},sendMcpFrame:r=>{this.aibotHandle.sendMcpFrame(e,r)},getAgentProfile:()=>this.agentProfile},s=e?this.bindingStore.get(e):void 0,i=this.globalConfigStore?.get(this.name),{initialModel:n,initialMode:a}=Ne({sessionBinding:s,globalDefaults:i,configInitialMode:this.config.acpInitialMode});return e&&n&&!s?.acpModelId&&this.bindingStore.setAcpModelId(e,n),(n||a)&&u.info(this.name,`[toolbar] hydrate from binding: session=${e} model=${n??"<none>"} mode=${a??"<none>"}`),new A({command:this.config.agent.command,args:this.config.agent.args,env:this.resolveSpawnEnv()},t,{acpAuthMethod:this.config.acpAuthMethod,acpInitialMode:a,acpInitialModel:n,acpMcpTools:this.config.acpMcpTools,rawTransport:o,eventResultsPath:this.config.eventResultsPath,approvalMode:this.config.approvalMode,bindingStore:this.config.enableSessionBinding?this.bindingStore:void 0,aibotSessionId:e,autoInjectArgs:this.config.autoInjectArgs,nativeProviderScope:this.name,bridgeLog:this.config.logDir?new Be(this.config.logDir,`${this.name}-${e}`):null})}async connectAibot(){const e=new V;this.aibotHandle=await e.connect(this.aibotConfig,{aborted:()=>this.stopped,label:this.name,packetLog:this.packetLog,maxRetries:this.config.connectMaxRetries});const o=this.aibotHandle.authAck;if(this.applyAgentProfile(o?.agent_name,o?.introduction,{source:"auth_ack",respawnOnChange:!1}),this.aibotHandle.onEvent(t=>{this.handleAibotEvent(t).catch(s=>{u.error(this.name,`handleAibotEvent failed: ${s}`),this.aibotHandle.sendEventAck({event_id:t.event_id,session_id:t.session_id,received_at:Date.now()});const i=s instanceof Error?s.message:String(s);if(/CWD must be|Bound directory does not exist|Bound path is not a directory/i.test(i)&&t.session_id){this.bindingStore.delete(t.session_id),this.sessionBindings.delete(t.session_id);const a=this.pool.getSlot(t.session_id);a?.adapter instanceof A&&a.adapter.getSessionBindings().delete(t.session_id);const r=this.config.adapterType??"acp",d=this.resolveBindingChannelKey(r);this.aibotHandle.sendMsg({event_id:t.event_id,session_id:t.session_id,msg_type:1,content:i,extra:{channel_data:{[d]:{sessionBinding:{status:"missing",reason:"binding_stale",error_code:g.invalidCwd}}}},quoted_message_id:t.msg_id});return}this.aibotHandle.sendEventResult({event_id:t.event_id,status:"failed",msg:i,updated_at:Date.now()})})}),this.aibotHandle.onStop(t=>{try{this.handleAibotStop(t)}catch(s){u.error(this.name,`handleAibotStop failed: ${s}`)}}),this.aibotHandle.onAgentDeleted(t=>{u.warn(this.name,`agent deleted on platform (source=${t?.source??"unknown"}${t?.reason?` reason=${t.reason}`:""}), notifying manager to clean up`);try{this.agentDeletedHandler?.()}catch(s){u.error(this.name,`agentDeletedHandler failed: ${s}`)}}),this.aibotHandle.onShareSet(t=>{try{this.shareSetHandler?.(Array.isArray(t?.shared_to)?t.shared_to:[])}catch(s){u.error(this.name,`onShareSet failed: ${s}`)}}),this.aibotHandle.onProfilePush(t=>{try{this.applyAgentProfile(t?.agent_name,t?.introduction,{source:"profile_push",respawnOnChange:!0})}catch(s){u.error(this.name,`onProfilePush failed: ${s}`)}}),this.aibotHandle.onRevoke(t=>{try{this.handleAibotRevoke(t)}catch(s){u.error(this.name,`handleAibotRevoke failed: ${s}`)}}),this.aibotHandle.onLocalAction(t=>{this.handleAibotLocalAction(t).catch(s=>{u.error(this.name,`handleAibotLocalAction failed: ${s}`)})}),this.aibotHandle.onEventCancel(t=>{u.info(this.name,`recv event_cancel event_id=${t.event_id} session_id=${t.session_id}`),this.handleEventCancel(t).catch(s=>{u.error(this.name,`handleEventCancel failed: ${s}`)})}),this.aibotHandle.onMcpFrame((t,s)=>{const i=this.pool.getSlot(t)?.adapter;i?.deliverMcpFrameToAgent?i.deliverMcpFrameToAgent(s):u.warn(this.name,`mcp_frame: no adapter for session=${t}`)}),this.aibotHandle.onQueueClear(t=>{const s=this.pool.clearQueue(t.session_id);this.aibotHandle.sendQueueClearResult({session_id:t.session_id,canceled_event_ids:s}),this.pushQueueSnapshotForSession(t.session_id)}),this.aibotHandle.onQueueReorder(t=>{const s=Array.isArray(t.ordered_event_ids)?t.ordered_event_ids.filter(n=>typeof n=="string"&&n.length>0):[],i=this.pool.reorderQueue(t.session_id,s);this.aibotHandle.sendQueueReorderResult({session_id:t.session_id,applied_event_ids:i}),this.pushQueueSnapshotForSession(t.session_id)}),this.aibotHandle.onQueueSnapshotQuery(t=>{this.replyQueueSnapshotForSession(t.session_id)}),u.info(this.name,"Connected to aibot"),this.activeEventStore){const t=await this.activeEventStore.drain();if(t.length>0){u.warn(this.name,`Recovering ${t.length} stale event(s) from previous run`);for(const s of t)u.info(this.name,`Failing stale event on startup: ${s}`),this.aibotHandle.sendEventResult({event_id:s,status:"failed",msg:"process restarted, event lost",updated_at:Date.now()})}}this.pushQueueSnapshots(),this.aibotHandle.onReconnected(()=>{this.pushQueueSnapshots();const t=this.aibotHandle.authAck;this.applyAgentProfile(t?.agent_name,t?.introduction,{source:"reconnect",respawnOnChange:!0})}),this.aibotHandle.onStreamRejected((t,s)=>{this.sendCtrl.markEventRejected(t)})}applyAgentProfile(e,o,t){const s=String(e??"").trim(),i=String(o??"").trim(),n=s!==this.agentProfile.agentName||i!==this.agentProfile.introduction;this.agentProfile={agentName:s,introduction:i},s||i?u.info(this.name,`agent profile (${t.source}): GOT name="${s}" intro_len=${i.length} changed=${n}`):u.warn(this.name,`agent profile (${t.source}): EMPTY \u2014 \u670D\u52A1\u7AEF\u672A\u4E0B\u53D1 agent_name/introduction\uFF08auth_ack \u5B57\u6BB5\u7F3A\u5931\u6216\u503C\u4E3A\u7A7A\uFF09`),n&&t.respawnOnChange&&this.applyProfileChangeToAdapters("agent_profile_changed")}applyProfileChangeToAdapters(e){if(!this.pool)return;const o=this.pool.getAllSlots();let t=0;for(const a of o)if(a.adapter.onAgentProfileChanged)try{a.adapter.onAgentProfileChanged(),t++}catch(r){u.warn(this.name,`onAgentProfileChanged failed for session=${a.sessionId}: ${r}`)}const s=o.filter(a=>a.adapter instanceof T),i=s.filter(a=>{if(a.state!=="ready")return!1;const r=a.adapter.getStatus();return!r.busy&&!r.backgroundBusy}),n=s.length-i.length;u.info(this.name,`${e}: notified ${t} adapter(s) via hook; respawning ${i.length} idle Claude slot(s), skipping ${n} busy`);for(const a of i)this.pool.removeSlot(a.sessionId).catch(r=>{u.warn(this.name,`removeSlot failed during ${e}: ${r}`)})}pushQueueSnapshots(){if(!(!this.config.eventQueue||!this.pool))for(const e of this.pool.getAllSlots())this.pushQueueSnapshotForSession(e.sessionId)}buildQueueSnapshotPayload(e){const o=this.pool?.getQueueSnapshot(e)??null,t=o?[...o.running]:[],s=o?o.running_items.map(n=>({event_id:n.event_id,...n.content_preview?{content_preview:n.content_preview}:{},...n.title?{title:n.title}:{},...n.summary?{summary:n.summary}:{},actions:[{type:"stop"}]})):[],i=o?o.queued.map(n=>({event_id:n.event_id,position:n.position,...n.content_preview?{content_preview:n.content_preview}:{},...n.title?{title:n.title}:{},...n.summary?{summary:n.summary}:{},actions:[{type:"cancel"}]})):[];if(t.length===0&&this.selfDrivenSessions.has(e)&&this.pool?.getSlot(e)){const n=`selfdrive_${e}`,a=this.selfDrivenLabels.get(e)??"Background task in progress";t.push(n),s.push({event_id:n,content_preview:a,title:a,summary:a,actions:[]})}return{session_id:e,running:t,running_items:s,queued:i}}pushQueueSnapshotForSession(e){if(!this.config.eventQueue||!this.pool)return;const o=this.buildQueueSnapshotPayload(e);u.info(this.name,`[queue-debug] push snapshot session=${e} running=${o.running.length} queued=${o.queued.length} running_ids=[${o.running.join(",")}]`),this.aibotHandle.sendQueueSnapshot(o)}replyQueueSnapshotForSession(e){!this.config.eventQueue||!this.pool||this.aibotHandle.sendQueueSnapshot(this.buildQueueSnapshotPayload(e))}async platformInvoke(e,o,t){return e==="file_link"?je(o):e==="file_upload"?this.uploadFileAndSendMedia(o):this.aibotHandle.agentInvoke(e,o,t)}async uploadFileAndSendMedia(e){const o=String(e.file_path??"").trim(),t=String(e.session_id??"").trim(),s=String(e.caption??"").trim(),i=String(e.reply_to_message_id??"").trim();if(!o)throw new Error("file_path is required");if(!t)throw new Error("session_id is required");const n=await Je({wsURL:this.config.aibot.url,apiKey:this.config.aibot.apiKey,sessionID:t,filePath:o}),a=await this.aibotHandle.sendMedia({session_id:t,msg_type:2,content:s||`[${n.attachment_type}]`,client_msg_id:`file_upload_${H()}`,...i?{quoted_message_id:i}:{},extra:n.extra});if(a.cmd!=="send_ack"){const d=a.payload??{},c=String(d.msg??a.cmd);throw new Error(`media message send failed: ${c}`)}const r=a.payload??{};return{ok:!0,file_name:n.file_name,attachment_type:n.attachment_type,access_url:n.access_url,message_id:r.msg_id!=null?String(r.msg_id):null}}sendReplyByRuntimeConfig(e,o,t,s,i){this.indexEventSession(e,o),this.sendCtrl.sendReply(e,o,t,s,i),t&&this.conversationLog?.logOutbound?.(o,e,"reply",t)}sendStreamChunkByRuntimeConfig(e,o,t,s,i,n,a){this.indexEventSession(e,o),this.sendCtrl.sendStreamChunk(e,o,t,s,i,n,a),(t||i)&&this.conversationLog?.logOutbound?.(o,e,i?"stream_chunk_finish":"stream_chunk",t)}sendRunErrorAsChunk(e,o,t){this.sendStreamChunkByRuntimeConfig(e,o,`
12
12
 
13
13
  Error: ${t}`,1,!1)}sendEventResultWithCleanup(e,o,t,s){this.sendCtrl.sendEventResult(e,o,t,s);const i=this.eventSessionIndex.get(e);i&&(this.pool.eventComplete(e,i),this.pushQueueSnapshotForSession(i),this.conversationLog?.logResult?.(i,e,o,t),this.eventSessionIndex.delete(e)),this.inflightEvents.delete(e),this.restartCount.delete(e),o==="responded"&&(this.cachedProviderQuotaSampledAtMs=null,this.maybeQueryProviderQuota().catch(()=>{}))}async handleSessionInternalError(e){const{eventId:o,sessionId:t,errorMsg:s}=e;if(this.stopped)return;const i=this.inflightEvents.get(o);if(!i){u.warn(this.name,`[recovery] no inflight event for internalError event=${o} session=${t}; surface failure directly`),this.sendRunErrorAsChunk(o,t,s),this.sendEventResultWithCleanup(o,"failed",s,"agent_stop_failure");return}const n=(this.restartCount.get(o)??0)+1;this.restartCount.set(o,n);const a=this.config.adapterType??"acp";if(n>O){u.error(this.name,`[recovery] adapter=${a} session=${t} event=${o} restart=${n}/${O} outcome=give-up err=${s}`),this.sendRunErrorAsChunk(o,t,s),this.sendEventResultWithCleanup(o,"failed",s,"agent_stop_failure");return}u.info(this.name,`[recovery] adapter=${a} session=${t} event=${o} restart=${n}/${O} outcome=restarting err=${s}`);const r=this.pool.drainQueuedForSession(t);r.length>0&&u.info(this.name,`[recovery] session=${t} preserved ${r.length} queued sibling event(s) across restart`);try{await this.pool.removeSlot(t)}catch(l){u.warn(this.name,`[recovery] removeSlot failed session=${t}: ${l instanceof Error?l.message:String(l)}`)}if(this.stopped)return;const d=this.resolveRecoveryPrompt(a,i),c={...i,content:d};try{await this.pool.deliverInboundEvent(c)}catch(l){u.error(this.name,`[recovery] redeliver failed event=${o} session=${t}: ${l instanceof Error?l.message:String(l)}`),this.sendEventResultWithCleanup(o,"failed",l instanceof Error?l.message:String(l));return}for(const l of r){if(this.stopped)break;try{await this.pool.deliverInboundEvent(l)}catch(h){u.error(this.name,`[recovery] sibling redeliver failed event=${l.event_id} session=${t}: ${h instanceof Error?h.message:String(h)}`),this.sendEventResultWithCleanup(l.event_id,"failed",h instanceof Error?h.message:String(h))}}}resolveRecoveryPrompt(e,o){return e==="acp"?"continue":o.content}sendThinkingByRuntimeConfig(e,o,t){this.sendCtrl.sendThinking(e,o,t)}bufferStreamChunk(e,o,t,s,i){this.sendCtrl.bufferOnly(e,o,t,s,i)}flushBufferedStreamText(e){}resolveEventRuntimeConfig(e){return this.sendCtrl.resolveEventRuntimeConfig(e)}captureEventRuntimeConfig(e){this.sendCtrl.captureEventRuntimeConfig(e),this.indexEventSession(e.event_id,e.session_id),e.event_id&&this.inflightEvents.set(e.event_id,e)}indexEventSession(e,o){!e||!o||this.eventSessionIndex.set(e,o)}shouldDropToolDisplayEvent(e){return this.sendCtrl.shouldDropToolDisplayEvent(e)}shouldDropThinkingDisplayEvent(e){return this.sendCtrl.shouldDropThinkingDisplayEvent(e)}shouldDropCodexDisplayEvent(e,o){return this.sendCtrl.shouldDropCodexDisplayEvent(e,o)}logCodexEventToConversation(e){if(!this.conversationLog||e.codex_method!=="item/agentMessage/delta")return;const t=e.codex_payload?.params?.delta;if(!t)return;const s=this.eventSessionIndex.get(e.event_id)??e.session_id;this.conversationLog.append(s,{ts:Date.now(),dir:"outbound",event_id:e.event_id,kind:"codex_delta",text_len:t.length,content:t})}isAcpRawTransportEnabled(){return(this.config.adapterOptions??{}).raw_transport===!0}shouldDropAcpRawDisplayEvent(e,o){return this.sendCtrl.shouldDropAcpRawDisplayEvent(e,o)}sendAcpRawEventEnvelope(e,o,t){this.shouldDropAcpRawDisplayEvent(e,t.type)||this.aibotHandle.sendMsg({event_id:e,session_id:o,msg_type:1,content:this.buildAcpRawEventFallbackText(t),extra:{channel_data:{acp:{raw_event:t}},agent_api_origin:!0}})}buildAcpRawEventFallbackText(e){const o=String(e.type??"").trim();if(!o)return"[acp] event";switch(o){case"permission_request":return`Permission required: ${String(e.payload?.tool_title??e.payload?.tool_name??"permission request")}`;case"tool_use":return`[tool] ${String(e.payload?.tool_name??"tool")}`;case"tool_result":return"[tool result]";case"thinking":return"[thinking]";case"error":return`[error] ${String(e.payload?.message??"agent error")}`;case"result":return"[result]";default:return`[acp] ${o}`}}sendToolExecutionCard(e,o,t,s){this.sendCtrl.sendToolExecutionCard(e,o,t,s)}async handleAibotEvent(e){if(this.relayEnvStale&&!this.hasPendingWork()&&await this.recycleAdaptersForRelayChange(),this.stopped){this.aibotHandle.sendEventAck({event_id:e.event_id,session_id:e.session_id,received_at:Date.now()}),this.aibotHandle.sendEventResult({event_id:e.event_id,status:"failed",msg:"agent shutting down",updated_at:Date.now()});return}this.logInboundConversation(e);const o=this.config.adapterType??"acp",t=et(e.extra);if(t.error){this.aibotHandle.sendEventAck({event_id:e.event_id,session_id:e.session_id,received_at:Date.now()}),this.aibotHandle.sendEventResult({event_id:e.event_id,status:"failed",code:"connector_config_invalid",msg:t.error,updated_at:Date.now()});return}const s=t.patch,i=Ue(e);if(i){if(i.verb===p.exec){await this.handleSessionControlCommand(i,e);return}if(i.verb===p.listSessions){await this.handleListSessionsTextCommand(e);return}if(o==="claude"){await this.handleSessionControlCommand(i,e);return}if(o==="codex"&&i.verb===p.open){await this.handleCodexSessionControlOpen(i,e);return}if(o==="pi"&&i.verb===p.open){await this.handlePiSessionControlOpen(i,e);return}if(o==="pi"&&i.verb===p.restart){await this.handlePiSessionControlRestart(e);return}if((o==="openhuman"||o==="opencode")&&i.verb===p.open){await this.handleOpenHumanSessionControlOpen(i,e);return}if(o==="codewhale"&&i.verb===p.open){await this.handleCodeWhaleSessionControlOpen(i,e);return}if(this.aibotHandle.sendEventAck({event_id:e.event_id,session_id:e.session_id,received_at:Date.now()}),i.verb===p.open){const r=i.args.trim();if(!r){this.aibotHandle.sendEventResult({event_id:e.event_id,status:"failed",code:g.cwdRequired,msg:"cwd is required",updated_at:Date.now()});return}try{const d=P.resolve(r);if(!(await M(d)).isDirectory()){this.aibotHandle.sendEventResult({event_id:e.event_id,status:"failed",code:g.invalidCwd,msg:`Path is not a directory: ${d}`,updated_at:Date.now()});return}}catch(d){const c=String(d?.code??""),l=c==="ENOENT"?`Directory does not exist: ${P.resolve(r)}`:c==="EACCES"||c==="EPERM"?"Directory is not accessible":`Invalid path: ${r}`;this.aibotHandle.sendEventResult({event_id:e.event_id,status:"failed",code:g.invalidCwd,msg:l,updated_at:Date.now()});return}}if(i.verb===p.open){const r=this.bindingStore.get(e.session_id);if(r?.cwd)try{await M(r.cwd)}catch{u.info("bridge",`Stale binding detected for session ${e.session_id}: ${r.cwd} no longer exists, clearing`),this.bindingStore.delete(e.session_id),this.sessionBindings.delete(e.session_id);const d=this.pool.getSlot(e.session_id);d?.adapter instanceof A&&d.adapter.getSessionBindings().delete(e.session_id)}}if(await this.handleSessionControlForPool(i,e),o==="acp"&&i.verb===p.stop){const d=this.bindingStore.get(e.session_id)?.cwd??"";await this.pool.removeSlot(e.session_id).catch(()=>{}),this.sessionBindings.delete(e.session_id),this.aibotHandle.sendEventResult({event_id:e.event_id,status:"responded",msg:`Session worker stopped for ${d}`,updated_at:Date.now()});return}if(q(i,e,this.sessionControlCtx(e.session_id),{...this.sessionControlSenders(),sendEventAck:()=>{}}),i.verb===p.open&&(await this.deferredMgr.release(e.session_id,this.deferredCallbacks()),o==="agy")){const r=this.bindingStore.get(e.session_id)?.cwd??"";r&&this.aibotHandle.sendUpdateBindingCard({session_id:e.session_id,worker_status:"ready",cwd:r,meta:this.buildAgyToolbarMeta(e.session_id)})}return}if(e.mirror_mode==="record_only"){this.aibotHandle.sendEventAck({event_id:e.event_id,session_id:e.session_id,received_at:Date.now()}),this.aibotHandle.sendEventResult({event_id:e.event_id,status:"responded",updated_at:Date.now()});return}if(this.isStaleEvent(e)){this.aibotHandle.sendEventAck({event_id:e.event_id,session_id:e.session_id,received_at:Date.now()}),this.aibotHandle.sendEventResult({event_id:e.event_id,status:"failed",code:"event_stale",msg:"event is stale and will not be processed",updated_at:Date.now()});return}if(ut.has(o)&&!this.bindingStore.get(e.session_id)?.cwd){const d=o;u.info(this.name,`[${d}] binding missing session_id=${e.session_id} event_id=${e.event_id}`),this.deferredMgr.defer(d,String(e.session_id??"").trim(),this.buildInboundEvent(e,s));const c=this.resolveBindingChannelKey(o);this.aibotHandle.sendEventAck({event_id:e.event_id,session_id:e.session_id,received_at:Date.now()}),this.aibotHandle.sendMsg({event_id:e.event_id,session_id:e.session_id,msg_type:1,content:"Session binding missing.",extra:{channel_data:{[c]:{sessionBinding:{status:"missing",reason:"binding_missing",error_code:g.bindingMissing}}}},quoted_message_id:e.msg_id});return}if((this.config.adapterType??"acp")==="acp"){const d=String(e.content??"").trim().match(/^\/(\S+)\s*(.*)/);if(d){const[,c,l]=d,m=this.pool.getSlot(e.session_id)?.adapter;if(m?.execCommand&&(m.getSupportedCommands?.()??[]).some(_=>_.name===c||_.name===`/${c}`)){this.aibotHandle.sendEventAck({event_id:e.event_id,session_id:e.session_id,received_at:Date.now()});try{const _=await m.execCommand(c,l.trim(),e.session_id);_.status==="options"&&_.data&&this.handleExecCommandOptions(e.session_id,c,_.data),this.aibotHandle.sendEventResult({event_id:e.event_id,status:_.status==="failed"?"failed":"responded",msg:_.message,updated_at:Date.now()})}catch(_){this.aibotHandle.sendEventResult({event_id:e.event_id,status:"failed",msg:_ instanceof Error?_.message:String(_),updated_at:Date.now()})}return}}}const a=this.buildInboundEvent(e,s);try{this.captureEventRuntimeConfig(a),await this.pool.deliverInboundEvent(a)}catch(r){if(this.aibotHandle.sendEventAck({event_id:e.event_id,session_id:e.session_id,received_at:Date.now()}),this.inflightEvents.delete(e.event_id),this.restartCount.delete(e.event_id),this.eventSessionIndex.delete(e.event_id),r instanceof Fe)this.aibotHandle.sendEventResult({event_id:e.event_id,status:"failed",msg:r.message,updated_at:Date.now()});else throw r}}async handleCodexSessionControlOpen(e,o){const t=o.session_id,s=e.args.trim(),i=()=>this.aibotHandle.sendEventAck({event_id:o.event_id,session_id:t,received_at:Date.now()}),n=(a,r)=>this.aibotHandle.sendEventResult({event_id:o.event_id,status:a,...r,updated_at:Date.now()});if(!s){i(),n("failed",{msg:"Usage: /grix open <working-directory>",code:g.cwdRequired});return}try{const a=await this.resolveCwdForBinding(s),r=this.bindingStore.get(t);if(r?.cwd){let d=!1;try{if(await this.resolveCwdForBinding(r.cwd)===a){i(),n("responded",{msg:`Session already bound to ${r.cwd}`});return}}catch{d=!0,u.info("bridge",`Stale codex binding for session ${t}: ${r.cwd} no longer exists, allowing rebind`)}if(!d){i(),n("failed",{msg:`Session already bound to ${r.cwd}. Rebinding is not allowed.`,code:g.rebindForbidden});return}}this.bindingStore.set(t,a),this.sessionBindings.set(t,a),this.deferredMgr.sendCodexDeferredReplayComposing(t,this.deferredCallbacks()),await this.bindSessionForPool(t,a),await this.deferredMgr.release(t,this.deferredCallbacks(),{announceComposing:!1}),this.aibotHandle.sendUpdateBindingCard({session_id:t,worker_status:"ready",cwd:a}),i(),n("responded",{msg:`Session bound to ${a}`})}catch(a){i(),n("failed",{code:g.invalidCwd,msg:a instanceof Error?a.message:String(a)})}}async handleSessionControlForPool(e,o){e.verb===p.open&&await this.bindSessionForPool(o.session_id,e.args.trim())}async replayDeferredEventsForSession(e){await this.deferredMgr.release(e,this.deferredCallbacks())}async handleSessionControlLocalActionForPool(e){if(String(e.action_type??"")!==b.sessionControl)return;const o=e.params??{};if(String(o.verb??"").trim().toLowerCase()!==p.open)return;const s=String(o.session_id??"").trim(),i=String(o.cwd??"").trim();!s||!i||await this.bindSessionForPool(s,i)}async handleCodexSessionControlLocalActionOpen(e){const o=e.params??{},t=String(o.session_id??"").trim(),s=String(o.cwd??"").trim(),i=String(o.agent_session_id??"").trim(),n=(a,r,d,c)=>{this.aibotHandle.sendLocalActionResult({action_id:e.action_id,status:a,...r!==void 0?{result:r}:{},...d?{error_code:d}:{},...c?{error_msg:c}:{}})};if(!t||!s){n("failed",void 0,g.cwdRequired,"session cwd is required");return}try{const a=await this.resolveCwdForBinding(s);this.ensureImportedAgentSession(i,a);const r=this.bindingStore.get(t);if(r?.cwd){let d=!1;try{const c=await this.resolveCwdForBinding(r.cwd);if(c===a){this.setResolvedAgentSessionId(t,i),n("ok",{outcome:"opened",binding:this.buildOpenedBindingResult(t,c)});return}}catch{d=!0,u.info("bridge",`Stale codex binding for session ${t}: ${r.cwd} no longer exists, allowing rebind`)}if(!d){n("failed",void 0,g.rebindForbidden,`Session already bound to ${r.cwd}`);return}}this.bindingStore.set(t,a),this.setResolvedAgentSessionId(t,i),this.sessionBindings.set(t,a),this.deferredMgr.sendCodexDeferredReplayComposing(t,this.deferredCallbacks()),await this.bindSessionForPool(t,a),await this.deferredMgr.release(t,this.deferredCallbacks(),{announceComposing:!1}),n("ok",{outcome:"opened",binding:this.buildOpenedBindingResult(t,a)})}catch(a){n("failed",void 0,a?.sessionControlErrorCode??g.invalidCwd,a instanceof Error?a.message:String(a))}}async handleCursorSessionControlLocalActionOpen(e){const o=e.params??{},t=String(o.session_id??"").trim(),s=String(o.cwd??"").trim(),i=String(o.agent_session_id??"").trim(),n=(a,r,d,c)=>{this.aibotHandle.sendLocalActionResult({action_id:e.action_id,status:a,...r!==void 0?{result:r}:{},...d?{error_code:d}:{},...c?{error_msg:c}:{}})};if(!t||!s){n("failed",void 0,g.cwdRequired,"session cwd is required");return}try{const a=await this.resolveCwdForBinding(s);this.ensureImportedAgentSession(i,a);const r=this.bindingStore.get(t);if(r?.cwd){let d=!1;try{const c=await this.resolveCwdForBinding(r.cwd);if(c===a){this.setResolvedAgentSessionId(t,i),n("ok",{outcome:"opened",binding:this.buildOpenedBindingResult(t,c)});return}}catch{d=!0,u.info("bridge",`Stale cursor binding for session ${t}: ${r.cwd} no longer exists, allowing rebind`)}if(!d){n("failed",void 0,g.rebindForbidden,`Session already bound to ${r.cwd}`);return}}this.bindingStore.set(t,a),this.setResolvedAgentSessionId(t,i),this.sessionBindings.set(t,a),this.deferredMgr.sendCursorDeferredReplayComposing(t,this.deferredCallbacks()),await this.bindSessionForPool(t,a),await this.deferredMgr.release(t,this.deferredCallbacks(),{announceComposing:!1}),this.aibotHandle.sendUpdateBindingCard({session_id:t,worker_status:"ready",cwd:a}),n("ok",{outcome:"opened",binding:this.buildOpenedBindingResult(t,a)})}catch(a){n("failed",void 0,a?.sessionControlErrorCode??g.invalidCwd,a instanceof Error?a.message:String(a))}}async handlePiSessionControlOpen(e,o){const t=o.session_id,s=e.args.trim(),i=()=>this.aibotHandle.sendEventAck({event_id:o.event_id,session_id:t,received_at:Date.now()}),n=(a,r)=>this.aibotHandle.sendEventResult({event_id:o.event_id,status:a,...r,updated_at:Date.now()});if(!s){i(),n("failed",{msg:"Usage: /grix open <working-directory>",code:g.cwdRequired});return}try{const a=await this.resolveCwdForBinding(s),r=this.bindingStore.get(t);if(r?.cwd){let d=!1;try{if(await this.resolveCwdForBinding(r.cwd)===a){i(),n("responded",{msg:`Session already bound to ${r.cwd}`}),this.aibotHandle.sendMsg({event_id:o.event_id,session_id:t,msg_type:1,content:`\u2705 Session already bound to \`${r.cwd}\``,quoted_message_id:o.msg_id});return}}catch{d=!0,u.info("bridge",`Stale pi binding for session ${t}: ${r.cwd} no longer exists, allowing rebind`)}if(!d){i(),n("failed",{msg:`Session already bound to ${r.cwd}. Rebinding is not allowed.`,code:g.rebindForbidden});return}}this.bindingStore.set(t,a),this.sessionBindings.set(t,a),await this.deferredMgr.release(t,this.deferredCallbacks()),this.aibotHandle.sendUpdateBindingCard({session_id:t,worker_status:"ready",cwd:a}),i(),n("responded",{msg:`Session bound to ${a}`}),this.aibotHandle.sendMsg({event_id:o.event_id,session_id:t,msg_type:1,content:`\u2705 Working directory bound: \`${a}\``,quoted_message_id:o.msg_id})}catch(a){i(),n("failed",{code:g.invalidCwd,msg:a instanceof Error?a.message:String(a)})}}async handlePiSessionControlRestart(e){const o=e.session_id,t=()=>this.aibotHandle.sendEventAck({event_id:e.event_id,session_id:o,received_at:Date.now()}),s=(a,r)=>this.aibotHandle.sendEventResult({event_id:e.event_id,status:a,...r,updated_at:Date.now()}),n=this.bindingStore.get(o)?.cwd??"";if(!n){t(),s("failed",{msg:"session binding was not found",code:g.bindingMissing});return}t(),await this.pool.removeSlot(o).catch(()=>{}),this.aibotHandle.sendUpdateBindingCard({session_id:o,worker_status:"ready",cwd:n}),s("responded",{msg:`Session worker restarted for ${n}`})}async handlePiSessionControlRestartLocalAction(e){const o=e.params??{},t=String(o.session_id??"").trim(),i=(t?this.bindingStore.get(t):void 0)?.cwd??"",n=(a,r,d,c)=>{this.aibotHandle.sendLocalActionResult({action_id:e.action_id,status:a,...r!==void 0?{result:r}:{},...d?{error_code:d}:{},...c?{error_msg:c}:{}})};if(!t){n("failed",void 0,"session_id_required","session_id is required for restart");return}if(!i){n("failed",void 0,g.bindingMissing,"Session binding missing. Open a workspace first.");return}await this.pool.removeSlot(t).catch(()=>{}),this.aibotHandle.sendUpdateBindingCard({session_id:t,worker_status:"ready",cwd:i}),n("ok",{outcome:"restarted",binding:{aibotSessionId:t,cwd:i,workerStatus:"ready"}})}async bindSessionForPool(e,o){const t=String(o??"").trim();if(!e||!t)return;const s=this.pool.getOrCreateSlot(e);if(!s||(s.startPromise&&await s.startPromise,!s.adapter))return;const i=s.adapter instanceof A?s.adapter:null;if(!i?.hasSessionBinding)return;const n=await this.resolveCwdForBinding(t);i.announceDeferredComposing(e),await i.bindSession(e,n),this.sessionBindings.set(e,n),this.sessionScanCache.invalidate(),i.replayDeferredEvents(e)}deferredCallbacks(){return{captureEventRuntimeConfig:e=>this.captureEventRuntimeConfig(e),deliverInboundEvent:e=>this.pool.deliverInboundEvent(e),sendEventResult:(e,o,t)=>this.sendEventResultWithCleanup(e,o,t),sendSessionComposing:(e,o,t)=>{const s={};o&&(s.ttl_ms=t?.ttlMs??3e4,t?.activity&&(s.activity=t.activity)),this.aibotHandle.sendSessionActivitySet({session_id:e,kind:"composing",active:o,...s})}}}async handleOpenHumanSessionControlOpen(e,o){const t=()=>this.aibotHandle.sendEventAck({event_id:o.event_id,session_id:o.session_id,received_at:Date.now()});try{await this.resolveCwdForBinding(e.args.trim()),await this.handleSessionControlForPool(e,o),q(e,o,this.sessionControlCtx(o.session_id),this.sessionControlSenders()),await this.deferredMgr.release(o.session_id,this.deferredCallbacks())}catch(s){t(),this.aibotHandle.sendEventResult({event_id:o.event_id,status:"failed",code:s?.cwdErrorCode,msg:s instanceof Error?s.message:String(s),updated_at:Date.now()})}}async handleCodeWhaleSessionControlOpen(e,o){const t=o.session_id,s=e.args.trim(),i=()=>this.aibotHandle.sendEventAck({event_id:o.event_id,session_id:t,received_at:Date.now()}),n=(a,r)=>this.aibotHandle.sendEventResult({event_id:o.event_id,status:a,...r,updated_at:Date.now()});if(!s){i(),n("failed",{msg:"Usage: /grix open <working-directory>",code:g.cwdRequired});return}try{const a=await this.resolveCwdForBinding(s),r=this.bindingStore.get(t);if(r?.cwd){if(await this.resolveCwdForBinding(r.cwd)!==a){i(),n("failed",{msg:`Session already bound to ${r.cwd}. Rebinding is not allowed.`,code:g.rebindForbidden});return}i(),n("responded",{msg:`Session already bound to ${r.cwd}`});return}this.bindingStore.set(t,a),this.sessionBindings.set(t,a),await this.bindSessionForPool(t,a),await this.deferredMgr.release(t,this.deferredCallbacks(),{announceComposing:!1}),this.aibotHandle.sendUpdateBindingCard({session_id:t,worker_status:"ready",cwd:a}),i(),n("responded",{msg:`Session bound to ${a}`})}catch(a){i(),n("failed",{code:g.invalidCwd,msg:a instanceof Error?a.message:String(a)})}}async handleCodeWhaleSessionControlLocalActionOpen(e){const o=e.params??{},t=String(o.session_id??"").trim(),s=String(o.cwd??"").trim(),i=String(o.agent_session_id??"").trim(),n=(a,r,d,c)=>{this.aibotHandle.sendLocalActionResult({action_id:e.action_id,status:a,...r!==void 0?{result:r}:{},...d?{error_code:d}:{},...c?{error_msg:c}:{}})};if(!t||!s){n("failed",void 0,g.cwdRequired,"session cwd is required");return}try{const a=await this.resolveCwdForBinding(s);this.ensureImportedAgentSession(i,a);const r=this.bindingStore.get(t);if(r?.cwd){const d=await this.resolveCwdForBinding(r.cwd);if(d!==a){n("failed",void 0,g.rebindForbidden,`Session already bound to ${r.cwd}`);return}this.setResolvedAgentSessionId(t,i),n("ok",{outcome:"opened",binding:this.buildOpenedBindingResult(t,d)});return}this.bindingStore.set(t,a),this.setResolvedAgentSessionId(t,i),this.sessionBindings.set(t,a),await this.bindSessionForPool(t,a),await this.deferredMgr.release(t,this.deferredCallbacks(),{announceComposing:!1}),this.aibotHandle.sendUpdateBindingCard({session_id:t,worker_status:"ready",cwd:a}),n("ok",{outcome:"opened",binding:this.buildOpenedBindingResult(t,a)})}catch(a){n("failed",void 0,a?.sessionControlErrorCode??g.invalidCwd,a instanceof Error?a.message:String(a))}}normalizeClaudeModeId(e){return String(e??"").trim().toLowerCase()===C.approval?C.approval:C.fullAuto}handleExecCommandOptions(e,o,t){const s=Array.isArray(t?.options)?t.options:[];if(s.length===0)return;const n=this.bindingStore.get(e)?.cwd??"",a={};if(o==="model"){a.available_models=s.map(d=>({id:d.id,displayName:d.label}));const r=s.find(d=>d.current);r&&(a.model_id=r.id)}else if(o==="mode"){a.available_modes=s.map(d=>({id:d.id,displayName:d.label}));const r=s.find(d=>d.current);r&&(a.mode_id=r.id)}else a[`${o}_options`]=s;this.aibotHandle.sendUpdateBindingCard({session_id:e,worker_status:"ready",cwd:n,meta:a})}resolveSessionModelId(e){const o=this.bindingStore.getModelId(e);if(o)return o;const t=String(this.globalConfigStore?.get(this.name)?.modelId??"").trim();if(t)return this.bindingStore.setModelId(e,t),t}resolveCodexSessionModelId(e){const o=this.bindingStore.getCodexModelId(e);if(o)return o;const t=String(this.globalConfigStore?.get(this.name)?.codexModelId??"").trim();if(t)return this.bindingStore.setCodexModelId(e,t),t}buildAgyToolbarMeta(e){if(!this.bindingStore.get(e))return;const t=ee(this.config.agent.command),s=t.length>0?t[0].id:"";let i=this.resolveSessionModelId(e)||s;t.length>0&&!t.some(a=>a.id===i)&&(i=s);const n=te();return{model_id:i,currentModelId:i,available_models:t.map(a=>({id:a.id,displayName:a.displayName})),...n.plan!==void 0&&{plan:n.plan},...n.quota_exhausted!==void 0&&{quota_exhausted:n.quota_exhausted},...n.quota_reset_at!==void 0&&{quota_reset_at:n.quota_reset_at},...n.available_credits!==void 0&&{available_credits:n.available_credits}}}async handleAgySetModel(e,o){const t=e.params??{},s=String(t.model_id??t.modelId??"").trim();if(!o){this.aibotHandle.sendLocalActionResult({action_id:e.action_id,status:"failed",error_code:"session_id_required",error_msg:"session_id is required for set_model"});return}if(!s){this.aibotHandle.sendLocalActionResult({action_id:e.action_id,status:"failed",error_code:"set_model_invalid",error_msg:"model_id is required"});return}const i=this.bindingStore.get(o);if(!i?.cwd){this.aibotHandle.sendLocalActionResult({action_id:e.action_id,status:"failed",error_code:g.bindingMissing,error_msg:"session binding was not found"});return}i.modelId!==s&&(this.bindingStore.setModelId(o,s),this.globalConfigStore?.set(this.name,{modelId:s})),this.aibotHandle.sendUpdateBindingCard({session_id:o,worker_status:"ready",cwd:i.cwd,meta:this.buildAgyToolbarMeta(o)}),this.aibotHandle.sendLocalActionResult({action_id:e.action_id,status:"ok",result:{outcome:"model_set",model_id:s,binding:{cwd:i.cwd,model_id:s}}})}buildClaudeToolbarMeta(e){const o=this.bindingStore.get(e);if(!o)return;const t=B(),s=t.length>0?t[0].id:"";let i=this.resolveSessionModelId(e)||s;t.length>0&&!t.some(l=>l.id===i)&&(i=s);const n=this.normalizeClaudeModeId(o.modeId),a={model_id:i,mode_id:n,currentModelId:i,currentModeId:n,available_models:t.map(l=>({id:l.id,displayName:l.displayName}))},r=this.pool.getSlot(e),d=r?.adapter instanceof T?r.adapter.getSessionState():null;(d?.rateLimits?.fiveHour||d?.rateLimits?.sevenDay)&&(this.cachedClaudeRateLimitState=d);const c=d??this.getFreshClaudeRateLimitState();if(c){const l=c.rateLimits;l&&(l.fiveHour||l.sevenDay)&&(a.rate_limits={...l.fiveHour?{fiveHour:l.fiveHour}:{},...l.sevenDay?{sevenDay:l.sevenDay}:{},sampledAt:c.sampledAt||Date.now()})}if(d){const l=d.contextWindow;l.usedPercentage!=null&&(a.context_window={usedPercentage:l.usedPercentage,remainingPercentage:l.remainingPercentage})}if(!a.rate_limits&&this.cachedProviderQuota?.success){this.isRateLimitsCacheFresh(this.cachedProviderQuotaSampledAtMs)||this.maybeQueryProviderQuota().catch(()=>{});const l=this.providerQuotaToRateLimits(this.cachedProviderQuota);l&&(a.rate_limits=l),u.info(this.name,`[toolbar-meta] provider quota fallback: hasCached=${!!this.cachedProviderQuota} fromProvider=${JSON.stringify(l)}`)}return a.rate_limits&&u.info(this.name,`[toolbar-meta] rate_limits included: ${JSON.stringify(a.rate_limits)}`),a}providerQuotaToCodexRateLimits(e){const o=e.tiers.find(i=>i.name==="five_hour"),t=e.tiers.find(i=>i.name==="weekly_limit"),s=i=>i||null;if(o||t){const i={credits:{hasCredits:!0,unlimited:!1,balance:null},sampledAt:this.cachedProviderQuotaSampledAtMs??Date.now()};let n=0,a=0,r=0,d=0;return o&&(i.primary={usedPercent:o.usedPercent,windowMinutes:300,resetsAt:s(o.resetsAt)},n=o.usedPercent,a=300),t&&(i.secondary={usedPercent:t.usedPercent,windowMinutes:10080,resetsAt:s(t.resetsAt)},r=t.usedPercent,d=10080),{rateLimits:i,primaryPercent:n,secondaryPercent:r,primaryWindowMin:a,secondaryWindowMin:d}}if(e.balance){const i=e.balance,n=i.total&&i.total>0?Math.min(100,(i.used??i.total-i.remaining)/i.total*100):0;return{rateLimits:{primary:{usedPercent:n,windowMinutes:0,resetsAt:null},secondary:{usedPercent:0,windowMinutes:0,resetsAt:null},credits:{hasCredits:!0,unlimited:!1,balance:i.remaining},sampledAt:this.cachedProviderQuotaSampledAtMs??Date.now()},primaryPercent:n,secondaryPercent:0,primaryWindowMin:0,secondaryWindowMin:0}}return null}providerQuotaToRateLimits(e){const o=i=>{if(!i)return 0;const n=Date.parse(i);return Number.isFinite(n)?Math.floor(n/1e3):0},t=e.tiers.find(i=>i.name==="five_hour"),s=e.tiers.find(i=>i.name==="weekly_limit");if(t||s)return{...t?{fiveHour:{usedPercentage:t.usedPercent,resetsAt:o(t.resetsAt)}}:{},...s?{sevenDay:{usedPercentage:s.usedPercent,resetsAt:o(s.resetsAt)}}:{},sampledAt:this.cachedProviderQuotaSampledAtMs??Date.now()};if(e.balance){const i=e.balance;return{credit:{remaining:i.remaining,total:i.total,used:i.used,unit:i.unit,resetsAt:i.resetsAt?o(i.resetsAt):0},planName:e.planName,sampledAt:this.cachedProviderQuotaSampledAtMs??Date.now()}}return null}async resolveCwdForBinding(e){const o=String(e??"").trim();if(process.platform!=="win32"&&(/^[a-zA-Z]:[\\/]/.test(o)||/^\\\\/.test(o))){const i=new Error(`Specified path is not valid on this host: ${o}`);throw i.cwdErrorCode=g.invalidCwd,i}const t=P.resolve(o);let s;try{s=await M(t)}catch(i){const n=String(i?.code??"");if(n==="ENOENT"){const a=new Error(`Specified path does not exist: ${t}`);throw a.cwdErrorCode=g.invalidCwd,a}if(n==="EACCES"||n==="EPERM"){const a=new Error("Specified path is not accessible.");throw a.cwdErrorCode=g.invalidCwd,a}throw i}if(!s.isDirectory()){const i=new Error("Specified path is not a directory.");throw i.cwdErrorCode=g.invalidCwd,i}try{return await G(t)}catch{return t}}getClaudeWorkerStatus(e){const o=this.pool.getSlot(e);return o?o.state==="starting"?"starting":o.state==="stopped"?"stopped":o.adapter.getStatus().busy?"busy":"ready":"stopped"}refreshClaudeWorkerStatusCard(e,o){const t=this.getClaudeWorkerStatus(e);return this.claudeWorkerStatus.set(e,t),this.aibotHandle.sendUpdateBindingCard({session_id:e,worker_status:t,cwd:o,meta:this.buildClaudeToolbarMeta(e)}),t}reportSessionSkills(e){const o=ne(this.config.adapterType,this.config.aibot.clientType),t=this.bindingStore.get(e)?.cwd||this.sessionBindings.get(e)||void 0,s=o==="kiro"?t:t||process.cwd();let i;try{i=se({mode:o,projectDir:s})}catch{return}if(i.length===0)return;const n=JSON.stringify(i.map(a=>`${a.source}:${a.name}`));if(n!==this.lastReportedSkillsHash){this.lastReportedSkillsHash=n;try{this.aibotHandle.sendSkillsUpdate({skills:i})}catch{}}}async ensureSlotStarted(e,o=6e4){const t=this.pool.getOrCreateSlot(e);if(!t)throw new Error("Failed to allocate session slot");t.startPromise&&(u.info(this.name,`ensureSlotStarted: awaiting startPromise for session=${e}`),await Promise.race([t.startPromise,new Promise((s,i)=>setTimeout(()=>i(new Error(`ensureSlotStarted timeout (${o}ms) session=${e}`)),o))]),u.info(this.name,`ensureSlotStarted: startPromise resolved for session=${e}`))}resolveOrphanTitle(e){if(!e||!this.reasonixTitleScan)return;const o=ve(e);if(!o)return;const t=this.reasonixTitleScan.get().filter(s=>s.stamp===o);return t.length===1?t[0].title:void 0}resolveAgentSessionId(e){switch(this.config.adapterType??"acp"){case"claude":return e.claudeSessionId;case"codex":return e.codexThreadId;case"pi":return e.piSessionPath;case"codewhale":return e.codewhaleThreadId;case"agy":return e.agyConversationId;default:return e.acpSessionId}}providerKeyForAdapter(){const e=this.config.adapterType??"acp";switch(e){case"claude":case"codex":case"pi":case"codewhale":return e;default:return"acp"}}setResolvedAgentSessionId(e,o){const t=String(e??"").trim(),s=String(o??"").trim();if(!t||!s)return;switch(this.config.adapterType??"acp"){case"claude":this.bindingStore.setClaudeSessionId(t,s);break;case"codex":this.bindingStore.setCodexThreadId(t,s);break;case"pi":this.bindingStore.setPiSessionPath(t,s);break;case"codewhale":this.bindingStore.setCodeWhaleThreadId(t,s);break;case"agy":this.bindingStore.setAgyConversationId(t,s);break;default:this.bindingStore.setAcpSessionId(t,s);break}this.sessionScanCache.invalidate()}normalizePathForCompare(e){const o=String(e??"").trim();if(!o)return"";const t=P.resolve(o);return process.platform==="win32"?t.toLowerCase():t}ensureImportedAgentSession(e,o){const t=String(e??"").trim();if(!t)return;const s=this.normalizePathForCompare(o);let i="";const n=this.config.adapterType??"acp";if(n==="codex"?i=this.sessionScanCache.get().find(d=>d.threadId===t)?.cwd??"":n==="claude"?i=this.sessionScanCache.get().find(d=>d.sessionId===t)?.cwd??"":n==="acp"&&(i=this.sessionScanCache.get().find(d=>d.sessionId===t)?.cwd??""),!i){for(const[,r]of this.bindingStore.entries())if(this.resolveAgentSessionId(r)===t){i=r.cwd??"";break}}if(!i){const r=new Error(`agent session not found: ${t}`);throw r.sessionControlErrorCode=g.invalidAgentSession,r}const a=this.normalizePathForCompare(i);if(a&&s&&a!==s){const r=new Error(`agent session cwd mismatch: expected ${o}, got ${i}`);throw r.sessionControlErrorCode=g.invalidAgentSession,r}}buildOpenedBindingResult(e,o,t="ready"){const s=this.bindingStore.get(e),i=s?String(this.resolveAgentSessionId(s)??"").trim():"",n={aibotSessionId:e,providerKey:this.providerKeyForAdapter(),cwd:o,workerStatus:t};return i&&(n.bindingId=i,n.agentSessionId=i),n}hasDiskScanner(){const e=this.config.adapterType??"acp";return e==="codex"||e==="claude"||e==="acp"}async handleListSessionsTextCommand(e){this.aibotHandle.sendEventAck({event_id:e.event_id,session_id:e.session_id,received_at:Date.now()});const o=this.config.adapterType??"acp",t=new Map;for(const r of this.pool.getAllSlots())t.set(r.sessionId,r);const s=Array.from(this.bindingStore.entries()),i=new Map;for(const[r,d]of s){const c=this.resolveAgentSessionId(d);if(c){const l=t.get(r),h=l?l.adapter.getStatus().busy?"busy":"ready":"inactive";i.set(c,{aibotSessionId:r,workerStatus:h})}}const n=[],a=new Set;if(o==="codex"){const r=this.sessionScanCache.get();for(const d of r){a.add(d.threadId);const c=i.get(d.threadId);d.title&&n.push(` Title: ${d.title}`),n.push(` Agent: ${d.threadId}`),c&&n.push(` AIBot: ${c.aibotSessionId}`),n.push(` CWD: ${d.cwd||"-"}`),n.push(` State: ${c?.workerStatus??(d.archived?"archived":"inactive")}`),n.push(` Created: ${new Date(d.createdAt).toISOString()}`),n.push(` Updated: ${new Date(d.updatedAt).toISOString()}`),n.push("---")}}else if(o==="claude"){const r=this.sessionScanCache.get();for(const d of r){a.add(d.sessionId);const c=i.get(d.sessionId);d.title&&n.push(` Title: ${d.title}`),n.push(` Agent: ${d.sessionId}`),c&&n.push(` AIBot: ${c.aibotSessionId}`),n.push(` CWD: ${d.cwd||"-"}`),n.push(` State: ${c?.workerStatus??"inactive"}`),n.push(` Updated: ${new Date(d.updatedAt).toISOString()}`),n.push("---")}}else if(o==="acp"){const r=this.sessionScanCache.get();for(const d of r){a.add(d.sessionId);const c=i.get(d.sessionId);d.title&&n.push(` Title: ${d.title}`),n.push(` Agent: ${d.sessionId}`),c&&n.push(` AIBot: ${c.aibotSessionId}`),n.push(` CWD: ${d.cwd||"-"}`),n.push(` State: ${c?.workerStatus??"inactive"}`),n.push(` Updated: ${new Date(d.updatedAt).toISOString()}`),n.push("---")}}else if(o==="pi"){const r=this.sessionScanCache.get();for(const d of r){a.add(d.sessionPath);const c=i.get(d.sessionPath);d.title&&n.push(` Title: ${d.title}`),n.push(` Agent: ${d.sessionPath}`),c&&n.push(` AIBot: ${c.aibotSessionId}`),n.push(` CWD: ${d.cwd||"-"}`),n.push(` State: ${c?.workerStatus??"inactive"}`),n.push(` Updated: ${new Date(d.updatedAt).toISOString()}`),n.push("---")}}else if(o==="codewhale"||o==="cursor"||o==="opencode"){const r=this.sessionScanCache.get();for(const d of r){a.add(d.sessionId);const c=i.get(d.sessionId);d.title&&n.push(` Title: ${d.title}`),n.push(` Agent: ${d.sessionId}`),c&&n.push(` AIBot: ${c.aibotSessionId}`),n.push(` CWD: ${d.cwd||"-"}`),n.push(` State: ${c?.workerStatus??"inactive"}`),n.push(` Updated: ${new Date(d.updatedAt).toISOString()}`),n.push("---")}}for(const[r,d]of s){const c=this.resolveAgentSessionId(d);if(c&&a.has(c))continue;const l=t.get(r),h=l?l.adapter.getStatus().busy?"busy":"ready":"closed",m=this.resolveOrphanTitle(c)??(c?c.slice(0,8)+"\u2026":r.slice(0,8)+"\u2026");n.push(` Title: ${m}`),n.push(` AIBot: ${r}`),c&&n.push(` Agent: ${c}`),n.push(` CWD: ${d.cwd??"-"}`),n.push(` State: ${h}`),n.push(` Updated: ${new Date(d.updatedAt).toISOString()}`),n.push("---")}if(n.length===0){this.aibotHandle.sendEventResult({event_id:e.event_id,status:"responded",msg:"No sessions found.",updated_at:Date.now()});return}this.aibotHandle.sendEventResult({event_id:e.event_id,status:"responded",msg:`Sessions (${n.filter(r=>r==="---").length}):
14
14
  ${n.join(`
@@ -1 +1 @@
1
- const a=25e3;class c{config;callbacks;running=new Map;queued=[];timers=new Map;composingTimers=new Map;adapterDoneEventIds=new Set;pauseReasons=new Set;get ready(){return this.pauseReasons.size===0}constructor(e,t){this.config=e,this.callbacks=t}pause(e){this.pauseReasons.add(e)}resume(e){this.pauseReasons.delete(e)&&this.ready&&this.drainNext()}submit(e){return this.running.has(e.event_id)||this.queued.some(t=>t.event_id===e.event_id)?"accepted":this.ready&&this.running.size<this.config.maxConcurrent?(this.startRunning(e),"accepted"):this.config.maxQueued<=0||this.queued.length>=this.config.maxQueued?(this.callbacks.onRejected(e,"queue full"),"rejected"):(this.enqueue(e),"accepted")}cancel(e){const t=this.queued.findIndex(i=>i.event_id===e);if(t>=0){if(!this.config.cancelableQueued)return!1;const[i]=this.queued.splice(t,1);return this.clearTimer(e),this.callbacks.onStateChange(e,i.session_id,"canceled",{reason:"canceled by user"}),this.broadcastQueuePositions(),this.drainNext(),this.checkStopComposing(i.session_id),!0}return this.running.has(e)&&this.config.cancelableRunning?(this.callbacks.onCancelRunning(e),!0):!1}removeQueued(e){const t=this.queued.findIndex(n=>n.event_id===e);if(t<0)return!1;const[i]=this.queued.splice(t,1);return this.clearTimer(e),this.broadcastQueuePositions(),this.checkStopComposing(i.session_id),!0}complete(e){const i=this.running.get(e)?.session_id;this.running.delete(e),this.adapterDoneEventIds.delete(e),this.clearTimer(e),queueMicrotask(()=>this.drainNext()),i&&this.checkStopComposing(i)}clear(e,t="queue cleared"){const i=[],n=[];for(const u of this.queued)u.session_id===e?(this.clearTimer(u.event_id),this.callbacks.onStateChange(u.event_id,e,"canceled",{reason:t}),i.push(u.event_id)):n.push(u);return this.queued=n,i.length>0&&this.broadcastQueuePositions(),this.checkStopComposing(e),i}drainQueuedForSession(e){const t=[],i=[];for(const n of this.queued)n.session_id===e?(this.clearTimer(n.event_id),t.push(n)):i.push(n);return this.queued=i,t}snapshot(e){const t=[...this.running.values()].filter(s=>s.session_id===e),i=t.map(s=>s.event_id),n=t.map(s=>({event_id:s.event_id,content_preview:this.buildQueueItemTitle(s.content),title:this.buildQueueItemTitle(s.content),summary:this.buildQueueItemTitle(s.content)})),u=this.queued.flatMap((s,o)=>s.session_id===e?[{event_id:s.event_id,position:o+1,content_preview:this.buildQueueItemTitle(s.content),title:this.buildQueueItemTitle(s.content),summary:this.buildQueueItemTitle(s.content)}]:[]);return{running:i,running_items:n,queued:u}}hasCapacity(){return this.running.size<this.config.maxConcurrent}get runningCount(){return this.running.size}get queuedCount(){return this.queued.length}destroy(){for(const e of this.timers.values())clearTimeout(e);this.timers.clear();for(const e of this.composingTimers.values())clearInterval(e);this.composingTimers.clear(),this.queued=[],this.running.clear(),this.pauseReasons.clear()}enqueue(e){this.queued.push(e);const t=this.queued.length;if(this.callbacks.onStateChange(e.event_id,e.session_id,"queued",{queue_position:t,queue_total:t,actions:this.config.cancelableQueued?[{type:"cancel"}]:[],content_preview:this.buildQueueItemTitle(e.content)}),this.config.queueTimeoutMs>0){const i=setTimeout(()=>{this.timeoutEvent(e.event_id)},this.config.queueTimeoutMs);i.unref(),this.timers.set(e.event_id,i)}this.ensureComposing(e.session_id)}timeoutEvent(e){const t=this.queued.findIndex(n=>n.event_id===e);if(t<0)return;const[i]=this.queued.splice(t,1);this.timers.delete(e),this.callbacks.onStateChange(e,i.session_id,"failed",{reason:"queue timeout"}),this.broadcastQueuePositions(),this.drainNext(),this.checkStopComposing(i.session_id)}startRunning(e){this.running.set(e.event_id,e),this.callbacks.onStateChange(e.event_id,e.session_id,"running",{actions:this.config.cancelableRunning?[{type:"stop"}]:[],content_preview:this.buildQueueItemTitle(e.content)}),this.ensureComposing(e.session_id),this.callbacks.onDeliver(e)}drainNext(){for(;this.ready&&this.running.size<this.config.maxConcurrent&&this.queued.length>0;){const e=this.queued.shift();this.clearTimer(e.event_id),this.startRunning(e)}this.queued.length>0&&this.broadcastQueuePositions()}broadcastQueuePositions(){const e=this.queued.length;for(let t=0;t<e;t++){const i=this.queued[t];this.callbacks.onStateChange(i.event_id,i.session_id,"queued",{queue_position:t+1,queue_total:e,actions:this.config.cancelableQueued?[{type:"cancel"}]:[],content_preview:this.buildQueueItemTitle(i.content)})}}clearTimer(e){const t=this.timers.get(e);t&&(clearTimeout(t),this.timers.delete(e))}buildQueueItemTitle(e){const t=String(e??"").replace(/\s+/g," ").trim();return t?t.length>64?`${t.slice(0,64)}...`:t:"Message"}ensureComposing(e){if(!this.callbacks.onComposing||this.composingTimers.has(e))return;this.callbacks.onComposing(e,!0,this.getFirstRunningEventId(e));const t=setInterval(()=>{this.sessionHasEvents(e)?this.callbacks.onComposing(e,!0,this.getFirstRunningEventId(e)):this.stopComposing(e)},25e3);t.unref(),this.composingTimers.set(e,t)}checkStopComposing(e){this.sessionHasEvents(e)||this.stopComposing(e)}markAdapterDone(e){const t=this.running.get(e);t&&(this.adapterDoneEventIds.add(e),this.sessionHasEvents(t.session_id)||this.stopComposing(t.session_id))}stopComposing(e){const t=this.composingTimers.get(e);t&&(clearInterval(t),this.composingTimers.delete(e)),this.callbacks.onComposing?.(e,!1)}sessionHasEvents(e){for(const t of this.running.values())if(t.session_id===e&&!this.adapterDoneEventIds.has(t.event_id))return!0;return this.queued.some(t=>t.session_id===e)}getFirstRunningEventId(e){for(const t of this.running.values())if(t.session_id===e&&!this.adapterDoneEventIds.has(t.event_id))return t.event_id}}export{c as EventQueue};
1
+ const h=25e3;class d{config;callbacks;running=new Map;queued=[];timers=new Map;composingTimers=new Map;adapterDoneEventIds=new Set;pauseReasons=new Set;get ready(){return this.pauseReasons.size===0}constructor(e,t){this.config=e,this.callbacks=t}pause(e){this.pauseReasons.add(e)}resume(e){this.pauseReasons.delete(e)&&this.ready&&this.drainNext()}submit(e){return this.running.has(e.event_id)||this.queued.some(t=>t.event_id===e.event_id)?"accepted":this.ready&&this.running.size<this.config.maxConcurrent?(this.startRunning(e),"accepted"):this.config.maxQueued<=0||this.queued.length>=this.config.maxQueued?(this.callbacks.onRejected(e,"queue full"),"rejected"):(this.enqueue(e),"accepted")}cancel(e){const t=this.queued.findIndex(i=>i.event_id===e);if(t>=0){if(!this.config.cancelableQueued)return!1;const[i]=this.queued.splice(t,1);return this.clearTimer(e),this.callbacks.onStateChange(e,i.session_id,"canceled",{reason:"canceled by user"}),this.broadcastQueuePositions(),this.drainNext(),this.checkStopComposing(i.session_id),!0}return this.running.has(e)&&this.config.cancelableRunning?(this.callbacks.onCancelRunning(e),!0):!1}removeQueued(e){const t=this.queued.findIndex(n=>n.event_id===e);if(t<0)return!1;const[i]=this.queued.splice(t,1);return this.clearTimer(e),this.broadcastQueuePositions(),this.checkStopComposing(i.session_id),!0}complete(e){const i=this.running.get(e)?.session_id;this.running.delete(e),this.adapterDoneEventIds.delete(e),this.clearTimer(e),queueMicrotask(()=>this.drainNext()),i&&this.checkStopComposing(i)}clear(e,t="queue cleared"){const i=[],n=[];for(const o of this.queued)o.session_id===e?(this.clearTimer(o.event_id),this.callbacks.onStateChange(o.event_id,e,"canceled",{reason:t}),i.push(o.event_id)):n.push(o);return this.queued=n,i.length>0&&this.broadcastQueuePositions(),this.checkStopComposing(e),i}reorder(e,t){const i=[],n=[];if(this.queued.forEach((u,r)=>{u.session_id===e&&(i.push(r),n.push(u))}),n.length===0)return[];const o=new Map(n.map(u=>[u.event_id,u])),s=[];for(const u of t){const r=o.get(u);r&&(o.delete(u),s.push(r))}for(const u of n)o.has(u.event_id)&&s.push(u);return s.some((u,r)=>u!==n[r])&&(i.forEach((u,r)=>{this.queued[u]=s[r]}),this.broadcastQueuePositions()),s.map(u=>u.event_id)}drainQueuedForSession(e){const t=[],i=[];for(const n of this.queued)n.session_id===e?(this.clearTimer(n.event_id),t.push(n)):i.push(n);return this.queued=i,t}snapshot(e){const t=[...this.running.values()].filter(s=>s.session_id===e),i=t.map(s=>s.event_id),n=t.map(s=>({event_id:s.event_id,content_preview:this.buildQueueItemTitle(s.content),title:this.buildQueueItemTitle(s.content),summary:this.buildQueueItemTitle(s.content)})),o=this.queued.flatMap((s,c)=>s.session_id===e?[{event_id:s.event_id,position:c+1,content_preview:this.buildQueueItemTitle(s.content),title:this.buildQueueItemTitle(s.content),summary:this.buildQueueItemTitle(s.content)}]:[]);return{running:i,running_items:n,queued:o}}hasCapacity(){return this.running.size<this.config.maxConcurrent}get runningCount(){return this.running.size}get queuedCount(){return this.queued.length}destroy(){for(const e of this.timers.values())clearTimeout(e);this.timers.clear();for(const e of this.composingTimers.values())clearInterval(e);this.composingTimers.clear(),this.queued=[],this.running.clear(),this.pauseReasons.clear()}enqueue(e){this.queued.push(e);const t=this.queued.length;if(this.callbacks.onStateChange(e.event_id,e.session_id,"queued",{queue_position:t,queue_total:t,actions:this.config.cancelableQueued?[{type:"cancel"}]:[],content_preview:this.buildQueueItemTitle(e.content)}),this.config.queueTimeoutMs>0){const i=setTimeout(()=>{this.timeoutEvent(e.event_id)},this.config.queueTimeoutMs);i.unref(),this.timers.set(e.event_id,i)}this.ensureComposing(e.session_id)}timeoutEvent(e){const t=this.queued.findIndex(n=>n.event_id===e);if(t<0)return;const[i]=this.queued.splice(t,1);this.timers.delete(e),this.callbacks.onStateChange(e,i.session_id,"failed",{reason:"queue timeout"}),this.broadcastQueuePositions(),this.drainNext(),this.checkStopComposing(i.session_id)}startRunning(e){this.running.set(e.event_id,e),this.callbacks.onStateChange(e.event_id,e.session_id,"running",{actions:this.config.cancelableRunning?[{type:"stop"}]:[],content_preview:this.buildQueueItemTitle(e.content)}),this.ensureComposing(e.session_id),this.callbacks.onDeliver(e)}drainNext(){for(;this.ready&&this.running.size<this.config.maxConcurrent&&this.queued.length>0;){const e=this.queued.shift();this.clearTimer(e.event_id),this.startRunning(e)}this.queued.length>0&&this.broadcastQueuePositions()}broadcastQueuePositions(){const e=this.queued.length;for(let t=0;t<e;t++){const i=this.queued[t];this.callbacks.onStateChange(i.event_id,i.session_id,"queued",{queue_position:t+1,queue_total:e,actions:this.config.cancelableQueued?[{type:"cancel"}]:[],content_preview:this.buildQueueItemTitle(i.content)})}}clearTimer(e){const t=this.timers.get(e);t&&(clearTimeout(t),this.timers.delete(e))}buildQueueItemTitle(e){const t=String(e??"").replace(/\s+/g," ").trim();return t?t.length>64?`${t.slice(0,64)}...`:t:"Message"}ensureComposing(e){if(!this.callbacks.onComposing||this.composingTimers.has(e))return;this.callbacks.onComposing(e,!0,this.getFirstRunningEventId(e));const t=setInterval(()=>{this.sessionHasEvents(e)?this.callbacks.onComposing(e,!0,this.getFirstRunningEventId(e)):this.stopComposing(e)},25e3);t.unref(),this.composingTimers.set(e,t)}checkStopComposing(e){this.sessionHasEvents(e)||this.stopComposing(e)}markAdapterDone(e){const t=this.running.get(e);t&&(this.adapterDoneEventIds.add(e),this.sessionHasEvents(t.session_id)||this.stopComposing(t.session_id))}stopComposing(e){const t=this.composingTimers.get(e);t&&(clearInterval(t),this.composingTimers.delete(e)),this.callbacks.onComposing?.(e,!1)}sessionHasEvents(e){for(const t of this.running.values())if(t.session_id===e&&!this.adapterDoneEventIds.has(t.event_id))return!0;return this.queued.some(t=>t.session_id===e)}getFirstRunningEventId(e){for(const t of this.running.values())if(t.session_id===e&&!this.adapterDoneEventIds.has(t.event_id))return t.event_id}}export{d as EventQueue};
@@ -1 +1 @@
1
- import{readJSONFile as o,writeJSONFileAtomic as e}from"../util/json-file.js";function n(t){const r=o(t);return r&&typeof r=="object"&&"owners"in r&&Array.isArray(r.owners)?r.owners.filter(i=>typeof i=="string"&&i.trim().length>0):[]}async function s(t,r){await e(t,{owners:r})}export{n as readAllowlist,s as writeAllowlist};
1
+ import{readJSONFile as o,writeJSONFileAtomic as i}from"../util/json-file.js";function n(t){const r=o(t);return r&&typeof r=="object"&&"owners"in r&&Array.isArray(r.owners)?r.owners.filter(e=>typeof e=="string"&&e.trim().length>0):[]}async function s(t,r){await i(t,{owners:r})}export{n as readAllowlist,s as writeAllowlist};
@@ -1,2 +1,2 @@
1
- import{EventEmitter as v}from"node:events";import{randomUUID as b}from"node:crypto";import p from"node:os";import m from"ws";import{log as a}from"../log/index.js";import{getMachineName as w}from"../util/index.js";import{detectTailnetIPv4 as E,ensureServerAndGetPort as q,getFileServerHttpsPort as A}from"../files/file-serve.js";import{AUTH_CODE_AGENT_DELETED as k,KICKED_REASON_AGENT_DELETED as S,AgentDeletedError as $}from"./errors.js";function y(g){return g.replace(/(?<=[\[:,\[]\s*)(\d{16,})(?=\s*[,}\]\n])/g,'"$1"')}function P(g){const e=[...g??["stream_chunk","local_action_v1","agent_invoke"]];return e.includes("agent_invoke")||e.push("agent_invoke"),e.includes("event_result_ack")||e.push("event_result_ack"),e}const R="aibot-agent-api-v1",T=1;class f extends v{static DROPPABLE_COMMANDS=new Set(["update_binding_card"]);static BUFFER_OVERFLOW_RETAIN_COMMANDS=new Set(["event_result","codex_event","client_stream_chunk"]);static MAX_OUTBOUND_BUFFER_SIZE=1e3;static BACKPRESSURE_THRESHOLD=64*1024;ws=null;seq=0;heartbeatTimer=null;heartbeatSec=30;heartbeatFailures=0;static HEARTBEAT_MAX_FAILURES=2;connected=!1;reconnecting=!1;reconnectAttempts=0;everConnected=!1;agentDeleted=!1;config;packetLog;pendingInvokes=new Map;seqEventMap=new Map;pendingRequests=new Map;outboundBuffer=[];ackPolicy=null;constructor(e,t){super(),this.packetLog=t?.packetLog??null,this.config={url:e.url,agentId:e.agentId,apiKey:e.apiKey,clientType:e.clientType,clientVersion:e.clientVersion??"",adapterHint:e.adapterHint??"",capabilities:P(e.capabilities),localActions:e.localActions??["exec_approve","exec_reject"],skills:e.skills}}get isConnected(){return this.connected}async connect(){let e,t,s;const i=(async()=>{try{if(e=await E(),e!==void 0)try{t=await q(e);const n=A();n>0&&(s=n)}catch(n){a.warn("aibot",`file server pre-start failed: ${n}`)}}catch(n){a.warn("aibot",`tailnet detect failed: ${n}`)}})();return new Promise((n,h)=>{const c=new m(this.config.url);this.ws=c;const o=setTimeout(()=>{h(new Error("Auth timeout: no auth_ack received within 15s")),this.cleanupSocket()},15e3),d=++this.seq,r=setTimeout(()=>{this.pendingRequests.delete(d),h(new Error("Auth request timeout")),this.cleanupSocket()},15e3);this.pendingRequests.set(d,{expected:["auth_ack"],resolve:u=>{clearTimeout(o);const l=u.payload;l.code===0?(this.connected=!0,this.everConnected=!0,this.reconnectAttempts=0,l.heartbeat_sec&&(this.heartbeatSec=l.heartbeat_sec),l.ack_policy&&(this.ackPolicy=l.ack_policy,a.info("aibot",`ack_policy received: push_ack_timeout_ms=${l.ack_policy.push_ack_timeout_ms??"default"} max_retries=${l.ack_policy.max_retries??"default"} timeout_action=${l.ack_policy.timeout_action??"default"}`)),this.startHeartbeat(),this.flushOutboundBuffer(),this.emit("auth",l),n(l)):l.code===k?(this.agentDeleted=!0,h(new $(`Agent deleted: code=${l.code} msg=${l.msg}`))):h(new Error(`Auth failed: code=${l.code} msg=${l.msg}`))},reject:u=>{clearTimeout(o),h(u)},timer:r}),c.on("open",async()=>{await i;const u={agent_id:this.config.agentId,api_key:this.config.apiKey,client_type:this.config.clientType,protocol_version:R,contract_version:T,capabilities:this.config.capabilities??[],local_actions:this.config.localActions,skills:this.config.skills};this.config.sharedOwnerId&&(u.shared_owner_id=this.config.sharedOwnerId),this.config.clientVersion&&(u.client="grix-connector",u.client_version=this.config.clientVersion,u.host_type=this.config.clientType,u.host_version=this.config.clientVersion),this.config.adapterHint&&(u.adapter_hint=this.config.adapterHint),u.host_meta={hostname:w(),platform:p.platform(),arch:p.arch(),os_release:p.release(),...e!==void 0&&{tailnet_ip:e},...t!==void 0&&t>0&&{file_server_port:t},...s!==void 0&&s>0&&{file_server_https_port:s}},this.config.concurrency&&(u.concurrency=this.config.concurrency),this.sendPacket("auth",u,d)}),c.on("message",u=>{if(this.ws!==c)return;let l;try{l=JSON.parse(y(u.toString()))}catch{return}try{this.handlePacket(l)}catch(_){this.emitClientError(new Error(`handlePacket error: ${_}`))}}),c.on("close",(u,l)=>{if(this.ws!==c)return;this.connected=!1,this.stopHeartbeat(),this.rejectAllPendingRequests("websocket closed"),this.emit("close",u,l.toString());const _=u!==1e3&&this.everConnected&&!this.agentDeleted;a.info("aibot",`ws closed agent=${this.config.clientType}:${this.config.agentId} code=${u} reason=${l.toString()||"<none>"} everConnected=${this.everConnected} reconnecting=${this.reconnecting} agentDeleted=${this.agentDeleted} willReconnect=${_}`),_&&this.attemptReconnect()}),c.on("error",u=>{this.ws===c&&(this.emitClientError(u instanceof Error?u:new Error(String(u))),this.connected||h(u))})})}handlePacket(e){if(this.packetLog?.logInboundPacket(e.cmd,e.seq,e.payload),e.seq>0&&this.pendingRequests.has(e.seq)){const t=this.pendingRequests.get(e.seq);this.pendingRequests.delete(e.seq),clearTimeout(t.timer),t.expected.includes(e.cmd)?t.resolve(e):t.reject(new Error(`unexpected response: got ${e.cmd}, expected ${t.expected.join("/")}`));return}switch(e.cmd){case"auth_ack":break;case"ping":{this.sendPacket("pong",e.payload??{});break}case"event_msg":{this.emit("event",e.payload);break}case"local_action":{this.emit("localAction",e.payload);break}case"event_stop":{this.emit("stop",e.payload);break}case"event_revoke":{this.emit("revoke",e.payload);break}case"event_edit":{this.emit("edit",e.payload);break}case"event_cancel":{this.emit("eventCancel",e.payload);break}case"queue_clear":{this.emit("queueClear",e.payload);break}case"queue_snapshot_query":{this.emit("queueSnapshotQuery",e.payload);break}case"control_share_set":{this.emit("shareSet",e.payload);break}case"agent_profile_push":{this.emit("profilePush",e.payload);break}case"kicked":{const t=e.payload;if(this.emit("kicked",t),this.connected=!1,this.stopHeartbeat(),this.rejectAllPendingRequests("kicked"),this.outboundBuffer.length=0,t?.reason===S){if(this.agentDeleted=!0,this.reconnecting=!1,this.ws){try{this.ws.close(4001,"kicked")}catch{}this.ws=null}a.error("aibot",`kicked: agent deleted on platform agent=${this.config.clientType}:${this.config.agentId}, reconnect disabled`),this.emit("agentDeleted",{source:"kicked",reason:t.reason});break}if(this.reconnectAttempts=Math.max(this.reconnectAttempts,3),this.ws){try{this.ws.close(4001,"kicked")}catch{}this.ws=null}break}case"error":{const t=e.payload,s=[t.ref_cmd?`ref_cmd=${t.ref_cmd}`:"",t.ref_id?`ref_id=${t.ref_id}`:""].filter(Boolean).join(" ");this.emitClientError(new Error(`Server error: code=${t.code} msg=${t.msg}${s?` ${s}`:""}`));break}case"agent_invoke_result":{this.handleInvokeResult(e.payload);break}case"mcp_frame":{const t=e.payload;this.emit("mcpFrame",t.session_id??"",t.frame??null);break}case"send_ack":break;case"send_nack":{const t=e.payload;if(t.code===4003&&e.seq>0){const s=this.seqEventMap.get(e.seq);s&&(this.seqEventMap.delete(e.seq),this.purgeBufferedStreamChunks(s),a.warn("aibot",`stream chunk rejected (4003), purging buffered chunks for event=${s}`),this.emit("streamRejected",s,t.code))}break}case"local_action_ack":break;default:break}}sendEventAck(e){this.sendPacket("event_ack",e)||a.warn("aibot",`event_ack NOT sent (ws not open) event=${e.event_id} ws=${this.ws?`state=${this.ws.readyState}`:"null"}`)}sendStreamChunk(e){!e.delta_content&&!e.is_finish&&(a.warn("aibot",`stream_chunk delta_content empty, patched to newline event=${e.event_id??""} session=${e.session_id} chunk_seq=${e.chunk_seq} is_finish=${e.is_finish}`),e={...e,delta_content:`
2
- `}),this.sendPacket("client_stream_chunk",e)}sendMsg(e){this.sendPacket("send_msg",e)||a.warn("aibot",`send_msg NOT sent (ws not open) event=${e.event_id??""} session=${e.session_id??""} ws=${this.ws?`state=${this.ws.readyState}`:"null"}`)}editMsg(e){this.sendPacket("edit_msg",e)}sendEventResult(e){if(!this.ws||this.ws.readyState!==m.OPEN){this.sendPacket("event_result",e);return}this.sendEventResultReliable(e)}sendLocalActionResult(e){this.sendPacket("local_action_result",e)}sendEventStopAck(e){this.sendPacket("event_stop_ack",e)}sendEventStopResult(e){this.sendPacket("event_stop_result",e)}sendSessionActivitySet(e){this.sendPacket("session_activity_set",e)}sendCodexEvent(e){this.sendPacket("codex_event",e)}sendUpdateBindingCard(e){this.sendPacket("update_binding_card",e)}sendSkillsUpdate(e){this.sendPacket("agent_skills_update",e)}sendPing(){this.sendPacket("ping",{})}sendEventState(e){this.sendPacket("event_state",e)}sendEventCancelResult(e){this.sendPacket("event_cancel_result",e)}sendQueueClearResult(e){this.sendPacket("queue_clear_result",e)}sendQueueSnapshot(e){this.sendPacket("queue_snapshot",e)}agentInvoke(e,t,s=15e3){return new Promise((i,n)=>{const h=b(),c=Math.max(1e3,Math.min(s,6e4)),o=setTimeout(()=>{this.pendingInvokes.delete(h),n(new Error(`agent_invoke timeout: ${e}`))},c);this.pendingInvokes.set(h,{resolve:i,reject:n,timer:o}),this.sendPacket("agent_invoke",{invoke_id:h,action:e,params:t,timeout_ms:c})})}sendMcpFrame(e,t){this.sendPacket("mcp_frame",{session_id:e,frame:t})}request(e,t,s){return new Promise((i,n)=>{const h=++this.seq,c=setTimeout(()=>{this.pendingRequests.delete(h),n(new Error(`request timeout: ${e} (expected ${s.expected.join("/")})`))},s.timeoutMs);this.pendingRequests.set(h,{expected:s.expected,resolve:i,reject:n,timer:c}),this.sendPacket(e,t,h)||(this.pendingRequests.delete(h),clearTimeout(c),n(new Error(`send failed: ${e}`)))})}async sendStreamChunkRequest(e,t=2e4){return this.request("client_stream_chunk",e,{expected:["send_ack","send_nack","error"],timeoutMs:t})}async sendText(e,t=2e4){return this.request("send_msg",{msg_type:1,...e},{expected:["send_ack","send_nack","error"],timeoutMs:t})}async sendMedia(e,t=2e4){return this.request("send_msg",{...e,msg_type:2},{expected:["send_ack","send_nack","error"],timeoutMs:t})}async editMessage(e,t=2e4){return this.request("edit_msg",e,{expected:["send_ack","send_nack","error"],timeoutMs:t})}async deleteMessage(e,t,s=2e4){return this.request("delete_msg",{session_id:e,msg_id:t},{expected:["send_ack","send_nack","error"],timeoutMs:s})}async sendEventResultRequest(e,t=5e3){return this.request("event_result",e,{expected:["send_ack","send_nack","error"],timeoutMs:t})}disconnect(){a.info("aibot",`disconnect() agent=${this.config.clientType}:${this.config.agentId} wasConnected=${this.connected} reconnecting=${this.reconnecting} reconnectAttempts=${this.reconnectAttempts}`),this.connected=!1,this.everConnected=!1,this.reconnecting=!1,this.reconnectAttempts=0,this.stopHeartbeat(),this.rejectAllPendingInvokes("disconnect"),this.rejectAllPendingRequests("disconnect"),this.outboundBuffer.length=0,this.ws&&(this.ws.close(1e3,"client disconnect"),this.ws=null)}static MAX_CONSECUTIVE_AUTH_FAILURES=5;async attemptReconnect(){if(this.reconnecting||this.agentDeleted)return;this.reconnecting=!0,a.info("aibot",`attemptReconnect start agent=${this.config.clientType}:${this.config.agentId} fromAttempts=${this.reconnectAttempts}`),this.emit("disconnected");let e=0;for(;this.reconnecting;){const t=Math.min(1e3*2**this.reconnectAttempts,3e4),s=Math.floor(t*.2*Math.random());if(this.reconnectAttempts++,await new Promise(i=>setTimeout(i,t+s)),!this.reconnecting)return;try{await this.connect();const i=this.reconnectAttempts;this.reconnectAttempts=0,this.reconnecting=!1,a.info("aibot",`reconnect succeeded agent=${this.config.clientType}:${this.config.agentId} attempt=${i}`);return}catch(i){if(this.ws){try{this.ws.close()}catch{}this.ws=null}const n=i instanceof Error?i.message:String(i);if(a.warn("aibot",`reconnect failed agent=${this.config.clientType}:${this.config.agentId} attempt=${this.reconnectAttempts} err=${n}`),i instanceof $){this.agentDeleted=!0,this.reconnecting=!1,a.error("aibot",`reconnect aborted: agent deleted on platform agent=${this.config.clientType}:${this.config.agentId}`),this.emit("agentDeleted",{source:"auth_ack",code:k});return}if(/Auth failed/i.test(n)){if(e++,e>=f.MAX_CONSECUTIVE_AUTH_FAILURES){this.reconnecting=!1,a.error("aibot",`reconnect giving up after ${e} consecutive auth failures agent=${this.config.clientType}:${this.config.agentId}`);return}}else e=0}}}sendPacket(e,t,s){if(this.ws&&this.ws.readyState===m.OPEN){const i=this.ws.bufferedAmount>f.BACKPRESSURE_THRESHOLD;if(!i||!f.DROPPABLE_COMMANDS.has(e)){if(i&&f.DROPPABLE_COMMANDS.has(e))return!1;const n=s??++this.seq;if(e==="client_stream_chunk"&&t&&typeof t=="object"){const c=t.event_id;if(c&&(this.seqEventMap.set(n,c),this.seqEventMap.size>200)){const o=this.seqEventMap.keys().next().value;o!==void 0&&this.seqEventMap.delete(o)}}const h={cmd:e,seq:n,payload:t};this.packetLog?.logOutboundPacket(e,n,t,"sent");try{const c=this.ws.readyState,o=this.ws.bufferedAmount;return this.ws.send(JSON.stringify(h),d=>{if(e==="event_result"){const r=t;d?a.warn("aibot",`event_result ws send callback failed event=${r.event_id??""} status=${r.status??""} seq=${n} readyState=${c} bufferedAmount=${o} err=${d.message}`):a.info("aibot",`event_result ws send callback ok event=${r.event_id??""} status=${r.status??""} seq=${n} readyState=${c} bufferedAmount=${o}`)}else if(e==="client_stream_chunk"){const r=t;d?a.warn("aibot",`stream_chunk ws send failed event=${r.event_id??""} session=${r.session_id??""} seq=${n} chunk_seq=${r.chunk_seq??""} is_finish=${r.is_finish??""} readyState=${c} bufferedAmount=${o} err=${d.message}`):a.info("aibot",`stream_chunk ws send ok event=${r.event_id??""} session=${r.session_id??""} seq=${n} chunk_seq=${r.chunk_seq??""} is_finish=${r.is_finish??""} client_msg_id=${r.client_msg_id??""} quoted_message_id=${r.quoted_message_id??""} readyState=${c} bufferedAmount=${o}`)}else if(e==="event_ack"){const r=t;d?a.warn("aibot",`event_ack ws send failed event=${r.event_id??""} seq=${n} readyState=${c} bufferedAmount=${o} err=${d.message}`):a.info("aibot",`event_ack ws send ok event=${r.event_id??""} seq=${n} readyState=${c} bufferedAmount=${o}`)}else if(e==="send_msg"){const r=t;d?a.warn("aibot",`send_msg ws send failed event=${r.event_id??""} session=${r.session_id??""} seq=${n} readyState=${c} bufferedAmount=${o} err=${d.message}`):a.info("aibot",`send_msg ws send ok event=${r.event_id??""} session=${r.session_id??""} seq=${n} readyState=${c} bufferedAmount=${o}`)}else if(d){const r=t;a.warn("aibot",`${e} ws send failed seq=${n} session=${r.session_id??""} event=${r.event_id??""} client_msg_id=${r.client_msg_id??""} readyState=${c} bufferedAmount=${o} err=${d.message}`)}}),!0}catch(c){return this.emitClientError(new Error(`sendPacket failed: ${c}`)),!1}}}if(f.DROPPABLE_COMMANDS.has(e))return this.packetLog?.logOutboundPacket(e,s??0,t,"dropped"),!1;if(s!==void 0)return this.packetLog?.logOutboundPacket(e,s,t,"dropped"),!1;if(this.outboundBuffer.length>=f.MAX_OUTBOUND_BUFFER_SIZE&&(this.outboundBuffer=this.outboundBuffer.filter(i=>f.BUFFER_OVERFLOW_RETAIN_COMMANDS.has(i.cmd)),this.outboundBuffer.length>=f.MAX_OUTBOUND_BUFFER_SIZE&&this.outboundBuffer.shift()),this.outboundBuffer.push({cmd:e,payload:t}),this.packetLog?.logOutboundPacket(e,s??0,t,"buffered"),e==="client_stream_chunk"){const i=t;a.info("aibot",`stream_chunk buffered (ws not open) event=${i.event_id??""} session=${i.session_id??""} chunk_seq=${i.chunk_seq??""} is_finish=${i.is_finish??""} ws=${this.ws?`state=${this.ws.readyState}`:"null"}`)}return!1}async sendEventResultReliable(e){const t=this.ackPolicy?.max_retries??3,s=this.ackPolicy?.push_ack_timeout_ms??5e3,i=750;for(let n=1;n<=t;n++){const h=this.ws?.readyState??-1,c=this.ws?.bufferedAmount??0;a.info("aibot",`event_result send attempt event=${e.event_id} status=${e.status} attempt=${n}/${t} readyState=${h} bufferedAmount=${c}`);try{const o=await this.sendEventResultRequest(e,s);if(o.cmd==="send_ack"){const r=o.payload;a.info("aibot",`event_result ack event=${e.event_id} status=${e.status} attempt=${n}/${t} ack_event=${r.event_id??""} ack_status=${r.status??""}`);return}const d=o.payload;if(a.warn("aibot",`event_result rejected event=${e.event_id} status=${e.status} attempt=${n}/${t} cmd=${o.cmd} code=${d.code??""} msg=${d.msg??""}${d.ref_cmd?` ref_cmd=${d.ref_cmd}`:""}${d.ref_id?` ref_id=${d.ref_id}`:""}`),d.code===4003){a.warn("aibot",`event_result stopping retries: 4003 ownership denied event=${e.event_id}`);return}return}catch(o){const d=o instanceof Error?o.message:String(o);if(a.warn("aibot",`event_result attempt failed event=${e.event_id} status=${e.status} attempt=${n}/${t} err=${d}`),n===t){this.emitClientError(new Error(`event_result ack failed after ${t} attempts: event=${e.event_id} status=${e.status}`));return}await new Promise(r=>setTimeout(r,i*n))}}}purgeBufferedStreamChunks(e){const t=this.outboundBuffer.length;this.outboundBuffer=this.outboundBuffer.filter(s=>s.cmd!=="client_stream_chunk"?!0:s.payload?.event_id!==e),this.outboundBuffer.length<t&&a.info("aibot",`purged ${t-this.outboundBuffer.length} buffered stream chunks for event=${e}`)}emitClientError(e){if(this.listenerCount("error")===0){a.warn("aibot",`Client error (no listeners): ${e.message}`);return}this.emit("error",e)}flushOutboundBuffer(){if(this.outboundBuffer.length===0||!this.ws||this.ws.readyState!==m.OPEN)return;const e=this.outboundBuffer;this.outboundBuffer=[];for(const{cmd:t,payload:s}of e){const i=++this.seq;if(t==="client_stream_chunk"&&s&&typeof s=="object"){const h=s.event_id;h&&this.seqEventMap.set(i,h)}const n={cmd:t,seq:i,payload:s};try{this.ws.send(JSON.stringify(n))}catch{break}}if(this.seqEventMap.size>200){const t=[...this.seqEventMap.entries()].sort((s,i)=>s[0]-i[0]);this.seqEventMap.clear();for(const[s,i]of t.slice(-100))this.seqEventMap.set(s,i)}}handleInvokeResult(e){const t=this.pendingInvokes.get(e.invoke_id);t&&(this.pendingInvokes.delete(e.invoke_id),clearTimeout(t.timer),e.code===0?t.resolve(e.data??null):t.reject(new Error(`agent_invoke error code=${e.code}: ${e.msg??""}`)))}rejectAllPendingInvokes(e){for(const[,t]of this.pendingInvokes)clearTimeout(t.timer),t.reject(new Error(`agent_invoke canceled: ${e}`));this.pendingInvokes.clear()}rejectAllPendingRequests(e){for(const[,t]of this.pendingRequests)clearTimeout(t.timer),t.reject(new Error(`request canceled: ${e}`));this.pendingRequests.clear()}cleanupSocket(){if(this.ws)try{this.ws.close()}catch{}}startHeartbeat(){this.stopHeartbeat(),this.heartbeatFailures=0,this.heartbeatTimer=setInterval(()=>{this.connected&&this.request("ping",{ts:Date.now()},{expected:["pong"],timeoutMs:5e3}).then(()=>{this.heartbeatFailures=0}).catch(()=>{this.connected&&(this.heartbeatFailures++,!(this.heartbeatFailures<f.HEARTBEAT_MAX_FAILURES)&&(this.cleanupSocket(),this.attemptReconnect()))})},this.heartbeatSec*1e3)}stopHeartbeat(){this.heartbeatTimer&&(clearInterval(this.heartbeatTimer),this.heartbeatTimer=null)}}export{f as AibotClient};
1
+ import{EventEmitter as v}from"node:events";import{randomUUID as b}from"node:crypto";import p from"node:os";import m from"ws";import{log as a}from"../log/index.js";import{getMachineName as w}from"../util/index.js";import{detectTailnetIPv4 as E,ensureServerAndGetPort as q,getFileServerHttpsPort as A}from"../files/file-serve.js";import{AUTH_CODE_AGENT_DELETED as k,KICKED_REASON_AGENT_DELETED as S,AgentDeletedError as $}from"./errors.js";function R(g){return g.replace(/(?<=[\[:,\[]\s*)(\d{16,})(?=\s*[,}\]\n])/g,'"$1"')}function y(g){const e=[...g??["stream_chunk","local_action_v1","agent_invoke"]];return e.includes("agent_invoke")||e.push("agent_invoke"),e.includes("event_result_ack")||e.push("event_result_ack"),e}const P="aibot-agent-api-v1",T=1;class f extends v{static DROPPABLE_COMMANDS=new Set(["update_binding_card"]);static BUFFER_OVERFLOW_RETAIN_COMMANDS=new Set(["event_result","codex_event","client_stream_chunk"]);static MAX_OUTBOUND_BUFFER_SIZE=1e3;static BACKPRESSURE_THRESHOLD=64*1024;ws=null;seq=0;heartbeatTimer=null;heartbeatSec=30;heartbeatFailures=0;static HEARTBEAT_MAX_FAILURES=2;connected=!1;reconnecting=!1;reconnectAttempts=0;everConnected=!1;agentDeleted=!1;config;packetLog;pendingInvokes=new Map;seqEventMap=new Map;pendingRequests=new Map;outboundBuffer=[];ackPolicy=null;constructor(e,t){super(),this.packetLog=t?.packetLog??null,this.config={url:e.url,agentId:e.agentId,apiKey:e.apiKey,clientType:e.clientType,clientVersion:e.clientVersion??"",adapterHint:e.adapterHint??"",capabilities:y(e.capabilities),localActions:e.localActions??["exec_approve","exec_reject"],skills:e.skills}}get isConnected(){return this.connected}async connect(){let e,t,s;const i=(async()=>{try{if(e=await E(),e!==void 0)try{t=await q(e);const n=A();n>0&&(s=n)}catch(n){a.warn("aibot",`file server pre-start failed: ${n}`)}}catch(n){a.warn("aibot",`tailnet detect failed: ${n}`)}})();return new Promise((n,h)=>{const c=new m(this.config.url);this.ws=c;const o=setTimeout(()=>{h(new Error("Auth timeout: no auth_ack received within 15s")),this.cleanupSocket()},15e3),d=++this.seq,r=setTimeout(()=>{this.pendingRequests.delete(d),h(new Error("Auth request timeout")),this.cleanupSocket()},15e3);this.pendingRequests.set(d,{expected:["auth_ack"],resolve:u=>{clearTimeout(o);const l=u.payload;l.code===0?(this.connected=!0,this.everConnected=!0,this.reconnectAttempts=0,l.heartbeat_sec&&(this.heartbeatSec=l.heartbeat_sec),l.ack_policy&&(this.ackPolicy=l.ack_policy,a.info("aibot",`ack_policy received: push_ack_timeout_ms=${l.ack_policy.push_ack_timeout_ms??"default"} max_retries=${l.ack_policy.max_retries??"default"} timeout_action=${l.ack_policy.timeout_action??"default"}`)),this.startHeartbeat(),this.flushOutboundBuffer(),this.emit("auth",l),n(l)):l.code===k?(this.agentDeleted=!0,h(new $(`Agent deleted: code=${l.code} msg=${l.msg}`))):h(new Error(`Auth failed: code=${l.code} msg=${l.msg}`))},reject:u=>{clearTimeout(o),h(u)},timer:r}),c.on("open",async()=>{await i;const u={agent_id:this.config.agentId,api_key:this.config.apiKey,client_type:this.config.clientType,protocol_version:P,contract_version:T,capabilities:this.config.capabilities??[],local_actions:this.config.localActions,skills:this.config.skills};this.config.sharedOwnerId&&(u.shared_owner_id=this.config.sharedOwnerId),this.config.clientVersion&&(u.client="grix-connector",u.client_version=this.config.clientVersion,u.host_type=this.config.clientType,u.host_version=this.config.clientVersion),this.config.adapterHint&&(u.adapter_hint=this.config.adapterHint),u.host_meta={hostname:w(),platform:p.platform(),arch:p.arch(),os_release:p.release(),...e!==void 0&&{tailnet_ip:e},...t!==void 0&&t>0&&{file_server_port:t},...s!==void 0&&s>0&&{file_server_https_port:s}},this.config.concurrency&&(u.concurrency=this.config.concurrency),this.sendPacket("auth",u,d)}),c.on("message",u=>{if(this.ws!==c)return;let l;try{l=JSON.parse(R(u.toString()))}catch{return}try{this.handlePacket(l)}catch(_){this.emitClientError(new Error(`handlePacket error: ${_}`))}}),c.on("close",(u,l)=>{if(this.ws!==c)return;this.connected=!1,this.stopHeartbeat(),this.rejectAllPendingRequests("websocket closed"),this.emit("close",u,l.toString());const _=u!==1e3&&this.everConnected&&!this.agentDeleted;a.info("aibot",`ws closed agent=${this.config.clientType}:${this.config.agentId} code=${u} reason=${l.toString()||"<none>"} everConnected=${this.everConnected} reconnecting=${this.reconnecting} agentDeleted=${this.agentDeleted} willReconnect=${_}`),_&&this.attemptReconnect()}),c.on("error",u=>{this.ws===c&&(this.emitClientError(u instanceof Error?u:new Error(String(u))),this.connected||h(u))})})}handlePacket(e){if(this.packetLog?.logInboundPacket(e.cmd,e.seq,e.payload),e.seq>0&&this.pendingRequests.has(e.seq)){const t=this.pendingRequests.get(e.seq);this.pendingRequests.delete(e.seq),clearTimeout(t.timer),t.expected.includes(e.cmd)?t.resolve(e):t.reject(new Error(`unexpected response: got ${e.cmd}, expected ${t.expected.join("/")}`));return}switch(e.cmd){case"auth_ack":break;case"ping":{this.sendPacket("pong",e.payload??{});break}case"event_msg":{this.emit("event",e.payload);break}case"local_action":{this.emit("localAction",e.payload);break}case"event_stop":{this.emit("stop",e.payload);break}case"event_revoke":{this.emit("revoke",e.payload);break}case"event_edit":{this.emit("edit",e.payload);break}case"event_cancel":{this.emit("eventCancel",e.payload);break}case"queue_clear":{this.emit("queueClear",e.payload);break}case"queue_reorder":{this.emit("queueReorder",e.payload);break}case"queue_snapshot_query":{this.emit("queueSnapshotQuery",e.payload);break}case"control_share_set":{this.emit("shareSet",e.payload);break}case"agent_profile_push":{this.emit("profilePush",e.payload);break}case"kicked":{const t=e.payload;if(this.emit("kicked",t),this.connected=!1,this.stopHeartbeat(),this.rejectAllPendingRequests("kicked"),this.outboundBuffer.length=0,t?.reason===S){if(this.agentDeleted=!0,this.reconnecting=!1,this.ws){try{this.ws.close(4001,"kicked")}catch{}this.ws=null}a.error("aibot",`kicked: agent deleted on platform agent=${this.config.clientType}:${this.config.agentId}, reconnect disabled`),this.emit("agentDeleted",{source:"kicked",reason:t.reason});break}if(this.reconnectAttempts=Math.max(this.reconnectAttempts,3),this.ws){try{this.ws.close(4001,"kicked")}catch{}this.ws=null}break}case"error":{const t=e.payload,s=[t.ref_cmd?`ref_cmd=${t.ref_cmd}`:"",t.ref_id?`ref_id=${t.ref_id}`:""].filter(Boolean).join(" ");this.emitClientError(new Error(`Server error: code=${t.code} msg=${t.msg}${s?` ${s}`:""}`));break}case"agent_invoke_result":{this.handleInvokeResult(e.payload);break}case"mcp_frame":{const t=e.payload;this.emit("mcpFrame",t.session_id??"",t.frame??null);break}case"send_ack":break;case"send_nack":{const t=e.payload;if(t.code===4003&&e.seq>0){const s=this.seqEventMap.get(e.seq);s&&(this.seqEventMap.delete(e.seq),this.purgeBufferedStreamChunks(s),a.warn("aibot",`stream chunk rejected (4003), purging buffered chunks for event=${s}`),this.emit("streamRejected",s,t.code))}break}case"local_action_ack":break;default:break}}sendEventAck(e){this.sendPacket("event_ack",e)||a.warn("aibot",`event_ack NOT sent (ws not open) event=${e.event_id} ws=${this.ws?`state=${this.ws.readyState}`:"null"}`)}sendStreamChunk(e){!e.delta_content&&!e.is_finish&&(a.warn("aibot",`stream_chunk delta_content empty, patched to newline event=${e.event_id??""} session=${e.session_id} chunk_seq=${e.chunk_seq} is_finish=${e.is_finish}`),e={...e,delta_content:`
2
+ `}),this.sendPacket("client_stream_chunk",e)}sendMsg(e){this.sendPacket("send_msg",e)||a.warn("aibot",`send_msg NOT sent (ws not open) event=${e.event_id??""} session=${e.session_id??""} ws=${this.ws?`state=${this.ws.readyState}`:"null"}`)}editMsg(e){this.sendPacket("edit_msg",e)}sendEventResult(e){if(!this.ws||this.ws.readyState!==m.OPEN){this.sendPacket("event_result",e);return}this.sendEventResultReliable(e)}sendLocalActionResult(e){this.sendPacket("local_action_result",e)}sendEventStopAck(e){this.sendPacket("event_stop_ack",e)}sendEventStopResult(e){this.sendPacket("event_stop_result",e)}sendSessionActivitySet(e){this.sendPacket("session_activity_set",e)}sendCodexEvent(e){this.sendPacket("codex_event",e)}sendUpdateBindingCard(e){this.sendPacket("update_binding_card",e)}sendSkillsUpdate(e){this.sendPacket("agent_skills_update",e)}sendPing(){this.sendPacket("ping",{})}sendEventState(e){this.sendPacket("event_state",e)}sendEventCancelResult(e){this.sendPacket("event_cancel_result",e)}sendQueueClearResult(e){this.sendPacket("queue_clear_result",e)}sendQueueReorderResult(e){this.sendPacket("queue_reorder_result",e)}sendQueueSnapshot(e){this.sendPacket("queue_snapshot",e)}agentInvoke(e,t,s=15e3){return new Promise((i,n)=>{const h=b(),c=Math.max(1e3,Math.min(s,6e4)),o=setTimeout(()=>{this.pendingInvokes.delete(h),n(new Error(`agent_invoke timeout: ${e}`))},c);this.pendingInvokes.set(h,{resolve:i,reject:n,timer:o}),this.sendPacket("agent_invoke",{invoke_id:h,action:e,params:t,timeout_ms:c})})}sendMcpFrame(e,t){this.sendPacket("mcp_frame",{session_id:e,frame:t})}request(e,t,s){return new Promise((i,n)=>{const h=++this.seq,c=setTimeout(()=>{this.pendingRequests.delete(h),n(new Error(`request timeout: ${e} (expected ${s.expected.join("/")})`))},s.timeoutMs);this.pendingRequests.set(h,{expected:s.expected,resolve:i,reject:n,timer:c}),this.sendPacket(e,t,h)||(this.pendingRequests.delete(h),clearTimeout(c),n(new Error(`send failed: ${e}`)))})}async sendStreamChunkRequest(e,t=2e4){return this.request("client_stream_chunk",e,{expected:["send_ack","send_nack","error"],timeoutMs:t})}async sendText(e,t=2e4){return this.request("send_msg",{msg_type:1,...e},{expected:["send_ack","send_nack","error"],timeoutMs:t})}async sendMedia(e,t=2e4){return this.request("send_msg",{...e,msg_type:2},{expected:["send_ack","send_nack","error"],timeoutMs:t})}async editMessage(e,t=2e4){return this.request("edit_msg",e,{expected:["send_ack","send_nack","error"],timeoutMs:t})}async deleteMessage(e,t,s=2e4){return this.request("delete_msg",{session_id:e,msg_id:t},{expected:["send_ack","send_nack","error"],timeoutMs:s})}async sendEventResultRequest(e,t=5e3){return this.request("event_result",e,{expected:["send_ack","send_nack","error"],timeoutMs:t})}disconnect(){a.info("aibot",`disconnect() agent=${this.config.clientType}:${this.config.agentId} wasConnected=${this.connected} reconnecting=${this.reconnecting} reconnectAttempts=${this.reconnectAttempts}`),this.connected=!1,this.everConnected=!1,this.reconnecting=!1,this.reconnectAttempts=0,this.stopHeartbeat(),this.rejectAllPendingInvokes("disconnect"),this.rejectAllPendingRequests("disconnect"),this.outboundBuffer.length=0,this.ws&&(this.ws.close(1e3,"client disconnect"),this.ws=null)}static MAX_CONSECUTIVE_AUTH_FAILURES=5;async attemptReconnect(){if(this.reconnecting||this.agentDeleted)return;this.reconnecting=!0,a.info("aibot",`attemptReconnect start agent=${this.config.clientType}:${this.config.agentId} fromAttempts=${this.reconnectAttempts}`),this.emit("disconnected");let e=0;for(;this.reconnecting;){const t=Math.min(1e3*2**this.reconnectAttempts,3e4),s=Math.floor(t*.2*Math.random());if(this.reconnectAttempts++,await new Promise(i=>setTimeout(i,t+s)),!this.reconnecting)return;try{await this.connect();const i=this.reconnectAttempts;this.reconnectAttempts=0,this.reconnecting=!1,a.info("aibot",`reconnect succeeded agent=${this.config.clientType}:${this.config.agentId} attempt=${i}`);return}catch(i){if(this.ws){try{this.ws.close()}catch{}this.ws=null}const n=i instanceof Error?i.message:String(i);if(a.warn("aibot",`reconnect failed agent=${this.config.clientType}:${this.config.agentId} attempt=${this.reconnectAttempts} err=${n}`),i instanceof $){this.agentDeleted=!0,this.reconnecting=!1,a.error("aibot",`reconnect aborted: agent deleted on platform agent=${this.config.clientType}:${this.config.agentId}`),this.emit("agentDeleted",{source:"auth_ack",code:k});return}if(/Auth failed/i.test(n)){if(e++,e>=f.MAX_CONSECUTIVE_AUTH_FAILURES){this.reconnecting=!1,a.error("aibot",`reconnect giving up after ${e} consecutive auth failures agent=${this.config.clientType}:${this.config.agentId}`);return}}else e=0}}}sendPacket(e,t,s){if(this.ws&&this.ws.readyState===m.OPEN){const i=this.ws.bufferedAmount>f.BACKPRESSURE_THRESHOLD;if(!i||!f.DROPPABLE_COMMANDS.has(e)){if(i&&f.DROPPABLE_COMMANDS.has(e))return!1;const n=s??++this.seq;if(e==="client_stream_chunk"&&t&&typeof t=="object"){const c=t.event_id;if(c&&(this.seqEventMap.set(n,c),this.seqEventMap.size>200)){const o=this.seqEventMap.keys().next().value;o!==void 0&&this.seqEventMap.delete(o)}}const h={cmd:e,seq:n,payload:t};this.packetLog?.logOutboundPacket(e,n,t,"sent");try{const c=this.ws.readyState,o=this.ws.bufferedAmount;return this.ws.send(JSON.stringify(h),d=>{if(e==="event_result"){const r=t;d?a.warn("aibot",`event_result ws send callback failed event=${r.event_id??""} status=${r.status??""} seq=${n} readyState=${c} bufferedAmount=${o} err=${d.message}`):a.info("aibot",`event_result ws send callback ok event=${r.event_id??""} status=${r.status??""} seq=${n} readyState=${c} bufferedAmount=${o}`)}else if(e==="client_stream_chunk"){const r=t;d?a.warn("aibot",`stream_chunk ws send failed event=${r.event_id??""} session=${r.session_id??""} seq=${n} chunk_seq=${r.chunk_seq??""} is_finish=${r.is_finish??""} readyState=${c} bufferedAmount=${o} err=${d.message}`):a.info("aibot",`stream_chunk ws send ok event=${r.event_id??""} session=${r.session_id??""} seq=${n} chunk_seq=${r.chunk_seq??""} is_finish=${r.is_finish??""} client_msg_id=${r.client_msg_id??""} quoted_message_id=${r.quoted_message_id??""} readyState=${c} bufferedAmount=${o}`)}else if(e==="event_ack"){const r=t;d?a.warn("aibot",`event_ack ws send failed event=${r.event_id??""} seq=${n} readyState=${c} bufferedAmount=${o} err=${d.message}`):a.info("aibot",`event_ack ws send ok event=${r.event_id??""} seq=${n} readyState=${c} bufferedAmount=${o}`)}else if(e==="send_msg"){const r=t;d?a.warn("aibot",`send_msg ws send failed event=${r.event_id??""} session=${r.session_id??""} seq=${n} readyState=${c} bufferedAmount=${o} err=${d.message}`):a.info("aibot",`send_msg ws send ok event=${r.event_id??""} session=${r.session_id??""} seq=${n} readyState=${c} bufferedAmount=${o}`)}else if(d){const r=t;a.warn("aibot",`${e} ws send failed seq=${n} session=${r.session_id??""} event=${r.event_id??""} client_msg_id=${r.client_msg_id??""} readyState=${c} bufferedAmount=${o} err=${d.message}`)}}),!0}catch(c){return this.emitClientError(new Error(`sendPacket failed: ${c}`)),!1}}}if(f.DROPPABLE_COMMANDS.has(e))return this.packetLog?.logOutboundPacket(e,s??0,t,"dropped"),!1;if(s!==void 0)return this.packetLog?.logOutboundPacket(e,s,t,"dropped"),!1;if(this.outboundBuffer.length>=f.MAX_OUTBOUND_BUFFER_SIZE&&(this.outboundBuffer=this.outboundBuffer.filter(i=>f.BUFFER_OVERFLOW_RETAIN_COMMANDS.has(i.cmd)),this.outboundBuffer.length>=f.MAX_OUTBOUND_BUFFER_SIZE&&this.outboundBuffer.shift()),this.outboundBuffer.push({cmd:e,payload:t}),this.packetLog?.logOutboundPacket(e,s??0,t,"buffered"),e==="client_stream_chunk"){const i=t;a.info("aibot",`stream_chunk buffered (ws not open) event=${i.event_id??""} session=${i.session_id??""} chunk_seq=${i.chunk_seq??""} is_finish=${i.is_finish??""} ws=${this.ws?`state=${this.ws.readyState}`:"null"}`)}return!1}async sendEventResultReliable(e){const t=this.ackPolicy?.max_retries??3,s=this.ackPolicy?.push_ack_timeout_ms??5e3,i=750;for(let n=1;n<=t;n++){const h=this.ws?.readyState??-1,c=this.ws?.bufferedAmount??0;a.info("aibot",`event_result send attempt event=${e.event_id} status=${e.status} attempt=${n}/${t} readyState=${h} bufferedAmount=${c}`);try{const o=await this.sendEventResultRequest(e,s);if(o.cmd==="send_ack"){const r=o.payload;a.info("aibot",`event_result ack event=${e.event_id} status=${e.status} attempt=${n}/${t} ack_event=${r.event_id??""} ack_status=${r.status??""}`);return}const d=o.payload;if(a.warn("aibot",`event_result rejected event=${e.event_id} status=${e.status} attempt=${n}/${t} cmd=${o.cmd} code=${d.code??""} msg=${d.msg??""}${d.ref_cmd?` ref_cmd=${d.ref_cmd}`:""}${d.ref_id?` ref_id=${d.ref_id}`:""}`),d.code===4003){a.warn("aibot",`event_result stopping retries: 4003 ownership denied event=${e.event_id}`);return}return}catch(o){const d=o instanceof Error?o.message:String(o);if(a.warn("aibot",`event_result attempt failed event=${e.event_id} status=${e.status} attempt=${n}/${t} err=${d}`),n===t){this.emitClientError(new Error(`event_result ack failed after ${t} attempts: event=${e.event_id} status=${e.status}`));return}await new Promise(r=>setTimeout(r,i*n))}}}purgeBufferedStreamChunks(e){const t=this.outboundBuffer.length;this.outboundBuffer=this.outboundBuffer.filter(s=>s.cmd!=="client_stream_chunk"?!0:s.payload?.event_id!==e),this.outboundBuffer.length<t&&a.info("aibot",`purged ${t-this.outboundBuffer.length} buffered stream chunks for event=${e}`)}emitClientError(e){if(this.listenerCount("error")===0){a.warn("aibot",`Client error (no listeners): ${e.message}`);return}this.emit("error",e)}flushOutboundBuffer(){if(this.outboundBuffer.length===0||!this.ws||this.ws.readyState!==m.OPEN)return;const e=this.outboundBuffer;this.outboundBuffer=[];for(const{cmd:t,payload:s}of e){const i=++this.seq;if(t==="client_stream_chunk"&&s&&typeof s=="object"){const h=s.event_id;h&&this.seqEventMap.set(i,h)}const n={cmd:t,seq:i,payload:s};try{this.ws.send(JSON.stringify(n))}catch{break}}if(this.seqEventMap.size>200){const t=[...this.seqEventMap.entries()].sort((s,i)=>s[0]-i[0]);this.seqEventMap.clear();for(const[s,i]of t.slice(-100))this.seqEventMap.set(s,i)}}handleInvokeResult(e){const t=this.pendingInvokes.get(e.invoke_id);t&&(this.pendingInvokes.delete(e.invoke_id),clearTimeout(t.timer),e.code===0?t.resolve(e.data??null):t.reject(new Error(`agent_invoke error code=${e.code}: ${e.msg??""}`)))}rejectAllPendingInvokes(e){for(const[,t]of this.pendingInvokes)clearTimeout(t.timer),t.reject(new Error(`agent_invoke canceled: ${e}`));this.pendingInvokes.clear()}rejectAllPendingRequests(e){for(const[,t]of this.pendingRequests)clearTimeout(t.timer),t.reject(new Error(`request canceled: ${e}`));this.pendingRequests.clear()}cleanupSocket(){if(this.ws)try{this.ws.close()}catch{}}startHeartbeat(){this.stopHeartbeat(),this.heartbeatFailures=0,this.heartbeatTimer=setInterval(()=>{this.connected&&this.request("ping",{ts:Date.now()},{expected:["pong"],timeoutMs:5e3}).then(()=>{this.heartbeatFailures=0}).catch(()=>{this.connected&&(this.heartbeatFailures++,!(this.heartbeatFailures<f.HEARTBEAT_MAX_FAILURES)&&(this.cleanupSocket(),this.attemptReconnect()))})},this.heartbeatSec*1e3)}stopHeartbeat(){this.heartbeatTimer&&(clearInterval(this.heartbeatTimer),this.heartbeatTimer=null)}}export{f as AibotClient};
@@ -1 +1 @@
1
- class r{client;_status="ready";connectedAt;listeners=[];authAck=null;constructor(e){this.client=e,this.connectedAt=Date.now(),this.addInternalListener("close",()=>{this._status!=="closing"&&(this._status="reconnecting")}),this.addInternalListener("auth",t=>{this._status="ready",t&&(this.authAck=t)}),this.addInternalListener("disconnected",()=>{this._status!=="closing"&&this._status!=="closed"&&(this._status="reconnecting")})}get status(){return this._status}getStatusSnapshot(){return{status:this._status,connectedAt:this.connectedAt,reconnectAttempts:0}}sendEventAck(e){this.client.sendEventAck(e)}sendStreamChunk(e){this.client.sendStreamChunk(e)}sendMsg(e){this.client.sendMsg(e)}editMsg(e){this.client.editMsg(e)}sendEventResult(e){this.client.sendEventResult(e)}sendLocalActionResult(e){this.client.sendLocalActionResult(e)}sendEventStopAck(e){this.client.sendEventStopAck(e)}sendEventStopResult(e){this.client.sendEventStopResult(e)}sendSessionActivitySet(e){this.client.sendSessionActivitySet(e)}sendCodexEvent(e){this.client.sendCodexEvent(e)}sendUpdateBindingCard(e){this.client.sendUpdateBindingCard(e)}sendSkillsUpdate(e){this.client.sendSkillsUpdate(e)}sendPing(){this.client.sendPing()}sendEventState(e){this.client.sendEventState(e)}sendEventCancelResult(e){this.client.sendEventCancelResult(e)}sendQueueClearResult(e){this.client.sendQueueClearResult(e)}sendQueueSnapshot(e){this.client.sendQueueSnapshot(e)}agentInvoke(e,t,s){return this.client.agentInvoke(e,t,s)}sendStreamChunkRequest(e,t){return this.client.sendStreamChunkRequest(e,t)}sendText(e,t){return this.client.sendText(e,t)}sendMedia(e,t){return this.client.sendMedia(e,t)}editMessage(e,t){return this.client.editMessage(e,t)}deleteMessage(e,t,s){return this.client.deleteMessage(e,t,s)}sendEventResultRequest(e,t){return this.client.sendEventResultRequest(e,t)}sendCodexEventReliable(e,t){return this.client.request("codex_event",e,{expected:["send_ack","send_nack","error"],timeoutMs:t??2e4})}onEvent(e){return this.subscribe("event",e)}onLocalAction(e){return this.subscribe("localAction",e)}onStop(e){return this.subscribe("stop",e)}onRevoke(e){return this.subscribe("revoke",e)}onEdit(e){return this.subscribe("edit",e)}onEventCancel(e){return this.subscribe("eventCancel",e)}onQueueClear(e){return this.subscribe("queueClear",e)}onQueueSnapshotQuery(e){return this.subscribe("queueSnapshotQuery",e)}onKicked(e){return this.subscribe("kicked",e)}onAgentDeleted(e){return this.subscribe("agentDeleted",e)}onShareSet(e){return this.subscribe("shareSet",e)}onProfilePush(e){return this.subscribe("profilePush",e)}onError(e){return this.subscribe("error",e)}onClose(e){return this.subscribe("close",e)}onDisconnected(e){return this.subscribe("disconnected",e)}onStreamRejected(e){return this.subscribe("streamRejected",e)}onReconnected(e){return this.subscribe("auth",e)}disconnect(){this._status="closing",this.removeAllListeners(),this.client.disconnect(),this._status="closed"}sendMcpFrame(e,t){this.client.sendMcpFrame(e,t)}onMcpFrame(e){return this.subscribe("mcpFrame",e)}subscribe(e,t){return this.client.on(e,t),this.listeners.push({event:e,handler:t}),()=>{this.client.removeListener(e,t);const s=this.listeners.findIndex(n=>n.event===e&&n.handler===t);s>=0&&this.listeners.splice(s,1)}}addInternalListener(e,t){this.client.on(e,t),this.listeners.push({event:e,handler:t})}removeAllListeners(){for(const{event:e,handler:t}of this.listeners)this.client.removeListener(e,t);this.listeners.length=0}}export{r as AibotConnectionHandleImpl};
1
+ class r{client;_status="ready";connectedAt;listeners=[];authAck=null;constructor(e){this.client=e,this.connectedAt=Date.now(),this.addInternalListener("close",()=>{this._status!=="closing"&&(this._status="reconnecting")}),this.addInternalListener("auth",t=>{this._status="ready",t&&(this.authAck=t)}),this.addInternalListener("disconnected",()=>{this._status!=="closing"&&this._status!=="closed"&&(this._status="reconnecting")})}get status(){return this._status}getStatusSnapshot(){return{status:this._status,connectedAt:this.connectedAt,reconnectAttempts:0}}sendEventAck(e){this.client.sendEventAck(e)}sendStreamChunk(e){this.client.sendStreamChunk(e)}sendMsg(e){this.client.sendMsg(e)}editMsg(e){this.client.editMsg(e)}sendEventResult(e){this.client.sendEventResult(e)}sendLocalActionResult(e){this.client.sendLocalActionResult(e)}sendEventStopAck(e){this.client.sendEventStopAck(e)}sendEventStopResult(e){this.client.sendEventStopResult(e)}sendSessionActivitySet(e){this.client.sendSessionActivitySet(e)}sendCodexEvent(e){this.client.sendCodexEvent(e)}sendUpdateBindingCard(e){this.client.sendUpdateBindingCard(e)}sendSkillsUpdate(e){this.client.sendSkillsUpdate(e)}sendPing(){this.client.sendPing()}sendEventState(e){this.client.sendEventState(e)}sendEventCancelResult(e){this.client.sendEventCancelResult(e)}sendQueueClearResult(e){this.client.sendQueueClearResult(e)}sendQueueReorderResult(e){this.client.sendQueueReorderResult(e)}sendQueueSnapshot(e){this.client.sendQueueSnapshot(e)}agentInvoke(e,t,s){return this.client.agentInvoke(e,t,s)}sendStreamChunkRequest(e,t){return this.client.sendStreamChunkRequest(e,t)}sendText(e,t){return this.client.sendText(e,t)}sendMedia(e,t){return this.client.sendMedia(e,t)}editMessage(e,t){return this.client.editMessage(e,t)}deleteMessage(e,t,s){return this.client.deleteMessage(e,t,s)}sendEventResultRequest(e,t){return this.client.sendEventResultRequest(e,t)}sendCodexEventReliable(e,t){return this.client.request("codex_event",e,{expected:["send_ack","send_nack","error"],timeoutMs:t??2e4})}onEvent(e){return this.subscribe("event",e)}onLocalAction(e){return this.subscribe("localAction",e)}onStop(e){return this.subscribe("stop",e)}onRevoke(e){return this.subscribe("revoke",e)}onEdit(e){return this.subscribe("edit",e)}onEventCancel(e){return this.subscribe("eventCancel",e)}onQueueClear(e){return this.subscribe("queueClear",e)}onQueueReorder(e){return this.subscribe("queueReorder",e)}onQueueSnapshotQuery(e){return this.subscribe("queueSnapshotQuery",e)}onKicked(e){return this.subscribe("kicked",e)}onAgentDeleted(e){return this.subscribe("agentDeleted",e)}onShareSet(e){return this.subscribe("shareSet",e)}onProfilePush(e){return this.subscribe("profilePush",e)}onError(e){return this.subscribe("error",e)}onClose(e){return this.subscribe("close",e)}onDisconnected(e){return this.subscribe("disconnected",e)}onStreamRejected(e){return this.subscribe("streamRejected",e)}onReconnected(e){return this.subscribe("auth",e)}disconnect(){this._status="closing",this.removeAllListeners(),this.client.disconnect(),this._status="closed"}sendMcpFrame(e,t){this.client.sendMcpFrame(e,t)}onMcpFrame(e){return this.subscribe("mcpFrame",e)}subscribe(e,t){return this.client.on(e,t),this.listeners.push({event:e,handler:t}),()=>{this.client.removeListener(e,t);const s=this.listeners.findIndex(n=>n.event===e&&n.handler===t);s>=0&&this.listeners.splice(s,1)}}addInternalListener(e,t){this.client.on(e,t),this.listeners.push({event:e,handler:t})}removeAllListeners(){for(const{event:e,handler:t}of this.listeners)this.client.removeListener(e,t);this.listeners.length=0}}export{r as AibotConnectionHandleImpl};
@@ -1 +1 @@
1
- import{readdir as r,stat as m}from"node:fs/promises";import{join as l,extname as d}from"node:path";const x={pdf:"application/pdf",doc:"application/msword",docx:"application/vnd.openxmlformats-officedocument.wordprocessingml.document",xls:"application/vnd.ms-excel",xlsx:"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",ppt:"application/vnd.ms-powerpoint",pptx:"application/vnd.openxmlformats-officedocument.presentationml.presentation",txt:"text/plain",md:"text/markdown",csv:"text/csv",json:"application/json",xml:"application/xml",yaml:"text/yaml",yml:"text/yaml",html:"text/html",css:"text/css",js:"text/javascript",ts:"text/typescript",zip:"application/zip",rar:"application/x-rar-compressed","7z":"application/x-7z-compressed",tar:"application/x-tar",gz:"application/gzip",jpg:"image/jpeg",jpeg:"image/jpeg",png:"image/png",gif:"image/gif",webp:"image/webp",svg:"image/svg+xml",mp4:"video/mp4",mov:"video/quicktime",avi:"video/x-msvideo",mkv:"video/x-matroska",webm:"video/webm",mp3:"audio/mpeg",wav:"audio/wav",flac:"audio/flac",aac:"audio/aac"};function n(a){const p=d(a).slice(1).toLowerCase();return x[p]}async function f(a,p=!1){const c=await r(a,{withFileTypes:!0}),s=[];for(const t of c){if(!p&&t.name.startsWith("."))continue;const i=l(a,t.name),e={id:i,name:t.name,is_directory:t.isDirectory()};try{if(t.isDirectory()){const o=await m(i);e.modified_at=o.mtime.toISOString()}else{const o=await m(i);e.size=o.size,e.modified_at=o.mtime.toISOString(),e.mime_type=n(t.name)}}catch{}s.push(e)}return s.sort((t,i)=>t.is_directory!==i.is_directory?t.is_directory?-1:1:t.name.localeCompare(i.name)),s}export{f as listFiles,n as resolveMimeType};
1
+ import{readdir as r,stat as m}from"node:fs/promises";import{join as l,extname as d}from"node:path";const x={pdf:"application/pdf",doc:"application/msword",docx:"application/vnd.openxmlformats-officedocument.wordprocessingml.document",xls:"application/vnd.ms-excel",xlsx:"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",ppt:"application/vnd.ms-powerpoint",pptx:"application/vnd.openxmlformats-officedocument.presentationml.presentation",txt:"text/plain",md:"text/markdown",csv:"text/csv",json:"application/json",xml:"application/xml",yaml:"text/yaml",yml:"text/yaml",html:"text/html",css:"text/css",js:"text/javascript",ts:"text/typescript",zip:"application/zip",rar:"application/x-rar-compressed","7z":"application/x-7z-compressed",tar:"application/x-tar",gz:"application/gzip",jpg:"image/jpeg",jpeg:"image/jpeg",png:"image/png",gif:"image/gif",webp:"image/webp",svg:"image/svg+xml",mp4:"video/mp4",mov:"video/quicktime",avi:"video/x-msvideo",mkv:"video/x-matroska",webm:"video/webm",mp3:"audio/mpeg",wav:"audio/wav",flac:"audio/flac",aac:"audio/aac"};function n(a){const p=d(a).slice(1).toLowerCase();return x[p]}async function f(a,p=!1){const c=await r(a,{withFileTypes:!0}),s=[];for(const i of c){if(!p&&i.name.startsWith("."))continue;const t=l(a,i.name),e={id:t,name:i.name,is_directory:i.isDirectory()};try{if(i.isDirectory()){const o=await m(t);e.modified_at=o.mtime.toISOString()}else{const o=await m(t);e.size=o.size,e.modified_at=o.mtime.toISOString(),e.mime_type=n(i.name)}}catch{}s.push(e)}return s.sort((i,t)=>i.is_directory!==t.is_directory?i.is_directory?-1:1:i.name.localeCompare(t.name)),s}export{f as listFiles,n as resolveMimeType};
@@ -1 +1 @@
1
- import{readFileSync as d,readdirSync as c,existsSync as a,mkdirSync as p,cpSync as k,writeFileSync as y,rmSync as h}from"node:fs";import{join as t,dirname as g}from"node:path";import{homedir as S}from"node:os";import{fileURLToPath as D}from"node:url";import{parseSkillFrontmatter as x,scanSkills as v,dedupeSkills as F}from"../adapter/claude/skill-scanner.js";import{resolveRuntimePaths as w}from"../core/config/index.js";import{log as u}from"../core/log/index.js";const l=g(D(import.meta.url)),f=".grix-managed";function m(){const e=[];let i;try{i=c(l,{withFileTypes:!0,encoding:"utf8"})}catch{return e}for(const r of i){if(!r.isDirectory())continue;const n=t(l,r.name,"SKILL.md");try{const s=d(n,"utf-8"),o=x(s);o?.name&&e.push({name:o.name,description:o.description,trigger:o.trigger,source:"connector"})}catch{}}return e}function M(e){let i=[];try{i=v(e)}catch{i=[]}return F([...i,...m()])}function b(e){const i=[];let r;try{r=c(l,{withFileTypes:!0,encoding:"utf8"})}catch{return i}for(const n of r){if(!n.isDirectory())continue;const s=t(l,n.name);if(a(t(s,"SKILL.md")))try{p(e,{recursive:!0});const o=t(e,n.name);k(s,o,{recursive:!0}),y(t(o,f),"","utf8"),i.push(n.name)}catch{}}return i}function L(){const e=S(),i=process.env.XDG_CONFIG_HOME||t(e,".config");return[t(w().dataDir,"claude-plugin","skills"),t(e,".codex","skills"),t(e,".gemini","skills"),t(e,".qwen","skills"),t(e,".pi","agent","skills"),t(e,".codewhale","skills"),t(i,"opencode","skills"),t(e,".cursor","skills"),t(e,".openhuman","skills"),t(e,".kiro","skills"),t(e,".reasonix","skills")]}function A(){const e=[];for(const i of L()){let r;try{r=c(i,{withFileTypes:!0,encoding:"utf8"})}catch{continue}for(const n of r){if(!n.isDirectory())continue;const s=t(i,n.name);if(a(t(s,f)))try{h(s,{recursive:!0,force:!0}),e.push(s)}catch{}}}return e.length>0&&u.info("default-skills",`Cleaned ${e.length} projected skill dir(s)`),e}function E(){const e=m();if(e.length===0){u.warn("default-skills","No connector system skills found \u2014 dist/default-skills/ may be missing SKILL.md files");return}u.info("default-skills",`Connector system skills available: [${e.map(i=>i.name).join(", ")}]`)}export{M as buildReportedSkills,A as cleanupProjectedSkills,E as logDefaultSkillsCheck,m as scanDefaultSkills,b as syncDefaultSkillsToDir};
1
+ import{readFileSync as p,readdirSync as c,existsSync as a,mkdirSync as k,cpSync as y,writeFileSync as h,rmSync as g}from"node:fs";import{join as t,dirname as S}from"node:path";import{homedir as m}from"node:os";import{fileURLToPath as D}from"node:url";import{parseSkillFrontmatter as v,scanSkills as x,dedupeSkills as F}from"../adapter/claude/skill-scanner.js";import{resolveRuntimePaths as w}from"../core/config/index.js";import{log as u}from"../core/log/index.js";const l=S(D(import.meta.url)),f=".grix-managed";function d(){const e=[];let i;try{i=c(l,{withFileTypes:!0,encoding:"utf8"})}catch{return e}for(const r of i){if(!r.isDirectory())continue;const n=t(l,r.name,"SKILL.md");try{const s=p(n,"utf-8"),o=v(s);o?.name&&e.push({name:o.name,description:o.description,trigger:o.trigger,source:"connector"})}catch{}}return e}function T(e){let i=[];try{i=x(e)}catch{i=[]}return F([...i,...d()])}function j(e){const i=[];let r;try{r=c(l,{withFileTypes:!0,encoding:"utf8"})}catch{return i}for(const n of r){if(!n.isDirectory())continue;const s=t(l,n.name);if(a(t(s,"SKILL.md")))try{k(e,{recursive:!0});const o=t(e,n.name);y(s,o,{recursive:!0}),h(t(o,f),"","utf8"),i.push(n.name)}catch{}}return i}function C(){return(process.env.KIMI_CODE_HOME||"").trim()||t(m(),".kimi-code")}function L(){const e=m(),i=process.env.XDG_CONFIG_HOME||t(e,".config");return[t(w().dataDir,"claude-plugin","skills"),t(e,".codex","skills"),t(e,".gemini","skills"),t(e,".qwen","skills"),t(e,".pi","agent","skills"),t(e,".codewhale","skills"),t(i,"opencode","skills"),t(e,".cursor","skills"),t(e,".openhuman","skills"),t(e,".kiro","skills"),t(e,".reasonix","skills"),t(C(),"skills")]}function O(){const e=[];for(const i of L()){let r;try{r=c(i,{withFileTypes:!0,encoding:"utf8"})}catch{continue}for(const n of r){if(!n.isDirectory())continue;const s=t(i,n.name);if(a(t(s,f)))try{g(s,{recursive:!0,force:!0}),e.push(s)}catch{}}}return e.length>0&&u.info("default-skills",`Cleaned ${e.length} projected skill dir(s)`),e}function b(){const e=d();if(e.length===0){u.warn("default-skills","No connector system skills found \u2014 dist/default-skills/ may be missing SKILL.md files");return}u.info("default-skills",`Connector system skills available: [${e.map(i=>i.name).join(", ")}]`)}export{T as buildReportedSkills,O as cleanupProjectedSkills,b as logDefaultSkillsCheck,C as resolveKimiCodeHome,d as scanDefaultSkills,j as syncDefaultSkillsToDir};
package/dist/log.js CHANGED
@@ -1,3 +1,3 @@
1
- import{createWriteStream as g,mkdirSync as l,existsSync as f}from"node:fs";import{join as t}from"node:path";import{homedir as m}from"node:os";const i=t(m(),".grix"),s={base:i,config:t(i,"config"),log:t(i,"log"),data:t(i,"data")};function S(){for(const o of Object.values(s))f(o)||l(o,{recursive:!0})}let a=null;function $(){const o=new Date().toISOString().slice(0,10),r=t(s.log,`grix-acp-${o}.log`);a=g(r,{flags:"a"})}function c(){return new Date().toISOString().slice(11,19)}const u={info(o,r,...n){const e=`${c()} [${o}] ${r}${n.length?" "+n.map(String).join(" "):""}`;console.log(e),a?.write(e+`
2
- `)},error(o,r,...n){const e=`${c()} [${o}] ERROR ${r}${n.length?" "+n.map(String).join(" "):""}`;console.error(e),a?.write(e+`
1
+ import{createWriteStream as g,mkdirSync as l,existsSync as f}from"node:fs";import{join as i}from"node:path";import{homedir as m}from"node:os";const n=i(m(),".grix"),s={base:n,config:i(n,"config"),log:i(n,"log"),data:i(n,"data")};function S(){for(const o of Object.values(s))f(o)||l(o,{recursive:!0})}let a=null;function $(){const o=new Date().toISOString().slice(0,10),r=i(s.log,`grix-acp-${o}.log`);a=g(r,{flags:"a"})}function c(){return new Date().toISOString().slice(11,19)}const u={info(o,r,...t){const e=`${c()} [${o}] ${r}${t.length?" "+t.map(String).join(" "):""}`;console.log(e),a?.write(e+`
2
+ `)},error(o,r,...t){const e=`${c()} [${o}] ERROR ${r}${t.length?" "+t.map(String).join(" "):""}`;console.error(e),a?.write(e+`
3
3
  `)}};export{s as GRIX_PATHS,S as ensureGrixDirs,$ as initLogger,u as log};
@@ -1 +1 @@
1
- import*as i from"node:net";const e={bind:"127.0.0.1",port:0,endpoint:"/mcp",sessionTimeoutMs:18e5,invokeTimeoutMs:3e4};function n(o){const u={bind:o?.bind??e.bind,port:o?.port??e.port,endpoint:o?.endpoint??e.endpoint,sessionTimeoutMs:o?.sessionTimeoutMs??e.sessionTimeoutMs,invokeTimeoutMs:o?.invokeTimeoutMs??e.invokeTimeoutMs,allowedOrigins:o?.allowedOrigins,allowedHosts:o?.allowedHosts};return s(u.bind),u.port!==0&&t(u.port),r(u.sessionTimeoutMs),u}function s(o){if(!o||!i.isIPv4(o)&&!i.isIPv6(o))throw new Error(`\u914D\u7F6E\u6821\u9A8C\u5931\u8D25: bind \u5730\u5740 "${o}" \u4E0D\u662F\u5408\u6CD5\u7684 IPv4 \u6216 IPv6 \u5730\u5740`)}function t(o){if(!Number.isInteger(o)||o<1||o>65535)throw new Error(`\u914D\u7F6E\u6821\u9A8C\u5931\u8D25: port \u503C ${o} \u4E0D\u5728\u5408\u6CD5\u8303\u56F4 1-65535 \u5185\u6216\u4E0D\u662F\u6574\u6570`)}function r(o){if(!Number.isInteger(o)||o<1e3||o>864e5)throw new Error(`\u914D\u7F6E\u6821\u9A8C\u5931\u8D25: session_timeout_ms \u503C ${o} \u4E0D\u5728\u5408\u6CD5\u8303\u56F4 1000-86400000 \u5185`)}export{n as createDefaultGatewayConfig};
1
+ import*as n from"node:net";const i={bind:"127.0.0.1",port:0,endpoint:"/mcp",sessionTimeoutMs:18e5,invokeTimeoutMs:3e4};function s(u){const e={bind:u?.bind??i.bind,port:u?.port??i.port,endpoint:u?.endpoint??i.endpoint,sessionTimeoutMs:u?.sessionTimeoutMs??i.sessionTimeoutMs,invokeTimeoutMs:u?.invokeTimeoutMs??i.invokeTimeoutMs,allowedOrigins:u?.allowedOrigins,allowedHosts:u?.allowedHosts};return t(e.bind),e.port!==0&&o(e.port),r(e.sessionTimeoutMs),e}function t(u){if(!u||!n.isIPv4(u)&&!n.isIPv6(u))throw new Error(`\u914D\u7F6E\u6821\u9A8C\u5931\u8D25: bind \u5730\u5740 "${u}" \u4E0D\u662F\u5408\u6CD5\u7684 IPv4 \u6216 IPv6 \u5730\u5740`)}function o(u){if(!Number.isInteger(u)||u<1||u>65535)throw new Error(`\u914D\u7F6E\u6821\u9A8C\u5931\u8D25: port \u503C ${u} \u4E0D\u5728\u5408\u6CD5\u8303\u56F4 1-65535 \u5185\u6216\u4E0D\u662F\u6574\u6570`)}function r(u){if(!Number.isInteger(u)||u<1e3||u>864e5)throw new Error(`\u914D\u7F6E\u6821\u9A8C\u5931\u8D25: session_timeout_ms \u503C ${u} \u4E0D\u5728\u5408\u6CD5\u8303\u56F4 1000-86400000 \u5185`)}export{s as createDefaultGatewayConfig};
@@ -1 +1 @@
1
- const r=3e4;class c{connectionManager;onDisconnected;bindings=new Map;constructor(n,i){this.connectionManager=n,this.onDisconnected=i}async bind(n,i){if(this.bindings.has(n))throw new Error(`Session ${n} is already bound to a connection`);const e=await this.connectWithTimeout(i),t=[],s=e.onDisconnected(()=>{this.removeBinding(n),this.onDisconnected(n)});return t.push(s),this.bindings.set(n,{sessionId:n,handle:e,subscriptions:t}),e}getHandle(n){return this.bindings.get(n)?.handle}unbind(n){const i=this.bindings.get(n);if(i){this.bindings.delete(n);for(const e of i.subscriptions)e();i.handle.disconnect()}}unbindAll(){const n=[...this.bindings.keys()];for(const i of n)this.unbind(i)}connectWithTimeout(n){return new Promise((i,e)=>{let t=!1;const s=setTimeout(()=>{t||(t=!0,e(new Error("Connection bind timeout after 30000ms")))},3e4);this.connectionManager.connect({agentId:n.agentId,apiKey:n.apiKey,url:n.wsUrl,clientType:n.clientType,capabilities:["agent_invoke"],adapterHint:`${n.clientType}/base`},{maxRetries:0}).then(o=>{t?o.disconnect():(t=!0,clearTimeout(s),i(o))}).catch(o=>{t||(t=!0,clearTimeout(s),e(o))})})}removeBinding(n){const i=this.bindings.get(n);if(i){this.bindings.delete(n);for(const e of i.subscriptions)e()}}}export{c as ConnectionBindingImpl};
1
+ const a=3e4;class c{connectionManager;onDisconnected;bindings=new Map;constructor(n,i){this.connectionManager=n,this.onDisconnected=i}async bind(n,i){if(this.bindings.has(n))throw new Error(`Session ${n} is already bound to a connection`);const e=await this.connectWithTimeout(i),t=[],s=e.onDisconnected(()=>{this.removeBinding(n),this.onDisconnected(n)});return t.push(s),this.bindings.set(n,{sessionId:n,handle:e,subscriptions:t}),e}getHandle(n){return this.bindings.get(n)?.handle}unbind(n){const i=this.bindings.get(n);if(i){this.bindings.delete(n);for(const e of i.subscriptions)e();i.handle.disconnect()}}unbindAll(){const n=[...this.bindings.keys()];for(const i of n)this.unbind(i)}connectWithTimeout(n){return new Promise((i,e)=>{let t=!1;const s=setTimeout(()=>{t||(t=!0,e(new Error("Connection bind timeout after 30000ms")))},3e4);this.connectionManager.connect({agentId:n.agentId,apiKey:n.apiKey,url:n.wsUrl,clientType:n.clientType,capabilities:["agent_invoke"],adapterHint:`${n.clientType}/base`},{maxRetries:0}).then(o=>{t?o.disconnect():(t=!0,clearTimeout(s),i(o))}).catch(o=>{t||(t=!0,clearTimeout(s),e(o))})})}removeBinding(n){const i=this.bindings.get(n);if(i){this.bindings.delete(n);for(const e of i.subscriptions)e()}}}export{c as ConnectionBindingImpl};
@@ -1 +1 @@
1
- function a(e){const t=new Set([`http://127.0.0.1:${e.serverPort}`,`http://localhost:${e.serverPort}`,...e.allowedOrigins]),o=new Set([`127.0.0.1:${e.serverPort}`,`localhost:${e.serverPort}`,...e.allowedHosts]);return{validateRequest(s){const r=i(s,t);if(!r.ok)return r;const n=l(s,o);return n.ok?{ok:!0}:n}}}function i(e,t){const o=e.headers.origin;return o?t.has(o)?{ok:!0}:{ok:!1,statusCode:403,message:`Origin not allowed: ${o}`}:{ok:!0}}function l(e,t){const o=e.headers.host;return o?t.has(o)?{ok:!0}:{ok:!1,statusCode:403,message:`Host not allowed: ${o}`}:{ok:!1,statusCode:403,message:"Missing Host header"}}export{a as createSecurityPolicy};
1
+ function a(o){const e=new Set([`http://127.0.0.1:${o.serverPort}`,`http://localhost:${o.serverPort}`,...o.allowedOrigins]),t=new Set([`127.0.0.1:${o.serverPort}`,`localhost:${o.serverPort}`,...o.allowedHosts]);return{validateRequest(s){const r=i(s,e);if(!r.ok)return r;const n=l(s,t);return n.ok?{ok:!0}:n}}}function i(o,e){const t=o.headers.origin;return t?e.has(t)?{ok:!0}:{ok:!1,statusCode:403,message:`Origin not allowed: ${t}`}:{ok:!0}}function l(o,e){const t=o.headers.host;return t?e.has(t)?{ok:!0}:{ok:!1,statusCode:403,message:`Host not allowed: ${t}`}:{ok:!1,statusCode:403,message:"Missing Host header"}}export{a as createSecurityPolicy};
@@ -1 +1 @@
1
- import{toolCallToInvoke as i}from"../../core/mcp/tools.js";import{ToolRegistryImpl as l}from"./tool-registry.js";import{validateToolArgs as a}from"./tool-schemas.js";import{isEventTool as p,executeEventTool as d}from"./event-tool-executor.js";class y{registry;constructor(){this.registry=new l}async execute(t,e,r,n){if(!this.registry.hasTool(e))return this.errorResult(`\u672A\u77E5\u5DE5\u5177: ${e}`);const s=a(e,r);if(!s.valid)return this.errorResult(`\u53C2\u6570\u6821\u9A8C\u5931\u8D25: ${s.error}`);if(t.status!=="ready")return this.errorResult(`\u8FDE\u63A5\u4E0D\u53EF\u7528: \u5F53\u524D\u72B6\u6001\u4E3A ${t.status}`);if(p(e))return this.executeEventTool(t,e,r);const o=i(e,r);try{const u=await t.agentInvoke(o.action,o.params,n);return this.normalizeResult(u)}catch(u){const c=u instanceof Error?u.message:String(u);return c.toLowerCase().includes("timeout")?this.errorResult(`\u8C03\u7528\u8D85\u65F6: ${c}`):this.errorResult(`\u8C03\u7528\u5931\u8D25: ${c}`)}}normalizeResult(t){if(t==null||typeof t!="object")return this.successResult(t??null);const e=t,r=typeof e.code=="number"?e.code:0;if(r===0){const s="data"in e?e.data:null;return this.successResult(s??null)}const n=typeof e.msg=="string"?e.msg:"\u672A\u77E5\u9519\u8BEF";return this.errorResult(`\u4E0A\u6E38\u9519\u8BEF [code=${r}]: ${n}`)}successResult(t){return{content:[{type:"text",text:JSON.stringify(t)}],isError:!1}}errorResult(t){return{content:[{type:"text",text:t}],isError:!0}}async executeEventTool(t,e,r){return e==="grix_access_control"?this.executeAccessControl(t,r):d(t,e,r)}async executeAccessControl(t,e){const r=String(e.action??""),n={pair_approve:"pair_approve",pair_deny:"pair_deny",allow_sender:"sender_allow",remove_sender:"sender_remove",set_policy:"policy_set"}[r];if(!n)return this.errorResult(`\u672A\u77E5 access_control action: ${r}`);const s={};e.code!=null&&(s.code=e.code),e.sender_id!=null&&(s.sender_id=e.sender_id),e.policy!=null&&(s.policy=e.policy);try{const o=await t.agentInvoke("claude_access_control",{verb:n,payload:s},3e4);return this.successResult(o)}catch(o){const u=o instanceof Error?o.message:String(o);return this.errorResult(`access_control \u8C03\u7528\u5931\u8D25: ${u}`)}}}export{y as ToolExecutorImpl};
1
+ import{toolCallToInvoke as i}from"../../core/mcp/tools.js";import{ToolRegistryImpl as l}from"./tool-registry.js";import{validateToolArgs as a}from"./tool-schemas.js";import{isEventTool as p,executeEventTool as d}from"./event-tool-executor.js";class y{registry;constructor(){this.registry=new l}async execute(r,e,t,n){if(!this.registry.hasTool(e))return this.errorResult(`\u672A\u77E5\u5DE5\u5177: ${e}`);const s=a(e,t);if(!s.valid)return this.errorResult(`\u53C2\u6570\u6821\u9A8C\u5931\u8D25: ${s.error}`);if(r.status!=="ready")return this.errorResult(`\u8FDE\u63A5\u4E0D\u53EF\u7528: \u5F53\u524D\u72B6\u6001\u4E3A ${r.status}`);if(p(e))return this.executeEventTool(r,e,t);const o=i(e,t);try{const u=await r.agentInvoke(o.action,o.params,n);return this.normalizeResult(u)}catch(u){const c=u instanceof Error?u.message:String(u);return c.toLowerCase().includes("timeout")?this.errorResult(`\u8C03\u7528\u8D85\u65F6: ${c}`):this.errorResult(`\u8C03\u7528\u5931\u8D25: ${c}`)}}normalizeResult(r){if(r==null||typeof r!="object")return this.successResult(r??null);const e=r,t=typeof e.code=="number"?e.code:0;if(t===0){const s="data"in e?e.data:null;return this.successResult(s??null)}const n=typeof e.msg=="string"?e.msg:"\u672A\u77E5\u9519\u8BEF";return this.errorResult(`\u4E0A\u6E38\u9519\u8BEF [code=${t}]: ${n}`)}successResult(r){return{content:[{type:"text",text:JSON.stringify(r)}],isError:!1}}errorResult(r){return{content:[{type:"text",text:r}],isError:!0}}async executeEventTool(r,e,t){return e==="grix_access_control"?this.executeAccessControl(r,t):d(r,e,t)}async executeAccessControl(r,e){const t=String(e.action??""),n={pair_approve:"pair_approve",pair_deny:"pair_deny",allow_sender:"sender_allow",remove_sender:"sender_remove",set_policy:"policy_set"}[t];if(!n)return this.errorResult(`\u672A\u77E5 access_control action: ${t}`);const s={};e.code!=null&&(s.code=e.code),e.sender_id!=null&&(s.sender_id=e.sender_id),e.policy!=null&&(s.policy=e.policy);try{const o=await r.agentInvoke("claude_access_control",{verb:n,payload:s},3e4);return this.successResult(o)}catch(o){const u=o instanceof Error?o.message:String(o);return this.errorResult(`access_control \u8C03\u7528\u5931\u8D25: ${u}`)}}}export{y as ToolExecutorImpl};
@@ -1 +1 @@
1
- import{TOOLS as s,EVENT_TOOLS as t}from"../../core/mcp/tools.js";const e=new Set(["grix_query","grix_group","grix_message_send","grix_message_unsend","grix_admin"]),r=new Set(["grix_reply","grix_complete","grix_event_ack","grix_composing","grix_access_control","grix_status"]);class a{tools;toolMap;constructor(){this.tools=[...s.filter(o=>e.has(o.name)),...t.filter(o=>r.has(o.name))],this.toolMap=new Map(this.tools.map(o=>[o.name,o]))}getTools(){return this.tools}getTool(o){return this.toolMap.get(o)}hasTool(o){return this.toolMap.has(o)}}export{a as ToolRegistryImpl};
1
+ import{TOOLS as o,EVENT_TOOLS as s}from"../../core/mcp/tools.js";const e=new Set(["grix_query","grix_group","grix_message_send","grix_message_unsend","grix_admin"]),r=new Set(["grix_reply","grix_complete","grix_event_ack","grix_composing","grix_access_control","grix_status"]);class a{tools;toolMap;constructor(){this.tools=[...o.filter(t=>e.has(t.name)),...s.filter(t=>r.has(t.name))],this.toolMap=new Map(this.tools.map(t=>[t.name,t]))}getTools(){return this.tools}getTool(t){return this.toolMap.get(t)}hasTool(t){return this.toolMap.has(t)}}export{a as ToolRegistryImpl};
@@ -1 +1 @@
1
- const o={required:["action"],properties:{action:{type:"string",enum:["contact_search","session_search","message_history","message_search"]},id:{type:"string"},keyword:{type:"string",maxLength:200},limit:{type:"integer",minimum:1,maximum:100},offset:{type:"integer",minimum:0},sessionId:{type:"string"},beforeId:{type:"string"}}},a={required:["action"],properties:{action:{type:"string",enum:["create","detail","leave","add_members","remove_members","update_member_role","update_all_members_muted","update_member_speaking","dissolve"]},sessionId:{type:"string"},name:{type:"string",maxLength:128},memberIds:{type:"array",items:{type:"string"},maxItems:100},memberTypes:{type:"array",items:{type:"integer",enum:[1,2]}},memberId:{type:"string"},role:{type:"integer",enum:[1,2]},memberType:{type:"integer"},allMembersMuted:{type:"boolean"},isSpeakMuted:{type:"boolean"},canSpeakWhenAllMuted:{type:"boolean"}}},p={required:["sessionId","content"],properties:{sessionId:{type:"string"},content:{type:"string",maxLength:1e4},msgType:{type:"integer"},quotedMessageId:{type:"string"},threadId:{type:"string"}}},m={required:["sessionId","msgId"],properties:{sessionId:{type:"string"},msgId:{type:"string"}}},g={required:["action"],properties:{action:{type:"string",enum:["create_agent","list_categories","create_category","update_category","assign_category","rotate_api_key"]},agentName:{type:"string"},introduction:{type:"string"},isMain:{type:"boolean"},agentId:{type:"string"},categoryId:{type:"string"},name:{type:"string"},parentId:{type:"string"},sortOrder:{type:"integer"}}},y={required:["session_id","text"],properties:{event_id:{type:"string"},session_id:{type:"string"},text:{type:"string",maxLength:5e4},quoted_message_id:{type:"string"},is_final:{type:"boolean"}}},d={required:["event_id","status"],properties:{event_id:{type:"string"},status:{type:"string",enum:["responded","canceled","failed"]},msg:{type:"string",maxLength:500}}},c={required:["event_id"],properties:{event_id:{type:"string"},session_id:{type:"string"}}},l={required:["session_id","active"],properties:{session_id:{type:"string"},active:{type:"boolean"},event_id:{type:"string"}}},_={required:["action"],properties:{action:{type:"string",enum:["pair_approve","pair_deny","allow_sender","remove_sender","set_policy"]},code:{type:"string"},sender_id:{type:"string"},policy:{type:"string",enum:["allowlist","open","disabled"]}}},f={required:[],properties:{}},C={grix_query:o,grix_group:a,grix_message_send:p,grix_message_unsend:m,grix_admin:g,grix_reply:y,grix_complete:d,grix_event_ack:c,grix_composing:l,grix_access_control:_,grix_status:f};function B(u,t){const e=C[u];if(!e)return{valid:!1,error:`\u672A\u77E5\u5DE5\u5177: ${u}`};for(const i of e.required)if(t[i]===void 0||t[i]===null)return{valid:!1,error:`\u7F3A\u5C11\u5FC5\u586B\u53C2\u6570: ${i}`};for(const[i,r]of Object.entries(t)){if(r==null)continue;const n=e.properties[i];if(!n)continue;const s=$(i,r,n);if(s)return{valid:!1,error:s}}return{valid:!0}}function $(u,t,e){switch(e.type){case"string":if(typeof t!="string")return`\u53C2\u6570 ${u} \u7C7B\u578B\u9519\u8BEF: \u671F\u671B string\uFF0C\u5B9E\u9645 ${typeof t}`;if(e.maxLength!==void 0&&t.length>e.maxLength)return`\u53C2\u6570 ${u} \u8D85\u8FC7\u6700\u5927\u957F\u5EA6 ${e.maxLength}\uFF0C\u5B9E\u9645 ${t.length}`;if(e.enum&&!e.enum.includes(t))return`\u53C2\u6570 ${u} \u503C "${t}" \u4E0D\u5728\u5141\u8BB8\u8303\u56F4 [${e.enum.join(", ")}]`;break;case"integer":if(typeof t!="number"||!Number.isInteger(t))return`\u53C2\u6570 ${u} \u7C7B\u578B\u9519\u8BEF: \u671F\u671B integer\uFF0C\u5B9E\u9645 ${typeof t=="number"?"\u6D6E\u70B9\u6570":typeof t}`;if(e.minimum!==void 0&&t<e.minimum)return`\u53C2\u6570 ${u} \u503C ${t} \u5C0F\u4E8E\u6700\u5C0F\u503C ${e.minimum}`;if(e.maximum!==void 0&&t>e.maximum)return`\u53C2\u6570 ${u} \u503C ${t} \u5927\u4E8E\u6700\u5927\u503C ${e.maximum}`;if(e.enum&&!e.enum.includes(t))return`\u53C2\u6570 ${u} \u503C ${t} \u4E0D\u5728\u5141\u8BB8\u8303\u56F4 [${e.enum.join(", ")}]`;break;case"boolean":if(typeof t!="boolean")return`\u53C2\u6570 ${u} \u7C7B\u578B\u9519\u8BEF: \u671F\u671B boolean\uFF0C\u5B9E\u9645 ${typeof t}`;break;case"array":if(!Array.isArray(t))return`\u53C2\u6570 ${u} \u7C7B\u578B\u9519\u8BEF: \u671F\u671B array\uFF0C\u5B9E\u9645 ${typeof t}`;if(e.maxItems!==void 0&&t.length>e.maxItems)return`\u53C2\u6570 ${u} \u8D85\u8FC7\u6700\u5927\u5143\u7D20\u6570 ${e.maxItems}\uFF0C\u5B9E\u9645 ${t.length}`;if(e.items)for(let i=0;i<t.length;i++){const r=t[i];if(e.items.type==="string"&&typeof r!="string")return`\u53C2\u6570 ${u}[${i}] \u7C7B\u578B\u9519\u8BEF: \u671F\u671B string\uFF0C\u5B9E\u9645 ${typeof r}`;if(e.items.type==="integer"){if(typeof r!="number"||!Number.isInteger(r))return`\u53C2\u6570 ${u}[${i}] \u7C7B\u578B\u9519\u8BEF: \u671F\u671B integer\uFF0C\u5B9E\u9645 ${typeof r}`;if(e.items.enum&&!e.items.enum.includes(r))return`\u53C2\u6570 ${u}[${i}] \u503C ${r} \u4E0D\u5728\u5141\u8BB8\u8303\u56F4 [${e.items.enum.join(", ")}]`}}break}}export{B as validateToolArgs};
1
+ const o={required:["action"],properties:{action:{type:"string",enum:["contact_search","session_search","message_history","message_search"]},id:{type:"string"},keyword:{type:"string",maxLength:200},limit:{type:"integer",minimum:1,maximum:100},offset:{type:"integer",minimum:0},sessionId:{type:"string"},beforeId:{type:"string"}}},a={required:["action"],properties:{action:{type:"string",enum:["create","detail","leave","add_members","remove_members","update_member_role","update_all_members_muted","update_member_speaking","dissolve"]},sessionId:{type:"string"},name:{type:"string",maxLength:128},memberIds:{type:"array",items:{type:"string"},maxItems:100},memberTypes:{type:"array",items:{type:"integer",enum:[1,2]}},memberId:{type:"string"},role:{type:"integer",enum:[1,2]},memberType:{type:"integer"},allMembersMuted:{type:"boolean"},isSpeakMuted:{type:"boolean"},canSpeakWhenAllMuted:{type:"boolean"}}},p={required:["sessionId","content"],properties:{sessionId:{type:"string"},content:{type:"string",maxLength:1e4},msgType:{type:"integer"},quotedMessageId:{type:"string"},threadId:{type:"string"}}},m={required:["sessionId","msgId"],properties:{sessionId:{type:"string"},msgId:{type:"string"}}},g={required:["action"],properties:{action:{type:"string",enum:["create_agent","list_categories","create_category","update_category","assign_category","rotate_api_key"]},agentName:{type:"string"},introduction:{type:"string"},isMain:{type:"boolean"},agentId:{type:"string"},categoryId:{type:"string"},name:{type:"string"},parentId:{type:"string"},sortOrder:{type:"integer"}}},y={required:["session_id","text"],properties:{event_id:{type:"string"},session_id:{type:"string"},text:{type:"string",maxLength:5e4},quoted_message_id:{type:"string"},is_final:{type:"boolean"}}},d={required:["event_id","status"],properties:{event_id:{type:"string"},status:{type:"string",enum:["responded","canceled","failed"]},msg:{type:"string",maxLength:500}}},c={required:["event_id"],properties:{event_id:{type:"string"},session_id:{type:"string"}}},l={required:["session_id","active"],properties:{session_id:{type:"string"},active:{type:"boolean"},event_id:{type:"string"}}},_={required:["action"],properties:{action:{type:"string",enum:["pair_approve","pair_deny","allow_sender","remove_sender","set_policy"]},code:{type:"string"},sender_id:{type:"string"},policy:{type:"string",enum:["allowlist","open","disabled"]}}},f={required:[],properties:{}},C={grix_query:o,grix_group:a,grix_message_send:p,grix_message_unsend:m,grix_admin:g,grix_reply:y,grix_complete:d,grix_event_ack:c,grix_composing:l,grix_access_control:_,grix_status:f};function B(r,t){const e=C[r];if(!e)return{valid:!1,error:`\u672A\u77E5\u5DE5\u5177: ${r}`};for(const i of e.required)if(t[i]===void 0||t[i]===null)return{valid:!1,error:`\u7F3A\u5C11\u5FC5\u586B\u53C2\u6570: ${i}`};for(const[i,u]of Object.entries(t)){if(u==null)continue;const n=e.properties[i];if(!n)continue;const s=$(i,u,n);if(s)return{valid:!1,error:s}}return{valid:!0}}function $(r,t,e){switch(e.type){case"string":if(typeof t!="string")return`\u53C2\u6570 ${r} \u7C7B\u578B\u9519\u8BEF: \u671F\u671B string\uFF0C\u5B9E\u9645 ${typeof t}`;if(e.maxLength!==void 0&&t.length>e.maxLength)return`\u53C2\u6570 ${r} \u8D85\u8FC7\u6700\u5927\u957F\u5EA6 ${e.maxLength}\uFF0C\u5B9E\u9645 ${t.length}`;if(e.enum&&!e.enum.includes(t))return`\u53C2\u6570 ${r} \u503C "${t}" \u4E0D\u5728\u5141\u8BB8\u8303\u56F4 [${e.enum.join(", ")}]`;break;case"integer":if(typeof t!="number"||!Number.isInteger(t))return`\u53C2\u6570 ${r} \u7C7B\u578B\u9519\u8BEF: \u671F\u671B integer\uFF0C\u5B9E\u9645 ${typeof t=="number"?"\u6D6E\u70B9\u6570":typeof t}`;if(e.minimum!==void 0&&t<e.minimum)return`\u53C2\u6570 ${r} \u503C ${t} \u5C0F\u4E8E\u6700\u5C0F\u503C ${e.minimum}`;if(e.maximum!==void 0&&t>e.maximum)return`\u53C2\u6570 ${r} \u503C ${t} \u5927\u4E8E\u6700\u5927\u503C ${e.maximum}`;if(e.enum&&!e.enum.includes(t))return`\u53C2\u6570 ${r} \u503C ${t} \u4E0D\u5728\u5141\u8BB8\u8303\u56F4 [${e.enum.join(", ")}]`;break;case"boolean":if(typeof t!="boolean")return`\u53C2\u6570 ${r} \u7C7B\u578B\u9519\u8BEF: \u671F\u671B boolean\uFF0C\u5B9E\u9645 ${typeof t}`;break;case"array":if(!Array.isArray(t))return`\u53C2\u6570 ${r} \u7C7B\u578B\u9519\u8BEF: \u671F\u671B array\uFF0C\u5B9E\u9645 ${typeof t}`;if(e.maxItems!==void 0&&t.length>e.maxItems)return`\u53C2\u6570 ${r} \u8D85\u8FC7\u6700\u5927\u5143\u7D20\u6570 ${e.maxItems}\uFF0C\u5B9E\u9645 ${t.length}`;if(e.items)for(let i=0;i<t.length;i++){const u=t[i];if(e.items.type==="string"&&typeof u!="string")return`\u53C2\u6570 ${r}[${i}] \u7C7B\u578B\u9519\u8BEF: \u671F\u671B string\uFF0C\u5B9E\u9645 ${typeof u}`;if(e.items.type==="integer"){if(typeof u!="number"||!Number.isInteger(u))return`\u53C2\u6570 ${r}[${i}] \u7C7B\u578B\u9519\u8BEF: \u671F\u671B integer\uFF0C\u5B9E\u9645 ${typeof u}`;if(e.items.enum&&!e.items.enum.includes(u))return`\u53C2\u6570 ${r}[${i}] \u503C ${u} \u4E0D\u5728\u5141\u8BB8\u8303\u56F4 [${e.items.enum.join(", ")}]`}}break}}export{B as validateToolArgs};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "grix-connector",
3
- "version": "3.9.1",
3
+ "version": "3.9.2",
4
4
  "description": "Connect local AI coding agents (Claude, Codex, Gemini, Qwen, DeepSeek, Cursor, OpenCode, Pi, OpenHuman, Reasonix) to the Grix scheduling platform. Also serves as an OpenClaw plugin for Grix channel transport.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",