grix-connector 3.29.0 → 3.29.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,6 +1,6 @@
1
1
  import{fileURLToPath as W}from"node:url";import m from"node:path";import{homedir as I}from"node:os";import{promises as g,statSync as k}from"node:fs";import{EventEmitter as P}from"node:events";import{spawn as $}from"node:child_process";import{AgentProcess as G}from"../../agent/process.js";import{hasChildProcesses as Q,listGroupPids as H}from"../../core/runtime/spawn.js";import{applyNativeProviderConfig as z}from"../shared/native-provider-config.js";import{buildSessionContextBlock as Y}from"../shared/session-context.js";import{compactToolInputForWire as E}from"../shared/tool-wire-payload.js";import{syncDefaultSkillsToDir as V,resolveKimiCodeHome as X}from"../../default-skills/index.js";import{prepareKimiLayerPlugin as J}from"../../kimi-plugins/index.js";import{AcpClient as Z,AcpAuthRequiredError as ee,isAuthRequiredError as te}from"../../protocol/acp-client.js";import{AgentEventType as f}from"../../types/events.js";import{InternalApiServer as ie}from"../../core/mcp/internal-api-server.js";import{EventResultsStore as se}from"../../core/persistence/event-results-store.js";import{QuotedMessageStream as C}from"../../core/util/quoted-message-stream.js";import{SafeMarkdownStreamSegmenter as ne}from"../../core/text-segmentation/index.js";import{extractAcpTurnInput as oe}from"../../core/protocol/payload-parser.js";import{injectMessageMetadata as re}from"../../core/protocol/message-metadata.js";import{log as r}from"../../core/log/index.js";function M(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 ae=5e3;function x(c,e){const t=e.toolName??"";return e.toolCallId&&(!t||t===e.toolCallId)?c.toolNamesById.get(e.toolCallId)??t:t}function A(c){const e=String(c??"").trim();if(!e)return"";try{const t=JSON.parse(e);return JSON.stringify(b(t))}catch{return e}}function b(c){if(c===null||typeof c!="object")return c;if(Array.isArray(c))return c.map(b);const e=c,t=Object.keys(e).sort(),i={};for(const s of t)i[s]=b(e[s]);return i}function N(c){const e=Array.isArray(c)?c:[c],t=[];for(const i of e){if(!i||typeof i!="object")continue;const s=i;if(typeof s.text=="string"&&s.text.trim()){t.push(s.text.trim());continue}const n=s.content;if(n&&typeof n=="object"&&!Array.isArray(n)){const o=n.text;typeof o=="string"&&o.trim()&&t.push(o.trim())}}return t.join(`
2
- `)}function le(c,e,t){const i=c&&typeof c=="object"?c:{},s=i.toolCall&&typeof i.toolCall=="object"?i.toolCall:{},n=[s.rawInput,s.raw_input,s.input,s.arguments,s.args,N(s.content),s.content,s.description,i.tool_input,i.toolInput,e];for(const o of n){const a=E(o);if(a&&a!==t)return a}return""}function ce(c){if(!`${c.toolName} ${c.toolTitle}`.toLowerCase().replace(/[^a-z]/g,"").includes("askuserquestion"))return null;const t=c.options.filter(o=>/^q\d+_opt_\d+$/.test(o.optionId));if(t.length===0)return null;const i=c.rawParams&&typeof c.rawParams=="object"?c.rawParams:{},s=i.toolCall&&typeof i.toolCall=="object"?i.toolCall:{},n=N(s.content).trim();return n?{prompt:n,answerOptions:t}:null}import{scanSkills as q}from"../claude/skill-scanner.js";import{checkKiroSessionActivity as de,checkKimiSessionActivity as ue,hasRunningKimiDetachedTask as he}from"./session-activity.js";import{resolveCliPath as pe,getCliVersion as me}from"../../core/util/cli-probe.js";const _=m.dirname(W(import.meta.url)),O=[{modelId:"deepseek-v4-pro",name:"DeepSeek V4 Pro"},{modelId:"deepseek-v4-flash",name:"DeepSeek V4 Flash"}],R=[{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 w(c,e){const t=Number(process.env[c]);return Number.isFinite(t)&&t>0?t:e}const fe=200,ge=60*1e3,ve=600*1e3,S=w("GRIX_ACP_TURN_INACTIVITY_TIMEOUT_MS",900*1e3),D=2,B=5e3,F=80,U=1200*1e3,Ie=w("GRIX_ACP_BUSY_RETRY_INTERVAL_MS",5e3),j=w("GRIX_ACP_BUSY_WAIT_MAX_MS",1800*1e3),ye=6e4,Ce=2e3,Ae=12e3,L=3e3,K=3e4,Se=["[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 Te(c){return!(!(c instanceof Error)||c.code!==-32603||te(c))}function be(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 v(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 _e(c){return c.replace(/\u001b\[[0-9;?]*[ -/]*[@-~]/g,"")}class Je extends P{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;pendingQuestions=new Map;clientMsgSeq=0;stopped=!1;acpAuthMethod;acpInitialMode;acpInitialModel;acpMcpTools;terminalAuthChild=null;rawTransport;approvalMode;autoInjectArgs;nativeProviderScope;reasonixAuditTranscriptPath;auditAgentType;trimToolWirePayload;activeAuditEventId=null;auditCaptures=new Map;completedAuditBoundaries=new Map;bindingStore;sessionBindings=new Map;deferredEvents=new Map;currentAibotSessionId;bridgeLog;sessionConnected=!1;sessionContextPending=!1;baselineGroupPids=[];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){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.reasonixAuditTranscriptPath=i?.reasonixAuditTranscriptPath,this.auditAgentType=String(i?.agentType??m.basename(e.command)).trim().toLowerCase();const s=m.basename(e.command).toLowerCase(),n=this.auditAgentType==="copilot"||/^copilot(?:\.cmd)?$/i.test(s)||/^gh(?:\.exe)?$/i.test(s)&&(e.args??[]).includes("copilot");if(this.trimToolWirePayload=["kimi","kiro","qwen","reasonix","copilot"].includes(this.auditAgentType)||s.includes("kimi")||s==="kiro-cli"||/^qwen(?:\.cmd)?$/i.test(s)||s.includes("reasonix")||n,this.bindingStore=i?.bindingStore??null,this.currentAibotSessionId=i?.aibotSessionId?String(i.aibotSessionId).trim():void 0,i?.eventResultsPath&&(this.eventResults=new se(i.eventResultsPath)),this.bindingStore&&this.currentAibotSessionId){const o=this.bindingStore.get(this.currentAibotSessionId);o?.cwd&&this.sessionBindings.set(this.currentAibotSessionId,o.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.finishRun("canceled","adapter stopped"),this.flushOrphanBuffer(),this.acpClient&&(this.acpClient.clearSettleTimer(),this.acpClient.removeAllListeners(),this.acpClient=null),this.agentProcess&&(await this.agentProcess.close(),this.agentProcess=null),this.baselineGroupPids=[],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&&(r.info("acp-adapter",`agent profile changed; clearing ${this.identityInjectedSessions.size} identity-injected session(s)`),this.identityInjectedSessions.clear())}sendPrompt(e){const t=new Re(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(K))}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.snapshotAuditBoundary(this.activeRun.eventId),this.clearRunTimers(this.activeRun),this.activeRun=null)}takeAuditBoundary(e){const t=this.completedAuditBoundaries.get(e);return t&&this.completedAuditBoundaries.delete(e),t}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(_,"../../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?Q(e,[e,...this.baselineGroupPids]):!1}captureBaselineGroupPids(){const e=this.agentProcess?.pid;e&&H(e).then(t=>{this.baselineGroupPids=t}).catch(()=>{})}async probe(e){const t=this.config.command||"gemini",i=await pe(t),s=i!==null;let n=null,o;if(s){const h=await me(t);n=h.version,h.error&&(o=h.error)}else o={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,...o?{error:o}:{}},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=q({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(`
3
- `):"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(){const e=this.acpClient?.sessionOptions??null,t=this.isReasonix?O:this.isKimi?R:null;if(!t||e&&e.models.length>0)return e;const i=this.resolveFallbackCurrentModelId(t,e?.currentModelId??"");return{modes:e?.modes??[],currentModeId:e?.currentModeId??"",models:[...t],currentModelId:i}}resolveFallbackCurrentModelId(e,t){if(e.some(s=>s.modelId===t))return t;const i=(this.autoInjectArgs?.model??this.acpInitialModel??"").trim();return i&&e.some(s=>s.modelId===i)?i:e[0]?.modelId??""}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 o=i?.modes.map(p=>({id:p.id,name:p.name}))??[],a=i?.models&&i.models.length>0,l=a?i.models:this.isReasonix?O:this.isKimi?R:[],d=a?"acp":this.isReasonix?"reasonix_fallback":this.isKimi?"kimi_fallback":"empty";this.isKimi&&!a&&r.warn("acp-adapter",`[toolbar-diag] kimi ACP session returned no models; using static fallback. command=${this.config.command} fallbackIds=${JSON.stringify(R.map(p=>p.modelId))}`),r.info("acp-adapter",`[toolbar-diag] buildToolbarContext outcome=${e} agent=${this.config.command} modelSource=${d} acpModelCount=${i?.models?.length??0} finalModelCount=${l.length}`);const h=l.map(p=>({modelId:p.modelId,name:p.name})),u=l.map(p=>({id:p.modelId,displayName:p.name})),T=i?.modes.map(p=>({id:p.id,displayName:p.name}))??[];u.length>0&&!u.some(p=>p.id===n)&&(n=this.resolveFallbackCurrentModelId(l,n));const y={outcome:e,...t?{cwd:t}:{},model_id:n,mode_id:s,currentModelId:n,currentModeId:s,available_models:u,available_modes:T,availableModels:u,availableModes:T,models:h,modes:o};return r.info("acp-adapter",`[toolbar] buildToolbarContext outcome=${e} model_id="${n}" available_models=${JSON.stringify(u.map(p=>p.id))} currentModelId=${n}`),y}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?(r.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=>{r.error("acp-adapter",`Failed to respond to permission: ${s}`)}),this.activeRun&&(this.activeRun.lastProgressAt=Date.now(),this.resetIdleTimer(this.activeRun)),!0):(r.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=>{r.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==="claude_interaction_reply")return this.handleAcpQuestionReply(e);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",o=String(i.decision??"");let a;return n&&(o==="allow-once"||o==="allow-always")?a=o:n?a="allow":a="deny",s?this.handleAcpApprovalAction(s,a)?(this.callbacks.sendLocalActionResult(e.action_id,"ok"),{handled:!0,kind:"approval"}):(this.callbacks.sendLocalActionResult(e.action_id,"failed",void 0,"approval_not_found",`no pending approval for tool_call_id: ${s}`),{handled:!0,kind:"approval"}):(this.callbacks.sendLocalActionResult(e.action_id,"failed",void 0,"tool_call_id_required","tool_call_id is required"),{handled:!0,kind:"approval"})}return{handled:!1,kind:""}}async handleQuestionReplyEvent(e){let t;if(e.action)t={type:"action",value:e.action};else if(e.response?.type==="single")t={type:"text",value:e.response.value};else if(e.response?.type==="map")t={type:"map",entries:e.response.entries};else return{delivered:!1,errorCode:"resolution_invalid",errorMsg:"Question reply response is invalid."};return this.deliverQuestionReply({request_id:e.request_id,resolution:t})}async handleAcpQuestionReply(e){const t=await this.deliverQuestionReply(e.params??{});return t.delivered?(this.callbacks.sendLocalActionResult(e.action_id,"ok",t.result),{handled:!0,kind:t.kind??"question_reply"}):(this.callbacks.sendLocalActionResult(e.action_id,"failed",void 0,t.errorCode,t.errorMsg),{handled:!0,kind:t.kind??"question_reply_failed"})}async deliverQuestionReply(e){const t=String(e.request_id??"").trim(),i=this.pendingQuestions.get(t);if(!t||!i||!this.acpClient)return{delivered:!1,errorCode:"interaction_request_not_pending",errorMsg:"The question is no longer pending; the reply was not delivered.",kind:"question_reply_not_pending"};const s=e.resolution&&typeof e.resolution=="object"?e.resolution:{},n=String(s.type??"");let o;if(n==="action"&&String(s.value??"")==="cancel")o=i.options.find(l=>/_skip$/.test(l.optionId)||l.kind.toLowerCase().includes("reject"));else{let l="";if(n==="text")l=String(s.value??"").trim();else if(n==="map"){const d=Array.isArray(s.entries)?s.entries:[];l=String(d[0]?.value??"").trim()}o=i.options.find(d=>d.name===l||d.optionId===l)}if(!o)return{delivered:!1,errorCode:"question_answer_invalid",errorMsg:"The selected answer does not match a pending question option.",kind:"question_reply_invalid"};try{await this.acpClient.respondPermissionOption(t,o.optionId)}catch(l){return{delivered:!1,errorCode:"question_reply_failed",errorMsg:l instanceof Error?l.message:String(l),kind:"question_reply_failed"}}this.pendingQuestions.delete(t),this.activeRun&&(this.activeRun.lastProgressAt=Date.now(),this.resetIdleTimer(this.activeRun));const a=/_skip$/.test(o.optionId);return{delivered:!0,result:{request_id:t,resolution:a?"cancel":"answer"},kind:a?"question_reply_cancel":"question_reply"}}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 g.stat(i),!1}catch{r.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=>{r.error("acp-adapter",`Failed to create session on bind: ${v(s)}`);const n=this.buildToolbarContext("bind_failed",t),o=Array.isArray(n.available_models)&&n.available_models.length>0;return this.callbacks.sendUpdateBindingCard(e,"failed",t,o?n:void 0),!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=q({mode:"kiro",projectDir:t})}catch(s){r.warn("acp-adapter",`Kiro skills scan failed: ${s instanceof Error?s.message:String(s)}`);return}try{this.callbacks.onSkillsUpdate(i,t)}catch(s){r.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){r.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),r.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(K),r.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){}sendTerminalEventWithoutRun(e,t,i){this.emit("eventDone",e),this.callbacks.sendEventResult(e,t,i)}deliverInboundEvent(e){if(this.eventResults?.has(e.session_id,e.event_id)){const t=this.eventResults.get(e.session_id,e.event_id);r.info("acp-adapter",`Deduplicating event ${e.event_id} (cached: ${t.status})`),this.sendTerminalEventWithoutRun(e.event_id,t.status,t.msg);return}if(this.compacting){r.info("acp-adapter",`Event ${e.event_id} deferred: compaction in progress`),this.deferEvent(e);return}if(this.activeRun){r.info("acp-adapter",`Event ${e.event_id} rejected: busy`),this.sendTerminalEventWithoutRun(e.event_id,"failed","agent busy");return}if(!this.agentProcess?.alive){r.warn("acp-adapter",`Event ${e.event_id} cannot start: agent process not alive; requesting session recovery`),this.emit("internalError",{eventId:e.event_id,sessionId:e.session_id,errorMsg:"agent not alive",replayOriginalContent:!0});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),r.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)r.info("acp-adapter",`Rejecting deferred event ${s.event_id}: ${e}`),this.sendTerminalEventWithoutRun(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){r.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.sendTerminalEventWithoutRun(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 ne,quotedMessageId:void 0,flushTimer:null,idleTimer:null,awaitingToolResult:!1,responded:!1,silent:t,lastProgressAt:Date.now(),lastIdleCheckAt:Date.now(),idleNoProgressCount:0,consecutivePingFailures: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;this.beginAuditCapture(e);const n=oe(e,this.resolveCwd());this.resetIdleTimer(s),n.modeId&&this.acpClient&&this.acpClient.setLiveMode(n.modeId).catch(()=>{}),n.modelId&&this.acpClient&&this.acpClient.setModel(n.modelId).catch(()=>{});const o=re(n.prompt,{messageId:e.msg_id,quotedMessageId:e.quoted_message_id}),a=this.injectQuoteProtocolOnce(s.sessionId,o),l=this.injectIdentityOnce(s.sessionId,a),d=this.injectSessionContextOnce(s.sessionId,l),h=this.injectRecoveryContext(s.sessionId,d);this.sendPromptWithRetry(s,h)}injectQuoteProtocolOnce(e,t){return!e||this.quoteProtocolTaughtSessions.has(e)?t:(this.quoteProtocolTaughtSessions.add(e),`${Se}
2
+ `)}function le(c,e,t){const i=c&&typeof c=="object"?c:{},s=i.toolCall&&typeof i.toolCall=="object"?i.toolCall:{},n=[s.rawInput,s.raw_input,s.input,s.arguments,s.args,N(s.content),s.content,s.description,i.tool_input,i.toolInput,e];for(const o of n){const a=E(o);if(a&&a!==t)return a}return""}function ce(c){if(!`${c.toolName} ${c.toolTitle}`.toLowerCase().replace(/[^a-z]/g,"").includes("askuserquestion"))return null;const t=c.options.filter(o=>/^q\d+_opt_\d+$/.test(o.optionId));if(t.length===0)return null;const i=c.rawParams&&typeof c.rawParams=="object"?c.rawParams:{},s=i.toolCall&&typeof i.toolCall=="object"?i.toolCall:{},n=N(s.content).trim();return n?{prompt:n,answerOptions:t}:null}import{scanSkills as q}from"../claude/skill-scanner.js";import{checkKiroSessionActivity as de,checkKimiSessionActivity as ue,hasRunningKimiDetachedTask as he}from"./session-activity.js";import{resolveCliPath as pe,getCliVersion as me}from"../../core/util/cli-probe.js";const _=m.dirname(W(import.meta.url)),O=[{modelId:"deepseek-v4-pro",name:"DeepSeek V4 Pro"},{modelId:"deepseek-v4-flash",name:"DeepSeek V4 Flash"}],R=[{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 w(c,e){const t=Number(process.env[c]);return Number.isFinite(t)&&t>0?t:e}const fe=200,ge=60*1e3,ve=600*1e3,S=w("GRIX_ACP_TURN_INACTIVITY_TIMEOUT_MS",900*1e3),D=2,B=5e3,F=80,j=1200*1e3,Ie=w("GRIX_ACP_BUSY_RETRY_INTERVAL_MS",5e3),U=w("GRIX_ACP_BUSY_WAIT_MAX_MS",1800*1e3),ye=6e4,Ce=2e3,Ae=12e3,L=3e3,K=3e4,Se=["[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 Te(c){return!(!(c instanceof Error)||c.code!==-32603||te(c))}function be(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 v(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 _e(c){return c.replace(/\u001b\[[0-9;?]*[ -/]*[@-~]/g,"")}class Je extends P{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;pendingQuestions=new Map;clientMsgSeq=0;stopped=!1;acpAuthMethod;acpInitialMode;acpInitialModel;acpMcpTools;terminalAuthChild=null;rawTransport;approvalMode;autoInjectArgs;nativeProviderScope;reasonixAuditTranscriptPath;auditAgentType;trimToolWirePayload;activeAuditEventId=null;auditCaptures=new Map;completedAuditBoundaries=new Map;bindingStore;sessionBindings=new Map;deferredEvents=new Map;currentAibotSessionId;bridgeLog;sessionConnected=!1;sessionContextPending=!1;baselineGroupPids=[];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){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.reasonixAuditTranscriptPath=i?.reasonixAuditTranscriptPath,this.auditAgentType=String(i?.agentType??m.basename(e.command)).trim().toLowerCase();const s=m.basename(e.command).toLowerCase(),n=this.auditAgentType==="copilot"||/^copilot(?:\.cmd)?$/i.test(s)||/^gh(?:\.exe)?$/i.test(s)&&(e.args??[]).includes("copilot");if(this.trimToolWirePayload=["kimi","kiro","qwen","reasonix","copilot"].includes(this.auditAgentType)||s.includes("kimi")||s==="kiro-cli"||/^qwen(?:\.cmd)?$/i.test(s)||s.includes("reasonix")||n,this.bindingStore=i?.bindingStore??null,this.currentAibotSessionId=i?.aibotSessionId?String(i.aibotSessionId).trim():void 0,i?.eventResultsPath&&(this.eventResults=new se(i.eventResultsPath)),this.bindingStore&&this.currentAibotSessionId){const o=this.bindingStore.get(this.currentAibotSessionId);o?.cwd&&this.sessionBindings.set(this.currentAibotSessionId,o.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.finishRun("canceled","adapter stopped"),this.flushOrphanBuffer(),this.acpClient&&(this.acpClient.clearSettleTimer(),this.acpClient.removeAllListeners(),this.acpClient=null),this.agentProcess&&(await this.agentProcess.close(),this.agentProcess=null),this.baselineGroupPids=[],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&&(r.info("acp-adapter",`agent profile changed; clearing ${this.identityInjectedSessions.size} identity-injected session(s)`),this.identityInjectedSessions.clear())}sendPrompt(e){const t=new Re(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(K))}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.snapshotAuditBoundary(this.activeRun.eventId),this.clearRunTimers(this.activeRun),this.activeRun=null)}takeAuditBoundary(e){const t=this.completedAuditBoundaries.get(e);return t&&this.completedAuditBoundaries.delete(e),t}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(_,"../../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?Q(e,[e,...this.baselineGroupPids]):!1}captureBaselineGroupPids(){const e=this.agentProcess?.pid;e&&H(e).then(t=>{this.baselineGroupPids=t}).catch(()=>{})}async probe(e){const t=this.config.command||"gemini",i=await pe(t),s=i!==null;let n=null,o;if(s){const h=await me(t);n=h.version,h.error&&(o=h.error)}else o={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,...o?{error:o}:{}},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=q({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(`
3
+ `):"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(){const e=this.acpClient?.sessionOptions??null,t=this.isReasonix?O:this.isKimi?R:null;if(!t||e&&e.models.length>0)return e;const i=this.resolveFallbackCurrentModelId(t,e?.currentModelId??"");return{modes:e?.modes??[],currentModeId:e?.currentModeId??"",models:[...t],currentModelId:i}}resolveFallbackCurrentModelId(e,t){if(e.some(s=>s.modelId===t))return t;const i=(this.autoInjectArgs?.model??this.acpInitialModel??"").trim();return i&&e.some(s=>s.modelId===i)?i:e[0]?.modelId??""}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 o=i?.modes.map(p=>({id:p.id,name:p.name}))??[],a=i?.models&&i.models.length>0,l=a?i.models:this.isReasonix?O:this.isKimi?R:[],d=a?"acp":this.isReasonix?"reasonix_fallback":this.isKimi?"kimi_fallback":"empty";this.isKimi&&!a&&r.warn("acp-adapter",`[toolbar-diag] kimi ACP session returned no models; using static fallback. command=${this.config.command} fallbackIds=${JSON.stringify(R.map(p=>p.modelId))}`),r.info("acp-adapter",`[toolbar-diag] buildToolbarContext outcome=${e} agent=${this.config.command} modelSource=${d} acpModelCount=${i?.models?.length??0} finalModelCount=${l.length}`);const h=l.map(p=>({modelId:p.modelId,name:p.name})),u=l.map(p=>({id:p.modelId,displayName:p.name})),T=i?.modes.map(p=>({id:p.id,displayName:p.name}))??[];u.length>0&&!u.some(p=>p.id===n)&&(n=this.resolveFallbackCurrentModelId(l,n));const y={outcome:e,...t?{cwd:t}:{},model_id:n,mode_id:s,currentModelId:n,currentModeId:s,available_models:u,available_modes:T,availableModels:u,availableModes:T,models:h,modes:o};return r.info("acp-adapter",`[toolbar] buildToolbarContext outcome=${e} model_id="${n}" available_models=${JSON.stringify(u.map(p=>p.id))} currentModelId=${n}`),y}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?(r.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=>{r.error("acp-adapter",`Failed to respond to permission: ${s}`)}),this.activeRun&&(this.activeRun.lastProgressAt=Date.now(),this.resetIdleTimer(this.activeRun)),!0):(r.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=>{r.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==="claude_interaction_reply"&&String(i.kind??"")==="permission"){const s=i.resolution&&typeof i.resolution=="object"?i.resolution:{},n=String(s.value??i.decision??"");return["allow","allow-once","allow-always","deny"].includes(n)?this.handleLocalAction({...e,action_type:n==="deny"?"permission_reject":"permission_approve",params:{...i,approval_command_id:i.request_id,decision:n}}):(this.callbacks.sendLocalActionResult(e.action_id,"failed",void 0,"permission_decision_invalid","permission decision is invalid"),{handled:!0,kind:"approval"})}if(t==="claude_interaction_reply")return this.handleAcpQuestionReply(e);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",o=String(i.decision??"");let a;return n&&(o==="allow-once"||o==="allow-always")?a=o:n?a="allow":a="deny",s?this.handleAcpApprovalAction(s,a)?(this.callbacks.sendLocalActionResult(e.action_id,"ok"),{handled:!0,kind:"approval"}):(this.callbacks.sendLocalActionResult(e.action_id,"failed",void 0,"approval_not_found",`no pending approval for tool_call_id: ${s}`),{handled:!0,kind:"approval"}):(this.callbacks.sendLocalActionResult(e.action_id,"failed",void 0,"tool_call_id_required","tool_call_id is required"),{handled:!0,kind:"approval"})}return{handled:!1,kind:""}}async handleQuestionReplyEvent(e){let t;if(e.action)t={type:"action",value:e.action};else if(e.response?.type==="single")t={type:"text",value:e.response.value};else if(e.response?.type==="map")t={type:"map",entries:e.response.entries};else return{delivered:!1,errorCode:"resolution_invalid",errorMsg:"Question reply response is invalid."};return this.deliverQuestionReply({request_id:e.request_id,resolution:t})}async handleAcpQuestionReply(e){const t=await this.deliverQuestionReply(e.params??{});return t.delivered?(this.callbacks.sendLocalActionResult(e.action_id,"ok",t.result),{handled:!0,kind:t.kind??"question_reply"}):(this.callbacks.sendLocalActionResult(e.action_id,"failed",void 0,t.errorCode,t.errorMsg),{handled:!0,kind:t.kind??"question_reply_failed"})}async deliverQuestionReply(e){const t=String(e.request_id??"").trim(),i=this.pendingQuestions.get(t);if(!t||!i||!this.acpClient)return{delivered:!1,errorCode:"interaction_request_not_pending",errorMsg:"The question is no longer pending; the reply was not delivered.",kind:"question_reply_not_pending"};const s=e.resolution&&typeof e.resolution=="object"?e.resolution:{},n=String(s.type??"");let o;if(n==="action"&&String(s.value??"")==="cancel")o=i.options.find(l=>/_skip$/.test(l.optionId)||l.kind.toLowerCase().includes("reject"));else{let l="";if(n==="text")l=String(s.value??"").trim();else if(n==="map"){const d=Array.isArray(s.entries)?s.entries:[];l=String(d[0]?.value??"").trim()}o=i.options.find(d=>d.name===l||d.optionId===l)}if(!o)return{delivered:!1,errorCode:"question_answer_invalid",errorMsg:"The selected answer does not match a pending question option.",kind:"question_reply_invalid"};try{await this.acpClient.respondPermissionOption(t,o.optionId)}catch(l){return{delivered:!1,errorCode:"question_reply_failed",errorMsg:l instanceof Error?l.message:String(l),kind:"question_reply_failed"}}this.pendingQuestions.delete(t),this.activeRun&&(this.activeRun.lastProgressAt=Date.now(),this.resetIdleTimer(this.activeRun));const a=/_skip$/.test(o.optionId);return{delivered:!0,result:{request_id:t,resolution:a?"cancel":"answer"},kind:a?"question_reply_cancel":"question_reply"}}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 g.stat(i),!1}catch{r.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=>{r.error("acp-adapter",`Failed to create session on bind: ${v(s)}`);const n=this.buildToolbarContext("bind_failed",t),o=Array.isArray(n.available_models)&&n.available_models.length>0;return this.callbacks.sendUpdateBindingCard(e,"failed",t,o?n:void 0),!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=q({mode:"kiro",projectDir:t})}catch(s){r.warn("acp-adapter",`Kiro skills scan failed: ${s instanceof Error?s.message:String(s)}`);return}try{this.callbacks.onSkillsUpdate(i,t)}catch(s){r.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){r.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),r.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(K),r.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){}sendTerminalEventWithoutRun(e,t,i){this.emit("eventDone",e),this.callbacks.sendEventResult(e,t,i)}deliverInboundEvent(e){if(this.eventResults?.has(e.session_id,e.event_id)){const t=this.eventResults.get(e.session_id,e.event_id);r.info("acp-adapter",`Deduplicating event ${e.event_id} (cached: ${t.status})`),this.sendTerminalEventWithoutRun(e.event_id,t.status,t.msg);return}if(this.compacting){r.info("acp-adapter",`Event ${e.event_id} deferred: compaction in progress`),this.deferEvent(e);return}if(this.activeRun){r.info("acp-adapter",`Event ${e.event_id} rejected: busy`),this.sendTerminalEventWithoutRun(e.event_id,"failed","agent busy");return}if(!this.agentProcess?.alive){r.warn("acp-adapter",`Event ${e.event_id} cannot start: agent process not alive; requesting session recovery`),this.emit("internalError",{eventId:e.event_id,sessionId:e.session_id,errorMsg:"agent not alive",replayOriginalContent:!0});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),r.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)r.info("acp-adapter",`Rejecting deferred event ${s.event_id}: ${e}`),this.sendTerminalEventWithoutRun(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){r.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.sendTerminalEventWithoutRun(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 ne,quotedMessageId:void 0,flushTimer:null,idleTimer:null,awaitingToolResult:!1,responded:!1,silent:t,lastProgressAt:Date.now(),lastIdleCheckAt:Date.now(),idleNoProgressCount:0,consecutivePingFailures: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;this.beginAuditCapture(e);const n=oe(e,this.resolveCwd());this.resetIdleTimer(s),n.modeId&&this.acpClient&&this.acpClient.setLiveMode(n.modeId).catch(()=>{}),n.modelId&&this.acpClient&&this.acpClient.setModel(n.modelId).catch(()=>{});const o=re(n.prompt,{messageId:e.msg_id,quotedMessageId:e.quoted_message_id}),a=this.injectQuoteProtocolOnce(s.sessionId,o),l=this.injectIdentityOnce(s.sessionId,a),d=this.injectSessionContextOnce(s.sessionId,l),h=this.injectRecoveryContext(s.sessionId,d);this.sendPromptWithRetry(s,h)}injectQuoteProtocolOnce(e,t){return!e||this.quoteProtocolTaughtSessions.has(e)?t:(this.quoteProtocolTaughtSessions.add(e),`${Se}
4
4
 
5
5
  ${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 o=["[system-identity]"];return s&&o.push(`Your name is "${s}".`),n&&o.push(`Your self-introduction: ${n}`),o.push("Treat this as an out-of-band instruction; do not echo or repeat it in your replies."),o.push("[/system-identity]"),r.info("acp-adapter",`Injected agent identity for session=${e} name="${s}"`),`${o.join(`
6
6
  `)}
@@ -10,7 +10,7 @@ ${t}`}injectSessionContextOnce(e,t){return!e||!this.sessionContextPending?t:(thi
10
10
  ${t}`)}injectRecoveryContext(e,t){const i=this.recoveryContextBySessionId.get(e);return i?(this.recoveryContextBySessionId.delete(e),r.info("acp-adapter",`Injecting recovery context for session ${e} (chars=${i.length})`),`${i}
11
11
 
12
12
  [\u5F53\u524D\u7528\u6237\u6D88\u606F]
13
- ${t}`):t}sendPromptWithRetry(e,t){this.acpClient.send(t).then(()=>{this.activeRun===e&&(e.busyWaiting=!1)}).catch(i=>{if(be(i)){this.scheduleAgentBusyRetry(e,t,v(i));return}if(r.error("acp-adapter",`Prompt failed: ${v(i)}`),Te(i)){const n=v(i),o=e.eventId,a=e.sessionId;r.info("acp-adapter",`Internal error escalated to bridge: event=${o} session=${a} err=${n}`),this.silentlyDiscardActiveRun(e),this.emit("internalError",{eventId:o,sessionId:a,errorMsg:n});return}const s=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>=j){r.error("acp-adapter",`agent busy wait exceeded ${Math.round(j/6e4)}min, giving up event=${e.eventId}: ${i}`),e.buffer+=`
13
+ ${t}`):t}sendPromptWithRetry(e,t){this.acpClient.send(t).then(()=>{this.activeRun===e&&(e.busyWaiting=!1)}).catch(i=>{if(be(i)){this.scheduleAgentBusyRetry(e,t,v(i));return}if(r.error("acp-adapter",`Prompt failed: ${v(i)}`),Te(i)){const n=v(i),o=e.eventId,a=e.sessionId;r.info("acp-adapter",`Internal error escalated to bridge: event=${o} session=${a} err=${n}`),this.silentlyDiscardActiveRun(e),this.emit("internalError",{eventId:o,sessionId:a,errorMsg:n});return}const s=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){r.error("acp-adapter",`agent busy wait exceeded ${Math.round(U/6e4)}min, giving up event=${e.eventId}: ${i}`),e.buffer+=`
14
14
 
15
15
  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 o=Math.min(Ie*2**e.busyRetryCount,ye);e.busyRetryCount+=1,r.info("acp-adapter",`agent busy, keeping event queued event=${e.eventId} waited=${Math.round(n/1e3)}s retry_in=${o}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)}},o),e.busyRetryTimer.unref?.()}silentlyDiscardActiveRun(e){this.activeRun===e&&(this.snapshotAuditBoundary(e.eventId),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.reasonixAuditTranscriptPath&&!e.includes("--transcript")&&(await g.mkdir(m.dirname(this.reasonixAuditTranscriptPath),{recursive:!0,mode:448}),await(await g.open(this.reasonixAuditTranscriptPath,"a",384)).close(),await g.chmod(this.reasonixAuditTranscriptPath,384),e.push("--transcript",this.reasonixAuditTranscriptPath)),this.agentProcess=new G(this.bridgeLog),this.baselineGroupPids=[];const i=this.resolveCwd();try{if(!(await g.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 z(this.config.command,i,this.config.env,this.nativeProviderScope).catch(s=>{r.warn("acp-adapter",`native provider config skipped: ${s instanceof Error?s.message:String(s)}`)}),r.info("acp-adapter",`Spawning ACP agent command=${this.config.command} args=${JSON.stringify(e)} cwd=${i} envKeys=${JSON.stringify(Object.keys(this.config.env??{}).sort())}`);try{if(this.isKimi){const s=J({...process.env,...this.config.env});r.info("acp-adapter",`Prepared Kimi Layer plugin at ${s.pluginRoot} (registrationChanged=${s.registrationChanged})`)}await this.agentProcess.start({command:this.config.command,args:e.length>0?e:void 0,cwd:i,env:this.config.env})}catch(s){throw r.error("acp-adapter",`Failed to spawn agent process: ${s}`),this.rejectDeferredEvents(`agent spawn failed: ${s instanceof Error?s.message:String(s)}`),this.emit("exit",null),s}r.info("acp-adapter","ACP agent process started"),this.agentProcess.on("error",s=>{this.stopped||(r.error("acp-adapter",`Agent process error: ${s.message}`),this.activeRun&&this.finishRun("failed",`agent process error: ${s.message}`),this.rejectDeferredEvents(`agent process error: ${s.message}`),this.emit("exit",null))}),this.agentProcess.on("exit",s=>{this.stopped||(r.error("acp-adapter",`Agent process exited unexpectedly (code=${s})`),this.activeRun&&this.finishRun("failed",`agent process exited (code=${s})`),this.rejectDeferredEvents(`agent process exited (code=${s})`),this.emit("exit",s))}),this.agentProcess.on("stderr",()=>{this.activeRun&&this.resetIdleTimer(this.activeRun)})}async connectSession(e){if(this.sessionConnected||!this.agentProcess?.transport)return;let t;this.acpMcpTools&&(t=await this.startInternalApiAndMcp()),this.acpClient=new Z,this.acpClient.on("event",l=>this.handleAcpEvent(l)),this.acpClient.on("activity",()=>{this.activeRun&&this.resetIdleTimer(this.activeRun)}),this.acpClient.on("session-lost",()=>{this.stopped||this.agentProcess?.alive&&(r.error("acp-adapter","ACP transport closed while agent process still alive, triggering respawn"),this.activeRun&&(this.callbacks.sendRunError(this.activeRun.eventId,this.activeRun.sessionId,"ACP transport closed"),this.finishRun("failed","ACP transport closed")),this.emit("exit",null))});const i=this.currentAibotSessionId&&this.bindingStore?this.bindingStore.getAcpSessionId(this.currentAibotSessionId):void 0,s=!!i&&i!=="__continue__",n=async l=>{await this.acpClient.connect({transport:this.agentProcess.transport,authMethod:this.acpAuthMethod,initialMode:this.acpInitialMode,initialModel:this.acpInitialModel,cwd:e||this.resolveCwd(),mcpServers:t,sessionId:l})};let o=!1,a;try{await n(i)}catch(l){if(l instanceof ee){await this.handleAuthRequired(l);return}if(!i)throw this.handleConnectFailure(l),l;if(r.warn("acp-adapter",`Failed to load persisted session ${i}, fallback to session/new: ${v(l)}`),o=!0,a=i,this.config.command==="kiro-cli"&&this.currentAibotSessionId){r.info("acp-adapter",`Building kiro recovery summary: aibotSession=${this.currentAibotSessionId} acpSession=${i}`);const d=await this.buildKiroRecoveryContext(this.currentAibotSessionId,i,e||this.resolveCwd());d?(this.recoveryContextBySessionId.set(this.currentAibotSessionId,d),r.info("acp-adapter",`Recovery context prepared for session ${this.currentAibotSessionId} (chars=${d.length})`)):r.warn("acp-adapter",`Recovery context skipped: no summary generated for acpSession=${i}`)}await n(void 0)}this.sessionConnected=!0,this.sessionContextPending=!s||o,this.captureBaselineGroupPids(),r.info("acp-adapter",`ACP session ready: ${this.acpClient.sessionId}`),this.emit("acpSessionReady",this.acpClient.sessionId),this.currentAibotSessionId&&this.bindingStore&&this.bindingStore.setAcpSessionId(this.currentAibotSessionId,this.acpClient.sessionId);for(const[l,d]of this.sessionBindings){this.bindingStore&&this.acpClient.sessionId&&this.bindingStore.setAcpSessionId(l,this.acpClient.sessionId);const h=o?"binding_recreated":"binding_ready",u=this.buildToolbarContext(h,d);o&&a&&(u.previous_session_id=a,u.recreated_session_id=this.acpClient.sessionId),this.callbacks.sendUpdateBindingCard(l,"ready",d,u)}}async buildKiroRecoveryContext(e,t,i){try{const s=m.join(I(),".kiro","sessions","cli",`${t}.json`);await g.access(s),r.info("acp-adapter",`Reading kiro source session file: ${s}`);const n=await this.generateSummaryByKiroCli(s,i);if(!n)return;const o=m.join(I(),".grix","data","session-summaries");await g.mkdir(o,{recursive:!0});const a=m.join(o,`${e}.md`),l=[`SOURCE_KIRO_SESSION_FILE: ${s}`,"",n.trim(),""].join(`
16
16
  `);return await g.writeFile(a,l,"utf8"),r.info("acp-adapter",`Recovery summary saved: ${a} (chars=${n.length})`),["[\u5386\u53F2\u4E0A\u4E0B\u6587\u56DE\u704C\u8BF4\u660E]","\u4EE5\u4E0B\u5185\u5BB9\u6765\u81EA\u65E7\u4F1A\u8BDD\u81EA\u52A8\u6458\u8981\uFF0C\u8BF7\u5728\u540E\u7EED\u56DE\u7B54\u4E2D\u5EF6\u7EED\u5176\u4E2D\u7684\u76EE\u6807\u3001\u7EA6\u675F\u548C\u7ED3\u8BBA\u3002",`SOURCE_KIRO_SESSION_FILE: ${s}`,`SUMMARY_FILE: ${a}`,"",n.trim()].join(`
@@ -23,6 +23,6 @@ ${s}
23
23
  Waiting for authentication to complete...`)}try{await this.acpClient.authenticate(t.id),r.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}),r.info("acp-adapter",`ACP session ready after auth: ${this.acpClient.sessionId}`),this.captureBaselineGroupPids(),this.emit("acpSessionReady",this.acpClient.sessionId);for(const[n,o]of this.sessionBindings)this.callbacks.sendUpdateBindingCard(n,"ready",o,this.buildToolbarContext("binding_ready",o))}catch(s){throw r.error("acp-adapter",`Auth retry failed: ${s}`),s}}runTerminalAuth(e,t){return new Promise((i,s)=>{const n=$(this.config.command,e.args??[],{env:{...process.env,...this.config.env??{}},stdio:["ignore","pipe","pipe"]});this.terminalAuthChild=n;const o=/https?:\/\/[^\s"')\]]+/;let a=!1,l=!1;const d=u=>{if(a)return;const y=u.toString().replace(/\x1b\[[0-9;]*m/g,"").match(o);y&&(a=!0,r.info("acp-adapter",`Terminal auth URL captured: ${y[0]}`),this.callbacks.sendAuthNotification(t,`Please open this URL on any device to authenticate:
24
24
  ${y[0]}
25
25
 
26
- 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 ${U}ms`)))},U);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{r.info("acp-adapter","incoming acp event",{eventType:e.type,sessionId:this.activeRun?.sessionId??null,eventKeys:Object.keys(e),rawEvent:M(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}this.captureAuditEvent(e),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=A(s);if(t.lastToolUse?.toolName===e.toolName&&A(t.lastToolUse.toolInput)===n)break;t.lastToolUse={toolName:e.toolName,toolInput:s},e.toolCallId&&t.sentToolUseIds.add(e.toolCallId);const o=this.trimToolWirePayload?E(s):s;this.emitRawEventEnvelope(t,{type:"tool_use",payload:{tool_name:e.toolName,tool_input:o}})||this.callbacks.sendToolUse(t.eventId,t.sessionId,e.toolName,o)}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=x(t,e),n=e.content,o=A(n);if(t.lastToolResult?.toolName===s&&A(t.lastToolResult.content)===o)break;if(t.lastToolResult={toolName:s,content:n},!this.trimToolWirePayload||e.toolStatus==="failed"){const a=this.trimToolWirePayload?"(failed)":n;this.emitRawEventEnvelope(t,{type:"tool_result",payload:{tool_name:s,content:a}})||this.callbacks.sendToolResult(t.eventId,t.sessionId,s,a)}}break}case f.ToolProgress:{if(i=!0,e.content){const s=e.toolCallId||e.toolName||"";if(t.lastToolProgress.get(s)===e.content||(t.lastToolProgress.set(s,e.content),this.trimToolWirePayload))break;const n=Date.now(),o=t.lastToolProgressSentAt.get(s);if(o!==void 0&&n-o<ae)break;t.lastToolProgressSentAt.set(s,n);const a=x(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}}),r.error("acp-adapter",`ACP error: ${s}`),this.callbacks.sendRunError(t.eventId,t.sessionId,s);break}case f.Result:{if(e.synthetic&&this.shouldDeferSyntheticResultForDetachedKimiTask(t,e)){r.info("acp-adapter",`Synthetic ACP Result deferred: kimi detached task still running event=${t.eventId} session=${t.sessionId}`),this.acpClient?.deferSettledResult();return}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(r.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:M(t,3e3)}),!t||!e.requestId||!this.acpClient){r.warn("acp-adapter","permission_request dropped: missing prerequisite",{hasPr:t!=null,hasRequestId:!!e.requestId,hasAcpClient:!!this.acpClient});return}const i=this.activeRun,s=ce(t);if(s&&i&&this.callbacks.sendAgentQuestionCard){i.idleTimer&&(clearTimeout(i.idleTimer),i.idleTimer=null),this.pendingQuestions.set(e.requestId,{options:[...t.options]}),this.callbacks.sendAgentQuestionCard(i.eventId,i.sessionId,{request_id:e.requestId,mode:"form",questions:[{header:s.prompt,prompt:s.prompt,options:s.answerOptions.map(a=>a.name)}]}),r.info("acp-adapter","AskUserQuestion forwarded via agent_question card",{sessionId:i.sessionId,eventId:i.eventId,requestId:e.requestId,optionsCount:s.answerOptions.length});return}if(this.approvalMode==="yolo"||this.approvalMode==="autoEdit"){r.info("acp-adapter",`Auto-approving (${this.approvalMode}): ${t.toolName}`),this.acpClient.respondPermission(t.requestId,{behavior:"allow"}).catch(()=>{});return}const n=t.toolCallId,o=le(t.rawParams,e.toolInput,t.toolTitle);this.pendingApprovals.set(n,e.requestId),i?(i.idleTimer&&(clearTimeout(i.idleTimer),i.idleTimer=null),this.emitRawEventEnvelope(i,{type:"permission_request",payload:{request_id:t.requestId,tool_call_id:n,tool_name:t.toolName,tool_title:t.toolTitle,...o?{tool_input:o}:{},options:t.options}})?r.info("acp-adapter","permission_request forwarded via rawEventEnvelope",{sessionId:i.sessionId,eventId:i.eventId,toolCallId:n,toolName:t.toolName}):(r.info("acp-adapter","permission_request falling back to sendPermissionCard",{sessionId:i.sessionId,eventId:i.eventId,toolCallId:n,toolName:t.toolName}),this.callbacks.sendPermissionCard({eventId:i.eventId,sessionId:i.sessionId,toolCallId:n,toolName:t.toolName,toolTitle:t.toolTitle,toolInput:o,options:t.options}))):(r.info("acp-adapter",`Permission request without active run, auto-approving: ${t.toolName}`),this.acpClient.respondPermission(e.requestId,{behavior:"allow"}),this.pendingApprovals.delete(n))}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,t){e.idleTimer&&clearTimeout(e.idleTimer);const i=t??(e.awaitingToolResult?ve:ge),s=e.lastProgressAt+S-Date.now(),n=Math.max(0,Math.min(i,s));e.idleTimer=setTimeout(()=>this.onIdleTimeout(e),n)}onIdleTimeout(e){if(this.activeRun?.eventId!==e.eventId)return;const t=Date.now()-e.lastProgressAt;if(t>=S){r.error("acp-adapter",`Turn inactive ${Math.round(t/1e3)}s \u2265 cap ${Math.round(S/1e3)}s, forcing timeout: ${e.eventId}`),this.finishRun("failed",`turn inactivity timeout after ${Math.round(t/1e3)}s with no ACP activity`),this.declareStuck();return}const i=this.acpClient;i?.isAlive?i.ping(5e3).then(async s=>{if(this.activeRun?.eventId!==e.eventId)return;if(!s){await this.handlePingFailure(e,i);return}if(e.consecutivePingFailures=0,e.awaitingToolResult){const d=e.lastIdleCheckAt,h=await this.checkSessionFileActivity(i.sessionId,d);if(this.activeRun?.eventId!==e.eventId)return;e.lastIdleCheckAt=Date.now(),h?(e.lastProgressAt=Date.now(),r.info("acp-adapter",`Idle timer: tool running, ping ok, session file active, refreshing watchdog: ${e.eventId}`)):r.info("acp-adapter",`Idle timer: tool running, ping ok, continuing until inactivity cap: ${e.eventId}`),e.idleNoProgressCount=0,this.resetIdleTimer(e);return}const n=e.lastIdleCheckAt,o=e.lastProgressAt>n,a=o?!1:await this.checkSessionFileActivity(i.sessionId,n);if(this.activeRun?.eventId!==e.eventId)return;const l=o||a;e.lastIdleCheckAt=Date.now(),l?(a&&(r.info("acp-adapter",`Idle timer: no stream progress but session file active, continuing: ${e.eventId}`),e.lastProgressAt=Date.now()),e.idleNoProgressCount=0,this.resetIdleTimer(e)):(e.idleNoProgressCount++,r.info("acp-adapter",`Idle timer: ping ok, no stream progress (cycle=${e.idleNoProgressCount}), extending until inactivity cap (${Math.round(S/6e4)}min): ${e.eventId}`),this.resetIdleTimer(e))}).catch(()=>{this.activeRun?.eventId===e.eventId&&this.handlePingFailure(e,i)}):(r.error("acp-adapter",`Agent idle (no client), declaring stuck: ${e.eventId}`),this.finishRun("failed","agent unreachable"),this.declareStuck())}async handlePingFailure(e,t){const i=e.lastIdleCheckAt,s=e.lastProgressAt>i,n=s?!1:await this.checkSessionFileActivity(t.sessionId,i);if(this.activeRun?.eventId===e.eventId){if(s||n||e.awaitingToolResult){const o=s?"stream progress":n?"session file activity":"tool in flight";r.info("acp-adapter",`Idle timer: ping failed but ${o}, continuing: ${e.eventId}`),e.consecutivePingFailures=0,e.lastIdleCheckAt=Date.now(),n&&(e.lastProgressAt=Date.now()),this.resetIdleTimer(e);return}if(e.consecutivePingFailures++,e.lastIdleCheckAt=Date.now(),e.consecutivePingFailures<D){r.warn("acp-adapter",`Idle timer: ping failed (${e.consecutivePingFailures}/${D}), rechecking in ${B/1e3}s: ${e.eventId}`),this.resetIdleTimer(e,B);return}r.error("acp-adapter",`Ping failed ${e.consecutivePingFailures}x with no activity evidence, declaring stuck: ${e.eventId}`),this.finishRun("failed","agent unreachable"),this.declareStuck()}}async checkSessionFileActivity(e,t){if(!e)return!1;try{return this.isKimi?await ue(e,t):this.config.command==="kiro-cli"?await de(e,t):!1}catch{return!1}}shouldDeferSyntheticResultForDetachedKimiTask(e,t){if(!this.isKimi)return!1;try{return he(t.sessionId||this.acpClient?.sessionId||e.sessionId)}catch{return!1}}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(),fe))}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,r.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>=Ae?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()},Ce),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){r.warn("acp-adapter",`orphan agent output dropped (${t?"no sendAgentMessage callback":"no session id"}) chars=${i.length}`);return}r.info("acp-adapter",`delivering orphan agent output session=${t} chars=${i.length}`);for(let s=0;s<i.length;s+=L)this.callbacks.sendAgentMessage(t,i.slice(s,s+L))}}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.snapshotAuditBoundary(i.eventId),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"?(r.error("acp-adapter",`finishRun failed: ${t} event=${i.eventId}`),i.buffer+=`
26
+ 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 ${j}ms`)))},j);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{r.info("acp-adapter","incoming acp event",{eventType:e.type,sessionId:this.activeRun?.sessionId??null,eventKeys:Object.keys(e),rawEvent:M(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}this.captureAuditEvent(e),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=A(s);if(t.lastToolUse?.toolName===e.toolName&&A(t.lastToolUse.toolInput)===n)break;t.lastToolUse={toolName:e.toolName,toolInput:s},e.toolCallId&&t.sentToolUseIds.add(e.toolCallId);const o=this.trimToolWirePayload?E(s):s;this.emitRawEventEnvelope(t,{type:"tool_use",payload:{tool_name:e.toolName,tool_input:o}})||this.callbacks.sendToolUse(t.eventId,t.sessionId,e.toolName,o)}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=x(t,e),n=e.content,o=A(n);if(t.lastToolResult?.toolName===s&&A(t.lastToolResult.content)===o)break;if(t.lastToolResult={toolName:s,content:n},!this.trimToolWirePayload||e.toolStatus==="failed"){const a=this.trimToolWirePayload?"(failed)":n;this.emitRawEventEnvelope(t,{type:"tool_result",payload:{tool_name:s,content:a}})||this.callbacks.sendToolResult(t.eventId,t.sessionId,s,a)}}break}case f.ToolProgress:{if(i=!0,e.content){const s=e.toolCallId||e.toolName||"";if(t.lastToolProgress.get(s)===e.content||(t.lastToolProgress.set(s,e.content),this.trimToolWirePayload))break;const n=Date.now(),o=t.lastToolProgressSentAt.get(s);if(o!==void 0&&n-o<ae)break;t.lastToolProgressSentAt.set(s,n);const a=x(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}}),r.error("acp-adapter",`ACP error: ${s}`),this.callbacks.sendRunError(t.eventId,t.sessionId,s);break}case f.Result:{if(e.synthetic&&this.shouldDeferSyntheticResultForDetachedKimiTask(t,e)){r.info("acp-adapter",`Synthetic ACP Result deferred: kimi detached task still running event=${t.eventId} session=${t.sessionId}`),this.acpClient?.deferSettledResult();return}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(r.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:M(t,3e3)}),!t||!e.requestId||!this.acpClient){r.warn("acp-adapter","permission_request dropped: missing prerequisite",{hasPr:t!=null,hasRequestId:!!e.requestId,hasAcpClient:!!this.acpClient});return}const i=this.activeRun,s=ce(t);if(s&&i&&this.callbacks.sendAgentQuestionCard){i.idleTimer&&(clearTimeout(i.idleTimer),i.idleTimer=null),this.pendingQuestions.set(e.requestId,{options:[...t.options]}),this.callbacks.sendAgentQuestionCard(i.eventId,i.sessionId,{request_id:e.requestId,mode:"form",questions:[{header:s.prompt,prompt:s.prompt,options:s.answerOptions.map(a=>a.name)}]}),r.info("acp-adapter","AskUserQuestion forwarded via agent_question card",{sessionId:i.sessionId,eventId:i.eventId,requestId:e.requestId,optionsCount:s.answerOptions.length});return}if(this.approvalMode==="yolo"||this.approvalMode==="autoEdit"){r.info("acp-adapter",`Auto-approving (${this.approvalMode}): ${t.toolName}`),this.acpClient.respondPermission(t.requestId,{behavior:"allow"}).catch(()=>{});return}const n=t.toolCallId,o=le(t.rawParams,e.toolInput,t.toolTitle);this.pendingApprovals.set(n,e.requestId),i?(i.idleTimer&&(clearTimeout(i.idleTimer),i.idleTimer=null),this.emitRawEventEnvelope(i,{type:"permission_request",payload:{request_id:t.requestId,tool_call_id:n,tool_name:t.toolName,tool_title:t.toolTitle,...o?{tool_input:o}:{},options:t.options}})?r.info("acp-adapter","permission_request forwarded via rawEventEnvelope",{sessionId:i.sessionId,eventId:i.eventId,toolCallId:n,toolName:t.toolName}):(r.info("acp-adapter","permission_request falling back to sendPermissionCard",{sessionId:i.sessionId,eventId:i.eventId,toolCallId:n,toolName:t.toolName}),this.callbacks.sendPermissionCard({eventId:i.eventId,sessionId:i.sessionId,toolCallId:n,toolName:t.toolName,toolTitle:t.toolTitle,toolInput:o,options:t.options}))):(r.info("acp-adapter",`Permission request without active run, auto-approving: ${t.toolName}`),this.acpClient.respondPermission(e.requestId,{behavior:"allow"}),this.pendingApprovals.delete(n))}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,t){e.idleTimer&&clearTimeout(e.idleTimer);const i=t??(e.awaitingToolResult?ve:ge),s=e.lastProgressAt+S-Date.now(),n=Math.max(0,Math.min(i,s));e.idleTimer=setTimeout(()=>this.onIdleTimeout(e),n)}onIdleTimeout(e){if(this.activeRun?.eventId!==e.eventId)return;const t=Date.now()-e.lastProgressAt;if(t>=S){r.error("acp-adapter",`Turn inactive ${Math.round(t/1e3)}s \u2265 cap ${Math.round(S/1e3)}s, forcing timeout: ${e.eventId}`),this.finishRun("failed",`turn inactivity timeout after ${Math.round(t/1e3)}s with no ACP activity`),this.declareStuck();return}const i=this.acpClient;i?.isAlive?i.ping(5e3).then(async s=>{if(this.activeRun?.eventId!==e.eventId)return;if(!s){await this.handlePingFailure(e,i);return}if(e.consecutivePingFailures=0,e.awaitingToolResult){const d=e.lastIdleCheckAt,h=await this.checkSessionFileActivity(i.sessionId,d);if(this.activeRun?.eventId!==e.eventId)return;e.lastIdleCheckAt=Date.now(),h?(e.lastProgressAt=Date.now(),r.info("acp-adapter",`Idle timer: tool running, ping ok, session file active, refreshing watchdog: ${e.eventId}`)):r.info("acp-adapter",`Idle timer: tool running, ping ok, continuing until inactivity cap: ${e.eventId}`),e.idleNoProgressCount=0,this.resetIdleTimer(e);return}const n=e.lastIdleCheckAt,o=e.lastProgressAt>n,a=o?!1:await this.checkSessionFileActivity(i.sessionId,n);if(this.activeRun?.eventId!==e.eventId)return;const l=o||a;e.lastIdleCheckAt=Date.now(),l?(a&&(r.info("acp-adapter",`Idle timer: no stream progress but session file active, continuing: ${e.eventId}`),e.lastProgressAt=Date.now()),e.idleNoProgressCount=0,this.resetIdleTimer(e)):(e.idleNoProgressCount++,r.info("acp-adapter",`Idle timer: ping ok, no stream progress (cycle=${e.idleNoProgressCount}), extending until inactivity cap (${Math.round(S/6e4)}min): ${e.eventId}`),this.resetIdleTimer(e))}).catch(()=>{this.activeRun?.eventId===e.eventId&&this.handlePingFailure(e,i)}):(r.error("acp-adapter",`Agent idle (no client), declaring stuck: ${e.eventId}`),this.finishRun("failed","agent unreachable"),this.declareStuck())}async handlePingFailure(e,t){const i=e.lastIdleCheckAt,s=e.lastProgressAt>i,n=s?!1:await this.checkSessionFileActivity(t.sessionId,i);if(this.activeRun?.eventId===e.eventId){if(s||n||e.awaitingToolResult){const o=s?"stream progress":n?"session file activity":"tool in flight";r.info("acp-adapter",`Idle timer: ping failed but ${o}, continuing: ${e.eventId}`),e.consecutivePingFailures=0,e.lastIdleCheckAt=Date.now(),n&&(e.lastProgressAt=Date.now()),this.resetIdleTimer(e);return}if(e.consecutivePingFailures++,e.lastIdleCheckAt=Date.now(),e.consecutivePingFailures<D){r.warn("acp-adapter",`Idle timer: ping failed (${e.consecutivePingFailures}/${D}), rechecking in ${B/1e3}s: ${e.eventId}`),this.resetIdleTimer(e,B);return}r.error("acp-adapter",`Ping failed ${e.consecutivePingFailures}x with no activity evidence, declaring stuck: ${e.eventId}`),this.finishRun("failed","agent unreachable"),this.declareStuck()}}async checkSessionFileActivity(e,t){if(!e)return!1;try{return this.isKimi?await ue(e,t):this.config.command==="kiro-cli"?await de(e,t):!1}catch{return!1}}shouldDeferSyntheticResultForDetachedKimiTask(e,t){if(!this.isKimi)return!1;try{return he(t.sessionId||this.acpClient?.sessionId||e.sessionId)}catch{return!1}}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(),fe))}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,r.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>=Ae?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()},Ce),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){r.warn("acp-adapter",`orphan agent output dropped (${t?"no sendAgentMessage callback":"no session id"}) chars=${i.length}`);return}r.info("acp-adapter",`delivering orphan agent output session=${t} chars=${i.length}`);for(let s=0;s<i.length;s+=L)this.callbacks.sendAgentMessage(t,i.slice(s,s+L))}}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.snapshotAuditBoundary(i.eventId),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"?(r.error("acp-adapter",`finishRun failed: ${t} event=${i.eventId}`),i.buffer+=`
27
27
 
28
28
  The task was interrupted. Please resend your instruction.`):r.info("acp-adapter",`finishRun ${e}: ${t} event=${i.eventId}`)),i.buffer&&(this.emitSegmentedStream(i,i.buffer),i.buffer="");const n=++i.chunkSeq;this.callbacks.sendFinalStreamChunkReliable?this.callbacks.sendFinalStreamChunkReliable(i.eventId,i.sessionId,i.currentClientMsgId).then(()=>{i.silent||this.callbacks.sendEventResult(i.eventId,e,t),this.persistEventResult(i,e,t),this.replayNextDeferredEvent(),this.tryRunPendingAutoCompact()}).catch(o=>{const a=`final output delivery failed: ${o instanceof Error?o.message:String(o)}`,l=e==="responded"?"failed":e,d=e==="responded"?a:t;r.warn("acp-adapter",`finalStreamChunk rejected/failed event=${i.eventId}: ${a}`),i.silent||this.callbacks.sendEventResult(i.eventId,l,d),this.persistEventResult(i,l,d),this.replayNextDeferredEvent(),this.tryRunPendingAutoCompact()}):(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())}beginAuditCapture(e){if(!e.audit?.enabled||this.auditCaptures.has(e.event_id))return;let t;if(this.reasonixAuditTranscriptPath)try{t=k(this.reasonixAuditTranscriptPath).size}catch{t=0}this.auditCaptures.set(e.event_id,{...t===void 0?{}:{transcriptStartOffset:t},events:[]}),this.activeAuditEventId=e.event_id}captureAuditEvent(e){if(this.isKimi)return;const t=this.activeRun?.eventId??this.activeAuditEventId;if(!t)return;const i=this.auditCaptures.get(t);i&&i.events.push({sequence:i.events.length,observedAt:new Date().toISOString(),event:structuredClone(e)})}snapshotAuditBoundary(e){const t=this.auditCaptures.get(e);if(!t)return;let i=t.transcriptStartOffset;if(this.reasonixAuditTranscriptPath)try{i=k(this.reasonixAuditTranscriptPath).size}catch{}this.completedAuditBoundaries.set(e,{adapterType:"acp",agentType:this.auditAgentType,cwd:this.resolveCwd(),...this.reasonixAuditTranscriptPath?{transcriptPath:this.reasonixAuditTranscriptPath,transcriptStartOffset:t.transcriptStartOffset??0,transcriptEndOffset:i??0}:{},providerSessionId:this.acpClient?.sessionId,...this.isKimi?{}:{acpEvidence:{events:t.events}}}),this.auditCaptures.delete(e),this.activeAuditEventId===e&&(this.activeAuditEventId=null)}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<F||this.pendingAutoCompact||this.compacting||(this.pendingAutoCompact=!0,r.info("acp-adapter",`[auto-compact] context_window usedPercentage=${t.toFixed(1)}% >= ${F}%, scheduling compact`),this.tryRunPendingAutoCompact())}tryRunPendingAutoCompact(){this.pendingAutoCompact&&(this.stopped||!this.acpClient?.isAlive||this.activeRun||this.compacting||(this.pendingAutoCompact=!1,this.execCommand("compact","",this.currentAibotSessionId??"").then(e=>{r.info("acp-adapter",`[auto-compact] compact done status=${e.status} msg=${e.message??""}`)}).catch(e=>{r.warn("acp-adapter",`[auto-compact] compact error: ${e instanceof Error?e.message:String(e)}`)})))}persistEventResult(e,t,i){this.eventResults&&!e.silent&&this.eventResults.set({sessionId:e.sessionId,eventId:e.eventId,status:t,msg:i,updatedAt:Date.now()})}}class Re extends P{adapterSessionId;constructor(e){super(),this.adapterSessionId=e}emitDone(e){this.emit("done",e)}emitError(e){if(this.listenerCount("error")===0){r.warn("acp-adapter",`Prompt handle error (no listeners): ${e.message}`);return}this.emit("error",e)}async cancel(){}}export{Je as AcpAdapter};
@@ -1,5 +1,6 @@
1
- import{execFile as E}from"node:child_process";import{createHash as R,randomUUID as O}from"node:crypto";import{closeSync as A,copyFileSync as m,cpSync as S,existsSync as a,mkdirSync as b,openSync as N,readFileSync as f,rmSync as u,statSync as y,writeFileSync as B}from"node:fs";import{join as o,resolve as D}from"node:path";import{fileURLToPath as I}from"node:url";import{promisify as C}from"node:util";import{resolveCliPath as _}from"../../core/util/cli-probe.js";import{COMPATIBLE_DSH_VERSIONS as P,isCompatibleDshVersion as w}from"./bridge-session-reliability.js";import{bridgeDiscoveryDirectory as G,discoverDshBridgeEndpoints as J,resolveDshProfileIdentity as M}from"./profile-resolver.js";const F=C(E),L=2*1024*1024,p="@grix/dsh-bridge";class ee{options;command;identity;constructor(e={}){this.options=e,this.command=e.command?.trim()||"dsh",this.identity=M({dshHome:e.dshHome,profileName:e.profileName})}async status(){const e=await _(this.command),r=this.bridgeAsset(),i=this.readInstalledBridgeVersion(),s=r.version,n=J(this.identity),l=n.length===1?n[0].bridgeVersion:null,c=n.length===1?n[0].dshVersion:null,g=e?await j(e,this.options.dshHome):null;let d;return e?!w(g)||c!==null&&!w(c)?d="bridge_incompatible":a(this.identity.profileRoot)?i?i!==s||this.installedBridgeContentStale(r)?d="bridge_update_required":n.length===0?d="profile_restart_required":n.length!==1?d="bridge_incompatible":l!==i||this.runningBridgePredatesInstalledContent(n[0])?d="profile_restart_required":d="ready":d="bridge_missing":d="profile_missing":d="dsh_missing",{readiness:d,command:this.command,commandPath:e,dshVersion:g,runningDshVersion:c,identity:this.identity,installedBridgeVersion:i,bundledBridgeVersion:s,runningBridgeVersion:l,endpointCount:n.length,compatibleDshVersions:P}}async install(){const e=await _(this.command);if(!e)throw Object.assign(new Error(`${this.command} not found`),{code:"dsh_missing"});const r=await j(e,this.options.dshHome);if(!w(r))throw Object.assign(new Error(`DSH ${r??"unknown"} is outside the verified Bridge compatibility matrix (${P.join(", ")})`),{code:"bridge_incompatible"});const i=this.bridgeAsset();return x(this.identity,async()=>{await h(e,["--profile",this.identity.profileName,"--dump-config"],this.options.dshHome);const s=this.readInstalledBridgeVersion();if(s===i.version&&!this.installedBridgeContentStale(i))return this.status();const n=o(this.identity.dshHome,"assets","grix-dsh-bridge");b(n,{recursive:!0,mode:448});const l=this.captureInstallBackup(n,s),c=o(n,`grix-dsh-bridge-${i.version}.tgz`);m(i.tarball,c);try{if(s===i.version&&a(this.bridgePackageRoot())&&(await h(e,["plugin","--profile",this.identity.profileName,"remove",p],this.options.dshHome),a(this.bridgePackageRoot())))throw Object.assign(new Error(`dsh plugin remove left ${p} in place; refusing to attest an unverified reinstall`),{code:"bridge_install_failed"});await h(e,["plugin","--profile",this.identity.profileName,"add",c],this.options.dshHome),await h(e,["--profile",this.identity.profileName,"--dump-config"],this.options.dshHome);const g=this.readInstalledBridgeVersion();if(g!==i.version)throw Object.assign(new Error(`Bridge package did not land on disk (installed ${g??"none"}, expected ${i.version})`),{code:"bridge_install_failed"});this.writeInstalledBridgeIntegrity(i.integrity)}catch(g){await this.rollbackInstall(e,n,l,s);try{u(c,{force:!0})}catch{}throw Object.assign(new Error(`Bridge install failed: ${k(g)}`),{code:"bridge_install_failed"})}return this.discardInstallBackup(l),this.status()})}async uninstall(){const e=await _(this.command);if(!e)throw Object.assign(new Error(`${this.command} not found`),{code:"dsh_missing"});return x(this.identity,async()=>(this.readInstalledBridgeVersion()&&await h(e,["plugin","--profile",this.identity.profileName,"remove",p],this.options.dshHome),this.status()))}bridgePackageRoot(){return o(this.identity.profileRoot,"node_modules","@grix","dsh-bridge")}profilePackageJsonPath(){return o(this.identity.profileRoot,"package.json")}captureInstallBackup(e,r){const i=o(e,"rollback",`${Date.now()}-${O()}`);b(i,{recursive:!0,mode:448});const s=this.bridgePackageRoot(),n=a(s);n&&S(s,o(i,"dsh-bridge"),{recursive:!0});const l=this.profilePackageJsonPath(),c=a(l);c&&m(l,o(i,"package.json"));const g=["pnpm-lock.yaml","package-lock.json","yarn.lock"];for(const d of g){const v=o(this.identity.profileRoot,d);a(v)&&m(v,o(i,d))}return B(o(i,"manifest.json"),`${JSON.stringify({priorVersion:r,hadBridge:n,hadPackageJson:c})}
2
- `,{mode:384}),{root:i,priorVersion:r,hadBridge:n,hadPackageJson:c}}async rollbackInstall(e,r,i,s){const n=s?o(r,`grix-dsh-bridge-${s}.tgz`):null;if(n&&a(n)&&await h(e,["plugin","--profile",this.identity.profileName,"add",n],this.options.dshHome).then(()=>!0).catch(()=>!1)){this.discardInstallBackup(i);return}if(i.hadBridge&&a(o(i.root,"dsh-bridge"))){this.restoreFilesystemBackup(i),this.discardInstallBackup(i);return}s?i.hadPackageJson&&this.restoreFilesystemBackup(i):(await h(e,["plugin","--profile",this.identity.profileName,"remove",p],this.options.dshHome).catch(()=>{}),this.cleanHalfInstalledBridge()),this.discardInstallBackup(i)}restoreFilesystemBackup(e){const r=this.bridgePackageRoot(),i=o(e.root,"dsh-bridge");e.hadBridge&&a(i)&&(u(r,{recursive:!0,force:!0}),b(o(this.identity.profileRoot,"node_modules","@grix"),{recursive:!0,mode:448}),S(i,r,{recursive:!0})),e.hadPackageJson&&a(o(e.root,"package.json"))&&m(o(e.root,"package.json"),this.profilePackageJsonPath());for(const s of["pnpm-lock.yaml","package-lock.json","yarn.lock"]){const n=o(e.root,s);a(n)&&m(n,o(this.identity.profileRoot,s))}}cleanHalfInstalledBridge(){try{u(this.bridgePackageRoot(),{recursive:!0,force:!0})}catch{}const e=this.profilePackageJsonPath();if(a(e))try{const r=JSON.parse(f(e,"utf8"));let i=!1;for(const s of["dependencies","devDependencies","optionalDependencies","peerDependencies"]){const n=r[s];n&&typeof n=="object"&&!Array.isArray(n)&&p in n&&(delete n[p],i=!0)}i&&B(e,`${JSON.stringify(r,null,2)}
1
+ import{execFile as R}from"node:child_process";import{createHash as O,randomUUID as N}from"node:crypto";import{closeSync as G,copyFileSync as m,cpSync as P,existsSync as d,mkdirSync as b,openSync as C,readFileSync as f,rmSync as u,statSync as y,writeFileSync as B}from"node:fs";import{join as o,resolve as I}from"node:path";import{fileURLToPath as $}from"node:url";import{promisify as M}from"node:util";import{log as T}from"../../core/log/index.js";import{resolveCliPath as _}from"../../core/util/cli-probe.js";import{COMPATIBLE_DSH_VERSIONS as H,isCompatibleDshVersion as w}from"./bridge-session-reliability.js";import{bridgeDiscoveryDirectory as J,discoverDshBridgeEndpoints as L,resolveDshProfileIdentity as F}from"./profile-resolver.js";const z=M(R),U=2*1024*1024,p="@grix/dsh-bridge",q="deepseek-harness-bridge-installer",k=2,X=1e3;class ae{options;command;identity;constructor(e={}){this.options=e,this.command=e.command?.trim()||"dsh",this.identity=F({dshHome:e.dshHome,profileName:e.profileName})}async status(){const e=await _(this.command),r=this.bridgeAsset(),i=this.readInstalledBridgeVersion(),s=r.version,n=L(this.identity),a=n.length===1?n[0].bridgeVersion:null,c=n.length===1?n[0].dshVersion:null,g=e?await A(e,this.options.dshHome):null;let l;return e?!w(g)||c!==null&&!w(c)?l="bridge_incompatible":d(this.identity.profileRoot)?i?i!==s||this.installedBridgeContentStale(r)?l="bridge_update_required":n.length===0?l="profile_restart_required":n.length!==1?l="bridge_incompatible":a!==i||this.runningBridgePredatesInstalledContent(n[0])?l="profile_restart_required":l="ready":l="bridge_missing":l="profile_missing":l="dsh_missing",{readiness:l,command:this.command,commandPath:e,dshVersion:g,runningDshVersion:c,identity:this.identity,installedBridgeVersion:i,bundledBridgeVersion:s,runningBridgeVersion:a,endpointCount:n.length,compatibleDshVersions:H}}async install(){const e=await _(this.command);if(!e)throw Object.assign(new Error(`${this.command} not found`),{code:"dsh_missing"});const r=await A(e,this.options.dshHome);if(!w(r))throw Object.assign(new Error(`DSH ${r??"unknown"} is outside the verified Bridge compatibility matrix (${H.join(", ")})`),{code:"bridge_incompatible"});const i=this.bridgeAsset();return j(this.identity,async()=>{await h(e,["--profile",this.identity.profileName,"--dump-config"],this.options.dshHome);const s=this.readInstalledBridgeVersion();if(s===i.version&&!this.installedBridgeContentStale(i))return this.status();const n=o(this.identity.dshHome,"assets","grix-dsh-bridge");b(n,{recursive:!0,mode:448});const a=this.captureInstallBackup(n,s),c=o(n,`grix-dsh-bridge-${i.version}.tgz`);m(i.tarball,c);try{if(s===i.version&&d(this.bridgePackageRoot())&&(await h(e,["plugin","--profile",this.identity.profileName,"remove",p],this.options.dshHome),d(this.bridgePackageRoot())))throw Object.assign(new Error(`dsh plugin remove left ${p} in place; refusing to attest an unverified reinstall`),{code:"bridge_install_failed"});await this.runPluginAdd(e,c),await h(e,["--profile",this.identity.profileName,"--dump-config"],this.options.dshHome);const g=this.readInstalledBridgeVersion();if(g!==i.version)throw Object.assign(new Error(`Bridge package did not land on disk (installed ${g??"none"}, expected ${i.version})`),{code:"bridge_install_failed"});this.writeInstalledBridgeIntegrity(i.integrity)}catch(g){await this.rollbackInstall(e,n,a,s);try{u(c,{force:!0})}catch{}throw Object.assign(new Error(`Bridge install failed: ${D(g)}`),{code:"bridge_install_failed"})}return this.discardInstallBackup(a),this.status()})}async uninstall(){const e=await _(this.command);if(!e)throw Object.assign(new Error(`${this.command} not found`),{code:"dsh_missing"});return j(this.identity,async()=>(this.readInstalledBridgeVersion()&&await h(e,["plugin","--profile",this.identity.profileName,"remove",p],this.options.dshHome),this.status()))}async runPluginAdd(e,r){const i=["plugin","--profile",this.identity.profileName,"add",r];let s;for(let n=1;n<=k;n++)try{await h(e,i,this.options.dshHome);return}catch(a){s=a,T.warn(q,`dsh plugin add attempt ${n}/${k} failed for profile ${this.identity.profileName}
2
+ ${W(a)}`),n<k&&await new Promise(c=>setTimeout(c,X))}throw s}bridgePackageRoot(){return o(this.identity.profileRoot,"node_modules","@grix","dsh-bridge")}profilePackageJsonPath(){return o(this.identity.profileRoot,"package.json")}captureInstallBackup(e,r){const i=o(e,"rollback",`${Date.now()}-${N()}`);b(i,{recursive:!0,mode:448});const s=this.bridgePackageRoot(),n=d(s);n&&P(s,o(i,"dsh-bridge"),{recursive:!0});const a=this.profilePackageJsonPath(),c=d(a);c&&m(a,o(i,"package.json"));const g=["pnpm-lock.yaml","package-lock.json","yarn.lock"];for(const l of g){const v=o(this.identity.profileRoot,l);d(v)&&m(v,o(i,l))}return B(o(i,"manifest.json"),`${JSON.stringify({priorVersion:r,hadBridge:n,hadPackageJson:c})}
3
+ `,{mode:384}),{root:i,priorVersion:r,hadBridge:n,hadPackageJson:c}}async rollbackInstall(e,r,i,s){const n=s?o(r,`grix-dsh-bridge-${s}.tgz`):null;if(n&&d(n)&&await h(e,["plugin","--profile",this.identity.profileName,"add",n],this.options.dshHome).then(()=>!0).catch(()=>!1)){this.discardInstallBackup(i);return}if(i.hadBridge&&d(o(i.root,"dsh-bridge"))){this.restoreFilesystemBackup(i),this.discardInstallBackup(i);return}s?i.hadPackageJson&&this.restoreFilesystemBackup(i):(await h(e,["plugin","--profile",this.identity.profileName,"remove",p],this.options.dshHome).catch(()=>{}),this.cleanHalfInstalledBridge()),this.discardInstallBackup(i)}restoreFilesystemBackup(e){const r=this.bridgePackageRoot(),i=o(e.root,"dsh-bridge");e.hadBridge&&d(i)&&(u(r,{recursive:!0,force:!0}),b(o(this.identity.profileRoot,"node_modules","@grix"),{recursive:!0,mode:448}),P(i,r,{recursive:!0})),e.hadPackageJson&&d(o(e.root,"package.json"))&&m(o(e.root,"package.json"),this.profilePackageJsonPath());for(const s of["pnpm-lock.yaml","package-lock.json","yarn.lock"]){const n=o(e.root,s);d(n)&&m(n,o(this.identity.profileRoot,s))}}cleanHalfInstalledBridge(){try{u(this.bridgePackageRoot(),{recursive:!0,force:!0})}catch{}const e=this.profilePackageJsonPath();if(d(e))try{const r=JSON.parse(f(e,"utf8"));let i=!1;for(const s of["dependencies","devDependencies","optionalDependencies","peerDependencies"]){const n=r[s];n&&typeof n=="object"&&!Array.isArray(n)&&p in n&&(delete n[p],i=!0)}i&&B(e,`${JSON.stringify(r,null,2)}
3
4
  `,{mode:384})}catch{}}discardInstallBackup(e){try{u(e.root,{recursive:!0,force:!0})}catch{}}readInstalledBridgeVersion(){const e=o(this.bridgePackageRoot(),"package.json");try{const r=JSON.parse(f(e,"utf8"));return typeof r.version=="string"?r.version:null}catch{return null}}installedIntegrityPath(){return o(this.bridgePackageRoot(),".grix-bridge-integrity")}readInstalledBridgeIntegrity(){try{const e=f(this.installedIntegrityPath(),"utf8").trim();return e.startsWith("sha512-")?e:null}catch{return null}}writeInstalledBridgeIntegrity(e){B(this.installedIntegrityPath(),`${e}
4
- `,{encoding:"utf8",mode:384})}installedBridgeContentStale(e=this.bridgeAsset()){return a(this.bridgePackageRoot())?this.readInstalledBridgeIntegrity()!==e.integrity:!0}runningBridgePredatesInstalledContent(e){try{const r=y(this.installedIntegrityPath()).mtimeMs,i=Date.parse(e.startedAt);return Number.isFinite(i)?r>i:!0}catch{return!1}}bridgeAsset(){return this.options.tarballPath?H(D(this.options.tarballPath)):$()}}function ie(t){if(t.readiness==="ready")return null;const e=t.identity.profileName,r=`dsh --profile ${e}`,i=t.endpointCount>0&&t.runningBridgeVersion&&t.installedBridgeVersion&&t.runningBridgeVersion!==t.installedBridgeVersion?`Grix Bridge in DSH profile "${e}" is running version ${t.runningBridgeVersion}, but ${t.installedBridgeVersion} is installed on disk. dsh does not hot-load plugin upgrades: stop the running profile process, then start it again with: ${r}`:`Grix Bridge is installed in DSH profile "${e}", but no live Bridge endpoint is available. dsh does not hot-load plugins: stop the running profile process, then start it again with: ${r}`,s={dsh_missing:"dsh is not installed or not on PATH. Install DeepSeek Harness (dsh), then retry.",profile_missing:`DSH profile "${e}" does not exist yet. Create/boot it once with: ${r}`,bridge_missing:`Grix Bridge is not installed in DSH profile "${e}". Install it from the DeepSeek Harness toolbar (or the dsh_install_bridge action), then restart the profile if it is already running.`,bridge_update_required:`Grix Bridge in DSH profile "${e}" needs an update (installed ${t.installedBridgeVersion??"none"}, expected ${t.bundledBridgeVersion}${t.installedBridgeVersion===t.bundledBridgeVersion?", content hash mismatch":""}). Update from the toolbar, then restart the profile \u2014 dsh does not hot-load plugin upgrades.`,profile_restart_required:i,bridge_incompatible:`DSH / Grix Bridge versions are incompatible for profile "${e}" (dsh=${t.dshVersion??"unknown"}, running=${t.runningDshVersion??"none"}, bridge=${t.runningBridgeVersion??t.installedBridgeVersion??"none"}). Supported DSH: ${t.compatibleDshVersions.join(", ")}.`};return Object.assign(new Error(s[t.readiness]),{code:t.readiness})}function te(){return $().tarball}function $(){const t=[I(new URL("../../assets/dsh-bridge/",import.meta.url)),I(new URL("../../../dist/assets/dsh-bridge/",import.meta.url))].find(i=>a(o(i,"manifest.json")));if(!t)throw Object.assign(new Error("Bundled Grix DSH Bridge asset is unavailable; run the Connector build first"),{code:"bridge_asset_missing"});const e=JSON.parse(f(o(t,"manifest.json"),"utf8"));if(typeof e.tarball!="string"||!e.tarball||typeof e.bridgeVersion!="string"||!e.bridgeVersion)throw Object.assign(new Error("Invalid Grix DSH Bridge asset manifest"),{code:"bridge_asset_invalid"});const r=H(o(t,e.tarball),e.bridgeVersion);if(Number(e.size)!==y(r.tarball).size)throw Object.assign(new Error("Grix DSH Bridge asset size mismatch"),{code:"bridge_asset_invalid"});if(typeof e.integrity=="string"&&e.integrity.startsWith("sha512-")&&e.integrity!==r.integrity)throw Object.assign(new Error("Grix DSH Bridge asset integrity mismatch"),{code:"bridge_asset_invalid"});return r}function H(t,e){if(!a(t)||!y(t).isFile())throw Object.assign(new Error(`Grix DSH Bridge asset is missing: ${t}`),{code:"bridge_asset_missing"});if(y(t).size>L)throw Object.assign(new Error("Grix DSH Bridge asset exceeds the 2 MiB safety limit"),{code:"bridge_asset_invalid"});const r=e??/grix-dsh-bridge-([0-9A-Za-z.+-]+)\.tgz$/.exec(t)?.[1];if(!r)throw Object.assign(new Error("Cannot determine Grix DSH Bridge version"),{code:"bridge_asset_invalid"});const i=`sha512-${R("sha512").update(f(t)).digest("base64")}`;return{tarball:t,version:r,integrity:i}}async function x(t,e){const r=G(t);b(r,{recursive:!0,mode:448});const i=o(r,"install.lock");let s=V(i);if(s===null){let n=0;try{n=Number(f(i,"utf8").trim())}catch{}let l=!1;try{l=n===0&&Date.now()-y(i).mtimeMs>5*6e4}catch{}if(l||Number.isSafeInteger(n)&&n>0&&!T(n)){try{u(i,{force:!0})}catch{}s=V(i)}}if(s===null)throw Object.assign(new Error(`Another Grix DSH Bridge install is active for profile ${t.profileName}`),{code:"bridge_install_busy"});try{return B(s,`${process.pid}
5
- `,"utf8"),await e()}finally{A(s);try{u(i,{force:!0})}catch{}}}function V(t){try{return N(t,"wx",384)}catch(e){if(e.code==="EEXIST")return null;throw Object.assign(new Error(`Cannot acquire Grix DSH Bridge install lock: ${k(e)}`),{code:"bridge_install_failed"})}}function T(t){try{return process.kill(t,0),!0}catch(e){return e.code==="EPERM"}}async function j(t,e){try{return(await h(t,["--version"],e)).trim().split(/\s+/)[0]||null}catch{return null}}async function h(t,e,r){try{return(await F(t,e,{env:{...process.env,...r?{DSH_HOME:D(r)}:{}},timeout:6e4,maxBuffer:8388608})).stdout}catch(i){throw Object.assign(new Error(k(i)),{code:"dsh_command_failed"})}}function k(t){const e=t&&typeof t=="object"?t:{};return String(e.stderr??e.message??t??"unknown error").replace(/(api[_-]?key|authorization|bearer)\s*[:=]\s*\S+/gi,"$1=[REDACTED]").replace(/[\r\n]+/g," ").slice(0,1024)}export{ee as DshBridgeInstaller,te as defaultBridgeTarball,ie as errorForDshBridgeReadiness};
5
+ `,{encoding:"utf8",mode:384})}installedBridgeContentStale(e=this.bridgeAsset()){return d(this.bridgePackageRoot())?this.readInstalledBridgeIntegrity()!==e.integrity:!0}runningBridgePredatesInstalledContent(e){try{const r=y(this.installedIntegrityPath()).mtimeMs,i=Date.parse(e.startedAt);return Number.isFinite(i)?r>i:!0}catch{return!1}}bridgeAsset(){return this.options.tarballPath?E(I(this.options.tarballPath)):x()}}function de(t){if(t.readiness==="ready")return null;const e=t.identity.profileName,r=`dsh --profile ${e}`,i=t.endpointCount>0&&t.runningBridgeVersion&&t.installedBridgeVersion&&t.runningBridgeVersion!==t.installedBridgeVersion?`Grix Bridge in DSH profile "${e}" is running version ${t.runningBridgeVersion}, but ${t.installedBridgeVersion} is installed on disk. dsh does not hot-load plugin upgrades: stop the running profile process, then start it again with: ${r}`:`Grix Bridge is installed in DSH profile "${e}", but no live Bridge endpoint is available. dsh does not hot-load plugins: stop the running profile process, then start it again with: ${r}`,s={dsh_missing:"dsh is not installed or not on PATH. Install DeepSeek Harness (dsh), then retry.",profile_missing:`DSH profile "${e}" does not exist yet. Create/boot it once with: ${r}`,bridge_missing:`Grix Bridge is not installed in DSH profile "${e}". Install it from the DeepSeek Harness toolbar (or the dsh_install_bridge action), then restart the profile if it is already running.`,bridge_update_required:`Grix Bridge in DSH profile "${e}" needs an update (installed ${t.installedBridgeVersion??"none"}, expected ${t.bundledBridgeVersion}${t.installedBridgeVersion===t.bundledBridgeVersion?", content hash mismatch":""}). Update from the toolbar, then restart the profile \u2014 dsh does not hot-load plugin upgrades.`,profile_restart_required:i,bridge_incompatible:`DSH / Grix Bridge versions are incompatible for profile "${e}" (dsh=${t.dshVersion??"unknown"}, running=${t.runningDshVersion??"none"}, bridge=${t.runningBridgeVersion??t.installedBridgeVersion??"none"}). Supported DSH: ${t.compatibleDshVersions.join(", ")}.`};return Object.assign(new Error(s[t.readiness]),{code:t.readiness})}function le(){return x().tarball}function x(){const t=[$(new URL("../../assets/dsh-bridge/",import.meta.url)),$(new URL("../../../dist/assets/dsh-bridge/",import.meta.url))].find(i=>d(o(i,"manifest.json")));if(!t)throw Object.assign(new Error("Bundled Grix DSH Bridge asset is unavailable; run the Connector build first"),{code:"bridge_asset_missing"});const e=JSON.parse(f(o(t,"manifest.json"),"utf8"));if(typeof e.tarball!="string"||!e.tarball||typeof e.bridgeVersion!="string"||!e.bridgeVersion)throw Object.assign(new Error("Invalid Grix DSH Bridge asset manifest"),{code:"bridge_asset_invalid"});const r=E(o(t,e.tarball),e.bridgeVersion);if(Number(e.size)!==y(r.tarball).size)throw Object.assign(new Error("Grix DSH Bridge asset size mismatch"),{code:"bridge_asset_invalid"});if(typeof e.integrity=="string"&&e.integrity.startsWith("sha512-")&&e.integrity!==r.integrity)throw Object.assign(new Error("Grix DSH Bridge asset integrity mismatch"),{code:"bridge_asset_invalid"});return r}function E(t,e){if(!d(t)||!y(t).isFile())throw Object.assign(new Error(`Grix DSH Bridge asset is missing: ${t}`),{code:"bridge_asset_missing"});if(y(t).size>U)throw Object.assign(new Error("Grix DSH Bridge asset exceeds the 2 MiB safety limit"),{code:"bridge_asset_invalid"});const r=e??/grix-dsh-bridge-([0-9A-Za-z.+-]+)\.tgz$/.exec(t)?.[1];if(!r)throw Object.assign(new Error("Cannot determine Grix DSH Bridge version"),{code:"bridge_asset_invalid"});const i=`sha512-${O("sha512").update(f(t)).digest("base64")}`;return{tarball:t,version:r,integrity:i}}async function j(t,e){const r=J(t);b(r,{recursive:!0,mode:448});const i=o(r,"install.lock");let s=V(i);if(s===null){let n=0;try{n=Number(f(i,"utf8").trim())}catch{}let a=!1;try{a=n===0&&Date.now()-y(i).mtimeMs>5*6e4}catch{}if(a||Number.isSafeInteger(n)&&n>0&&!Y(n)){try{u(i,{force:!0})}catch{}s=V(i)}}if(s===null)throw Object.assign(new Error(`Another Grix DSH Bridge install is active for profile ${t.profileName}`),{code:"bridge_install_busy"});try{return B(s,`${process.pid}
6
+ `,"utf8"),await e()}finally{G(s);try{u(i,{force:!0})}catch{}}}function V(t){try{return C(t,"wx",384)}catch(e){if(e.code==="EEXIST")return null;throw Object.assign(new Error(`Cannot acquire Grix DSH Bridge install lock: ${D(e)}`),{code:"bridge_install_failed"})}}function Y(t){try{return process.kill(t,0),!0}catch(e){return e.code==="EPERM"}}async function A(t,e){try{return(await h(t,["--version"],e)).trim().split(/\s+/)[0]||null}catch{return null}}async function h(t,e,r){try{return(await z(t,e,{env:{...process.env,...r?{DSH_HOME:I(r)}:{}},timeout:6e4,maxBuffer:8388608})).stdout}catch(i){const s=i&&typeof i=="object"?i:{};throw Object.assign(new Error(D(i)),{code:"dsh_command_failed",stderr:typeof s.stderr=="string"?S(s.stderr):void 0})}}function S(t){return t.replace(/(api[_-]?key|authorization|bearer)\s*[:=]\s*\S+/gi,"$1=[REDACTED]")}function D(t){const e=t&&typeof t=="object"?t:{};return S(String(e.stderr??e.message??t??"unknown error")).replace(/[\r\n]+/g," ").slice(0,1024)}function W(t){const e=t&&typeof t=="object"?t:{};return S(String(e.stderr??e.message??t??"unknown error")).slice(0,8*1024)}export{ae as DshBridgeInstaller,le as defaultBridgeTarball,de as errorForDshBridgeReadiness};
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "schemaVersion": 1,
3
- "bridgeVersion": "3.29.0",
4
- "tarball": "grix-dsh-bridge-3.29.0.tgz",
5
- "size": 13784,
3
+ "bridgeVersion": "3.29.2",
4
+ "tarball": "grix-dsh-bridge-3.29.2.tgz",
5
+ "size": 13786,
6
6
  "unpackedSize": 52666,
7
- "shasum": "1e9dbf9b08482e46ba777531668095c680f5857b",
8
- "integrity": "sha512-mNUxDMBnizQ7lJmx5+/G1Hflo8n1NnLos/g9tSsJ2EwOGDVrWVKN6gDJZvNQ9JdkOqrGuzpg1dH5bOZEMYF8Ww=="
7
+ "shasum": "32a6ea0a5fe2fafcef8d8d5a5a41c3d1884d1556",
8
+ "integrity": "sha512-oTf9yBGzZZH/pyH76Kk/pK8MSbMH49etkDHkXKZCs3Iq+tN8v1f1JyosdeS8a4+m6CV1omq2ubFNOEwUN99kyw=="
9
9
  }
@@ -4,7 +4,7 @@ Error: ${s}`,r,!1,a)},getAgentProfile:()=>this.agentProfile},t=this.config.adapt
4
4
 
5
5
  Error: ${s}`,0,!1)},sendUpdateBindingCard:(o,i,s,r)=>this.aibotHandle.sendUpdateBindingCard({session_id:o,worker_status:i,cwd:s,...this.providerQuotaMetaPayload(r,o,s)}),agentInvoke:async(o,i,s)=>this.platformInvoke(o,i,s),sendLocalActionResult:(o,i,s,r,a)=>{this.aibotHandle.sendLocalActionResult({action_id:o,status:i,...s!==void 0?{result:s}:{},...r?{error_code:r}:{},...a?{error_msg:a}:{}})},getAgentProfile:()=>this.agentProfile,getAgentId:()=>this.config.aibot.agentId},t=this.config.adapterOptions??{};return new $e({command:this.config.agent.command,args:this.config.agent.args,env:this.resolveSpawnEnv(),options:t},n,{port:t.port,host:t.host,workspaceDir:t.workspace_dir,sessionToken:t.session_token,enableSessionBinding:!0,aibotSessionId:e})}createOpenCodeAdapter(e){const n={sendStreamChunk:(o,i,s,r,a,d)=>{this.sendStreamChunkByRuntimeConfig(o,i,s,r,a,d)},sendFinalStreamChunkReliable:this.reliableFinalWiring("opencode"),sendEventResult:(o,i,s)=>{this.sendEventResultWithCleanup(o,i,s),h.info("bridge",`[opencode] sendEventResult event=${o} status=${i}`)},sendEventAck:(o,i)=>this.aibotHandle.sendEventAck({event_id:o,session_id:i,received_at:Date.now()}),sendToolUse:(o,i,s,r)=>{this.sendToolExecutionCard(o,i,L(s,r))},sendToolResult:(o,i,s,r)=>{this.sendToolExecutionCard(o,i,x(s,r))},sendThinking:(o,i,s)=>{this.sendThinkingByRuntimeConfig(o,i,s)},sendRunError:(o,i,s)=>{this.sendRunErrorAsChunk(o,i,s)},sendUpdateBindingCard:(o,i,s,r)=>this.aibotHandle.sendUpdateBindingCard({session_id:o,worker_status:i,cwd:s,...this.providerQuotaMetaPayload(r,o,s)}),agentInvoke:async(o,i,s)=>this.platformInvoke(o,i,s),sendLocalActionResult:(o,i,s,r,a)=>{this.aibotHandle.sendLocalActionResult({action_id:o,status:i,...s!==void 0?{result:s}:{},...r?{error_code:r}:{},...a?{error_msg:a}:{}})},sendPermissionCard:o=>{this.sendGrixApprovalCard(o,"opencode")},sendAgentQuestionCard:(o,i,s)=>{this.sendGrixAgentQuestionCard(o,i,s)},getAgentProfile:()=>this.agentProfile,getAgentId:()=>this.config.aibot.agentId},t=this.config.adapterOptions??{};return new q({command:this.config.agent.command,args:this.config.agent.args,env:this.resolveSpawnEnv(),options:t},n,{port:t.port,hostname:t.hostname,model:t.model??this.resolveSessionModelId(e),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 n={sendStreamChunk:(o,i,s,r,a,d)=>{this.sendStreamChunkByRuntimeConfig(o,i,s,r,a,d)},sendEventResult:(o,i,s)=>{this.sendEventResultWithCleanup(o,i,s)},sendEventAck:(o,i)=>{this.aibotHandle.sendEventAck({event_id:o,session_id:i,received_at:Date.now()})},agentInvoke:async(o,i,s)=>this.platformInvoke(o,i,s),forceCompleteInternalEvent:(o,i)=>{this.pool.eventComplete(o,i),this.pushQueueSnapshotForSession(i)},persistConversationId:(o,i)=>{this.bindingStore.setAgyConversationId(o,i)},getAgentProfile:()=>this.agentProfile,getAgentId:()=>this.config.aibot.agentId},t=o=>{const i=this.bindingStore.get(o);return{cwd:i?.cwd,modelId:this.resolveSessionModelId(o),conversationId:i?.agyConversationId}};return new re({command:this.config.agent.command,args:this.config.agent.args,env:this.resolveSpawnEnv(),options:this.config.adapterOptions??{}},n,t)}createAcpAdapter(e){const n=this.isAcpRawTransportEnabled(),t={sendStreamChunk:(c,l,u,g,m,f,S)=>{this.sendStreamChunkByRuntimeConfig(c,l,u,g,m,f,S)},sendFinalStreamChunkReliable:this.reliableFinalWiring("acp",4e3),sendEventResult:(c,l,u)=>{this.sendEventResultWithCleanup(c,l,u)},sendEventAck:(c,l)=>{this.aibotHandle.sendEventAck({event_id:c,session_id:l,received_at:Date.now()})},agentInvoke:async(c,l,u)=>this.platformInvoke(c,l,u),sendLocalActionResult:(c,l,u,g,m)=>{this.aibotHandle.sendLocalActionResult({action_id:c,status:l,...u!==void 0?{result:u}:{},...g?{error_code:g}:{},...m?{error_msg:m}:{}})},sendRawEventEnvelope:(c,l,u)=>{this.sendAcpRawEventEnvelope(c,l,u)},sendToolUse:(c,l,u,g)=>{if(n){this.sendAcpRawEventEnvelope(c,l,{type:"tool_use",payload:{tool_name:u,tool_input:g??""}});return}this.sendToolExecutionCard(c,l,L(u,g))},sendToolResult:(c,l,u,g)=>{this.sendToolExecutionCard(c,l,x(u,g))},sendThinking:(c,l,u)=>{this.sendThinkingByRuntimeConfig(c,l,u)},sendRunError:(c,l,u)=>{this.sendStreamChunkByRuntimeConfig(c,l,`
6
6
 
7
- Error: ${u}`,1,!1)},sendAgentQuestionCard:(c,l,u)=>{this.sendGrixAgentQuestionCard(c,l,u)},sendPermissionCard:c=>{h.info("bridge","sendPermissionCard callback entered",{rawTransport:n,eventId:c.eventId,sessionId:c.sessionId,toolCallId:c.toolCallId,toolName:c.toolName,toolTitle:c.toolTitle});const l=c.toolInput&&c.toolTitle&&c.toolInput!==c.toolTitle?`${c.toolTitle}: ${c.toolInput}`:c.toolTitle||c.toolInput||c.toolName;if(n){this.sendAcpRawEventEnvelope(c.eventId,c.sessionId,{type:"permission_request",payload:{tool_call_id:c.toolCallId,tool_name:c.toolName,tool_title:c.toolTitle,...c.toolInput?{tool_input:c.toolInput}:{},options:c.options}}),h.info("bridge","sendPermissionCard: sent via rawEventEnvelope",{eventId:c.eventId,toolCallId:c.toolCallId});return}const u=`perm_${j()}`,g={event_id:c.eventId,session_id:c.sessionId,client_msg_id:u,msg_type:1,content:c.toolTitle?`Permission required: ${c.toolTitle}`:"Permission request",extra:{channel_data:{execApproval:{approvalId:c.toolCallId,approvalSlug:c.toolName},grix:{execApproval:{approval_command_id:c.toolCallId,command:l,host:"acp"}}},agent_api_origin:!0}};h.info("bridge","sendPermissionCard: about to invoke aibotHandle.sendMsg",{eventId:c.eventId,sessionId:c.sessionId,clientMsgId:u,toolCallId:c.toolCallId,contentLength:g.content.length});try{this.aibotHandle.sendMsg(g),h.info("bridge","sendPermissionCard: aibotHandle.sendMsg returned",{eventId:c.eventId,clientMsgId:u})}catch(m){h.error("bridge","sendPermissionCard: aibotHandle.sendMsg threw",{eventId:c.eventId,clientMsgId:u,error:m instanceof Error?m.message:String(m)})}},sendAuthNotification:(c,l)=>{c&&this.aibotHandle.sendMsg({session_id:c,msg_type:1,content:l,extra:{biz_card:{version:1,type:"agent_error",payload:{error:{name:"AuthRequired",message:l}}}}})},sendAgentMessage:(c,l)=>{c&&l&&this.aibotHandle.sendMsg({session_id:c,msg_type:1,content:l})},sendUpdateBindingCard:(c,l,u,g)=>{const m={...g??{}};if(!m.rate_limits&&this.cachedProviderQuota?.success){this.isRateLimitsCacheFresh(this.cachedProviderQuotaSampledAtMs)||this.maybeQueryProviderQuota().catch(()=>{});const f=this.providerQuotaToRateLimits(this.cachedProviderQuota);f&&(m.rate_limits=f)}!m.provider_quota&&this.cachedProviderQuota?.success&&(m.provider_quota=this.cachedProviderQuota),this.aibotHandle.sendUpdateBindingCard({session_id:c,worker_status:l,cwd:u,...Object.keys(m).length>0?{meta:m}:{}})},onSkillsUpdate:(c,l)=>{try{this.aibotHandle.sendSkillsUpdate({skills:ce(c,$.skills),library_skills:this.buildLibrarySkillsReport(l)})}catch(u){}},onContextWindowUpdated:c=>{this.cachedAcpContextWindow=c,this.cachedAcpContextWindowSampledAtMs=Date.now();const l="usedPercentage"in c?c.usedPercentage.toFixed(1):(c.used/c.size*100).toFixed(1);h.info(this.name,`[acp] context_window updated: ${l}%`);const u=this.bindingStore.get(e);if(u?.cwd){const g="usedPercentage"in c?c.usedPercentage:Math.min(100,c.used/c.size*100),m={context_window:{..."usedPercentage"in c?{}:c,usedPercentage:g,remainingPercentage:100-g}};if((this.config.aibot.clientType==="kiro"||this.config.aibot.clientType==="kimi")&&this.cachedProviderQuota?.success){this.isRateLimitsCacheFresh(this.cachedProviderQuotaSampledAtMs)||this.maybeQueryProviderQuota().catch(()=>{}),m.provider_quota=this.cachedProviderQuota;const f=this.providerQuotaToRateLimits(this.cachedProviderQuota);f&&(m.rate_limits=f)}this.aibotHandle.sendUpdateBindingCard({session_id:e,worker_status:"ready",cwd:u.cwd,meta:m})}},sendMcpFrame:c=>{this.aibotHandle.sendMcpFrame(e,c)},getAgentProfile:()=>this.agentProfile,getAgentId:()=>this.config.aibot.agentId},o=e?this.bindingStore.get(e):void 0,i=e?this.auditController.getSession(e):void 0,s=this.config.aibot.clientType==="reasonix"&&i?Li(this.name,e,i.auditId):void 0,r=this.globalConfigStore?.get(this.name),{initialModel:a,initialMode:d}=Yt({sessionBinding:o,globalDefaults:r,configInitialMode:this.config.acpInitialMode});return e&&a&&!o?.acpModelId&&this.bindingStore.setAcpModelId(e,a),(a||d)&&h.info(this.name,`[toolbar] hydrate from binding: session=${e} model=${a??"<none>"} mode=${d??"<none>"}`),new E({command:this.config.agent.command,args:this.config.agent.args,env:this.resolveSpawnEnv()},t,{acpAuthMethod:this.config.acpAuthMethod,acpInitialMode:d,acpInitialModel:a,acpMcpTools:this.config.acpMcpTools,rawTransport:n,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 Ft(this.config.logDir,`${this.name}-${e}`):null,reasonixAuditTranscriptPath:s,agentType:this.config.aibot.clientType})}async connectAibot(){const e=new Ee;this.aibotHandle=await e.connect(this.aibotConfig,{aborted:()=>this.stopped,label:this.name,packetLog:this.packetLog,maxRetries:this.config.connectMaxRetries});const n=this.aibotHandle.authAck;if(this.applyAgentProfile(n?.agent_name,n?.introduction,{source:"auth_ack",respawnOnChange:!1},n?.system_prompt),this.aibotHandle.onEvent(t=>{this.handleAibotEvent(t).catch(o=>{h.error(this.name,`handleAibotEvent failed: ${o}`),this.aibotHandle.sendEventAck({event_id:t.event_id,session_id:t.session_id,received_at:Date.now()});const i=o instanceof Error?o.message:String(o);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),this.sessionProviderHints.delete(t.session_id),this.sessionProviderQuotas.delete(t.session_id),this.sessionProviderMeta.delete(t.session_id);const r=this.pool.getSlot(t.session_id);r?.adapter instanceof E&&r.adapter.getSessionBindings().delete(t.session_id);const a=this.config.adapterType??"acp",d=this.resolveBindingChannelKey(a);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:p.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(o){h.error(this.name,`handleAibotStop failed: ${o}`)}}),this.aibotHandle.onAgentDeleted(t=>{h.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(o){h.error(this.name,`agentDeletedHandler failed: ${o}`)}}),this.aibotHandle.onShareSet(t=>{try{this.shareSetHandler?.(Array.isArray(t?.shared_to)?t.shared_to:[])}catch(o){h.error(this.name,`onShareSet failed: ${o}`)}}),this.aibotHandle.onProfilePush(t=>{try{this.applyAgentProfile(t?.agent_name,t?.introduction,{source:"profile_push",respawnOnChange:!0},t?.system_prompt)}catch(o){h.error(this.name,`onProfilePush failed: ${o}`)}}),this.aibotHandle.onSkillSync(t=>{try{h.info(this.name,`skill_sync received owner=${t?.owner_id??""} name=${t?.name??""}`),this.skillSyncHandler?.()}catch(o){h.error(this.name,`onSkillSync failed: ${o}`)}}),this.aibotHandle.onRevoke(t=>{try{this.handleAibotRevoke(t)}catch(o){h.error(this.name,`handleAibotRevoke failed: ${o}`)}}),this.aibotHandle.onLocalAction(t=>{this.handleAibotLocalAction(t).catch(o=>{h.error(this.name,`handleAibotLocalAction failed: ${o}`)})}),this.aibotHandle.onEventCancel(t=>{h.info(this.name,`recv event_cancel event_id=${t.event_id} session_id=${t.session_id}`),this.handleEventCancel(t).catch(o=>{h.error(this.name,`handleEventCancel failed: ${o}`)})}),this.aibotHandle.onMcpFrame((t,o)=>{const i=this.pool.getSlot(t)?.adapter;i?.deliverMcpFrameToAgent?i.deliverMcpFrameToAgent(o):h.warn(this.name,`mcp_frame: no adapter for session=${t}`)}),this.aibotHandle.onQueueClear(t=>{const o=this.pool.clearQueue(t.session_id);this.aibotHandle.sendQueueClearResult({session_id:t.session_id,canceled_event_ids:o}),this.pushQueueSnapshotForSession(t.session_id)}),typeof this.aibotHandle.onQueueReorder=="function"&&this.aibotHandle.onQueueReorder(t=>{const o=Array.isArray(t.ordered_event_ids)?t.ordered_event_ids.filter(s=>typeof s=="string"&&s.length>0):[],i=this.pool.reorderQueue(t.session_id,o);this.aibotHandle.sendQueueReorderResult({session_id:t.session_id,applied_event_ids:i}),this.pushQueueSnapshotForSession(t.session_id)}),typeof this.aibotHandle.onEventHold=="function"&&this.aibotHandle.onEventHold(t=>{const o=typeof t.session_id=="string"?t.session_id:"",i=typeof t.event_id=="string"?t.event_id:"",s=t.hold!==!1;if(h.info(this.name,`recv event_hold event_id=${i} session_id=${o} hold=${s} reason=${t.reason??""}`),!o||!i){this.aibotHandle.sendEventHoldResult({session_id:o,event_id:i,ok:!1,held:!1,error:"bad_request"});return}const r=typeof t.reason=="string"?t.reason:"",a=typeof t.ttl_ms=="number"?t.ttl_ms:void 0,d=this.pool.holdEvent(o,i,s,r,a),c=d==="ok";this.aibotHandle.sendEventHoldResult({session_id:o,event_id:i,ok:c,held:c?s:!1,...c?{}:{error:d}}),c&&this.pushQueueSnapshotForSession(o)}),typeof this.aibotHandle.onQueueEdit=="function"&&this.aibotHandle.onQueueEdit(t=>{const o=typeof t.session_id=="string"?t.session_id:"",i=typeof t.event_id=="string"?t.event_id:"";if(h.info(this.name,`recv queue_edit event_id=${i} session_id=${o}`),!o||!i){this.aibotHandle.sendQueueEditResult({session_id:o,event_id:i,ok:!1,error:"bad_request"});return}const s=typeof t.content=="string"?t.content:"",r=this.pool.editQueuedEvent(o,i,s),a=r==="ok";this.aibotHandle.sendQueueEditResult({session_id:o,event_id:i,ok:a,...a?{}:{error:r}}),a&&this.pushQueueSnapshotForSession(o)}),typeof this.aibotHandle.onQueueSnapshotQuery=="function"&&this.aibotHandle.onQueueSnapshotQuery(t=>{this.replyQueueSnapshotForSession(t.session_id)}),h.info(this.name,"Connected to aibot"),this.activeEventStore){const t=await this.activeEventStore.drain();if(t.length>0){h.warn(this.name,`Recovering ${t.length} stale event(s) from previous run`);for(const o of t)h.info(this.name,`Failing stale event on startup: ${o}`),this.aibotHandle.sendEventResult({event_id:o,status:"failed",msg:"process restarted, event lost",updated_at:Date.now()})}}this.pushQueueSnapshots(),this.relayStateSyncOnConnect(),this.aibotHandle.onReconnected(()=>{this.pushQueueSnapshots();const t=this.aibotHandle.authAck;this.applyAgentProfile(t?.agent_name,t?.introduction,{source:"reconnect",respawnOnChange:!0},t?.system_prompt),this.forceRefreshSkills(void 0,{force:!0}),this.skillSyncHandler?.(),this.relayStateSyncOnConnect()}),this.aibotHandle.onStreamRejected((t,o)=>{this.sendCtrl.markEventRejected(t)})}applyAgentProfile(e,n,t,o){const i=String(e??"").trim(),s=String(n??"").trim(),r=o===void 0?this.agentSystemPrompt:String(o),a=i!==this.agentProfile.agentName||s!==this.agentProfile.introduction||r!==this.agentSystemPrompt;this.agentProfile={agentName:i,introduction:s},this.agentSystemPrompt=r,i||s?h.info(this.name,`agent profile (${t.source}): GOT name="${i}" intro_len=${s.length} system_prompt_len=${r.length} changed=${a}`):h.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`),a&&t.respawnOnChange&&this.applyProfileChangeToAdapters("agent_profile_changed")}applyProfileChangeToAdapters(e){if(!this.pool)return;const n=this.pool.getAllSlots();let t=0;for(const r of n)if(r.adapter.onAgentProfileChanged)try{r.adapter.onAgentProfileChanged(),t++}catch(a){h.warn(this.name,`onAgentProfileChanged failed for session=${r.sessionId}: ${a}`)}const o=n.filter(r=>r.adapter instanceof I),i=o.filter(r=>{if(r.state!=="ready")return!1;const a=r.adapter.getStatus();return!a.busy&&!a.backgroundBusy}),s=o.length-i.length;h.info(this.name,`${e}: notified ${t} adapter(s) via hook; respawning ${i.length} idle Claude slot(s), skipping ${s} busy`);for(const r of i)this.pool.removeSlot(r.sessionId).catch(a=>{h.warn(this.name,`removeSlot failed during ${e}: ${a}`)})}pushQueueSnapshots(){if(!(!this.config.eventQueue||!this.pool))for(const e of this.pool.getAllSlots())this.pushQueueSnapshotForSession(e.sessionId)}buildQueueSnapshotPayload(e){const n=this.pool?.getQueueSnapshot(e)??null,t=n?[...n.running]:[],o=n?n.running_items.map(s=>({event_id:s.event_id,...s.content_preview?{content_preview:s.content_preview}:{},...s.title?{title:s.title}:{},...s.summary?{summary:s.summary}:{},actions:[{type:"stop"}]})):[],i=n?n.queued.map(s=>({event_id:s.event_id,position:s.position,...s.content_preview?{content_preview:s.content_preview}:{},...typeof s.content=="string"?{content:s.content}:{},...s.title?{title:s.title}:{},...s.summary?{summary:s.summary}:{},held:s.held===!0,held_reason:s.held_reason??"",actions:[{type:"cancel"}]})):[];if(t.length===0&&this.selfDrivenSessions.has(e)&&this.pool?.getSlot(e)){const s=`selfdrive_${e}`,r=this.selfDrivenLabels.get(e)??"Background task in progress";t.push(s),o.push({event_id:s,content_preview:r,title:r,summary:r,actions:[]})}return{session_id:e,running:t,running_items:o,queued:i}}pushQueueSnapshotForSession(e){if(!this.config.eventQueue||!this.pool)return;const n=this.buildQueueSnapshotPayload(e);h.info(this.name,`[queue-debug] push snapshot session=${e} running=${n.running.length} queued=${n.queued.length} running_ids=[${n.running.join(",")}]`),this.aibotHandle.sendQueueSnapshot(n)}replyQueueSnapshotForSession(e){!this.config.eventQueue||!this.pool||this.aibotHandle.sendQueueSnapshot(this.buildQueueSnapshotPayload(e))}async platformInvoke(e,n,t){return e==="file_link"?ei(n):e==="file_upload"?this.uploadFileAndSendMedia(n):this.aibotHandle.agentInvoke(e,n,t)}async uploadFileAndSendMedia(e){const n=String(e.file_path??"").trim(),t=String(e.session_id??"").trim(),o=String(e.caption??"").trim(),i=String(e.reply_to_message_id??"").trim();if(!n)throw new Error("file_path is required");if(!t)throw new Error("session_id is required");const s=await ii({wsURL:this.config.aibot.url,apiKey:this.config.aibot.apiKey,sessionID:t,filePath:n}),r=await this.aibotHandle.sendMedia({session_id:t,msg_type:2,content:o||`[${s.attachment_type}]`,client_msg_id:`file_upload_${j()}`,...i?{quoted_message_id:i}:{},extra:s.extra});if(r.cmd!=="send_ack"){const d=r.payload??{},c=String(d.msg??r.cmd);throw new Error(`media message send failed: ${c}`)}const a=r.payload??{};return{ok:!0,file_name:s.file_name,attachment_type:s.attachment_type,access_url:s.access_url,message_id:a.msg_id!=null?String(a.msg_id):null}}sendReplyByRuntimeConfig(e,n,t,o,i){const s=this.indexEventSession(e,n)??n;this.auditController.captureReply(e,t),this.sendCtrl.sendReply(e,s,t,o,i),t&&this.conversationLog?.logOutbound?.(s,e,"reply",t)}stampCodexEventQuote(e){if(e.quoted_message_id!=null){if(e.quoted_message_id)return e;const{quoted_message_id:s,...r}=e;return r}const n=e;if(n.codex_method!=="item/agentMessage/delta")return e;const o=n.codex_payload?.params?.phase;if(typeof o=="string"&&o.trim()&&o.trim().toLowerCase()!=="final_answer")return e;const i=this.sendCtrl.getDefaultQuotedMessageId(e.event_id);return i?{...e,quoted_message_id:i}:e}captureCodexAuditOutput(e){if(e.codex_method!=="item/agentMessage/delta")return;const n=e.codex_payload?.params,t=typeof n?.phase=="string"?n.phase.trim().toLowerCase():"";if(t&&t!=="final_answer")return;const o=n?.delta;typeof o!="string"||!o||this.auditController.captureUnsequencedStreamChunk(e.event_id,o)}discardEventTrackingState(e){this.inflightEvents.delete(e),this.restartCount.delete(e),this.eventSessionIndex.delete(e),this.sendCtrl.discardEventState(e)}reliableFinalWiring(e,n){return async(t,o,i)=>{try{await this.sendCtrl.sendFinalStreamChunkReliable(t,o,i,n)}catch(s){throw h.error("bridge",`[${e}] sendFinalStreamChunkReliable ACK failed event=${t}: ${s}`),s}h.info("bridge",`[${e}] sendFinalStreamChunkReliable done event=${t}`)}}sendStreamChunkByRuntimeConfig(e,n,t,o,i,s,r){const a=this.indexEventSession(e,n)??n;this.auditController.captureStreamChunk(e,o,t),this.sendCtrl.sendStreamChunk(e,a,t,o,i,s,r),(t||i)&&this.conversationLog?.logOutbound?.(a,e,i?"stream_chunk_finish":"stream_chunk",t)}surfacedRunErrorEvents=new Set;sendRunErrorAsChunk(e,n,t){this.surfacedRunErrorEvents.has(e)||(this.surfacedRunErrorEvents.add(e),this.sendStreamChunkByRuntimeConfig(e,n,`
7
+ Error: ${u}`,1,!1)},sendAgentQuestionCard:(c,l,u)=>{this.sendGrixAgentQuestionCard(c,l,u)},sendPermissionCard:c=>{h.info("bridge","sendPermissionCard callback entered",{rawTransport:n,eventId:c.eventId,sessionId:c.sessionId,toolCallId:c.toolCallId,toolName:c.toolName,toolTitle:c.toolTitle});const l=c.toolInput&&c.toolTitle&&c.toolInput!==c.toolTitle?`${c.toolTitle}: ${c.toolInput}`:c.toolTitle||c.toolInput||c.toolName;if(n){this.sendAcpRawEventEnvelope(c.eventId,c.sessionId,{type:"permission_request",payload:{tool_call_id:c.toolCallId,tool_name:c.toolName,tool_title:c.toolTitle,...c.toolInput?{tool_input:c.toolInput}:{},options:c.options}}),h.info("bridge","sendPermissionCard: sent via rawEventEnvelope",{eventId:c.eventId,toolCallId:c.toolCallId});return}const u=`perm_${j()}`,g={event_id:c.eventId,session_id:c.sessionId,client_msg_id:u,msg_type:1,content:c.toolTitle?`Permission required: ${c.toolTitle}`:"Permission request",extra:{channel_data:{execApproval:{approvalId:c.toolCallId,approvalSlug:c.toolName},grix:{execApproval:{approval_command_id:c.toolCallId,approval_type:"permission",command:l,host:"acp"}}},agent_api_origin:!0}};h.info("bridge","sendPermissionCard: about to invoke aibotHandle.sendMsg",{eventId:c.eventId,sessionId:c.sessionId,clientMsgId:u,toolCallId:c.toolCallId,contentLength:g.content.length});try{this.aibotHandle.sendMsg(g),h.info("bridge","sendPermissionCard: aibotHandle.sendMsg returned",{eventId:c.eventId,clientMsgId:u})}catch(m){h.error("bridge","sendPermissionCard: aibotHandle.sendMsg threw",{eventId:c.eventId,clientMsgId:u,error:m instanceof Error?m.message:String(m)})}},sendAuthNotification:(c,l)=>{c&&this.aibotHandle.sendMsg({session_id:c,msg_type:1,content:l,extra:{biz_card:{version:1,type:"agent_error",payload:{error:{name:"AuthRequired",message:l}}}}})},sendAgentMessage:(c,l)=>{c&&l&&this.aibotHandle.sendMsg({session_id:c,msg_type:1,content:l})},sendUpdateBindingCard:(c,l,u,g)=>{const m={...g??{}};if(!m.rate_limits&&this.cachedProviderQuota?.success){this.isRateLimitsCacheFresh(this.cachedProviderQuotaSampledAtMs)||this.maybeQueryProviderQuota().catch(()=>{});const f=this.providerQuotaToRateLimits(this.cachedProviderQuota);f&&(m.rate_limits=f)}!m.provider_quota&&this.cachedProviderQuota?.success&&(m.provider_quota=this.cachedProviderQuota),this.aibotHandle.sendUpdateBindingCard({session_id:c,worker_status:l,cwd:u,...Object.keys(m).length>0?{meta:m}:{}})},onSkillsUpdate:(c,l)=>{try{this.aibotHandle.sendSkillsUpdate({skills:ce(c,$.skills),library_skills:this.buildLibrarySkillsReport(l)})}catch(u){}},onContextWindowUpdated:c=>{this.cachedAcpContextWindow=c,this.cachedAcpContextWindowSampledAtMs=Date.now();const l="usedPercentage"in c?c.usedPercentage.toFixed(1):(c.used/c.size*100).toFixed(1);h.info(this.name,`[acp] context_window updated: ${l}%`);const u=this.bindingStore.get(e);if(u?.cwd){const g="usedPercentage"in c?c.usedPercentage:Math.min(100,c.used/c.size*100),m={context_window:{..."usedPercentage"in c?{}:c,usedPercentage:g,remainingPercentage:100-g}};if((this.config.aibot.clientType==="kiro"||this.config.aibot.clientType==="kimi")&&this.cachedProviderQuota?.success){this.isRateLimitsCacheFresh(this.cachedProviderQuotaSampledAtMs)||this.maybeQueryProviderQuota().catch(()=>{}),m.provider_quota=this.cachedProviderQuota;const f=this.providerQuotaToRateLimits(this.cachedProviderQuota);f&&(m.rate_limits=f)}this.aibotHandle.sendUpdateBindingCard({session_id:e,worker_status:"ready",cwd:u.cwd,meta:m})}},sendMcpFrame:c=>{this.aibotHandle.sendMcpFrame(e,c)},getAgentProfile:()=>this.agentProfile,getAgentId:()=>this.config.aibot.agentId},o=e?this.bindingStore.get(e):void 0,i=e?this.auditController.getSession(e):void 0,s=this.config.aibot.clientType==="reasonix"&&i?Li(this.name,e,i.auditId):void 0,r=this.globalConfigStore?.get(this.name),{initialModel:a,initialMode:d}=Yt({sessionBinding:o,globalDefaults:r,configInitialMode:this.config.acpInitialMode});return e&&a&&!o?.acpModelId&&this.bindingStore.setAcpModelId(e,a),(a||d)&&h.info(this.name,`[toolbar] hydrate from binding: session=${e} model=${a??"<none>"} mode=${d??"<none>"}`),new E({command:this.config.agent.command,args:this.config.agent.args,env:this.resolveSpawnEnv()},t,{acpAuthMethod:this.config.acpAuthMethod,acpInitialMode:d,acpInitialModel:a,acpMcpTools:this.config.acpMcpTools,rawTransport:n,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 Ft(this.config.logDir,`${this.name}-${e}`):null,reasonixAuditTranscriptPath:s,agentType:this.config.aibot.clientType})}async connectAibot(){const e=new Ee;this.aibotHandle=await e.connect(this.aibotConfig,{aborted:()=>this.stopped,label:this.name,packetLog:this.packetLog,maxRetries:this.config.connectMaxRetries});const n=this.aibotHandle.authAck;if(this.applyAgentProfile(n?.agent_name,n?.introduction,{source:"auth_ack",respawnOnChange:!1},n?.system_prompt),this.aibotHandle.onEvent(t=>{this.handleAibotEvent(t).catch(o=>{h.error(this.name,`handleAibotEvent failed: ${o}`),this.aibotHandle.sendEventAck({event_id:t.event_id,session_id:t.session_id,received_at:Date.now()});const i=o instanceof Error?o.message:String(o);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),this.sessionProviderHints.delete(t.session_id),this.sessionProviderQuotas.delete(t.session_id),this.sessionProviderMeta.delete(t.session_id);const r=this.pool.getSlot(t.session_id);r?.adapter instanceof E&&r.adapter.getSessionBindings().delete(t.session_id);const a=this.config.adapterType??"acp",d=this.resolveBindingChannelKey(a);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:p.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(o){h.error(this.name,`handleAibotStop failed: ${o}`)}}),this.aibotHandle.onAgentDeleted(t=>{h.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(o){h.error(this.name,`agentDeletedHandler failed: ${o}`)}}),this.aibotHandle.onShareSet(t=>{try{this.shareSetHandler?.(Array.isArray(t?.shared_to)?t.shared_to:[])}catch(o){h.error(this.name,`onShareSet failed: ${o}`)}}),this.aibotHandle.onProfilePush(t=>{try{this.applyAgentProfile(t?.agent_name,t?.introduction,{source:"profile_push",respawnOnChange:!0},t?.system_prompt)}catch(o){h.error(this.name,`onProfilePush failed: ${o}`)}}),this.aibotHandle.onSkillSync(t=>{try{h.info(this.name,`skill_sync received owner=${t?.owner_id??""} name=${t?.name??""}`),this.skillSyncHandler?.()}catch(o){h.error(this.name,`onSkillSync failed: ${o}`)}}),this.aibotHandle.onRevoke(t=>{try{this.handleAibotRevoke(t)}catch(o){h.error(this.name,`handleAibotRevoke failed: ${o}`)}}),this.aibotHandle.onLocalAction(t=>{this.handleAibotLocalAction(t).catch(o=>{h.error(this.name,`handleAibotLocalAction failed: ${o}`)})}),this.aibotHandle.onEventCancel(t=>{h.info(this.name,`recv event_cancel event_id=${t.event_id} session_id=${t.session_id}`),this.handleEventCancel(t).catch(o=>{h.error(this.name,`handleEventCancel failed: ${o}`)})}),this.aibotHandle.onMcpFrame((t,o)=>{const i=this.pool.getSlot(t)?.adapter;i?.deliverMcpFrameToAgent?i.deliverMcpFrameToAgent(o):h.warn(this.name,`mcp_frame: no adapter for session=${t}`)}),this.aibotHandle.onQueueClear(t=>{const o=this.pool.clearQueue(t.session_id);this.aibotHandle.sendQueueClearResult({session_id:t.session_id,canceled_event_ids:o}),this.pushQueueSnapshotForSession(t.session_id)}),typeof this.aibotHandle.onQueueReorder=="function"&&this.aibotHandle.onQueueReorder(t=>{const o=Array.isArray(t.ordered_event_ids)?t.ordered_event_ids.filter(s=>typeof s=="string"&&s.length>0):[],i=this.pool.reorderQueue(t.session_id,o);this.aibotHandle.sendQueueReorderResult({session_id:t.session_id,applied_event_ids:i}),this.pushQueueSnapshotForSession(t.session_id)}),typeof this.aibotHandle.onEventHold=="function"&&this.aibotHandle.onEventHold(t=>{const o=typeof t.session_id=="string"?t.session_id:"",i=typeof t.event_id=="string"?t.event_id:"",s=t.hold!==!1;if(h.info(this.name,`recv event_hold event_id=${i} session_id=${o} hold=${s} reason=${t.reason??""}`),!o||!i){this.aibotHandle.sendEventHoldResult({session_id:o,event_id:i,ok:!1,held:!1,error:"bad_request"});return}const r=typeof t.reason=="string"?t.reason:"",a=typeof t.ttl_ms=="number"?t.ttl_ms:void 0,d=this.pool.holdEvent(o,i,s,r,a),c=d==="ok";this.aibotHandle.sendEventHoldResult({session_id:o,event_id:i,ok:c,held:c?s:!1,...c?{}:{error:d}}),c&&this.pushQueueSnapshotForSession(o)}),typeof this.aibotHandle.onQueueEdit=="function"&&this.aibotHandle.onQueueEdit(t=>{const o=typeof t.session_id=="string"?t.session_id:"",i=typeof t.event_id=="string"?t.event_id:"";if(h.info(this.name,`recv queue_edit event_id=${i} session_id=${o}`),!o||!i){this.aibotHandle.sendQueueEditResult({session_id:o,event_id:i,ok:!1,error:"bad_request"});return}const s=typeof t.content=="string"?t.content:"",r=this.pool.editQueuedEvent(o,i,s),a=r==="ok";this.aibotHandle.sendQueueEditResult({session_id:o,event_id:i,ok:a,...a?{}:{error:r}}),a&&this.pushQueueSnapshotForSession(o)}),typeof this.aibotHandle.onQueueSnapshotQuery=="function"&&this.aibotHandle.onQueueSnapshotQuery(t=>{this.replyQueueSnapshotForSession(t.session_id)}),h.info(this.name,"Connected to aibot"),this.activeEventStore){const t=await this.activeEventStore.drain();if(t.length>0){h.warn(this.name,`Recovering ${t.length} stale event(s) from previous run`);for(const o of t)h.info(this.name,`Failing stale event on startup: ${o}`),this.aibotHandle.sendEventResult({event_id:o,status:"failed",msg:"process restarted, event lost",updated_at:Date.now()})}}this.pushQueueSnapshots(),this.relayStateSyncOnConnect(),this.aibotHandle.onReconnected(()=>{this.pushQueueSnapshots();const t=this.aibotHandle.authAck;this.applyAgentProfile(t?.agent_name,t?.introduction,{source:"reconnect",respawnOnChange:!0},t?.system_prompt),this.forceRefreshSkills(void 0,{force:!0}),this.skillSyncHandler?.(),this.relayStateSyncOnConnect()}),this.aibotHandle.onStreamRejected((t,o)=>{this.sendCtrl.markEventRejected(t)})}applyAgentProfile(e,n,t,o){const i=String(e??"").trim(),s=String(n??"").trim(),r=o===void 0?this.agentSystemPrompt:String(o),a=i!==this.agentProfile.agentName||s!==this.agentProfile.introduction||r!==this.agentSystemPrompt;this.agentProfile={agentName:i,introduction:s},this.agentSystemPrompt=r,i||s?h.info(this.name,`agent profile (${t.source}): GOT name="${i}" intro_len=${s.length} system_prompt_len=${r.length} changed=${a}`):h.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`),a&&t.respawnOnChange&&this.applyProfileChangeToAdapters("agent_profile_changed")}applyProfileChangeToAdapters(e){if(!this.pool)return;const n=this.pool.getAllSlots();let t=0;for(const r of n)if(r.adapter.onAgentProfileChanged)try{r.adapter.onAgentProfileChanged(),t++}catch(a){h.warn(this.name,`onAgentProfileChanged failed for session=${r.sessionId}: ${a}`)}const o=n.filter(r=>r.adapter instanceof I),i=o.filter(r=>{if(r.state!=="ready")return!1;const a=r.adapter.getStatus();return!a.busy&&!a.backgroundBusy}),s=o.length-i.length;h.info(this.name,`${e}: notified ${t} adapter(s) via hook; respawning ${i.length} idle Claude slot(s), skipping ${s} busy`);for(const r of i)this.pool.removeSlot(r.sessionId).catch(a=>{h.warn(this.name,`removeSlot failed during ${e}: ${a}`)})}pushQueueSnapshots(){if(!(!this.config.eventQueue||!this.pool))for(const e of this.pool.getAllSlots())this.pushQueueSnapshotForSession(e.sessionId)}buildQueueSnapshotPayload(e){const n=this.pool?.getQueueSnapshot(e)??null,t=n?[...n.running]:[],o=n?n.running_items.map(s=>({event_id:s.event_id,...s.content_preview?{content_preview:s.content_preview}:{},...s.title?{title:s.title}:{},...s.summary?{summary:s.summary}:{},actions:[{type:"stop"}]})):[],i=n?n.queued.map(s=>({event_id:s.event_id,position:s.position,...s.content_preview?{content_preview:s.content_preview}:{},...typeof s.content=="string"?{content:s.content}:{},...s.title?{title:s.title}:{},...s.summary?{summary:s.summary}:{},held:s.held===!0,held_reason:s.held_reason??"",actions:[{type:"cancel"}]})):[];if(t.length===0&&this.selfDrivenSessions.has(e)&&this.pool?.getSlot(e)){const s=`selfdrive_${e}`,r=this.selfDrivenLabels.get(e)??"Background task in progress";t.push(s),o.push({event_id:s,content_preview:r,title:r,summary:r,actions:[]})}return{session_id:e,running:t,running_items:o,queued:i}}pushQueueSnapshotForSession(e){if(!this.config.eventQueue||!this.pool)return;const n=this.buildQueueSnapshotPayload(e);h.info(this.name,`[queue-debug] push snapshot session=${e} running=${n.running.length} queued=${n.queued.length} running_ids=[${n.running.join(",")}]`),this.aibotHandle.sendQueueSnapshot(n)}replyQueueSnapshotForSession(e){!this.config.eventQueue||!this.pool||this.aibotHandle.sendQueueSnapshot(this.buildQueueSnapshotPayload(e))}async platformInvoke(e,n,t){return e==="file_link"?ei(n):e==="file_upload"?this.uploadFileAndSendMedia(n):this.aibotHandle.agentInvoke(e,n,t)}async uploadFileAndSendMedia(e){const n=String(e.file_path??"").trim(),t=String(e.session_id??"").trim(),o=String(e.caption??"").trim(),i=String(e.reply_to_message_id??"").trim();if(!n)throw new Error("file_path is required");if(!t)throw new Error("session_id is required");const s=await ii({wsURL:this.config.aibot.url,apiKey:this.config.aibot.apiKey,sessionID:t,filePath:n}),r=await this.aibotHandle.sendMedia({session_id:t,msg_type:2,content:o||`[${s.attachment_type}]`,client_msg_id:`file_upload_${j()}`,...i?{quoted_message_id:i}:{},extra:s.extra});if(r.cmd!=="send_ack"){const d=r.payload??{},c=String(d.msg??r.cmd);throw new Error(`media message send failed: ${c}`)}const a=r.payload??{};return{ok:!0,file_name:s.file_name,attachment_type:s.attachment_type,access_url:s.access_url,message_id:a.msg_id!=null?String(a.msg_id):null}}sendReplyByRuntimeConfig(e,n,t,o,i){const s=this.indexEventSession(e,n)??n;this.auditController.captureReply(e,t),this.sendCtrl.sendReply(e,s,t,o,i),t&&this.conversationLog?.logOutbound?.(s,e,"reply",t)}stampCodexEventQuote(e){if(e.quoted_message_id!=null){if(e.quoted_message_id)return e;const{quoted_message_id:s,...r}=e;return r}const n=e;if(n.codex_method!=="item/agentMessage/delta")return e;const o=n.codex_payload?.params?.phase;if(typeof o=="string"&&o.trim()&&o.trim().toLowerCase()!=="final_answer")return e;const i=this.sendCtrl.getDefaultQuotedMessageId(e.event_id);return i?{...e,quoted_message_id:i}:e}captureCodexAuditOutput(e){if(e.codex_method!=="item/agentMessage/delta")return;const n=e.codex_payload?.params,t=typeof n?.phase=="string"?n.phase.trim().toLowerCase():"";if(t&&t!=="final_answer")return;const o=n?.delta;typeof o!="string"||!o||this.auditController.captureUnsequencedStreamChunk(e.event_id,o)}discardEventTrackingState(e){this.inflightEvents.delete(e),this.restartCount.delete(e),this.eventSessionIndex.delete(e),this.sendCtrl.discardEventState(e)}reliableFinalWiring(e,n){return async(t,o,i)=>{try{await this.sendCtrl.sendFinalStreamChunkReliable(t,o,i,n)}catch(s){throw h.error("bridge",`[${e}] sendFinalStreamChunkReliable ACK failed event=${t}: ${s}`),s}h.info("bridge",`[${e}] sendFinalStreamChunkReliable done event=${t}`)}}sendStreamChunkByRuntimeConfig(e,n,t,o,i,s,r){const a=this.indexEventSession(e,n)??n;this.auditController.captureStreamChunk(e,o,t),this.sendCtrl.sendStreamChunk(e,a,t,o,i,s,r),(t||i)&&this.conversationLog?.logOutbound?.(a,e,i?"stream_chunk_finish":"stream_chunk",t)}surfacedRunErrorEvents=new Set;sendRunErrorAsChunk(e,n,t){this.surfacedRunErrorEvents.has(e)||(this.surfacedRunErrorEvents.add(e),this.sendStreamChunkByRuntimeConfig(e,n,`
8
8
 
9
9
  Error: ${t}`,1,!1))}sendEventResultWithCleanup(e,n,t,o,i=!1){const s=this.eventSessionIndex.get(e);i&&this.surfacedRunErrorEvents.add(e),n==="failed"&&s&&t?.trim()&&this.sendRunErrorAsChunk(e,s,t),this.auditController.markResponded(e,n,t),this.sendCtrl.sendEventResult(e,n,t,o),s&&(this.pool.eventComplete(e,s)===!1&&h.error(this.name,`Event terminal result could not release queue slot event=${e} session=${s} status=${n}`),this.pushQueueSnapshotForSession(s),this.conversationLog?.logResult?.(s,e,n,t),this.eventSessionIndex.delete(e)),this.inflightEvents.delete(e),this.restartCount.delete(e),this.surfacedRunErrorEvents.delete(e),n==="responded"&&(this.cachedProviderQuotaSampledAtMs=null,this.refreshAndPushProviderQuota(!0).catch(()=>{}))}async handleSessionInternalError(e){const{eventId:n,sessionId:t,errorMsg:o}=e;if(this.stopped)return;const i=this.inflightEvents.get(n);if(!i){h.warn(this.name,`[recovery] no inflight event for internalError event=${n} session=${t}; surface failure directly`),this.sendRunErrorAsChunk(n,t,o),this.sendEventResultWithCleanup(n,"failed",o,"agent_stop_failure");return}const s=(this.restartCount.get(n)??0)+1;this.restartCount.set(n,s);const r=this.config.adapterType??"acp";if(s>se){h.error(this.name,`[recovery] adapter=${r} session=${t} event=${n} restart=${s}/${se} outcome=give-up err=${o}`),this.sendRunErrorAsChunk(n,t,o),this.sendEventResultWithCleanup(n,"failed",o,"agent_stop_failure");return}h.info(this.name,`[recovery] adapter=${r} session=${t} event=${n} restart=${s}/${se} outcome=restarting err=${o}`);const a=this.pool.drainQueuedForSession(t);a.length>0&&h.info(this.name,`[recovery] session=${t} preserved ${a.length} queued sibling event(s) across restart`);try{await this.pool.removeSlot(t)}catch(l){h.warn(this.name,`[recovery] removeSlot failed session=${t}: ${l instanceof Error?l.message:String(l)}`)}if(this.stopped)return;const d=e.replayOriginalContent?i.content:this.resolveRecoveryPrompt(r,i),c={...i,content:d};try{await this.pool.deliverInboundEvent(c)}catch(l){h.error(this.name,`[recovery] redeliver failed event=${n} session=${t}: ${l instanceof Error?l.message:String(l)}`),this.sendEventResultWithCleanup(n,"failed",l instanceof Error?l.message:String(l));return}for(const l of a){if(this.stopped)break;try{await this.pool.deliverInboundEvent(l)}catch(u){h.error(this.name,`[recovery] sibling redeliver failed event=${l.event_id} session=${t}: ${u instanceof Error?u.message:String(u)}`),this.sendEventResultWithCleanup(l.event_id,"failed",u instanceof Error?u.message:String(u))}}}resolveRecoveryPrompt(e,n){return e==="acp"?"continue":n.content}sendThinkingByRuntimeConfig(e,n,t){this.sendCtrl.sendThinking(e,n,t)}bufferStreamChunk(e,n,t,o,i){this.auditController.captureUnsequencedStreamChunk(e,t),this.sendCtrl.bufferOnly(e,n,t,o,i)}flushBufferedStreamText(e){}resolveEventRuntimeConfig(e){return this.sendCtrl.resolveEventRuntimeConfig(e)}captureEventRuntimeConfig(e){this.sendCtrl.captureEventRuntimeConfig(e),this.indexEventSession(e.event_id,e.session_id),this.auditController.markRecording(e.event_id),e.event_id&&!this.inflightEvents.has(e.event_id)&&this.inflightEvents.set(e.event_id,e)}indexEventSession(e,n){if(!e||!n)return;const t=this.eventSessionIndex.get(e);return t?(t!==n&&h.warn(this.name,`Ignoring event session mismatch event=${e} indexed=${t} supplied=${n}`),t):(this.eventSessionIndex.set(e,n),n)}shouldDropToolDisplayEvent(e){return this.sendCtrl.shouldDropToolDisplayEvent(e)}shouldDropThinkingDisplayEvent(e){return this.sendCtrl.shouldDropThinkingDisplayEvent(e)}shouldDropCodexDisplayEvent(e,n){return this.sendCtrl.shouldDropCodexDisplayEvent(e,n)}logCodexEventToConversation(e){if(!this.conversationLog||e.codex_method!=="item/agentMessage/delta")return;const t=e.codex_payload?.params?.delta;if(!t)return;const o=this.eventSessionIndex.get(e.event_id)??e.session_id;this.conversationLog.append(o,{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,n){return this.sendCtrl.shouldDropAcpRawDisplayEvent(e,n)}sendAcpRawEventEnvelope(e,n,t){this.shouldDropAcpRawDisplayEvent(e,t.type)||this.deliverRawEventEnvelope(e,n,t,"acp",this.buildAcpRawEventFallbackText(t))}buildAcpRawEventFallbackText(e){const n=String(e.type??"").trim();if(!n)return"[acp] event";switch(n){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] ${n}`}}rawDetailSeq=0;deliverRawEventEnvelope(e,n,t,o,i){const s=pi({envelope:t,fallbackText:i,channelKey:o,allocateRefId:()=>`${e}_rawd_${++this.rawDetailSeq}`}),r=()=>{this.aibotHandle.sendMsg({event_id:e,session_id:n,msg_type:1,content:s.fallbackText,extra:{channel_data:{[o]:{raw_event:s.envelope}},agent_api_origin:!0}})};if(!s.sharded){r();return}h.info("bridge",`${o} raw_event oversized, sharded delivery: event=${e} fields=${s.oversizedFields.map(a=>a.field).join(",")}`),(async()=>{for(const a of s.oversizedFields)await this.sendCtrl.deliverAuxiliaryLargeText(e,n,fi({envelopeType:t.type,field:a.field,fullText:a.fullText}),a.refClientMsgId);r()})().catch(a=>{h.warn("bridge",`${o} raw_event sharded delivery failed event=${e}: ${a}`)})}buildCursorRawEventFallbackText(e){const n=String(e?.type??"").trim(),t=e?.payload&&typeof e.payload=="object"?e.payload:{};switch(n){case"permission_request":return`Permission required: ${String(t.tool_title??t.tool_name??"permission request")}`;case"tool_use":case"tool_call":case"tool_execution_start":return`[tool] ${String(t.tool_name??t.toolName??"tool")}`;case"tool_result":case"tool_execution_end":case"tool_execution_update":return"[tool result]";case"error":return`[error] ${String(t.message??"agent error")}`;default:return n?`[cursor] ${n}`:"[cursor] event"}}sendToolExecutionCard(e,n,t,o){this.sendCtrl.sendToolExecutionCard(e,n,t,o)}sendGrixApprovalCard(e,n){this.aibotHandle.sendMsg({event_id:e.eventId,session_id:e.sessionId,client_msg_id:`perm_${j()}`,msg_type:1,content:e.toolTitle?`Permission required: ${e.toolTitle}`:"Permission request",extra:{channel_data:{execApproval:{approvalId:e.approvalId,approvalSlug:e.toolName},grix:{execApproval:{approval_command_id:e.approvalId,command:e.toolTitle||e.toolName,host:n}}},agent_api_origin:!0}})}sendGrixAgentQuestionCard(e,n,t){const o=t.questions.map(s=>s.header).join(", "),i=we(`[Agent Question] ${t.request_id}`,"agent_question",t);this.aibotHandle.sendText({event_id:e,session_id:n,content:i,msg_type:1,extra:{card_type:"agent_question",summary_text:o}})}async handleAgentQuestionReplyEvent(e){const n=Vt(String(e.content??""));if(!n)return!1;this.aibotHandle.sendEventAck({event_id:e.event_id,session_id:e.session_id,received_at:Date.now()});const o=this.pool.getSlot(e.session_id)?.adapter,i=o instanceof E?"acp":o instanceof q?"opencode":this.config.adapterType??"unknown",s=o instanceof q?o.handleQuestionReplyEvent(n):o instanceof E?await o.handleQuestionReplyEvent(n):{delivered:!1,errorCode:"interaction_request_not_pending",errorMsg:"The question is no longer pending; the reply was not delivered."};return s.delivered?(h.info(this.name,`[${i}] question reply delivered event=${e.event_id} session=${e.session_id} request=${n.request_id}`),this.aibotHandle.sendEventResult({event_id:e.event_id,status:"responded",updated_at:Date.now()})):(h.warn(this.name,`[${i}] question reply rejected event=${e.event_id} session=${e.session_id} request=${n.request_id} code=${s.errorCode??"interaction_reply_failed"}`),this.aibotHandle.sendEventResult({event_id:e.event_id,status:"failed",code:s.errorCode??"interaction_reply_failed",msg:s.errorMsg??"The question reply was not delivered.",updated_at:Date.now()})),!0}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 n=this.config.adapterType??"acp";if((n==="opencode"||n==="acp")&&await this.handleAgentQuestionReplyEvent(e))return;const t=ri(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 o=t.patch,i=Ei(e.extra);if((i.state!=="absent"||i.error)&&h.info(this.name,`[audit-marker] event=${e.event_id} session=${e.session_id} msg=${e.msg_id??""} state=${i.state??"error"} scope=${i.options?.enabled?i.options.scope:""} error=${i.error??""}`),i.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:"audit_config_invalid",msg:i.error,updated_at:Date.now()});return}if(i.state==="enabled"&&n!=="claude"&&n!=="codex"&&n!=="cursor"&&n!=="opencode"&&n!=="pi"&&n!=="codewhale"&&n!=="deepseek-harness"&&n!=="acp"){if(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:"audit_provider_unsupported",msg:`Audit replay is not supported for adapter: ${n}`,updated_at:Date.now()}),i.options?.enabled&&i.options.scope==="turn")try{this.aibotHandle.sendAuditState(te({state:"failed",eventId:e.event_id,sessionId:e.session_id,...e.msg_id===void 0?{}:{msgId:e.msg_id},errorCode:"audit_provider_unsupported",errorMessage:`Audit replay is not supported for adapter: ${n}`},Date.now()))}catch{}return}const s=Gt(e);if(s){if(i.state!=="absent"||s.verb===v.open)try{this.auditController.resolveSession({sessionId:e.session_id,provider:this.resolveAuditProvider(),extra:e.extra})}catch(c){this.sendAuditConfigurationError(e,c);return}if(s.verb===v.exec){await this.handleSessionControlCommand(s,e);return}if(s.verb===v.listSessions){await this.handleListSessionsTextCommand(e);return}if(n==="claude"){await this.handleSessionControlCommand(s,e);return}if(n==="codex"&&s.verb===v.open){await this.handleCodexSessionControlOpen(s,e);return}if(n==="pi"&&s.verb===v.open){await this.handlePiSessionControlOpen(s,e);return}if(n==="pi"&&s.verb===v.restart){await this.handlePiSessionControlRestart(e);return}if((n==="openhuman"||n==="opencode")&&s.verb===v.open){await this.handleOpenHumanSessionControlOpen(s,e);return}if(n==="codewhale"&&s.verb===v.open){await this.handleCodeWhaleSessionControlOpen(s,e);return}if(this.aibotHandle.sendEventAck({event_id:e.event_id,session_id:e.session_id,received_at:Date.now()}),s.verb===v.open){const c=s.args.trim();if(!c){this.aibotHandle.sendEventResult({event_id:e.event_id,status:"failed",code:p.cwdRequired,msg:"cwd is required",updated_at:Date.now()});return}try{const l=P.resolve(c);if(!(await N(l)).isDirectory()){this.aibotHandle.sendEventResult({event_id:e.event_id,status:"failed",code:p.invalidCwd,msg:`Path is not a directory: ${l}`,updated_at:Date.now()});return}}catch(l){const u=String(l?.code??""),g=u==="ENOENT"?`Directory does not exist: ${P.resolve(c)}`:u==="EACCES"||u==="EPERM"?"Directory is not accessible":`Invalid path: ${c}`;this.aibotHandle.sendEventResult({event_id:e.event_id,status:"failed",code:p.invalidCwd,msg:g,updated_at:Date.now()});return}}if(s.verb===v.open){const c=this.bindingStore.get(e.session_id);if(c?.cwd)try{await N(c.cwd)}catch{h.info("bridge",`Stale binding detected for session ${e.session_id}: ${c.cwd} no longer exists, clearing`),this.bindingStore.delete(e.session_id),this.sessionBindings.delete(e.session_id),this.sessionProviderHints.delete(e.session_id),this.sessionProviderQuotas.delete(e.session_id),this.sessionProviderMeta.delete(e.session_id);const l=this.pool.getSlot(e.session_id);l?.adapter instanceof E&&l.adapter.getSessionBindings().delete(e.session_id)}}if(await this.handleSessionControlForPool(s,e),n==="acp"&&s.verb===v.stop){const l=this.bindingStore.get(e.session_id)?.cwd??"";await this.pool.removeSlot(e.session_id).catch(()=>{}),this.sessionBindings.delete(e.session_id),this.sessionProviderHints.delete(e.session_id),this.sessionProviderQuotas.delete(e.session_id),this.sessionProviderMeta.delete(e.session_id),this.aibotHandle.sendEventResult({event_id:e.event_id,status:"responded",msg:`Session worker stopped for ${l}`,updated_at:Date.now()});return}if(pe(s,e,this.sessionControlCtx(e.session_id),{...this.sessionControlSenders(),sendEventAck:()=>{}}),s.verb===v.open&&(await this.deferredMgr.release(e.session_id,this.deferredCallbacks()),n==="agy")){const c=this.bindingStore.get(e.session_id)?.cwd??"";c&&this.aibotHandle.sendUpdateBindingCard({session_id:e.session_id,worker_status:"ready",cwd:c,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}let r;try{r=this.auditController.resolveSession({sessionId:e.session_id,provider:this.resolveAuditProvider(),extra:e.extra,eventId:e.event_id}).session}catch(c){this.sendAuditConfigurationError(e,c);return}if(xi.has(n)&&!this.bindingStore.get(e.session_id)?.cwd){const l=n;h.info(this.name,`[${l}] binding missing session_id=${e.session_id} event_id=${e.event_id}`),this.deferredMgr.defer(l,String(e.session_id??"").trim(),this.prepareAuditedInboundEvent(e,o,r));const u=this.resolveBindingChannelKey(n);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:{[u]:{sessionBinding:{status:"missing",reason:"binding_missing",error_code:p.bindingMissing}}}},quoted_message_id:e.msg_id});return}if((this.config.adapterType??"acp")==="acp"){const l=String(e.content??"").trim().match(/^\/(\S+)\s*(.*)/);if(l){const[,u,g]=l,f=this.pool.getSlot(e.session_id)?.adapter;if(f?.execCommand&&(f.getSupportedCommands?.()??[]).some(_=>_.name===u||_.name===`/${u}`)){this.aibotHandle.sendEventAck({event_id:e.event_id,session_id:e.session_id,received_at:Date.now()});try{const _=await f.execCommand(u,g.trim(),e.session_id);_.status==="options"&&_.data&&this.handleExecCommandOptions(e.session_id,u,_.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}}}if(n==="codex"&&de(r?.options)){const c=this.pool.getSlot(e.session_id);if(c?.adapter instanceof ne&&!c.adapter.hasRawApiCaptureRelay()){const l=c.eventQueue.snapshot(e.session_id);l.running.length===0&&l.queued.length===0?(h.info(this.name,`[audit-raw-capture] recreating idle Codex adapter before event=${e.event_id} session=${e.session_id}`),await this.pool.removeSlot(e.session_id)):h.warn(this.name,`[audit-raw-capture] Codex adapter lacks relay but is not idle event=${e.event_id} session=${e.session_id} running=${l.running.length} queued=${l.queued.length}`)}}const d=this.prepareAuditedInboundEvent(e,o,r);try{this.captureEventRuntimeConfig(d),await this.pool.deliverInboundEvent(d)}catch(c){if(this.aibotHandle.sendEventAck({event_id:e.event_id,session_id:e.session_id,received_at:Date.now()}),this.auditController.closeWithoutAdapter(e.event_id,"failed",c instanceof Error?c.message:String(c),{deliveryFailed:!0}),this.discardEventTrackingState(e.event_id),c instanceof Kt)this.aibotHandle.sendEventResult({event_id:e.event_id,status:"failed",msg:c.message,updated_at:Date.now()});else throw c}}async handleCodexSessionControlOpen(e,n){const t=n.session_id,o=e.args.trim(),i=()=>this.aibotHandle.sendEventAck({event_id:n.event_id,session_id:t,received_at:Date.now()}),s=(r,a)=>this.aibotHandle.sendEventResult({event_id:n.event_id,status:r,...a,updated_at:Date.now()});if(!o){i(),s("failed",{msg:"Usage: /grix open <working-directory>",code:p.cwdRequired});return}try{const r=await this.resolveCwdForBinding(o),a=this.bindingStore.get(t);if(a?.cwd){let d=!1;try{if(await this.resolveCwdForBinding(a.cwd)===r){i(),s("responded",{msg:`Session already bound to ${a.cwd}`});return}}catch{d=!0,h.info("bridge",`Stale codex binding for session ${t}: ${a.cwd} no longer exists, allowing rebind`)}if(!d){i(),s("failed",{msg:`Session already bound to ${a.cwd}. Rebinding is not allowed.`,code:p.rebindForbidden});return}}this.bindingStore.set(t,r),this.sessionBindings.set(t,r),this.deferredMgr.sendCodexDeferredReplayComposing(t,this.deferredCallbacks()),await this.bindSessionForPool(t,r),await this.deferredMgr.release(t,this.deferredCallbacks(),{announceComposing:!1}),this.aibotHandle.sendUpdateBindingCard({session_id:t,worker_status:"ready",cwd:r}),i(),s("responded",{msg:`Session bound to ${r}`})}catch(r){i(),s("failed",{code:p.invalidCwd,msg:r instanceof Error?r.message:String(r)})}}async handleSessionControlForPool(e,n){e.verb===v.open&&await this.bindSessionForPool(n.session_id,e.args.trim())}async replayDeferredEventsForSession(e){await this.deferredMgr.release(e,this.deferredCallbacks())}async handleSessionControlLocalActionForPool(e){if(String(e.action_type??"")!==C.sessionControl)return;const n=e.params??{};if(String(n.verb??"").trim().toLowerCase()!==v.open)return;const o=String(n.session_id??"").trim(),i=String(n.cwd??"").trim();!o||!i||await this.bindSessionForPool(o,i)}async handleCodexSessionControlLocalActionOpen(e){const n=e.params??{},t=String(n.session_id??"").trim(),o=String(n.cwd??"").trim(),i=String(n.agent_session_id??"").trim(),s=(r,a,d,c)=>{this.aibotHandle.sendLocalActionResult({action_id:e.action_id,status:r,...a!==void 0?{result:a}:{},...d?{error_code:d}:{},...c?{error_msg:c}:{}})};if(!t||!o){s("failed",void 0,p.cwdRequired,"session cwd is required");return}try{const r=await this.resolveCwdForBinding(o);this.ensureImportedAgentSession(i,r);const a=this.bindingStore.get(t);if(a?.cwd){let d=!1;try{const c=await this.resolveCwdForBinding(a.cwd);if(c===r){this.setResolvedAgentSessionId(t,i),s("ok",{outcome:"opened",binding:this.buildOpenedBindingResult(t,c)});return}}catch{d=!0,h.info("bridge",`Stale codex binding for session ${t}: ${a.cwd} no longer exists, allowing rebind`)}if(!d){s("failed",void 0,p.rebindForbidden,`Session already bound to ${a.cwd}`);return}}this.bindingStore.set(t,r),this.setResolvedAgentSessionId(t,i),this.sessionBindings.set(t,r),this.deferredMgr.sendCodexDeferredReplayComposing(t,this.deferredCallbacks()),await this.bindSessionForPool(t,r),await this.deferredMgr.release(t,this.deferredCallbacks(),{announceComposing:!1}),s("ok",{outcome:"opened",binding:this.buildOpenedBindingResult(t,r)})}catch(r){s("failed",void 0,r?.sessionControlErrorCode??p.invalidCwd,r instanceof Error?r.message:String(r))}}async handleCursorSessionControlLocalActionOpen(e){const n=e.params??{},t=String(n.session_id??"").trim(),o=String(n.cwd??"").trim(),i=String(n.agent_session_id??"").trim(),s=(r,a,d,c)=>{this.aibotHandle.sendLocalActionResult({action_id:e.action_id,status:r,...a!==void 0?{result:a}:{},...d?{error_code:d}:{},...c?{error_msg:c}:{}})};if(!t||!o){s("failed",void 0,p.cwdRequired,"session cwd is required");return}try{const r=await this.resolveCwdForBinding(o);this.ensureImportedAgentSession(i,r);const a=this.bindingStore.get(t);if(a?.cwd){let d=!1;try{const c=await this.resolveCwdForBinding(a.cwd);if(c===r){this.setResolvedAgentSessionId(t,i),this.sessionBindings.set(t,c),await this.bindSessionForPool(t,c),this.aibotHandle.sendUpdateBindingCard({session_id:t,worker_status:"ready",cwd:c,meta:this.buildCursorToolbarMeta(t)}),s("ok",{outcome:"opened",binding:this.buildOpenedBindingResult(t,c)});return}}catch{d=!0,h.info("bridge",`Stale cursor binding for session ${t}: ${a.cwd} no longer exists, allowing rebind`)}if(!d){s("failed",void 0,p.rebindForbidden,`Session already bound to ${a.cwd}`);return}}this.bindingStore.set(t,r),this.setResolvedAgentSessionId(t,i),this.sessionBindings.set(t,r),this.deferredMgr.sendCursorDeferredReplayComposing(t,this.deferredCallbacks()),await this.bindSessionForPool(t,r),await this.deferredMgr.release(t,this.deferredCallbacks(),{announceComposing:!1}),this.aibotHandle.sendUpdateBindingCard({session_id:t,worker_status:"ready",cwd:r,meta:this.buildCursorToolbarMeta(t)}),s("ok",{outcome:"opened",binding:this.buildOpenedBindingResult(t,r)})}catch(r){s("failed",void 0,r?.sessionControlErrorCode??p.invalidCwd,r instanceof Error?r.message:String(r))}}async handlePiSessionControlOpen(e,n){const t=n.session_id,o=e.args.trim(),i=()=>this.aibotHandle.sendEventAck({event_id:n.event_id,session_id:t,received_at:Date.now()}),s=(r,a)=>this.aibotHandle.sendEventResult({event_id:n.event_id,status:r,...a,updated_at:Date.now()});if(!o){i(),s("failed",{msg:"Usage: /grix open <working-directory>",code:p.cwdRequired});return}try{const r=await this.resolveCwdForBinding(o),a=this.bindingStore.get(t);if(a?.cwd){let d=!1;try{if(await this.resolveCwdForBinding(a.cwd)===r){i(),s("responded",{msg:`Session already bound to ${a.cwd}`}),this.aibotHandle.sendMsg({event_id:n.event_id,session_id:t,msg_type:1,content:`\u2705 Session already bound to \`${a.cwd}\``,quoted_message_id:n.msg_id});return}}catch{d=!0,h.info("bridge",`Stale pi binding for session ${t}: ${a.cwd} no longer exists, allowing rebind`)}if(!d){i(),s("failed",{msg:`Session already bound to ${a.cwd}. Rebinding is not allowed.`,code:p.rebindForbidden});return}}this.bindingStore.set(t,r),this.sessionBindings.set(t,r),await this.ensureSlotStarted(t).catch(d=>{h.warn("bridge",`pi ensureSlotStarted on bind failed (non-fatal): ${d instanceof Error?d.message:String(d)}`)}),await this.deferredMgr.release(t,this.deferredCallbacks()),this.aibotHandle.sendUpdateBindingCard({session_id:t,worker_status:"ready",cwd:r}),i(),s("responded",{msg:`Session bound to ${r}`}),this.aibotHandle.sendMsg({event_id:n.event_id,session_id:t,msg_type:1,content:`\u2705 Working directory bound: \`${r}\``,quoted_message_id:n.msg_id})}catch(r){i(),s("failed",{code:p.invalidCwd,msg:r instanceof Error?r.message:String(r)})}}async handlePiSessionControlRestart(e){const n=e.session_id,t=()=>this.aibotHandle.sendEventAck({event_id:e.event_id,session_id:n,received_at:Date.now()}),o=(r,a)=>this.aibotHandle.sendEventResult({event_id:e.event_id,status:r,...a,updated_at:Date.now()}),s=this.bindingStore.get(n)?.cwd??"";if(!s){t(),o("failed",{msg:"session binding was not found",code:p.bindingMissing});return}t(),await this.pool.removeSlot(n).catch(()=>{}),this.aibotHandle.sendUpdateBindingCard({session_id:n,worker_status:"ready",cwd:s}),o("responded",{msg:`Session worker restarted for ${s}`})}async handlePiSessionControlRestartLocalAction(e){const n=e.params??{},t=String(n.session_id??"").trim(),i=(t?this.bindingStore.get(t):void 0)?.cwd??"",s=(r,a,d,c)=>{this.aibotHandle.sendLocalActionResult({action_id:e.action_id,status:r,...a!==void 0?{result:a}:{},...d?{error_code:d}:{},...c?{error_msg:c}:{}})};if(!t){s("failed",void 0,"session_id_required","session_id is required for restart");return}if(!i){s("failed",void 0,p.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}),s("ok",{outcome:"restarted",binding:{aibotSessionId:t,cwd:i,workerStatus:"ready"}})}async syncOpenCodeBinding(e,n){if(!e||!n)return;try{this.resolveSessionModelId(e),this.resolveSessionModeId(e)}catch(o){h.warn(this.name,`[opencode] resolve global toolbar default failed: ${o instanceof Error?o.message:String(o)}`)}let t=this.pool.getSlot(e);!t&&(this.config.adapterType??"acp")==="opencode"&&(await this.ensureSlotStarted(e),t=this.pool.getSlot(e)),t?.startPromise&&await t.startPromise,t?.adapter instanceof q&&await t.adapter.bindSession(e,n)}async bindSessionForPool(e,n){const t=String(n??"").trim();if(!e||!t)return;const o=this.pool.getOrCreateSlot(e);if(!o||(o.startPromise&&await o.startPromise,!o.adapter))return;const i=o.adapter instanceof E?o.adapter:null;if(!i?.hasSessionBinding)return;const s=await this.resolveCwdForBinding(t);i.announceDeferredComposing(e),await i.bindSession(e,s),this.sessionBindings.set(e,s),this.sessionScanCache.invalidate(),i.replayDeferredEvents(e)}deferredCallbacks(){return{captureEventRuntimeConfig:e=>this.captureEventRuntimeConfig(e),deliverInboundEvent:e=>this.pool.deliverInboundEvent(e),sendEventResult:(e,n,t)=>this.sendEventResultWithCleanup(e,n,t),sendSessionComposing:(e,n,t)=>{const o={};n&&(o.ttl_ms=t?.ttlMs??3e4,t?.activity&&(o.activity=t.activity)),this.aibotHandle.sendSessionActivitySet({session_id:e,kind:"composing",active:n,...o})}}}async handleOpenHumanSessionControlOpen(e,n){const t=()=>this.aibotHandle.sendEventAck({event_id:n.event_id,session_id:n.session_id,received_at:Date.now()});try{const o=await this.resolveCwdForBinding(e.args.trim());await this.handleSessionControlForPool(e,n),await this.syncOpenCodeBinding(n.session_id,o),pe(e,n,this.sessionControlCtx(n.session_id),this.sessionControlSenders()),await this.deferredMgr.release(n.session_id,this.deferredCallbacks())}catch(o){t(),this.aibotHandle.sendEventResult({event_id:n.event_id,status:"failed",code:o?.cwdErrorCode,msg:o instanceof Error?o.message:String(o),updated_at:Date.now()})}}async handleCodeWhaleSessionControlOpen(e,n){const t=n.session_id,o=e.args.trim(),i=()=>this.aibotHandle.sendEventAck({event_id:n.event_id,session_id:t,received_at:Date.now()}),s=(r,a)=>this.aibotHandle.sendEventResult({event_id:n.event_id,status:r,...a,updated_at:Date.now()});if(!o){i(),s("failed",{msg:"Usage: /grix open <working-directory>",code:p.cwdRequired});return}try{const r=await this.resolveCwdForBinding(o),a=this.bindingStore.get(t);if(a?.cwd){if(await this.resolveCwdForBinding(a.cwd)!==r){i(),s("failed",{msg:`Session already bound to ${a.cwd}. Rebinding is not allowed.`,code:p.rebindForbidden});return}i(),s("responded",{msg:`Session already bound to ${a.cwd}`});return}this.bindingStore.set(t,r),this.sessionBindings.set(t,r),await this.bindSessionForPool(t,r),await this.deferredMgr.release(t,this.deferredCallbacks(),{announceComposing:!1}),this.aibotHandle.sendUpdateBindingCard({session_id:t,worker_status:"ready",cwd:r}),i(),s("responded",{msg:`Session bound to ${r}`})}catch(r){i(),s("failed",{code:p.invalidCwd,msg:r instanceof Error?r.message:String(r)})}}async handleCodeWhaleSessionControlLocalActionOpen(e){const n=e.params??{},t=String(n.session_id??"").trim(),o=String(n.cwd??"").trim(),i=String(n.agent_session_id??"").trim(),s=(r,a,d,c)=>{this.aibotHandle.sendLocalActionResult({action_id:e.action_id,status:r,...a!==void 0?{result:a}:{},...d?{error_code:d}:{},...c?{error_msg:c}:{}})};if(!t||!o){s("failed",void 0,p.cwdRequired,"session cwd is required");return}try{const r=await this.resolveCwdForBinding(o);this.ensureImportedAgentSession(i,r);const a=this.bindingStore.get(t);if(a?.cwd){const d=await this.resolveCwdForBinding(a.cwd);if(d!==r){s("failed",void 0,p.rebindForbidden,`Session already bound to ${a.cwd}`);return}this.setResolvedAgentSessionId(t,i),s("ok",{outcome:"opened",binding:this.buildOpenedBindingResult(t,d)});return}this.bindingStore.set(t,r),this.setResolvedAgentSessionId(t,i),this.sessionBindings.set(t,r),await this.bindSessionForPool(t,r),await this.deferredMgr.release(t,this.deferredCallbacks(),{announceComposing:!1}),this.aibotHandle.sendUpdateBindingCard({session_id:t,worker_status:"ready",cwd:r}),s("ok",{outcome:"opened",binding:this.buildOpenedBindingResult(t,r)})}catch(r){s("failed",void 0,r?.sessionControlErrorCode??p.invalidCwd,r instanceof Error?r.message:String(r))}}async handleDeepSeekSessionControlLocalActionOpen(e){const n=e.params??{},t=String(n.session_id??"").trim(),o=String(n.cwd??"").trim(),i=String(n.agent_session_id??"").trim(),s=(r,a,d,c)=>{this.aibotHandle.sendLocalActionResult({action_id:e.action_id,status:r,...a!==void 0?{result:a}:{},...d?{error_code:d}:{},...c?{error_msg:c}:{}})};if(!t||!o){s("failed",void 0,p.cwdRequired,"session cwd is required");return}try{const r=await this.resolveCwdForBinding(o);this.ensureImportedAgentSession(i,r);const a=this.bindingStore.get(t);if(a?.cwd){const d=await this.resolveCwdForBinding(a.cwd);if(d!==r){s("failed",void 0,p.rebindForbidden,`Session already bound to ${a.cwd}`);return}this.setResolvedAgentSessionId(t,i),this.sessionBindings.set(t,d),await this.deferredMgr.release(t,this.deferredCallbacks()),this.aibotHandle.sendUpdateBindingCard({session_id:t,worker_status:"ready",cwd:d,meta:this.buildDshOpenedToolbarMeta(t)}),s("ok",{outcome:"opened",binding:this.buildOpenedBindingResult(t,d)});return}this.bindingStore.set(t,r),this.setResolvedAgentSessionId(t,i),this.sessionBindings.set(t,r),await this.deferredMgr.release(t,this.deferredCallbacks()),this.aibotHandle.sendUpdateBindingCard({session_id:t,worker_status:"ready",cwd:r,meta:this.buildDshOpenedToolbarMeta(t)}),s("ok",{outcome:"opened",binding:this.buildOpenedBindingResult(t,r)})}catch(r){s("failed",void 0,r?.sessionControlErrorCode??r?.cwdErrorCode??p.invalidCwd,r instanceof Error?r.message:String(r))}}normalizeClaudeModeId(e){return String(e??"").trim().toLowerCase()===y.approval?y.approval:y.fullAuto}handleExecCommandOptions(e,n,t){const o=Array.isArray(t?.options)?t.options:[];if(o.length===0)return;const s=this.bindingStore.get(e)?.cwd??"",r={};if(n==="model"){r.available_models=o.map(d=>({id:d.id,displayName:d.label}));const a=o.find(d=>d.current);a&&(r.model_id=a.id)}else if(n==="mode"){r.available_modes=o.map(d=>({id:d.id,displayName:d.label}));const a=o.find(d=>d.current);a&&(r.mode_id=a.id)}else r[`${n}_options`]=o;this.aibotHandle.sendUpdateBindingCard({session_id:e,worker_status:"ready",cwd:s,meta:r})}resolveSessionModelId(e){const n=this.bindingStore.getModelId(e);if(n)return n;const t=String(this.globalConfigStore?.get(this.name)?.modelId??"").trim();if(t)return this.bindingStore.setModelId(e,t),t}resolveSessionModeId(e){const n=this.bindingStore.getModeId(e);if(n)return n;const t=String(this.globalConfigStore?.get(this.name)?.modeId??"").trim();if(t)return this.bindingStore.setModeId(e,t),t}resolveClaudeSessionEffort(e){const n=this.bindingStore.getEffort(e);if(n)return n;if(this.bindingStore.get(e)?.claudeSessionId)return;const o=J(this.globalConfigStore?.get(this.name)?.effort);if(o)return this.bindingStore.setEffort(e,o),o}resolveCodexSessionModelId(e){const n=this.bindingStore.getCodexModelId(e);if(n)return n;if(this.bindingStore.getCodexThreadId(e))return;const t=String(this.globalConfigStore?.get(this.name)?.codexModelId??"").trim();if(t)return this.bindingStore.setCodexModelId(e,t),t}resolveCodexNewSessionGlobalDefault(e,n,t){const o=String(n??"").trim();if(o&&!this.bindingStore.getCodexThreadId(e))return t(o),o}buildCursorToolbarMeta(e){const n=this.pool.getSlot(e);if(n?.adapter instanceof O)return n.adapter.getToolbarMeta(e);const t=this.bindingStore.getModelId(e)??"auto",o=this.bindingStore.getCursorModeId(e)??"full_auto";return{model_id:t,mode_id:o,currentModelId:t,currentModeId:o,available_models:[],available_modes:Pe.map(i=>({...i}))}}buildAgyToolbarMeta(e){if(!this.bindingStore.get(e))return;const t=qe(this.config.agent.command),o=t.length>0?t[0].id:"";let i=this.resolveSessionModelId(e)||o;t.length>0&&!t.some(r=>r.id===i)&&(i=o);const s=Ue();return{model_id:i,currentModelId:i,available_models:t.map(r=>({id:r.id,displayName:r.displayName})),...s.plan!==void 0&&{plan:s.plan},...s.quota_exhausted!==void 0&&{quota_exhausted:s.quota_exhausted},...s.quota_reset_at!==void 0&&{quota_reset_at:s.quota_reset_at},...s.available_credits!==void 0&&{available_credits:s.available_credits}}}async handleAgySetModel(e,n){const t=e.params??{},o=String(t.model_id??t.modelId??"").trim();if(!n){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(!o){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(n);if(!i?.cwd){this.aibotHandle.sendLocalActionResult({action_id:e.action_id,status:"failed",error_code:p.bindingMissing,error_msg:"session binding was not found"});return}i.modelId!==o&&(this.bindingStore.setModelId(n,o),this.globalConfigStore?.set(this.name,{modelId:o})),await Fe().catch(()=>{}),this.aibotHandle.sendUpdateBindingCard({session_id:n,worker_status:"ready",cwd:i.cwd,meta:this.buildAgyToolbarMeta(n)}),this.aibotHandle.sendLocalActionResult({action_id:e.action_id,status:"ok",result:{outcome:"model_set",model_id:o,binding:{cwd:i.cwd,model_id:o}}})}buildClaudeToolbarMeta(e){const n=this.bindingStore.get(e);if(!n)return;const t=Y(),o=t.length>0?t[0].id:"";let i=this.resolveSessionModelId(e)||o;t.length>0&&!t.some(g=>g.id===i)&&(i=o);const s=this.normalizeClaudeModeId(n.modeId);let r;try{const g=this.providerResolver?this.providerResolver():this.config.agent.provider;r=J(g?.effort)}catch{}const a=this.resolveClaudeSessionEffort(e)??r??"auto",d={model_id:i,mode_id:s,currentModelId:i,currentModeId:s,available_efforts:[...D],effort:a,reasoning_effort:a,available_models:t.map(g=>({id:g.id,displayName:g.displayName}))},c=this.pool.getSlot(e),l=c?.adapter instanceof I?c.adapter.getSessionState():null;(l?.rateLimits?.fiveHour||l?.rateLimits?.sevenDay)&&(this.cachedClaudeRateLimitState=l);const u=l??this.getFreshClaudeRateLimitState();if(u){const g=u.rateLimits;g&&(g.fiveHour||g.sevenDay)&&(d.rate_limits={...g.fiveHour?{fiveHour:g.fiveHour}:{},...g.sevenDay?{sevenDay:g.sevenDay}:{},sampledAt:u.sampledAt||Date.now()})}if(l){const g=l.contextWindow;g.usedPercentage!=null&&(d.context_window={usedPercentage:g.usedPercentage,remainingPercentage:g.remainingPercentage})}if(!d.rate_limits&&this.cachedProviderQuota?.success){this.isRateLimitsCacheFresh(this.cachedProviderQuotaSampledAtMs)||this.maybeQueryProviderQuota().catch(()=>{});const g=this.providerQuotaToRateLimits(this.cachedProviderQuota);g&&(d.rate_limits=g),h.info(this.name,`[toolbar-meta] provider quota fallback: hasCached=${!!this.cachedProviderQuota} fromProvider=${JSON.stringify(g)}`)}return d.rate_limits&&h.info(this.name,`[toolbar-meta] rate_limits included: ${JSON.stringify(d.rate_limits)}`),d}providerQuotaToCodexRateLimits(e){return Se(e,this.cachedProviderQuotaSampledAtMs??Date.now())}providerQuotaToRateLimits(e){return ve(e,this.cachedProviderQuotaSampledAtMs??Date.now())}async resolveCwdForBinding(e){const n=String(e??"").trim();if(process.platform!=="win32"&&(/^[a-zA-Z]:[\\/]/.test(n)||/^\\\\/.test(n))){const i=new Error(`Specified path is not valid on this host: ${n}`);throw i.cwdErrorCode=p.invalidCwd,i}const t=P.resolve(n);let o;try{o=await N(t)}catch(i){const s=String(i?.code??"");if(s==="ENOENT"){const r=new Error(`Specified path does not exist: ${t}`);throw r.cwdErrorCode=p.invalidCwd,r}if(s==="EACCES"||s==="EPERM"){const r=new Error("Specified path is not accessible.");throw r.cwdErrorCode=p.invalidCwd,r}throw i}if(!o.isDirectory()){const i=new Error("Specified path is not a directory.");throw i.cwdErrorCode=p.invalidCwd,i}try{return await Ae(t)}catch{return t}}getClaudeWorkerStatus(e){const n=this.pool.getSlot(e);return n?n.state==="starting"?"starting":n.state==="stopped"?"stopped":n.adapter.getStatus().busy?"busy":"ready":"stopped"}refreshClaudeWorkerStatusCard(e,n){const t=this.getClaudeWorkerStatus(e);return this.claudeWorkerStatus.set(e,t),this.aibotHandle.sendUpdateBindingCard({session_id:e,worker_status:t,cwd:n,meta:this.buildClaudeToolbarMeta(e)}),t}async handleSkillUploadLocalAction(e){const n=e.params??{},t=String(n.name??"").trim(),o=String(n.session_id??"").trim();if(!t){this.aibotHandle.sendLocalActionResult({action_id:e.action_id,status:"failed",error_code:"MISSING_SKILL_NAME",error_msg:"name is required"});return}const i=M(this.config.adapterType,this.config.aibot.clientType),s=this.bindingStore.get(o)?.cwd||this.sessionBindings.get(o)||void 0,r=i==="kiro"?s:s||process.cwd();try{await Xe(t,{mode:i,projectDir:r},{apiKey:this.config.aibot.apiKey,wsUrl:this.config.aibot.url}),this.aibotHandle.sendLocalActionResult({action_id:e.action_id,status:"ok",result:{name:t}})}catch(a){h.warn(this.name,`skill_upload failed name=${t}: ${a instanceof Error?a.message:String(a)}`),this.aibotHandle.sendLocalActionResult({action_id:e.action_id,status:"failed",error_code:"SKILL_UPLOAD_FAILED",error_msg:a instanceof Error?a.message:String(a)})}}async handleSkillEnableLocalAction(e){const n=e.params??{},t=String(n.name??"").trim(),o=String(n.session_id??"").trim(),i=String(n.scope??"").trim(),s=String(n.actor_id??"").trim(),r=n.force==="replace_link"||n.force==="replace_with_link"?n.force:void 0,a=M(this.config.adapterType,this.config.aibot.clientType),d=this.bindingStore.get(o)?.cwd||this.sessionBindings.get(o)||void 0;try{const c=await et({skillsDir:$.skills,mode:a,home:K(),cwd:d,env:this.skillLookupEnv()},{name:t,scope:i,actorId:s,force:r});h.info(this.name,`skill_enable name=${t} scope=${i} actor=${s||"-"} changed=${c.changed}`),this.aibotHandle.sendLocalActionResult({action_id:e.action_id,status:"ok",result:{name:c.name,scope:c.scope,path:c.path,changed:c.changed,enable_state:c.status,uninstallable:!0}})}catch(c){const l=c instanceof le?c.code:"SKILL_ENABLE_FAILED";h.warn(this.name,`skill_enable failed name=${t} scope=${i}: ${c instanceof Error?c.message:String(c)}`),this.aibotHandle.sendLocalActionResult({action_id:e.action_id,status:"failed",error_code:l,error_msg:c instanceof Error?c.message:String(c),result:{conflict_kind:l==="CONFLICT"||l==="NEEDS_FORCE"||l==="BLOCKED"?l.toLowerCase():void 0}})}this.forceRefreshSkills()}handleSkillRefreshLocalAction(e){const n=e.params??{},t=String(n.session_id??"").trim();if(this.forceRefreshSkills(t||void 0,{force:!0})){this.aibotHandle.sendLocalActionResult({action_id:e.action_id,status:"ok",result:{session_id:t||void 0}});return}h.warn(this.name,`skill_refresh produced no report session=${t||"-"}`),this.aibotHandle.sendLocalActionResult({action_id:e.action_id,status:"failed",error_code:"SKILL_REFRESH_FAILED",error_msg:"skill rescan produced no report (scan error or empty skill set)"})}async handleSkillDisableLocalAction(e){const n=e.params??{},t=String(n.name??"").trim(),o=String(n.session_id??"").trim(),i=String(n.scope??"").trim(),s=String(n.actor_id??"").trim(),r=M(this.config.adapterType,this.config.aibot.clientType),a=this.bindingStore.get(o)?.cwd||this.sessionBindings.get(o)||void 0;try{const d=await tt({skillsDir:$.skills,mode:r,home:K(),cwd:a,env:this.skillLookupEnv()},{name:t,scope:i,actorId:s});h.info(this.name,`skill_disable name=${t} scope=${i} actor=${s||"-"} removed=${d.removed}`),this.aibotHandle.sendLocalActionResult({action_id:e.action_id,status:"ok",result:{name:d.name,scope:d.scope,path:d.path,removed:d.removed,enable_state:"none",uninstallable:!1}})}catch(d){const c=d instanceof le?d.code:"SKILL_DISABLE_FAILED";h.warn(this.name,`skill_disable failed name=${t} scope=${i}: ${d instanceof Error?d.message:String(d)}`),this.aibotHandle.sendLocalActionResult({action_id:e.action_id,status:"failed",error_code:c,error_msg:d instanceof Error?d.message:String(d),result:{conflict_kind:c==="CONFLICT"||c==="BLOCKED"?c.toLowerCase():void 0}})}this.forceRefreshSkills()}computeSkillReport(e){const n=M(this.config.adapterType,this.config.aibot.clientType),t=n==="kiro"?e:e||process.cwd();let o;try{o=Je({mode:n,projectDir:t,env:this.skillLookupEnv()})}catch{return null}if(o.length===0)return null;const i=JSON.stringify(o.map(s=>`${s.source}:${s.name}`));return{skills:ce(o,$.skills),hash:i}}reportSessionSkills(e){const n=this.bindingStore.get(e)?.cwd||this.sessionBindings.get(e)||void 0,t=this.computeSkillReport(n);if(!t)return;const o=this.buildLibrarySkillsReport(n),i=ie(t.skills,o);if(!(t.hash===this.lastReportedSkillsHash&&i===this.lastReportedSkillsWireHash)){this.lastReportedSkillsHash=t.hash,this.lastReportedSkillsWireHash=i;try{this.aibotHandle.sendSkillsUpdate({skills:t.skills,library_skills:o})}catch{}}}skillsSyncGroupKey(){const e=String(this.config.aibot.clientType??"").trim().toLowerCase()||"unknown",n=String(this.config.aibot.sharedOwnerId??"").trim();return n?`shared:${n}:${e}`:`primary:${e}`}forceRefreshSkills(e,n){const t=e?.trim(),o=t||this.bindingStore.getMostRecentlyUpdatedSessionId({requireCwd:!0}),i=t?this.bindingStore.get(t)?.cwd||this.sessionBindings.get(t)||void 0:o?this.bindingStore.get(o)?.cwd:void 0,s=this.computeSkillReport(i);if(!s)return!1;const r=this.buildLibrarySkillsReport(i),a=ie(s.skills,r);if(!n?.force&&a===this.lastReportedSkillsWireHash)return this.lastReportedSkillsHash=s.hash,!0;this.lastReportedSkillsHash=s.hash,this.lastReportedSkillsWireHash=a;try{return this.aibotHandle.sendSkillsUpdate({skills:s.skills,library_skills:r,...n?.ownerLibrarySync?{owner_library_sync:!0}:{}}),!0}catch{return!1}}adoptSkillsWireHashFromDisk(){const e=this.bindingStore.getMostRecentlyUpdatedSessionId({requireCwd:!0}),n=e?this.bindingStore.get(e)?.cwd:void 0,t=this.computeSkillReport(n);if(!t)return;const o=this.buildLibrarySkillsReport(n);this.lastReportedSkillsHash=t.hash,this.lastReportedSkillsWireHash=ie(t.skills,o)}buildLibrarySkillsReport(e){const n=M(this.config.adapterType,this.config.aibot.clientType);return Ze({skillsDir:$.skills,mode:n,home:K(),cwd:e,env:this.skillLookupEnv()})}skillLookupEnv(){return{...process.env,...this.config.agent.env}}async ensureSlotStarted(e,n=6e4){const t=this.pool.getOrCreateSlot(e);if(!t)throw new Error("Failed to allocate session slot");t.startPromise&&(h.info(this.name,`ensureSlotStarted: awaiting startPromise for session=${e}`),await Promise.race([t.startPromise,new Promise((o,i)=>setTimeout(()=>i(new Error(`ensureSlotStarted timeout (${n}ms) session=${e}`)),n))]),h.info(this.name,`ensureSlotStarted: startPromise resolved for session=${e}`))}resolveOrphanTitle(e){if(e){if(this.reasonixTitleScan){const n=wt(e);if(!n)return;const t=this.reasonixTitleScan.get().filter(o=>o.stamp===n);return t.length===1?t[0].title:void 0}if((this.config.adapterType??"acp")==="cursor")return this.sessionScanCache.get().find(t=>t.sessionId===e)?.title}}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;case"deepseek-harness":return e.dshProfileSessionId??e.acpSessionId;default:return e.acpSessionId}}providerKeyForAdapter(){const e=this.config.adapterType??"acp";switch(e){case"claude":case"codex":case"pi":case"codewhale":case"deepseek-harness":return e;default:return"acp"}}setResolvedAgentSessionId(e,n){const t=String(e??"").trim(),o=String(n??"").trim();if(!t||!o)return;switch(this.config.adapterType??"acp"){case"claude":this.bindingStore.setClaudeSessionId(t,o);break;case"codex":this.bindingStore.setCodexThreadId(t,o);break;case"pi":this.bindingStore.setPiSessionPath(t,o);break;case"codewhale":this.bindingStore.setCodeWhaleThreadId(t,o);break;case"agy":this.bindingStore.setAgyConversationId(t,o);break;default:this.bindingStore.setAcpSessionId(t,o);break}this.sessionScanCache.invalidate()}normalizePathForCompare(e){const n=String(e??"").trim();if(!n)return"";const t=P.resolve(n);return process.platform==="win32"?t.toLowerCase():t}ensureImportedAgentSession(e,n){const t=String(e??"").trim();if(!t)return;const o=this.normalizePathForCompare(n);let i="";const s=this.config.adapterType??"acp";if(s==="codex"?i=this.sessionScanCache.get().find(d=>d.threadId===t)?.cwd??"":s==="claude"?i=this.sessionScanCache.get().find(d=>d.sessionId===t)?.cwd??"":s==="cursor"?i=this.sessionScanCache.get().find(d=>d.sessionId===t)?.cwd??"":s==="acp"?i=this.sessionScanCache.get().find(d=>d.sessionId===t)?.cwd??"":s==="deepseek-harness"&&(i=this.sessionScanCache.get().find(d=>d.sessionId===t)?.cwd??""),!i){for(const[,a]of this.bindingStore.entries())if(this.resolveAgentSessionId(a)===t){i=a.cwd??"";break}}if(!i){const a=new Error(`agent session not found: ${t}`);throw a.sessionControlErrorCode=p.invalidAgentSession,a}const r=this.normalizePathForCompare(i);if(r&&o&&r!==o){const a=new Error(`agent session cwd mismatch: expected ${n}, got ${i}`);throw a.sessionControlErrorCode=p.invalidAgentSession,a}}buildOpenedBindingResult(e,n,t="ready"){const o=this.bindingStore.get(e),i=o?String(this.resolveAgentSessionId(o)??"").trim():"",s={aibotSessionId:e,providerKey:this.providerKeyForAdapter(),cwd:n,workerStatus:t};return i&&(s.bindingId=i,s.agentSessionId=i),(this.config.adapterType??"acp")==="deepseek-harness"&&Object.assign(s,this.buildDshOpenedToolbarMeta(e)),s}buildDshOpenedToolbarMeta(e){const n=this.globalConfigStore?.get(this.name),t=this.bindingStore.get(e),o=this.config.adapterOptions??{},i=typeof o.dshHome=="string"?o.dshHome:void 0;if(!this.bindingStore.getDshAgentPreset(e)){const A=De(n?.dshAgentPreset,Le);this.bindingStore.setDshAgentPreset(e,A)}const s=this.bindingStore.getDshSelectedProfile(e),r=Oe({binding:s,global:n?.dshProfile,adapter:typeof o.dshProfile=="string"?o.dshProfile:void 0});s||this.bindingStore.setDshSelectedProfile(e,r);const a=this.dshCatalogDataRoot(),d=this.bindingStore.getDshSettings(e),c=d?.providerId??n?.dshProviderId,l=Qe({dataRoot:a,providerId:c}),u=[d?.modelId,n?.dshModelId,l.models[0]?.id].find(A=>!!A&&l.models.some(b=>b.id===A))??l.models[0]?.id,g=ae(t?.dshModeId,n?.dshModeId),m={};!t?.dshProviderId&&l.providerId&&(m.providerId=l.providerId),!t?.dshModelId&&u&&(m.modelId=u),t?.dshModeId||(m.modeId=g),Object.keys(m).length>0&&this.bindingStore.updateDshSettings(e,m);const f=this.bindingStore.getDshSettings(e);let S=[],_=!1;try{S=xe({dshHome:i,profileName:r,enabledPluginIds:this.bindingStore.getDshEnabledPlugins(e)}),_=!1}catch{}return He({agentPreset:f?.agentPreset,agentPresetLocked:this.bindingStore.isDshAgentPresetLocked(e),profileName:r,profiles:Ie({dshHome:i}),profileLocked:this.bindingStore.isDshProfileLocked(e),plugins:S,pluginRestartRequired:_,providerId:f?.providerId??l.providerId,modelId:f?.modelId??u,modeId:ae(this.bindingStore.get(e)?.dshModeId,n?.dshModeId),providers:l.providers,models:l.models})}dshCatalogDataRoot(){const e=G("sha256").update(this.config.aibot.agentId).digest("hex").slice(0,24);return P.join($.data,"deepseek-harness",e)}hasDiskScanner(){const e=this.config.adapterType??"acp";return e==="codex"||e==="claude"||e==="acp"||e==="deepseek-harness"}async handleListSessionsTextCommand(e){this.aibotHandle.sendEventAck({event_id:e.event_id,session_id:e.session_id,received_at:Date.now()});const n=this.config.adapterType??"acp",t=new Map;for(const a of this.pool.getAllSlots())t.set(a.sessionId,a);const o=Array.from(this.bindingStore.entries()),i=new Map;for(const[a,d]of o){const c=this.resolveAgentSessionId(d);if(c){const l=t.get(a),u=l?l.adapter.getStatus().busy?"busy":"ready":"inactive";i.set(c,{aibotSessionId:a,workerStatus:u})}}const s=[],r=new Set;if(n==="codex"){const a=this.sessionScanCache.get();for(const d of a){r.add(d.threadId);const c=i.get(d.threadId);d.title&&s.push(` Title: ${d.title}`),s.push(` Agent: ${d.threadId}`),c&&s.push(` AIBot: ${c.aibotSessionId}`),s.push(` CWD: ${d.cwd||"-"}`),s.push(` State: ${c?.workerStatus??(d.archived?"archived":"inactive")}`),s.push(` Created: ${new Date(d.createdAt).toISOString()}`),s.push(` Updated: ${new Date(d.updatedAt).toISOString()}`),s.push("---")}}else if(n==="claude"){const a=this.sessionScanCache.get();for(const d of a){r.add(d.sessionId);const c=i.get(d.sessionId);d.title&&s.push(` Title: ${d.title}`),s.push(` Agent: ${d.sessionId}`),c&&s.push(` AIBot: ${c.aibotSessionId}`),s.push(` CWD: ${d.cwd||"-"}`),s.push(` State: ${c?.workerStatus??"inactive"}`),s.push(` Updated: ${new Date(d.updatedAt).toISOString()}`),s.push("---")}}else if(n==="acp"){const a=this.sessionScanCache.get();for(const d of a){r.add(d.sessionId);const c=i.get(d.sessionId);d.title&&s.push(` Title: ${d.title}`),s.push(` Agent: ${d.sessionId}`),c&&s.push(` AIBot: ${c.aibotSessionId}`),s.push(` CWD: ${d.cwd||"-"}`),s.push(` State: ${c?.workerStatus??"inactive"}`),s.push(` Updated: ${new Date(d.updatedAt).toISOString()}`),s.push("---")}}else if(n==="deepseek-harness"){const a=this.sessionScanCache.get();for(const d of a){r.add(d.sessionId);const c=i.get(d.sessionId);d.title&&s.push(` Title: ${d.title}`),s.push(` Agent: ${d.sessionId}`),c&&s.push(` AIBot: ${c.aibotSessionId}`),s.push(` CWD: ${d.cwd||"-"}`),s.push(` State: ${c?.workerStatus??"inactive"}`),d.createdAt&&s.push(` Created: ${new Date(d.createdAt).toISOString()}`),s.push(` Updated: ${new Date(d.updatedAt).toISOString()}`),s.push("---")}}else if(n==="pi"){const a=this.sessionScanCache.get();for(const d of a){r.add(d.sessionPath);const c=i.get(d.sessionPath);d.title&&s.push(` Title: ${d.title}`),s.push(` Agent: ${d.sessionPath}`),c&&s.push(` AIBot: ${c.aibotSessionId}`),s.push(` CWD: ${d.cwd||"-"}`),s.push(` State: ${c?.workerStatus??"inactive"}`),s.push(` Updated: ${new Date(d.updatedAt).toISOString()}`),s.push("---")}}else if(n==="codewhale"||n==="opencode"){const a=this.sessionScanCache.get();for(const d of a){r.add(d.sessionId);const c=i.get(d.sessionId);d.title&&s.push(` Title: ${d.title}`),s.push(` Agent: ${d.sessionId}`),c&&s.push(` AIBot: ${c.aibotSessionId}`),s.push(` CWD: ${d.cwd||"-"}`),s.push(` State: ${c?.workerStatus??"inactive"}`),s.push(` Updated: ${new Date(d.updatedAt).toISOString()}`),s.push("---")}}for(const[a,d]of o){const c=this.resolveAgentSessionId(d);if(c&&r.has(c))continue;const l=t.get(a),u=l?l.adapter.getStatus().busy?"busy":"ready":"closed",g=this.resolveOrphanTitle(c)??(c?c.slice(0,8)+"\u2026":a.slice(0,8)+"\u2026");s.push(` Title: ${g}`),s.push(` AIBot: ${a}`),c&&s.push(` Agent: ${c}`),s.push(` CWD: ${d.cwd??"-"}`),s.push(` State: ${u}`),s.push(` Updated: ${new Date(d.updatedAt).toISOString()}`),s.push("---")}if(s.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 (${s.filter(a=>a==="---").length}):
10
10
  ${s.join(`
@@ -1,2 +1,2 @@
1
1
  import{EventEmitter as A}from"node:events";import{randomUUID as T}from"node:crypto";import $ from"node:os";import y from"ws";import{log as m}from"../log/index.js";import{getMachineName as S}from"../util/index.js";import{detectTailnetIPv4 as I,ensureServerAndGetPort as P,getFileServerHttpsPort as F}from"../files/file-serve.js";import{TerminalOutbox as M}from"../persistence/terminal-outbox.js";import{TerminalCommitTokenStore as q}from"../persistence/terminal-commit-token-store.js";import{StopResultOutbox as B}from"../persistence/stop-result-outbox.js";import{AUTH_CODE_AGENT_DELETED as w,KICKED_REASON_AGENT_DELETED as R,AgentDeletedError as C,RequestTimeoutError as N}from"./errors.js";function D(E){let e="",t=0,n=!1,s=!1;for(;t<E.length;){const i=E[t];if(n){e+=i,s?s=!1:i==="\\"?s=!0:i==='"'&&(n=!1),t++;continue}if(i==='"'){n=!0,e+=i,t++;continue}if(i==="-"||i>="0"&&i<="9"){const r=t;i==="-"&&t++;const u=t;for(;t<E.length&&E[t]>="0"&&E[t]<="9";)t++;let o=!1;if(E[t]===".")for(o=!0,t++;t<E.length&&E[t]>="0"&&E[t]<="9";)t++;if(E[t]==="e"||E[t]==="E")for(o=!0,t++,(E[t]==="+"||E[t]==="-")&&t++;t<E.length&&E[t]>="0"&&E[t]<="9";)t++;const f=E.slice(r,t),l=t-u;e+=!o&&l>=16?`"${f}"`:f;continue}e+=i,t++}return e}function x(E){const e=[...E??["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.includes("terminal_commit_v1")||e.push("terminal_commit_v1"),e.includes("audit_replay_v2")||e.push("audit_replay_v2"),e.includes("session_send_quote_v1")||e.push("session_send_quote_v1"),e}function W(E){const e=[...E??["exec_approve","exec_reject"]];return e.includes("apply_relay_state")||e.push("apply_relay_state"),e}const U="aibot-agent-api-v1",L=1;class p extends A{static DROPPABLE_COMMANDS=new Set(["update_binding_card"]);static BUFFER_OVERFLOW_RETAIN_COMMANDS=new Set(["event_result","codex_event","client_stream_chunk","send_msg","audit_state"]);static MAX_OUTBOUND_BUFFER_SIZE=1e3;static MAX_OUTBOUND_BUFFER_HARD_SIZE=1200;static BACKPRESSURE_THRESHOLD=64*1024;static TERMINAL_RETRY_DELAY_MS=15e3;static MAX_TOKENIZED_TERMINAL_REJECTIONS=5;static EVENT_CORRELATION_TTL_MS=5*6e4;static MAX_EVENT_CORRELATIONS=1024;static MAX_BLOCKED_OUTPUT_EVENTS=4096;static MAX_COMMITTED_TERMINAL_EVENTS=4096;static OUTBOUND_WRITE_CALLBACK_TIMEOUT_MS=1e4;static MIN_REQUIRED_OUTPUT_ACK_TIMEOUT_MS=2e4;static ORDERED_OUTPUT_COMMANDS=new Set(["client_stream_chunk","send_msg","codex_event"]);ws=null;seq=0;heartbeatTimer=null;heartbeatSec=30;heartbeatFailures=0;static HEARTBEAT_MAX_FAILURES=2;connected=!1;reconnecting=!1;reconnectAttempts=0;reconnectRunGeneration=0;everConnected=!1;agentDeleted=!1;config;packetLog;pendingInvokes=new Map;eventCorrelations=new Map;clientMsgEventMap=new Map;eventCorrelationCleanupTimer=null;lastCorrelationPruneAt=0;pendingRequests=new Map;outboundBuffer=[];outboundEntryOrders=new WeakMap;nextOutboundEntryOrder=0;connectionGeneration=0;nextOutboundFlushGeneration=0;outboundFlushInFlight=null;pendingOutputWrites=new Map;pendingOutputSeqsByEvent=new Map;pendingAcceptedOutputs=new Map;pendingAcceptedOutputSeqsByEvent=new Map;pendingAcceptedSeqsByClientMsgId=new Map;nonReplayableOutputEventsBySocket=new Map;provisionalRespondedTerminals=new Map;outputIntegrityFailed=new Map;blockedOutputEvents=new Map;committedTerminalEvents=new Set;ackPolicy=null;negotiatedCapabilities=new Set;terminalOutbox;terminalCommitTokens;stopResultOutbox;terminalInFlight=new Set;terminalRetryTimers=new Map;terminalReplayAfterFlight=new Set;tokenizedTerminalRejections=new Map;stopResultInFlight=new Set;stopResultRetryTimers=new Map;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:x(e.capabilities),localActions:W(e.localActions),skills:e.skills,librarySkills:e.librarySkills,sharedOwnerId:e.sharedOwnerId,concurrency:e.concurrency,terminalOutboxPath:e.terminalOutboxPath,terminalCommitTokenStorePath:e.terminalCommitTokenStorePath,stopResultOutboxPath:e.stopResultOutboxPath},this.terminalOutbox=new M(this.config.terminalOutboxPath),this.terminalCommitTokens=new q(this.config.terminalCommitTokenStorePath??(this.config.terminalOutboxPath?`${this.config.terminalOutboxPath}.tokens`:void 0)),this.stopResultOutbox=new B(this.config.stopResultOutboxPath??(this.config.terminalOutboxPath?`${this.config.terminalOutboxPath}.stops`:void 0));for(const n of this.terminalOutbox.listPending())n.payload.code==="agent_output_integrity_failed"&&this.blockEventOutput(n.payload.event_id)}get isConnected(){return this.connected}async connect(){this.negotiatedCapabilities.clear();const e=this.ws,t=this.connectionGeneration;if(this.pendingRequests.size>0&&this.rejectAllPendingRequests("connection generation replaced"),e){this.connected=!1,this.stopHeartbeat(),this.clearTerminalRetryTimers(),this.clearStopResultRetryTimers();const o=this.outboundFlushInFlight;o&&o.socket===e&&o.connectionGeneration===t&&this.cancelOutboundFlush(o,"connection_lost","connection generation replaced"),this.failNonReplayableOutputsForSocket(e,"connection generation replaced"),this.abortPendingOutputWrites(!0,e,t,"connection generation replaced"),this.abortPendingAcceptedOutputs(!0,e,t),this.clearAllEventCorrelations(),this.connectionGeneration++;try{e.close(1012,"connection generation replaced")}catch{}this.ws===e&&(this.ws=null)}else this.outboundFlushInFlight&&this.cancelOutboundFlush(this.outboundFlushInFlight,"discard","detached connection generation replaced");const n=++this.connectionGeneration;let s,i,r;const u=(async()=>{try{if(s=await I(),s!==void 0)try{i=await P(s);const o=F();o>0&&(r=o)}catch(o){m.warn("aibot",`file server pre-start failed: ${o}`)}}catch(o){m.warn("aibot",`tailnet detect failed: ${o}`)}})();return new Promise((o,f)=>{const l=new y(this.config.url);this.ws=l;const d=setTimeout(()=>{if(!this.isConnectionCurrent(l,n))return;const c=this.pendingRequests.get(_);c&&(this.pendingRequests.delete(_),clearTimeout(c.timer),c.reject(new Error("Auth timeout: no auth_ack received within 15s"))),this.cleanupSocket(l,n)},15e3),_=++this.seq,b=setTimeout(()=>{if(!this.isConnectionCurrent(l,n))return;const c=this.pendingRequests.get(_);c&&(this.pendingRequests.delete(_),c.reject(new Error("Auth request timeout"))),this.cleanupSocket(l,n)},15e3);this.pendingRequests.set(_,{expected:["auth_ack"],resolve:c=>{clearTimeout(d);const a=c.payload;if(a.code===0){this.negotiatedCapabilities=new Set(Array.isArray(a.supported_capabilities)?a.supported_capabilities:[]),this.connected=!0,this.everConnected=!0,this.reconnectAttempts=0,a.heartbeat_sec&&(this.heartbeatSec=a.heartbeat_sec),a.ack_policy&&(this.ackPolicy=a.ack_policy,m.info("aibot",`ack_policy received: push_ack_timeout_ms=${a.ack_policy.push_ack_timeout_ms??"default"} max_retries=${a.ack_policy.max_retries??"default"} timeout_action=${a.ack_policy.timeout_action??"default"}`)),this.startHeartbeat();const h=this.flushOutboundBuffer(l,n);this.emit("auth",a),o(a),h.then(g=>{this.isConnectionCurrent(l,n)&&(g?(this.replayTerminalOutbox(),this.replayStopResultOutbox()):this.reconnectAfterOutboundFlushFailure(l,n))})}else a.code===w?(this.agentDeleted=!0,f(new C(`Agent deleted: code=${a.code} msg=${a.msg}`))):f(new Error(`Auth failed: code=${a.code} msg=${a.msg}`))},reject:c=>{clearTimeout(d),f(c)},timer:b}),l.on("open",async()=>{if(await u,!this.isConnectionCurrent(l,n))return;const c={agent_id:this.config.agentId,api_key:this.config.apiKey,client_type:this.config.clientType,protocol_version:U,contract_version:L,capabilities:this.config.capabilities??[],local_actions:this.config.localActions,skills:this.config.skills,library_skills:this.config.librarySkills};this.config.sharedOwnerId&&(c.shared_owner_id=this.config.sharedOwnerId),this.config.clientVersion&&(c.client="grix-connector",c.client_version=this.config.clientVersion,c.host_type=this.config.clientType,c.host_version=this.config.clientVersion),this.config.adapterHint&&(c.adapter_hint=this.config.adapterHint),c.host_meta={hostname:S(),platform:$.platform(),arch:$.arch(),os_release:$.release(),...s!==void 0&&{tailnet_ip:s},...i!==void 0&&i>0&&{file_server_port:i},...r!==void 0&&r>0&&{file_server_https_port:r}},this.config.concurrency&&(c.concurrency=this.config.concurrency),this.sendPacket("auth",c,_)}),l.on("message",c=>{if(!this.isConnectionCurrent(l,n))return;let a;try{a=JSON.parse(D(c.toString()))}catch{return}try{this.handlePacket(a)}catch(h){this.emitClientError(new Error(`handlePacket error: ${h}`))}}),l.on("close",(c,a)=>{if(!this.isConnectionCurrent(l,n))return;const h=this.everConnected&&!this.agentDeleted;this.connected=!1,this.negotiatedCapabilities.clear(),this.stopHeartbeat(),this.clearTerminalRetryTimers(),this.clearStopResultRetryTimers(),this.rejectAllPendingRequests("websocket closed");const g=`websocket closed code=${c} reason=${a.toString()||"<none>"}`,O=this.outboundFlushInFlight;O&&O.socket===l&&O.connectionGeneration===n&&this.cancelOutboundFlush(O,h?"connection_lost":"discard",g),h?this.failNonReplayableOutputsForSocket(l,g):this.nonReplayableOutputEventsBySocket.delete(l),this.abortPendingOutputWrites(h,l,n,h?g:void 0),this.abortPendingAcceptedOutputs(h,l,n),this.clearAllEventCorrelations(),this.ws===l&&(this.ws=null),this.connectionGeneration++,this.emit("close",c,a.toString());const k=h;m.info("aibot",`ws closed agent=${this.config.clientType}:${this.config.agentId} code=${c} reason=${a.toString()||"<none>"} everConnected=${this.everConnected} reconnecting=${this.reconnecting} agentDeleted=${this.agentDeleted} willReconnect=${k}`),k&&this.attemptReconnect()}),l.on("error",c=>{if(this.isConnectionCurrent(l,n)&&(this.emitClientError(c instanceof Error?c:new Error(String(c))),!this.connected)){const a=this.pendingRequests.get(_);a&&(this.pendingRequests.delete(_),clearTimeout(a.timer),a.reject(c instanceof Error?c:new Error(String(c))))}})})}handlePacket(e){if(this.packetLog?.logInboundPacket(e.cmd,e.seq,e.payload),this.handleCorrelatedSendResponse(e),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":{const t=e.payload;if(!this.captureInboundTerminalCommitToken(t.event_id,t.terminal_commit_token))break;this.emit("event",t);break}case"local_action":{this.emit("localAction",e.payload);break}case"event_stop":{const t=e.payload;if(!this.captureInboundTerminalCommitToken(t.event_id,t.terminal_commit_token))break;this.emit("stop",t);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"event_hold":{this.emit("eventHold",e.payload);break}case"queue_edit":{this.emit("queueEdit",e.payload);break}case"control_share_set":{this.emit("shareSet",e.payload);break}case"agent_profile_push":{this.emit("profilePush",e.payload);break}case"skill_sync":{this.emit("skillSync",e.payload);break}case"kicked":{const t=e.payload;this.emit("kicked",t),this.connected=!1,this.negotiatedCapabilities.clear(),this.stopHeartbeat(),this.clearTerminalRetryTimers(),this.clearStopResultRetryTimers(),this.rejectAllPendingRequests("kicked");const n=this.ws,s=this.connectionGeneration,i=this.outboundFlushInFlight;if(i&&n&&i.socket===n&&i.connectionGeneration===this.connectionGeneration&&this.cancelOutboundFlush(i,t?.reason===R?"discard":"connection_lost",`kicked reason=${t?.reason??"<none>"}`),t?.reason!==R&&n&&this.failNonReplayableOutputsForSocket(n,`kicked reason=${t?.reason??"<none>"}`),this.abortPendingOutputWrites(t?.reason!==R,n??void 0,s,t?.reason!==R?`kicked reason=${t?.reason??"<none>"}`:void 0),this.abortPendingAcceptedOutputs(t?.reason!==R,n??void 0,s),this.clearAllEventCorrelations(),this.connectionGeneration++,t?.reason===R){if(this.agentDeleted=!0,this.reconnecting=!1,this.outboundBuffer.length=0,this.outputIntegrityFailed.clear(),this.nonReplayableOutputEventsBySocket.clear(),this.ws){try{this.ws.close(4001,"kicked")}catch{}this.ws=null}m.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===n&&(this.ws=null)}this.attemptReconnect();break}case"error":{const t=e.payload,n=[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}${n?` ${n}`:""}`));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":break;case"local_action_ack":break;default:break}}captureInboundTerminalCommitToken(e,t){const n=t?.trim();if(!n)return!0;if(!e?.trim())return this.rejectInboundTerminalCommitToken("tokenized event is missing event_id"),!1;if(!this.negotiatedCapabilities.has("terminal_commit_v1"))return this.rejectInboundTerminalCommitToken(`tokenized event received without terminal_commit_v1 negotiation event=${e}`),!1;try{return this.terminalCommitTokens.register(e,n),!0}catch(s){return this.rejectInboundTerminalCommitToken(`terminal commit token persist failed event=${e}: ${s instanceof Error?s.message:s}`),!1}}rejectInboundTerminalCommitToken(e){this.emitClientError(new Error(e));const t=this.ws;if(t)try{t.close(1011,"terminal commit token persistence failed")}catch{}}withTerminalCommitToken(e){const t=e.event_id.trim();if(!t)throw new Error("terminal event_result is missing event_id");const n=e.terminal_commit_token?.trim(),s=this.terminalCommitTokens.get(t);if(n&&s&&n!==s)throw new Error(`terminal commit token mismatch for event=${t}`);n&&!s&&this.terminalCommitTokens.register(t,n);const i=s??n;return i?{...e,event_id:t,terminal_commit_token:i}:{...e,event_id:t}}sendEventAck(e){this.sendPacket("event_ack",e)||m.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&&(m.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){const t={...e,client_msg_id:e.client_msg_id?.trim()||T()};this.sendPacket("send_msg",t)||m.warn("aibot",`send_msg NOT sent (ws not open) event=${t.event_id??""} session=${t.session_id??""} ws=${this.ws?`state=${this.ws.readyState}`:"null"}`)}editMsg(e){this.sendPacket("edit_msg",e)}sendEventResult(e){try{e=this.withTerminalCommitToken(e)}catch(o){this.emitClientError(new Error(`event_result token validation failed: event=${e.event_id}: ${o instanceof Error?o.message:o}`));return}const t=this.terminalOutbox.get(e.event_id);let n,s=this.outputIntegrityFailed.get(e.event_id);const i=t;if(!s&&i?.payload.code==="agent_output_integrity_failed"&&(s={reason:i.payload.msg??"required agent output was discarded before delivery",terminalObserved:!1,terminalSettled:!1},this.outputIntegrityFailed.set(e.event_id,s)),s){if(s.terminalObserved=!0,this.provisionalRespondedTerminals.delete(e.event_id),i&&i.payload.code!=="agent_output_integrity_failed"&&!this.canReplaceUnsentOutputGuard(i)){this.scheduleTerminalDelivery(e.event_id,{ignoreNotBefore:!0});return}if(i?.payload.code==="agent_output_integrity_failed"){this.scheduleTerminalDelivery(e.event_id,{ignoreNotBefore:!0});return}if(s.terminalSettled||this.committedTerminalEvents.has(e.event_id)&&!i){this.outputIntegrityFailed.delete(e.event_id);return}try{n=this.terminalOutbox.enqueue(this.makeOutputIntegrityFailurePayload(e.event_id,s.reason))}catch(o){this.emitClientError(new Error(`output-integrity terminal persist failed: event=${e.event_id}: ${o instanceof Error?o.message:o}`));return}this.scheduleTerminalDelivery(n.payload.event_id,{ignoreNotBefore:!0});return}if(this.committedTerminalEvents.has(e.event_id)&&!t){m.info("aibot",`ignored duplicate terminal after committed ACK event=${e.event_id} status=${e.status}`);return}if(t){if(!(this.canReplaceUnsentOutputGuard(t)&&(e.status==="canceled"||e.status==="failed"))){m.info("aibot",`preserving first durable terminal event=${e.event_id} existing_status=${t.payload.status} ignored_status=${e.status}`),this.scheduleTerminalDelivery(e.event_id,{ignoreNotBefore:!0});return}this.provisionalRespondedTerminals.delete(e.event_id)}if(e.status==="responded"&&this.hasNonReplayableOutput(e.event_id)&&!this.hasPendingOutputAcceptanceFence(e.event_id)){this.markOutputIntegrityFailed(e.event_id,"terminal requested before a complete output acceptance fence");const o=this.outputIntegrityFailed.get(e.event_id);o&&(o.terminalObserved=!0);return}const r=e.status==="responded"&&this.hasUnconfirmedEventOutput(e.event_id),u=r?{...e,status:"failed",code:"agent_output_unconfirmed",msg:"required agent output was not durably confirmed before connector restart",updated_at:Date.now()}:e;r?this.provisionalRespondedTerminals.set(e.event_id,{...e}):this.provisionalRespondedTerminals.delete(e.event_id);try{n=this.terminalOutbox.enqueue(u)}catch(o){this.provisionalRespondedTerminals.delete(e.event_id),this.emitClientError(new Error(`event_result outbox persist failed: event=${e.event_id} status=${e.status}: ${o instanceof Error?o.message:o}`));return}this.scheduleTerminalDelivery(n.payload.event_id,{ignoreNotBefore:!0})}sendLocalActionResult(e){this.sendPacket("local_action_result",e)}sendEventStopAck(e){this.sendPacket("event_stop_ack",e)}sendEventStopResult(e){const t=e.event_id?.trim(),n=e.terminal_commit_token?.trim(),s=t?this.terminalCommitTokens.get(t):void 0;if(n&&s&&n!==s){this.emitClientError(new Error(`event_stop_result token mismatch event=${t??""}`));return}const i=s??n;if(t&&i&&(e.status==="stopped"||e.status==="already_finished")){try{const r=this.stopResultOutbox.enqueue({...e,event_id:t,terminal_commit_token:i});this.scheduleStopResultDelivery(r,{ignoreNotBefore:!0})}catch(r){this.emitClientError(new Error(`event_stop_result outbox persist failed: event=${t} stop=${e.stop_id}: ${r instanceof Error?r.message:r}`))}return}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)}sendAuditState(e){this.sendPacket("audit_state",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)}sendEventHoldResult(e){this.sendPacket("event_hold_result",e)}sendQueueEditResult(e){this.sendPacket("queue_edit_result",e)}sendQueueSnapshot(e){this.sendPacket("queue_snapshot",e)}agentInvoke(e,t,n=15e3){return new Promise((s,i)=>{const r=T(),u=Math.max(1e3,Math.min(n,6e4)),o=setTimeout(()=>{this.pendingInvokes.delete(r),i(new Error(`agent_invoke timeout: ${e}`))},u);this.pendingInvokes.set(r,{resolve:s,reject:i,timer:o}),this.sendPacket("agent_invoke",{invoke_id:r,action:e,params:t,timeout_ms:u})})}sendMcpFrame(e,t){this.sendPacket("mcp_frame",{session_id:e,frame:t})}async request(e,t,n){const s=this.outboundFlushInFlight;if(s){if(!await s.promise)throw new Error(`outbound flush failed before request: ${e}`);if(!this.isConnectionCurrent(s.socket,s.connectionGeneration))throw new Error(`connection changed while waiting for outbound flush: ${e}`)}if(!this.connected||!this.ws||this.ws.readyState!==y.OPEN)throw new Error(`send failed: ${e} (websocket not connected)`);return new Promise((i,r)=>{const u=++this.seq,o=setTimeout(()=>{this.pendingRequests.delete(u),r(new N(`request timeout: ${e} (expected ${n.expected.join("/")})`))},n.timeoutMs);this.pendingRequests.set(u,{expected:n.expected,resolve:i,reject:r,timer:o}),this.sendPacket(e,t,u)||(this.pendingRequests.delete(u),clearTimeout(o),r(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 relayCredentialRequest(e,t=15e3){return this.request("relay_credential_request",e,{expected:["relay_credential_result","error"],timeoutMs:t})}async relayStateSyncRequest(e,t=15e3){return this.request("relay_state_sync_request",e,{expected:["relay_state_sync_result","error"],timeoutMs:t})}sendRelayStateReport(e){this.sendPacket("relay_state_report",e)}async sendText(e,t=2e4){return this.request("send_msg",{msg_type:1,...e,client_msg_id:e.client_msg_id?.trim()||T()},{expected:["send_ack","send_nack","error"],timeoutMs:t})}async sendMedia(e,t=2e4){return this.request("send_msg",{...e,msg_type:2,client_msg_id:e.client_msg_id?.trim()||T()},{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,n=2e4){return this.request("delete_msg",{session_id:e,msg_id:t},{expected:["send_ack","send_nack","error"],timeoutMs:n})}async sendEventResultRequest(e,t=5e3){return this.request("event_result",e,{expected:["send_ack","send_nack","error"],timeoutMs:t})}disconnect(){m.info("aibot",`disconnect() agent=${this.config.clientType}:${this.config.agentId} wasConnected=${this.connected} reconnecting=${this.reconnecting} reconnectAttempts=${this.reconnectAttempts}`),this.connected=!1,this.negotiatedCapabilities.clear(),this.everConnected=!1,this.reconnecting=!1,this.reconnectRunGeneration++,this.reconnectAttempts=0,this.stopHeartbeat(),this.clearTerminalRetryTimers(),this.clearStopResultRetryTimers();const e=this.ws,t=this.connectionGeneration,n=this.outboundFlushInFlight;n&&this.cancelOutboundFlush(n,"discard","explicit disconnect"),this.connectionGeneration++,this.rejectAllPendingInvokes("disconnect"),this.rejectAllPendingRequests("disconnect"),this.abortPendingOutputWrites(!1,e??void 0,t),this.abortPendingAcceptedOutputs(!1,e??void 0,t),this.clearAllEventCorrelations(),this.outboundBuffer.length=0,this.provisionalRespondedTerminals.clear(),this.outputIntegrityFailed.clear(),this.nonReplayableOutputEventsBySocket.clear(),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;const e=++this.reconnectRunGeneration;m.info("aibot",`attemptReconnect start agent=${this.config.clientType}:${this.config.agentId} fromAttempts=${this.reconnectAttempts}`),this.emit("disconnected");let t=0;for(;this.reconnecting&&this.reconnectRunGeneration===e;){const n=Math.min(1e3*2**this.reconnectAttempts,3e4),s=Math.floor(n*.2*Math.random());if(this.reconnectAttempts++,await new Promise(u=>setTimeout(u,n+s)),!this.reconnecting||this.reconnectRunGeneration!==e)return;let i=null,r=this.connectionGeneration;try{const u=this.connect();if(i=this.ws,r=this.connectionGeneration,await u,this.reconnectRunGeneration!==e)return;if(!i||!this.isConnectionCurrent(i,r)){if(this.connected&&this.ws){this.reconnecting=!1;return}continue}const o=this.reconnectAttempts;this.reconnectAttempts=0,this.reconnecting=!1,m.info("aibot",`reconnect succeeded agent=${this.config.clientType}:${this.config.agentId} attempt=${o}`);return}catch(u){if(this.reconnectRunGeneration!==e)return;if(i&&!this.isConnectionCurrent(i,r)&&this.connected&&this.ws){this.reconnecting=!1;return}if(i&&this.isConnectionCurrent(i,r)){this.connectionGeneration++;try{i.close()}catch{}this.ws===i&&(this.ws=null)}const o=u instanceof Error?u.message:String(u);if(m.warn("aibot",`reconnect failed agent=${this.config.clientType}:${this.config.agentId} attempt=${this.reconnectAttempts} err=${o}`),u instanceof C){this.agentDeleted=!0,this.reconnecting=!1,m.error("aibot",`reconnect aborted: agent deleted on platform agent=${this.config.clientType}:${this.config.agentId}`),this.emit("agentDeleted",{source:"auth_ack",code:w});return}if(/Auth failed/i.test(o)){if(t++,t>=p.MAX_CONSECUTIVE_AUTH_FAILURES){this.reconnecting=!1,m.error("aibot",`reconnect giving up after ${t} consecutive auth failures agent=${this.config.clientType}:${this.config.agentId}`);return}}else t=0}}}sendPacket(e,t,n){const s=this.outboundEventId(e,t);if(s&&this.blockedOutputEvents.has(s))return this.packetLog?.logOutboundPacket(e,n??0,t,"dropped"),m.warn("aibot",`blocked late output after irreversible event failure event=${s} cmd=${e}`),!1;const i=n===void 0?this.createOutboundEntry(e,t):void 0;if(i&&this.outboundFlushInFlight&&!this.outboundFlushInFlight.finalized&&this.isConnectionCurrent(this.outboundFlushInFlight.socket,this.outboundFlushInFlight.connectionGeneration)&&!p.DROPPABLE_COMMANDS.has(e))return this.bufferOutboundEntry(i),!1;if(this.ws&&this.ws.readyState===y.OPEN&&(e==="auth"||this.connected)){const u=this.ws.bufferedAmount>p.BACKPRESSURE_THRESHOLD;if(!u||!p.DROPPABLE_COMMANDS.has(e)){if(u&&p.DROPPABLE_COMMANDS.has(e))return!1;const o=n??++this.seq,f={cmd:e,seq:o,payload:t};this.packetLog?.logOutboundPacket(e,o,t,"sent");const l=this.ws,d=this.connectionGeneration,_=s;_&&this.trackOutboundEventCorrelation(e,o,t),i&&_&&(this.registerPendingOutputWrite(o,_,i,l,d),this.isAckRequiredOutput(e,t)&&this.registerPendingAcceptedOutput(o,_,i,l,d));let b=!1;try{const c=l.readyState,a=l.bufferedAmount;let h=!1;return l.send(JSON.stringify(f),g=>{if(g&&!h&&(b=!0),e==="event_result"){const v=t;g?m.warn("aibot",`event_result ws send callback failed event=${v.event_id??""} status=${v.status??""} seq=${o} readyState=${c} bufferedAmount=${a} err=${g.message}`):m.info("aibot",`event_result ws send callback ok event=${v.event_id??""} status=${v.status??""} seq=${o} readyState=${c} bufferedAmount=${a}`)}else if(e==="client_stream_chunk"){const v=t;g?m.warn("aibot",`stream_chunk ws send failed event=${v.event_id??""} session=${v.session_id??""} seq=${o} chunk_seq=${v.chunk_seq??""} is_finish=${v.is_finish??""} readyState=${c} bufferedAmount=${a} err=${g.message}`):m.info("aibot",`stream_chunk ws send ok event=${v.event_id??""} session=${v.session_id??""} seq=${o} chunk_seq=${v.chunk_seq??""} is_finish=${v.is_finish??""} client_msg_id=${v.client_msg_id??""} quoted_message_id=${v.quoted_message_id??""} readyState=${c} bufferedAmount=${a}`)}else if(e==="event_ack"){const v=t;g?m.warn("aibot",`event_ack ws send failed event=${v.event_id??""} seq=${o} readyState=${c} bufferedAmount=${a} err=${g.message}`):m.info("aibot",`event_ack ws send ok event=${v.event_id??""} seq=${o} readyState=${c} bufferedAmount=${a}`)}else if(e==="send_msg"){const v=t;g?m.warn("aibot",`send_msg ws send failed event=${v.event_id??""} session=${v.session_id??""} seq=${o} readyState=${c} bufferedAmount=${a} err=${g.message}`):m.info("aibot",`send_msg ws send ok event=${v.event_id??""} session=${v.session_id??""} seq=${o} readyState=${c} bufferedAmount=${a}`)}else if(g){const v=t;m.warn("aibot",`${e} ws send failed seq=${o} session=${v.session_id??""} event=${v.event_id??""} client_msg_id=${v.client_msg_id??""} readyState=${c} bufferedAmount=${a} err=${g.message}`)}const O=this.pendingOutputWrites.get(o),k=!!(O&&O.socket===l&&O.connectionGeneration===d);if(!this.isConnectionCurrent(l,d)){k&&(this.settlePendingOutputWrite(o,!1,!1,l,d),this.settlePendingAcceptedOutput(o,"retry",!1,l,d));return}if(!(i&&_&&!k)){if(g)this.removeSeqFromEventCorrelation(o),_?(this.settlePendingOutputWrite(o,!1,!0,l,d),this.settlePendingAcceptedOutput(o,"retry",!1,l,d)):i&&this.bufferOutboundEntry(i),n!==void 0?this.rejectPendingRequestAfterWriteFailure(o,e,g):i&&this.reconnectAfterOutboundWriteFailure(l,`${e} callback failed`);else if(_){const v=k;this.settlePendingOutputWrite(o,!0,!1,l,d),v&&this.trackNonReplayableOutput(l,e,t,o)}}}),h=!0,!b}catch(c){return this.isConnectionCurrent(l,d)?(this.removeSeqFromEventCorrelation(o),_?(this.settlePendingOutputWrite(o,!1,!0,l,d),this.settlePendingAcceptedOutput(o,"retry",!1,l,d)):i&&this.bufferOutboundEntry(i),this.emitClientError(new Error(`sendPacket failed: ${c}`)),i&&this.reconnectAfterOutboundWriteFailure(l,`${e} send threw`),!1):(this.settlePendingOutputWrite(o,!1,!1,l,d),this.settlePendingAcceptedOutput(o,"retry",!1,l,d),!1)}}}if(p.DROPPABLE_COMMANDS.has(e))return this.packetLog?.logOutboundPacket(e,n??0,t,"dropped"),!1;if(n!==void 0)return this.packetLog?.logOutboundPacket(e,n,t,"dropped"),!1;const r=this.bufferOutboundEntry(i??this.createOutboundEntry(e,t));if(this.packetLog?.logOutboundPacket(e,n??0,t,r?"buffered":"dropped"),r&&e==="client_stream_chunk"){const u=t;m.info("aibot",`stream_chunk buffered (ws not open) event=${u.event_id??""} session=${u.session_id??""} chunk_seq=${u.chunk_seq??""} is_finish=${u.is_finish??""} ws=${this.ws?`state=${this.ws.readyState}`:"null"}`)}return!1}replayTerminalOutbox(){for(const e of this.terminalOutbox.listPending())this.scheduleTerminalDelivery(e.payload.event_id,{ignoreNotBefore:!0})}outboundEventId(e,t){if(!p.ORDERED_OUTPUT_COMMANDS.has(e)||!t||typeof t!="object")return"";const n=t.event_id;return typeof n=="string"?n.trim():""}blockEventOutput(e){const t=e.trim();if(t)for(this.blockedOutputEvents.delete(t),this.blockedOutputEvents.set(t,Date.now()),this.outboundBuffer=this.outboundBuffer.filter(n=>this.outboundEventId(n.cmd,n.payload)!==t);this.blockedOutputEvents.size>p.MAX_BLOCKED_OUTPUT_EVENTS;){const n=this.blockedOutputEvents.keys().next().value;if(!n)break;this.blockedOutputEvents.delete(n)}}isAckRequiredOutput(e,t){return e==="send_msg"?!0:e==="client_stream_chunk"&&!!t&&typeof t=="object"&&t.is_finish===!0}isNonReplayableOutput(e,t){return e==="codex_event"?!0:e==="client_stream_chunk"&&!!t&&typeof t=="object"&&t.is_finish!==!0}trackNonReplayableOutput(e,t,n,s){if(!this.isNonReplayableOutput(t,n))return;const i=this.outboundEventId(t,n);if(!i||this.outputIntegrityFailed.has(i)||this.blockedOutputEvents.has(i))return;let r=this.nonReplayableOutputEventsBySocket.get(e);r||(r=new Map,this.nonReplayableOutputEventsBySocket.set(e,r)),r.set(i,Math.max(r.get(i)??0,s))}clearNonReplayableOutputThrough(e,t,n){if(!e||n<=0)return;const s=this.nonReplayableOutputEventsBySocket.get(e);if(!s)return;const i=s.get(t);i===void 0||i>n||(s.delete(t),s.size===0&&this.nonReplayableOutputEventsBySocket.delete(e))}clearNonReplayableOutputEvent(e){for(const[t,n]of this.nonReplayableOutputEventsBySocket)n.delete(e),n.size===0&&this.nonReplayableOutputEventsBySocket.delete(t)}failNonReplayableOutputsForSocket(e,t){const n=this.nonReplayableOutputEventsBySocket.get(e);if(n){this.nonReplayableOutputEventsBySocket.delete(e);for(const s of n.keys())this.markOutputIntegrityFailed(s,`connection lost before a complete output acceptance fence: ${t}`)}}hasNonReplayableOutput(e){for(const t of this.nonReplayableOutputEventsBySocket.values())if(t.has(e))return!0;return!1}hasPendingOutputAcceptanceFence(e){return!!(this.pendingAcceptedOutputSeqsByEvent.get(e)?.size||this.outboundBuffer.some(t=>this.outboundEventId(t.cmd,t.payload)===e&&this.isAckRequiredOutput(t.cmd,t.payload)))}hasUnconfirmedEventOutput(e){return!!(this.pendingOutputSeqsByEvent.get(e)?.size||this.pendingAcceptedOutputSeqsByEvent.get(e)?.size||this.outboundBuffer.some(t=>this.outboundEventId(t.cmd,t.payload)===e))}trackOutboundEventCorrelation(e,t,n){const s=this.outboundEventId(e,n);if(!s)return;const i=n,r=typeof i.client_msg_id=="string"?i.client_msg_id.trim():"";this.pruneExpiredEventCorrelations();const u=Date.now();let o=this.eventCorrelations.get(s);if(!o){if(this.eventCorrelations.size>=p.MAX_EVENT_CORRELATIONS){let l;for(const d of this.eventCorrelations.values())this.eventHasCriticalDeliveryState(d.eventId)||(!l||d.touchedAt<l.touchedAt)&&(l=d);l&&this.releaseEventCorrelations(l.eventId)}o={eventId:s,clientMsgIds:new Set,seqRanges:[],touchedAt:u,expiresAt:u+p.EVENT_CORRELATION_TTL_MS},this.eventCorrelations.set(s,o)}o.touchedAt=u,o.expiresAt=u+p.EVENT_CORRELATION_TTL_MS;const f=o.seqRanges[o.seqRanges.length-1];if(f&&t===f.end+1?f.end=t:(!f||t<f.start||t>f.end)&&o.seqRanges.push({start:t,end:t}),r){o.clientMsgIds.add(r);let l=this.clientMsgEventMap.get(r);l||(l=new Set,this.clientMsgEventMap.set(r,l)),l.add(s)}this.scheduleEventCorrelationCleanup()}uniqueEventIdForClientMsgId(e){const t=this.clientMsgEventMap.get(e);if(t?.size===1)return t.values().next().value}eventHasCriticalDeliveryState(e){return!!(this.terminalOutbox.get(e)||this.pendingAcceptedOutputSeqsByEvent.get(e)?.size)}eventIdForSeq(e){if(!(e<=0)){for(const t of this.eventCorrelations.values())if(t.seqRanges.some(n=>e>=n.start&&e<=n.end))return t.eventId}}removeSeqFromEventCorrelation(e){if(!(e<=0))for(const t of this.eventCorrelations.values()){const n=t.seqRanges.findIndex(i=>e>=i.start&&e<=i.end);if(n<0)continue;const s=t.seqRanges[n];s.start===s.end?t.seqRanges.splice(n,1):e===s.start?s.start++:e===s.end?s.end--:t.seqRanges.splice(n,1,{start:s.start,end:e-1},{start:e+1,end:s.end});return}}releaseEventCorrelations(e){const t=e.trim();if(!t)return;const n=this.eventCorrelations.get(t);if(n){for(const s of n.clientMsgIds){const i=this.clientMsgEventMap.get(s);i?.delete(t),i?.size===0&&this.clientMsgEventMap.delete(s)}this.eventCorrelations.delete(t)}this.cancelEventCorrelationCleanupIfEmpty()}clearAllEventCorrelations(){this.eventCorrelationCleanupTimer&&(clearTimeout(this.eventCorrelationCleanupTimer),this.eventCorrelationCleanupTimer=null),this.eventCorrelations.clear(),this.clientMsgEventMap.clear(),this.lastCorrelationPruneAt=0}pruneExpiredEventCorrelations(e=Date.now(),t=!1){if(!(!t&&e-this.lastCorrelationPruneAt<1e3)){this.lastCorrelationPruneAt=e;for(const n of[...this.eventCorrelations.values()])n.expiresAt<=e&&!this.eventHasCriticalDeliveryState(n.eventId)&&this.releaseEventCorrelations(n.eventId);this.cancelEventCorrelationCleanupIfEmpty()}}scheduleEventCorrelationCleanup(){if(this.eventCorrelationCleanupTimer||this.eventCorrelations.size===0)return;let e=Number.POSITIVE_INFINITY;for(const t of this.eventCorrelations.values())this.eventHasCriticalDeliveryState(t.eventId)||(e=Math.min(e,t.expiresAt));Number.isFinite(e)&&(this.eventCorrelationCleanupTimer=setTimeout(()=>{this.eventCorrelationCleanupTimer=null,this.pruneExpiredEventCorrelations(Date.now(),!0),this.scheduleEventCorrelationCleanup()},Math.max(1,e-Date.now())),this.eventCorrelationCleanupTimer.unref?.())}cancelEventCorrelationCleanupIfEmpty(){!this.eventCorrelationCleanupTimer||this.eventCorrelations.size>0||(clearTimeout(this.eventCorrelationCleanupTimer),this.eventCorrelationCleanupTimer=null)}handleCorrelatedSendResponse(e){if(e.cmd==="send_ack"){const f=e.payload,l=typeof f.client_msg_id=="string"?f.client_msg_id.trim():"";let d=e.seq>0&&this.pendingAcceptedOutputs.has(e.seq)?e.seq:void 0,_;if(d===void 0&&l){const a=this.pendingAcceptedSeqsByClientMsgId.get(l);if(a?.size===1){const h=a.values().next().value,g=this.pendingAcceptedOutputs.get(h),O=this.uniqueEventIdForClientMsgId(l);g&&O&&g.eventId===O&&(d=h,_=O)}}const b=(d!==void 0?this.pendingAcceptedOutputs.get(d)?.eventId:void 0)??_??this.eventIdForSeq(e.seq),c=d??e.seq;if(d!==void 0){const a=this.pendingOutputWrites.get(d);a&&this.settlePendingOutputWrite(d,!0,!1,a.socket,a.connectionGeneration),this.settlePendingAcceptedOutput(d,"accepted",!1)}else if(e.seq>0){const a=this.pendingOutputWrites.get(e.seq);a&&this.settlePendingOutputWrite(e.seq,!0,!1,a.socket,a.connectionGeneration)}b&&this.clearNonReplayableOutputThrough(this.ws,b,c);return}if(e.cmd!=="send_nack"&&e.cmd!=="error")return;this.pruneExpiredEventCorrelations();const t=e.payload,n=typeof t.client_msg_id=="string"?t.client_msg_id.trim():"",s=e.cmd==="error"&&typeof t.ref_id=="string"?t.ref_id.trim():"",i=e.cmd==="error"&&typeof t.ref_cmd=="string"?t.ref_cmd.trim():"",r=this.eventIdForSeq(e.seq),u=s&&(!i||p.ORDERED_OUTPUT_COMMANDS.has(i))?this.uniqueEventIdForClientMsgId(s)??(this.eventCorrelations.has(s)?s:void 0):void 0,o=r??(n?this.uniqueEventIdForClientMsgId(n):void 0)??u;o&&(this.blockEventOutput(o),this.settlePendingOutputWritesForEvent(o,!0),this.clearNonReplayableOutputEvent(o),e.seq>0&&this.pendingAcceptedOutputs.get(e.seq)?.eventId===o?this.settlePendingAcceptedOutput(e.seq,"rejected",!1):this.settlePendingAcceptedOutputsForEvent(o,"rejected",!1),t.code===4003&&(this.purgeBufferedStreamChunks(o),m.warn("aibot",`event payload rejected (4003), purging buffered chunks for event=${o}`)),this.promotePendingTerminalAfterOutputRejection(o,Number(t.code??0),t.msg),this.emit("streamRejected",o,Number(t.code??0)))}promotePendingTerminalAfterOutputRejection(e,t,n){this.provisionalRespondedTerminals.delete(e);const s=this.terminalOutbox.get(e);if(!(!s||s.payload.status!=="responded"&&s.payload.code!=="agent_output_unconfirmed"||s.deliveryStartedAt))try{const i=this.terminalOutbox.enqueue({...s.payload,status:"failed",code:"agent_output_rejected",msg:n?`required agent output was rejected: ${n}`:`required agent output was rejected by the server (code=${t})`,updated_at:Date.now()});this.scheduleTerminalDelivery(i.payload.event_id,{ignoreNotBefore:!0})}catch(i){this.emitClientError(new Error(`failed to persist output-rejection terminal promotion: event=${e} code=${t}: ${i instanceof Error?i.message:i}`))}}scheduleTerminalDelivery(e,t){if(!this.connected||!this.ws||this.ws.readyState!==y.OPEN)return;if(this.terminalInFlight.has(e)){t?.ignoreNotBefore&&this.terminalReplayAfterFlight.add(e);return}const n=this.terminalOutbox.get(e);if(!n)return;const s=this.terminalRetryTimers.get(e);s&&(clearTimeout(s),this.terminalRetryTimers.delete(e));const i=t?.ignoreNotBefore?0:Math.max(0,n.nextAttemptAt-Date.now()),r=Math.max(i,t?.minimumDelayMs??0);if(r>0){const u=setTimeout(()=>{this.terminalRetryTimers.delete(e),this.scheduleTerminalDelivery(e,{ignoreNotBefore:!0})},r);this.terminalRetryTimers.set(e,u);return}this.deliverTerminalEntry(n)}async deliverTerminalEntry(e){const t=e.payload.event_id;if(this.terminalInFlight.has(t)||!this.terminalOutbox.isCurrent(e))return;this.terminalInFlight.add(t);let n=0;try{n=await this.sendEventResultReliable(e)}finally{this.terminalInFlight.delete(t);const s=this.terminalReplayAfterFlight.delete(t);this.terminalOutbox.get(t)&&this.scheduleTerminalDelivery(t,s?{ignoreNotBefore:!0}:{minimumDelayMs:n})}}clearTerminalRetryTimers(){for(const e of this.terminalRetryTimers.values())clearTimeout(e);this.terminalRetryTimers.clear()}async sendEventResultReliable(e){const t=e.payload.event_id,n=e.payload.terminal_commit_token?.trim();if(n&&!this.negotiatedCapabilities.has("terminal_commit_v1")){const d=this.ws;return this.emitClientError(new Error(`refusing tokenized event_result on an unnegotiated connection: event=${t}`)),d&&this.reconnectAfterOutboundWriteFailure(d,"terminal_commit_v1 not negotiated"),p.TERMINAL_RETRY_DELAY_MS}if(!await this.awaitOutputDeliveryBarrier(t))return p.TERMINAL_RETRY_DELAY_MS;const s=this.provisionalRespondedTerminals.get(t);if(s&&this.terminalOutbox.isCurrent(e))if(!this.canReplaceUnsentOutputGuard(e))this.provisionalRespondedTerminals.delete(t);else try{return this.terminalOutbox.enqueue(s),this.provisionalRespondedTerminals.delete(t),0}catch(d){return this.emitClientError(new Error(`failed to promote confirmed output terminal to responded: event=${t}: ${d instanceof Error?d.message:d}`)),p.TERMINAL_RETRY_DELAY_MS}const i=e.payload;try{if(!this.terminalOutbox.markDeliveryStarted(e))return 0}catch(d){return this.emitClientError(new Error(`event_result delivery-start persist failed: event=${t}: ${d instanceof Error?d.message:d}`)),p.TERMINAL_RETRY_DELAY_MS}const r=Math.max(1,this.ackPolicy?.max_retries??3),u=this.ackPolicy?.push_ack_timeout_ms??5e3,o=750;let f="unknown delivery failure";for(let d=1;d<=r;d++){if(!this.terminalOutbox.isCurrent(e))return 0;const _=this.ws?.readyState??-1,b=this.ws?.bufferedAmount??0;m.info("aibot",`event_result send attempt event=${i.event_id} status=${i.status} attempt=${d}/${r} readyState=${_} bufferedAmount=${b}`);try{const c=await this.sendEventResultRequest(i,u);if(!this.terminalOutbox.isCurrent(e))return 0;if(c.cmd==="send_ack"){const h=c.payload;if(m.info("aibot",`event_result ack event=${i.event_id} status=${i.status} attempt=${d}/${r} ack_event=${h.event_id??""} ack_status=${h.status??""}`),h.event_id!==i.event_id||h.status!==i.status)throw new Error(`event_result ACK mismatch: expected event=${i.event_id} status=${i.status}, got event=${h.event_id??""} status=${h.status??""}`);if(n&&(h.terminal_commit_token?.trim()!==n||h.terminal_committed!==!0))throw new Error(`event_result terminal commit ACK mismatch: event=${i.event_id}`);if(n)try{this.terminalCommitTokens.remove(i.event_id,n)}catch(g){throw new Error(`terminal commit token cleanup failed: event=${i.event_id}: ${g instanceof Error?g.message:g}`)}return this.terminalOutbox.acknowledge(e,h.event_id,h.status,h.terminal_commit_token,h.terminal_committed)&&(this.rememberCommittedTerminalEvent(i.event_id),this.tokenizedTerminalRejections.delete(i.event_id),this.blockEventOutput(i.event_id),this.releaseEventCorrelations(i.event_id),this.clearNonReplayableOutputEvent(i.event_id),this.settleOutputIntegrityTerminal(i.event_id),this.scheduleStopResultsForEvent(i.event_id)),0}const a=c.payload;if(m.warn("aibot",`event_result rejected event=${i.event_id} status=${i.status} attempt=${d}/${r} cmd=${c.cmd} code=${a.code??""} msg=${a.msg??""}${a.ref_cmd?` ref_cmd=${a.ref_cmd}`:""}${a.ref_id?` ref_id=${a.ref_id}`:""}`),n){f=`tokenized terminal rejected: cmd=${c.cmd} code=${a.code??""} msg=${a.msg??""}`;const h=(this.tokenizedTerminalRejections.get(i.event_id)??0)+1;if(this.tokenizedTerminalRejections.set(i.event_id,h),h<p.MAX_TOKENIZED_TERMINAL_REJECTIONS)break;this.tokenizedTerminalRejections.delete(i.event_id);try{this.terminalCommitTokens.remove(i.event_id,n),this.terminalOutbox.moveToDeadLetter(e,{responseCmd:c.cmd,code:a.code,message:a.msg})&&(this.blockEventOutput(i.event_id),this.releaseEventCorrelations(i.event_id),this.clearNonReplayableOutputEvent(i.event_id),this.settleOutputIntegrityTerminal(i.event_id),this.emitClientError(new Error(`tokenized event_result permanently rejected after ${h} rejections, moved to dead-letter: event=${i.event_id} status=${i.status} cmd=${c.cmd} code=${a.code??""} msg=${a.msg??""}`)))}catch(g){f=`dead-letter persist failed: ${g instanceof Error?g.message:g}`,this.emitClientError(new Error(`tokenized event_result dead-letter persist failed: event=${i.event_id} status=${i.status}: ${f}`));break}return 0}try{return this.terminalOutbox.moveToDeadLetter(e,{responseCmd:c.cmd,code:a.code,message:a.msg})&&(this.tokenizedTerminalRejections.delete(i.event_id),this.blockEventOutput(i.event_id),this.releaseEventCorrelations(i.event_id),this.clearNonReplayableOutputEvent(i.event_id),this.settleOutputIntegrityTerminal(i.event_id),this.emitClientError(new Error(`event_result rejected and moved to dead-letter: event=${i.event_id} status=${i.status} cmd=${c.cmd} code=${a.code??""} msg=${a.msg??""}`))),0}catch(h){f=`dead-letter persist failed: ${h instanceof Error?h.message:h}`,this.emitClientError(new Error(`event_result dead-letter persist failed: event=${i.event_id} status=${i.status}: ${f}`));break}}catch(c){const a=c instanceof Error?c.message:String(c);if(f=a,m.warn("aibot",`event_result attempt failed event=${i.event_id} status=${i.status} attempt=${d}/${r} err=${a}`),d===r)break;await new Promise(h=>setTimeout(h,o*d))}}const l=Date.now()+p.TERMINAL_RETRY_DELAY_MS;try{this.terminalOutbox.recordRetry(e,l,f)}catch(d){this.emitClientError(new Error(`event_result retry state persist failed: event=${i.event_id} status=${i.status}: ${d instanceof Error?d.message:d}`))}return this.emitClientError(new Error(`event_result ack failed after ${r} attempts; retained for retry: event=${i.event_id} status=${i.status} err=${f}`)),p.TERMINAL_RETRY_DELAY_MS}purgeBufferedStreamChunks(e){const t=this.outboundBuffer.length;this.outboundBuffer=this.outboundBuffer.filter(n=>n.cmd!=="client_stream_chunk"?!0:n.payload?.event_id!==e),this.outboundBuffer.length<t&&m.info("aibot",`purged ${t-this.outboundBuffer.length} buffered stream chunks for event=${e}`)}emitClientError(e){if(this.listenerCount("error")===0){m.warn("aibot",`Client error (no listeners): ${e.message}`);return}this.emit("error",e)}createOutboundEntry(e,t){const n={cmd:e,payload:t};return this.outboundEntryOrders.set(n,++this.nextOutboundEntryOrder),n}makeOutputIntegrityFailurePayload(e,t){return this.withTerminalCommitToken({event_id:e,status:"failed",code:"agent_output_integrity_failed",msg:`required agent output was discarded before delivery: ${t}`,updated_at:Date.now()})}canReplaceUnsentOutputGuard(e){return e.payload.code==="agent_output_unconfirmed"&&!e.deliveryStartedAt}rememberCommittedTerminalEvent(e){for(this.committedTerminalEvents.delete(e),this.committedTerminalEvents.add(e);this.committedTerminalEvents.size>p.MAX_COMMITTED_TERMINAL_EVENTS;){const t=this.committedTerminalEvents.values().next().value;if(!t)break;this.committedTerminalEvents.delete(t)}}replayStopResultOutbox(){for(const e of this.stopResultOutbox.listPending())this.scheduleStopResultDelivery(e,{ignoreNotBefore:!0})}scheduleStopResultsForEvent(e){for(const t of this.stopResultOutbox.listPending())t.payload.event_id===e&&this.scheduleStopResultDelivery(t,{ignoreNotBefore:!0})}scheduleStopResultDelivery(e,t){if(!this.connected||!this.ws||this.ws.readyState!==y.OPEN||!this.stopResultOutbox.isCurrent(e))return;const n=e.payload.event_id;if(this.terminalOutbox.get(n)||this.terminalCommitTokens.get(n))return;const s=this.stopResultRetryTimers.get(e.key);s&&(clearTimeout(s),this.stopResultRetryTimers.delete(e.key));const i=t?.ignoreNotBefore?0:Math.max(0,e.nextAttemptAt-Date.now());if(i>0){const r=setTimeout(()=>{this.stopResultRetryTimers.delete(e.key),this.scheduleStopResultDelivery(e,{ignoreNotBefore:!0})},i);this.stopResultRetryTimers.set(e.key,r);return}this.deliverStopResultEntry(e)}async deliverStopResultEntry(e){if(this.stopResultInFlight.has(e.key)||!this.stopResultOutbox.isCurrent(e))return;this.stopResultInFlight.add(e.key);let t=p.TERMINAL_RETRY_DELAY_MS;try{t=await this.sendStopResultReliable(e)}finally{if(this.stopResultInFlight.delete(e.key),this.stopResultOutbox.isCurrent(e)){const n=setTimeout(()=>{this.stopResultRetryTimers.delete(e.key),this.scheduleStopResultDelivery(e,{ignoreNotBefore:!0})},t);this.stopResultRetryTimers.set(e.key,n)}}}async sendStopResultReliable(e){const t=this.ackPolicy?.push_ack_timeout_ms??5e3;let n="unknown delivery failure";try{const i=await this.request("event_stop_result",e.payload,{expected:["send_ack","send_nack","error"],timeoutMs:t});if(!this.stopResultOutbox.isCurrent(e))return 0;if(i.cmd==="send_ack"){const r=i.payload;if(this.stopResultOutbox.acknowledge(e,r.event_id,r.terminal_commit_token,r.terminal_committed))return 0;n=`event_stop_result ACK mismatch event=${e.payload.event_id}`}else{const r=i.payload;if(n=`event_stop_result rejected: cmd=${i.cmd} code=${r.code??""} msg=${r.msg??""}`,r.code===4001||r.code===4003){try{this.stopResultOutbox.discard(e)&&this.emitClientError(new Error(`event_stop_result permanently rejected and discarded: event=${e.payload.event_id} stop=${e.payload.stop_id} code=${r.code} msg=${r.msg??""}`))}catch(u){this.emitClientError(new Error(`event_stop_result discard persist failed: event=${e.payload.event_id} stop=${e.payload.stop_id}: ${u instanceof Error?u.message:u}`))}return 0}}}catch(i){n=i instanceof Error?i.message:String(i)}const s=Date.now()+p.TERMINAL_RETRY_DELAY_MS;try{this.stopResultOutbox.recordRetry(e,s,n)}catch(i){this.emitClientError(new Error(`event_stop_result retry state persist failed: event=${e.payload.event_id} stop=${e.payload.stop_id}: ${i instanceof Error?i.message:i}`))}return this.emitClientError(new Error(`event_stop_result retained for retry: event=${e.payload.event_id} stop=${e.payload.stop_id} err=${n}`)),p.TERMINAL_RETRY_DELAY_MS}clearStopResultRetryTimers(){for(const e of this.stopResultRetryTimers.values())clearTimeout(e);this.stopResultRetryTimers.clear()}markOutputIntegrityFailed(e,t){if(this.blockEventOutput(e),this.outputIntegrityFailed.has(e))return;const n={reason:t,terminalObserved:!!(this.provisionalRespondedTerminals.has(e)||this.terminalOutbox.get(e)),terminalSettled:!1};this.outputIntegrityFailed.set(e,n),this.provisionalRespondedTerminals.delete(e),this.clearNonReplayableOutputEvent(e);const s=this.outboundBuffer.length;this.outboundBuffer=this.outboundBuffer.filter(u=>this.outboundEventId(u.cmd,u.payload)!==e);const i=this.terminalOutbox.get(e),r=i?this.canReplaceUnsentOutputGuard(i):!1;if(i&&!r)n.terminalObserved=!0,this.scheduleTerminalDelivery(e,{ignoreNotBefore:!0});else if(!this.committedTerminalEvents.has(e))try{const u=this.terminalOutbox.enqueue(this.makeOutputIntegrityFailurePayload(e,t));this.scheduleTerminalDelivery(u.payload.event_id,{ignoreNotBefore:!0})}catch(u){this.emitClientError(new Error(`output-integrity terminal persist failed: event=${e}: ${u instanceof Error?u.message:u}`))}for(const u of[...this.pendingOutputSeqsByEvent.get(e)??[]])this.settlePendingOutputWrite(u,!1,!1);this.settlePendingAcceptedOutputsForEvent(e,"rejected",!1),this.emit("streamRejected",e,0),this.emitClientError(new Error(`required output integrity failed: event=${e} removed=${s-this.outboundBuffer.length} reason=${t}`))}settleOutputIntegrityTerminal(e){const t=this.outputIntegrityFailed.get(e);t&&(t.terminalSettled=!0,t.terminalObserved&&this.outputIntegrityFailed.delete(e))}bufferOutboundEntry(e){if(this.outboundEntryOrders.has(e)||this.outboundEntryOrders.set(e,++this.nextOutboundEntryOrder),this.outboundBuffer.includes(e))return!0;const t=this.outboundEventId(e.cmd,e.payload);return t&&(this.outputIntegrityFailed.has(t)||this.blockedOutputEvents.has(t))?!1:(this.outboundBuffer.length>=p.MAX_OUTBOUND_BUFFER_SIZE&&(this.outboundBuffer=this.outboundBuffer.filter(n=>p.BUFFER_OVERFLOW_RETAIN_COMMANDS.has(n.cmd)),this.outboundBuffer.length>=p.MAX_OUTBOUND_BUFFER_SIZE&&m.warn("aibot",`outbound buffer soft limit exceeded by ${this.outboundBuffer.length} retained packet(s); preserving required output`)),this.outboundBuffer.length>=p.MAX_OUTBOUND_BUFFER_HARD_SIZE?(t?this.markOutputIntegrityFailed(t,`outbound buffer hard limit ${p.MAX_OUTBOUND_BUFFER_HARD_SIZE} reached`):this.emitClientError(new Error(`outbound buffer hard limit ${p.MAX_OUTBOUND_BUFFER_HARD_SIZE} reached; rejected non-event packet cmd=${e.cmd}`)),!1):(this.outboundBuffer.push(e),this.outboundBuffer.sort((n,s)=>(this.outboundEntryOrders.get(n)??Number.MAX_SAFE_INTEGER)-(this.outboundEntryOrders.get(s)??Number.MAX_SAFE_INTEGER)),!0))}registerPendingOutputWrite(e,t,n,s,i){let r;const u=new Promise(l=>{r=l}),o=setTimeout(()=>{const l=this.pendingOutputWrites.get(e);if(!l||l.socket!==s||l.connectionGeneration!==i)return;if(!this.isConnectionCurrent(s,i)){this.settlePendingOutputWrite(e,!1,!1,s,i);return}this.removeSeqFromEventCorrelation(e);const d=this.isNonReplayableOutput(n.cmd,n.payload);this.settlePendingOutputWrite(e,!1,!d,s,i),d&&this.markOutputIntegrityFailed(t,`websocket write callback timed out for non-replayable ${n.cmd} seq=${e}`),this.emitClientError(new Error(`outbound websocket write callback timeout: cmd=${n.cmd} event=${t} seq=${e} delivery_unknown=${d}`)),this.reconnectAfterOutboundWriteFailure(s,`${n.cmd} callback timeout`)},p.OUTBOUND_WRITE_CALLBACK_TIMEOUT_MS);o.unref?.(),this.pendingOutputWrites.set(e,{eventId:t,entry:n,socket:s,connectionGeneration:i,promise:u,resolve:r,timer:o});let f=this.pendingOutputSeqsByEvent.get(t);f||(f=new Set,this.pendingOutputSeqsByEvent.set(t,f)),f.add(e)}settlePendingOutputWrite(e,t,n,s,i){const r=this.pendingOutputWrites.get(e);if(!r||s&&(r.socket!==s||r.connectionGeneration!==i))return;this.pendingOutputWrites.delete(e),clearTimeout(r.timer);const u=this.pendingOutputSeqsByEvent.get(r.eventId);u?.delete(e),u?.size===0&&this.pendingOutputSeqsByEvent.delete(r.eventId),n&&this.bufferOutboundEntry(r.entry),r.resolve(t)}settlePendingOutputWritesForEvent(e,t){const n=this.pendingOutputSeqsByEvent.get(e);if(n)for(const s of[...n]){const i=this.pendingOutputWrites.get(s);i&&this.settlePendingOutputWrite(s,t,!1,i.socket,i.connectionGeneration)}}abortPendingOutputWrites(e,t,n,s){for(const[i,r]of[...this.pendingOutputWrites.entries()])if(!(t&&(r.socket!==t||r.connectionGeneration!==n))){if(this.removeSeqFromEventCorrelation(i),s&&this.isNonReplayableOutput(r.entry.cmd,r.entry.payload)){this.settlePendingOutputWrite(i,!1,!1,t,n),this.markOutputIntegrityFailed(r.eventId,`connection lost while websocket write completion was unknown: ${s}`);continue}this.settlePendingOutputWrite(i,!1,e,t,n)}}registerPendingAcceptedOutput(e,t,n,s,i){let r;const u=new Promise(h=>{r=h}),o=Math.max(1,this.ackPolicy?.push_ack_timeout_ms??5e3),f=Math.max(1,this.ackPolicy?.max_retries??3),l=Math.max(1e3,Math.ceil(o*.25)),d=Math.max(p.MIN_REQUIRED_OUTPUT_ACK_TIMEOUT_MS,o*f+l),_=setTimeout(()=>{const h=this.pendingAcceptedOutputs.get(e);if(!(!h||h.socket!==s||h.connectionGeneration!==i)){if(!this.isConnectionCurrent(s,i)){this.settlePendingAcceptedOutput(e,"retry",!1,s,i);return}this.settlePendingAcceptedOutput(e,"retry",!0,s,i),this.emitClientError(new Error(`required output ACK timeout; retained for reconnect retry: event=${t} seq=${e}`)),this.reconnectAfterOutboundWriteFailure(s,"required output ACK timeout")}},d);_.unref?.(),this.pendingAcceptedOutputs.set(e,{eventId:t,entry:n,socket:s,connectionGeneration:i,promise:u,resolve:r,timer:_});const b=n.payload,c=typeof b.client_msg_id=="string"?b.client_msg_id.trim():"";if(c){let h=this.pendingAcceptedSeqsByClientMsgId.get(c);h||(h=new Set,this.pendingAcceptedSeqsByClientMsgId.set(c,h)),h.add(e)}let a=this.pendingAcceptedOutputSeqsByEvent.get(t);a||(a=new Set,this.pendingAcceptedOutputSeqsByEvent.set(t,a)),a.add(e)}settlePendingAcceptedOutput(e,t,n,s,i){const r=this.pendingAcceptedOutputs.get(e);if(!r||s&&(r.socket!==s||r.connectionGeneration!==i))return;this.pendingAcceptedOutputs.delete(e),clearTimeout(r.timer);const u=this.pendingAcceptedOutputSeqsByEvent.get(r.eventId);u?.delete(e),u?.size===0&&this.pendingAcceptedOutputSeqsByEvent.delete(r.eventId);const o=r.entry.payload,f=typeof o.client_msg_id=="string"?o.client_msg_id.trim():"";if(f){const l=this.pendingAcceptedSeqsByClientMsgId.get(f);l?.delete(e),l?.size===0&&this.pendingAcceptedSeqsByClientMsgId.delete(f)}n&&this.bufferOutboundEntry(r.entry),r.resolve(t)}settlePendingAcceptedOutputsForEvent(e,t,n){const s=this.pendingAcceptedOutputSeqsByEvent.get(e);if(s)for(const i of[...s])this.settlePendingAcceptedOutput(i,t,n)}abortPendingAcceptedOutputs(e,t,n){for(const[s,i]of[...this.pendingAcceptedOutputs.entries()])t&&(i.socket!==t||i.connectionGeneration!==n)||this.settlePendingAcceptedOutput(s,"retry",e,t,n)}rejectPendingRequestAfterWriteFailure(e,t,n){const s=this.pendingRequests.get(e);s&&(this.pendingRequests.delete(e),clearTimeout(s.timer),s.reject(new Error(`websocket write failed: ${t}: ${n.message}`)))}async awaitOutputDeliveryBarrier(e){const t=this.outboundFlushInFlight;if(t&&!await t.promise)return!1;for(;;){const n=this.pendingOutputSeqsByEvent.get(e);if(!n||n.size===0)break;const s=[...n].map(r=>this.pendingOutputWrites.get(r)?.promise).filter(r=>!!r);if(s.length===0)break;if((await Promise.all(s)).some(r=>!r))return!1}for(;;){const n=this.pendingAcceptedOutputSeqsByEvent.get(e);if(!n||n.size===0)break;const s=[...n].map(r=>this.pendingAcceptedOutputs.get(r)?.promise).filter(r=>!!r);if(s.length===0)break;const i=await Promise.all(s);if(i.includes("retry"))return!1;if(i.includes("rejected")){const r=this.terminalOutbox.get(e);if(!r||r.payload.status==="responded")return!1}}return this.outboundBuffer.some(n=>this.outboundEventId(n.cmd,n.payload)===e)?!1:this.hasNonReplayableOutput(e)?(this.markOutputIntegrityFailed(e,"output flush completed without a complete acceptance fence"),!1):!!(this.connected&&this.ws&&this.ws.readyState===y.OPEN)}isConnectionCurrent(e,t){return this.ws===e&&this.connectionGeneration===t}isOutboundFlushCurrent(e){return!e.finalized&&this.outboundFlushInFlight===e&&this.isConnectionCurrent(e.socket,e.connectionGeneration)}flushOutboundBuffer(e=this.ws,t=this.connectionGeneration){if(!e||!this.isConnectionCurrent(e,t))return Promise.resolve(!1);const n=this.outboundFlushInFlight;if(n){if(!n.finalized&&n.socket===e&&n.connectionGeneration===t)return n.promise;this.cancelOutboundFlush(n,"discard","superseded stale flush")}if(this.outboundBuffer.length===0)return Promise.resolve(!0);const s={connectionGeneration:t,flushGeneration:++this.nextOutboundFlushGeneration,socket:e,promise:Promise.resolve(!1),finalized:!1,activeBatch:null,nextIndex:0,currentWritePending:!1,currentWriteOutcome:null,cancelCurrentWrite:null,remainderHandled:!1};return this.outboundFlushInFlight=s,s.promise=this.performOutboundBufferFlush(s),s.promise.finally(()=>{s.finalized=!0,s.cancelCurrentWrite?.(),s.cancelCurrentWrite=null,this.outboundFlushInFlight===s&&(this.outboundFlushInFlight=null)}),s.promise}async performOutboundBufferFlush(e){for(;this.isOutboundFlushCurrent(e)&&this.outboundBuffer.length>0;){const{socket:t}=e;if(t.readyState!==y.OPEN)return!1;const n=this.outboundBuffer;this.outboundBuffer=[],e.activeBatch=n,e.nextIndex=0,e.remainderHandled=!1;for(let s=0;s<n.length;s++){if(!this.isOutboundFlushCurrent(e))return!1;const i=n[s],{cmd:r,payload:u}=i,o=this.outboundEventId(r,u);if(o&&this.blockedOutputEvents.has(o)){this.packetLog?.logOutboundPacket(r,0,u,"dropped"),e.nextIndex=s+1,e.currentWritePending=!1,e.currentWriteOutcome=null;continue}const f=++this.seq,l={cmd:r,seq:f,payload:u};e.nextIndex=s,e.currentWritePending=!0,e.currentWriteOutcome=null,o&&this.isAckRequiredOutput(r,u)&&this.registerPendingAcceptedOutput(f,o,i,t,e.connectionGeneration);const d=await new Promise(_=>{let b=!1;const c=h=>{b||(b=!0,clearTimeout(a),e.currentWriteOutcome=h,_(h))},a=setTimeout(()=>c("uncertain"),p.OUTBOUND_WRITE_CALLBACK_TIMEOUT_MS);a.unref?.(),e.cancelCurrentWrite=()=>c("canceled");try{this.trackOutboundEventCorrelation(r,f,u),t.send(JSON.stringify(l),h=>{c(h?"retry":"written")})}catch{c("retry")}});if(e.cancelCurrentWrite=null,!this.isOutboundFlushCurrent(e))return!1;if(d!=="written"||t.readyState!==y.OPEN){d==="retry"&&this.removeSeqFromEventCorrelation(f),this.settlePendingAcceptedOutput(f,"retry",!1);const _=this.retainOutboundFlushRemainder(e,d,`auth flush write failed for ${r} seq=${f} outcome=${d}`);return this.emitClientError(new Error(`outbound buffer flush failed at cmd=${r} seq=${f} outcome=${d}; ${_} packet(s) retained`)),!1}this.trackNonReplayableOutput(t,r,u,f),e.currentWritePending=!1,e.currentWriteOutcome="written",e.nextIndex=s+1}e.activeBatch=null,e.currentWritePending=!1,e.currentWriteOutcome=null,e.remainderHandled=!0}return this.isOutboundFlushCurrent(e)}retainOutboundFlushRemainder(e,t,n){if(e.remainderHandled||!e.activeBatch)return 0;e.remainderHandled=!0;const s=e.activeBatch,i=Math.min(e.nextIndex,s.length);let r=i;if(e.currentWritePending&&i<s.length){const o=s[i],f=this.outboundEventId(o.cmd,o.payload);t!=="retry"&&this.isNonReplayableOutput(o.cmd,o.payload)&&f&&(this.markOutputIntegrityFailed(f,n),r=i+1)}let u=0;for(const o of s.slice(r))this.bufferOutboundEntry(o)&&u++;return e.activeBatch=null,e.nextIndex=s.length,e.currentWritePending=!1,u}cancelOutboundFlush(e,t,n){if(e.finalized){this.outboundFlushInFlight===e&&(this.outboundFlushInFlight=null);return}e.finalized=!0,this.outboundFlushInFlight===e&&(this.outboundFlushInFlight=null),t==="connection_lost"?this.retainOutboundFlushRemainder(e,e.currentWriteOutcome,`connection lost during auth flush: ${n}`):(e.remainderHandled=!0,e.activeBatch=null,e.currentWritePending=!1);const s=e.cancelCurrentWrite;e.cancelCurrentWrite=null,s?.()}reconnectAfterOutboundFlushFailure(e,t){this.agentDeleted||!this.isConnectionCurrent(e,t)||this.reconnectAfterOutboundWriteFailure(e,"outbound flush failed")}reconnectAfterOutboundWriteFailure(e,t){if(this.agentDeleted||this.ws!==e)return;const n=this.connectionGeneration;this.connected=!1,this.negotiatedCapabilities.clear(),this.stopHeartbeat(),this.clearTerminalRetryTimers(),this.clearStopResultRetryTimers(),this.rejectAllPendingRequests(t);const s=this.outboundFlushInFlight;s&&s.socket===e&&s.connectionGeneration===n&&this.cancelOutboundFlush(s,"connection_lost",t),this.failNonReplayableOutputsForSocket(e,t),this.abortPendingOutputWrites(!0,e,n,t),this.abortPendingAcceptedOutputs(!0,e,n),this.clearAllEventCorrelations(),this.connectionGeneration++;try{e.close(1011,t)}catch{}this.ws===e&&(this.ws=null),this.attemptReconnect()}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(e=this.ws,t=this.connectionGeneration){if(!(!e||!this.isConnectionCurrent(e,t)))try{e.close()}catch{}}startHeartbeat(){this.stopHeartbeat(),this.heartbeatFailures=0,this.heartbeatTimer=setInterval(()=>{const e=this.ws,t=this.connectionGeneration;!this.connected||!e||this.request("ping",{ts:Date.now()},{expected:["pong"],timeoutMs:5e3}).then(()=>{this.isConnectionCurrent(e,t)&&(this.heartbeatFailures=0)}).catch(()=>{!this.connected||!this.isConnectionCurrent(e,t)||(this.heartbeatFailures++,!(this.heartbeatFailures<p.HEARTBEAT_MAX_FAILURES)&&(this.cleanupSocket(e,t),this.attemptReconnect()))})},this.heartbeatSec*1e3)}stopHeartbeat(){this.heartbeatTimer&&(clearInterval(this.heartbeatTimer),this.heartbeatTimer=null)}}export{p as AibotClient,D as preprocessLargeIntegers,x as withRequiredCapabilities};
2
+ `}),this.sendPacket("client_stream_chunk",e)}sendMsg(e){const t={...e,client_msg_id:e.client_msg_id?.trim()||T()};this.sendPacket("send_msg",t)||m.warn("aibot",`send_msg NOT sent (ws not open) event=${t.event_id??""} session=${t.session_id??""} ws=${this.ws?`state=${this.ws.readyState}`:"null"}`)}editMsg(e){this.sendPacket("edit_msg",e)}sendEventResult(e){try{e=this.withTerminalCommitToken(e)}catch(o){this.emitClientError(new Error(`event_result token validation failed: event=${e.event_id}: ${o instanceof Error?o.message:o}`));return}const t=this.terminalOutbox.get(e.event_id);let n,s=this.outputIntegrityFailed.get(e.event_id);const i=t;if(!s&&i?.payload.code==="agent_output_integrity_failed"&&(s={reason:i.payload.msg??"required agent output was discarded before delivery",terminalObserved:!1,terminalSettled:!1},this.outputIntegrityFailed.set(e.event_id,s)),s){if(s.terminalObserved=!0,this.provisionalRespondedTerminals.delete(e.event_id),i&&i.payload.code!=="agent_output_integrity_failed"&&!this.canReplaceUnsentOutputGuard(i)){this.scheduleTerminalDelivery(e.event_id,{ignoreNotBefore:!0});return}if(i?.payload.code==="agent_output_integrity_failed"){this.scheduleTerminalDelivery(e.event_id,{ignoreNotBefore:!0});return}if(s.terminalSettled||this.committedTerminalEvents.has(e.event_id)&&!i){this.outputIntegrityFailed.delete(e.event_id);return}try{n=this.terminalOutbox.enqueue(this.makeOutputIntegrityFailurePayload(e.event_id,s.reason))}catch(o){this.emitClientError(new Error(`output-integrity terminal persist failed: event=${e.event_id}: ${o instanceof Error?o.message:o}`));return}this.scheduleTerminalDelivery(n.payload.event_id,{ignoreNotBefore:!0});return}if(this.committedTerminalEvents.has(e.event_id)&&!t){m.info("aibot",`ignored duplicate terminal after committed ACK event=${e.event_id} status=${e.status}`);return}if(t){if(!(this.canReplaceUnsentOutputGuard(t)&&(e.status==="canceled"||e.status==="failed"))){m.info("aibot",`preserving first durable terminal event=${e.event_id} existing_status=${t.payload.status} ignored_status=${e.status}`),this.scheduleTerminalDelivery(e.event_id,{ignoreNotBefore:!0});return}this.provisionalRespondedTerminals.delete(e.event_id)}if(e.status==="responded"&&this.hasNonReplayableOutput(e.event_id)&&!this.hasPendingOutputAcceptanceFence(e.event_id)){this.markOutputIntegrityFailed(e.event_id,"terminal requested before a complete output acceptance fence");const o=this.outputIntegrityFailed.get(e.event_id);o&&(o.terminalObserved=!0);return}const r=e.status==="responded"&&this.hasUnconfirmedEventOutput(e.event_id),u=r?{...e,status:"failed",code:"agent_output_unconfirmed",msg:"required agent output was not durably confirmed before connector restart",updated_at:Date.now()}:e;r?this.provisionalRespondedTerminals.set(e.event_id,{...e}):this.provisionalRespondedTerminals.delete(e.event_id);try{n=this.terminalOutbox.enqueue(u)}catch(o){this.provisionalRespondedTerminals.delete(e.event_id),this.emitClientError(new Error(`event_result outbox persist failed: event=${e.event_id} status=${e.status}: ${o instanceof Error?o.message:o}`));return}this.scheduleTerminalDelivery(n.payload.event_id,{ignoreNotBefore:!0})}sendLocalActionResult(e){this.sendPacket("local_action_result",e)}sendEventStopAck(e){this.sendPacket("event_stop_ack",e)}sendEventStopResult(e){const t=e.event_id?.trim(),n=e.terminal_commit_token?.trim(),s=t?this.terminalCommitTokens.get(t):void 0;if(n&&s&&n!==s){this.emitClientError(new Error(`event_stop_result token mismatch event=${t??""}`));return}const i=s??n;if(t&&i&&(e.status==="stopped"||e.status==="already_finished")){try{const r=this.stopResultOutbox.enqueue({...e,event_id:t,terminal_commit_token:i});this.scheduleStopResultDelivery(r,{ignoreNotBefore:!0})}catch(r){this.emitClientError(new Error(`event_stop_result outbox persist failed: event=${t} stop=${e.stop_id}: ${r instanceof Error?r.message:r}`))}return}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)}sendAuditState(e){this.sendPacket("audit_state",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)}sendEventHoldResult(e){this.sendPacket("event_hold_result",e)}sendQueueEditResult(e){this.sendPacket("queue_edit_result",e)}sendQueueSnapshot(e){this.sendPacket("queue_snapshot",e)}agentInvoke(e,t,n=15e3){return new Promise((s,i)=>{const r=T(),u=Math.max(1e3,Math.min(n,75e3)),o=setTimeout(()=>{this.pendingInvokes.delete(r),i(new Error(`agent_invoke timeout: ${e}`))},u);this.pendingInvokes.set(r,{resolve:s,reject:i,timer:o}),this.sendPacket("agent_invoke",{invoke_id:r,action:e,params:t,timeout_ms:u})})}sendMcpFrame(e,t){this.sendPacket("mcp_frame",{session_id:e,frame:t})}async request(e,t,n){const s=this.outboundFlushInFlight;if(s){if(!await s.promise)throw new Error(`outbound flush failed before request: ${e}`);if(!this.isConnectionCurrent(s.socket,s.connectionGeneration))throw new Error(`connection changed while waiting for outbound flush: ${e}`)}if(!this.connected||!this.ws||this.ws.readyState!==y.OPEN)throw new Error(`send failed: ${e} (websocket not connected)`);return new Promise((i,r)=>{const u=++this.seq,o=setTimeout(()=>{this.pendingRequests.delete(u),r(new N(`request timeout: ${e} (expected ${n.expected.join("/")})`))},n.timeoutMs);this.pendingRequests.set(u,{expected:n.expected,resolve:i,reject:r,timer:o}),this.sendPacket(e,t,u)||(this.pendingRequests.delete(u),clearTimeout(o),r(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 relayCredentialRequest(e,t=15e3){return this.request("relay_credential_request",e,{expected:["relay_credential_result","error"],timeoutMs:t})}async relayStateSyncRequest(e,t=15e3){return this.request("relay_state_sync_request",e,{expected:["relay_state_sync_result","error"],timeoutMs:t})}sendRelayStateReport(e){this.sendPacket("relay_state_report",e)}async sendText(e,t=2e4){return this.request("send_msg",{msg_type:1,...e,client_msg_id:e.client_msg_id?.trim()||T()},{expected:["send_ack","send_nack","error"],timeoutMs:t})}async sendMedia(e,t=2e4){return this.request("send_msg",{...e,msg_type:2,client_msg_id:e.client_msg_id?.trim()||T()},{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,n=2e4){return this.request("delete_msg",{session_id:e,msg_id:t},{expected:["send_ack","send_nack","error"],timeoutMs:n})}async sendEventResultRequest(e,t=5e3){return this.request("event_result",e,{expected:["send_ack","send_nack","error"],timeoutMs:t})}disconnect(){m.info("aibot",`disconnect() agent=${this.config.clientType}:${this.config.agentId} wasConnected=${this.connected} reconnecting=${this.reconnecting} reconnectAttempts=${this.reconnectAttempts}`),this.connected=!1,this.negotiatedCapabilities.clear(),this.everConnected=!1,this.reconnecting=!1,this.reconnectRunGeneration++,this.reconnectAttempts=0,this.stopHeartbeat(),this.clearTerminalRetryTimers(),this.clearStopResultRetryTimers();const e=this.ws,t=this.connectionGeneration,n=this.outboundFlushInFlight;n&&this.cancelOutboundFlush(n,"discard","explicit disconnect"),this.connectionGeneration++,this.rejectAllPendingInvokes("disconnect"),this.rejectAllPendingRequests("disconnect"),this.abortPendingOutputWrites(!1,e??void 0,t),this.abortPendingAcceptedOutputs(!1,e??void 0,t),this.clearAllEventCorrelations(),this.outboundBuffer.length=0,this.provisionalRespondedTerminals.clear(),this.outputIntegrityFailed.clear(),this.nonReplayableOutputEventsBySocket.clear(),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;const e=++this.reconnectRunGeneration;m.info("aibot",`attemptReconnect start agent=${this.config.clientType}:${this.config.agentId} fromAttempts=${this.reconnectAttempts}`),this.emit("disconnected");let t=0;for(;this.reconnecting&&this.reconnectRunGeneration===e;){const n=Math.min(1e3*2**this.reconnectAttempts,3e4),s=Math.floor(n*.2*Math.random());if(this.reconnectAttempts++,await new Promise(u=>setTimeout(u,n+s)),!this.reconnecting||this.reconnectRunGeneration!==e)return;let i=null,r=this.connectionGeneration;try{const u=this.connect();if(i=this.ws,r=this.connectionGeneration,await u,this.reconnectRunGeneration!==e)return;if(!i||!this.isConnectionCurrent(i,r)){if(this.connected&&this.ws){this.reconnecting=!1;return}continue}const o=this.reconnectAttempts;this.reconnectAttempts=0,this.reconnecting=!1,m.info("aibot",`reconnect succeeded agent=${this.config.clientType}:${this.config.agentId} attempt=${o}`);return}catch(u){if(this.reconnectRunGeneration!==e)return;if(i&&!this.isConnectionCurrent(i,r)&&this.connected&&this.ws){this.reconnecting=!1;return}if(i&&this.isConnectionCurrent(i,r)){this.connectionGeneration++;try{i.close()}catch{}this.ws===i&&(this.ws=null)}const o=u instanceof Error?u.message:String(u);if(m.warn("aibot",`reconnect failed agent=${this.config.clientType}:${this.config.agentId} attempt=${this.reconnectAttempts} err=${o}`),u instanceof C){this.agentDeleted=!0,this.reconnecting=!1,m.error("aibot",`reconnect aborted: agent deleted on platform agent=${this.config.clientType}:${this.config.agentId}`),this.emit("agentDeleted",{source:"auth_ack",code:w});return}if(/Auth failed/i.test(o)){if(t++,t>=p.MAX_CONSECUTIVE_AUTH_FAILURES){this.reconnecting=!1,m.error("aibot",`reconnect giving up after ${t} consecutive auth failures agent=${this.config.clientType}:${this.config.agentId}`);return}}else t=0}}}sendPacket(e,t,n){const s=this.outboundEventId(e,t);if(s&&this.blockedOutputEvents.has(s))return this.packetLog?.logOutboundPacket(e,n??0,t,"dropped"),m.warn("aibot",`blocked late output after irreversible event failure event=${s} cmd=${e}`),!1;const i=n===void 0?this.createOutboundEntry(e,t):void 0;if(i&&this.outboundFlushInFlight&&!this.outboundFlushInFlight.finalized&&this.isConnectionCurrent(this.outboundFlushInFlight.socket,this.outboundFlushInFlight.connectionGeneration)&&!p.DROPPABLE_COMMANDS.has(e))return this.bufferOutboundEntry(i),!1;if(this.ws&&this.ws.readyState===y.OPEN&&(e==="auth"||this.connected)){const u=this.ws.bufferedAmount>p.BACKPRESSURE_THRESHOLD;if(!u||!p.DROPPABLE_COMMANDS.has(e)){if(u&&p.DROPPABLE_COMMANDS.has(e))return!1;const o=n??++this.seq,f={cmd:e,seq:o,payload:t};this.packetLog?.logOutboundPacket(e,o,t,"sent");const l=this.ws,d=this.connectionGeneration,_=s;_&&this.trackOutboundEventCorrelation(e,o,t),i&&_&&(this.registerPendingOutputWrite(o,_,i,l,d),this.isAckRequiredOutput(e,t)&&this.registerPendingAcceptedOutput(o,_,i,l,d));let b=!1;try{const c=l.readyState,a=l.bufferedAmount;let h=!1;return l.send(JSON.stringify(f),g=>{if(g&&!h&&(b=!0),e==="event_result"){const v=t;g?m.warn("aibot",`event_result ws send callback failed event=${v.event_id??""} status=${v.status??""} seq=${o} readyState=${c} bufferedAmount=${a} err=${g.message}`):m.info("aibot",`event_result ws send callback ok event=${v.event_id??""} status=${v.status??""} seq=${o} readyState=${c} bufferedAmount=${a}`)}else if(e==="client_stream_chunk"){const v=t;g?m.warn("aibot",`stream_chunk ws send failed event=${v.event_id??""} session=${v.session_id??""} seq=${o} chunk_seq=${v.chunk_seq??""} is_finish=${v.is_finish??""} readyState=${c} bufferedAmount=${a} err=${g.message}`):m.info("aibot",`stream_chunk ws send ok event=${v.event_id??""} session=${v.session_id??""} seq=${o} chunk_seq=${v.chunk_seq??""} is_finish=${v.is_finish??""} client_msg_id=${v.client_msg_id??""} quoted_message_id=${v.quoted_message_id??""} readyState=${c} bufferedAmount=${a}`)}else if(e==="event_ack"){const v=t;g?m.warn("aibot",`event_ack ws send failed event=${v.event_id??""} seq=${o} readyState=${c} bufferedAmount=${a} err=${g.message}`):m.info("aibot",`event_ack ws send ok event=${v.event_id??""} seq=${o} readyState=${c} bufferedAmount=${a}`)}else if(e==="send_msg"){const v=t;g?m.warn("aibot",`send_msg ws send failed event=${v.event_id??""} session=${v.session_id??""} seq=${o} readyState=${c} bufferedAmount=${a} err=${g.message}`):m.info("aibot",`send_msg ws send ok event=${v.event_id??""} session=${v.session_id??""} seq=${o} readyState=${c} bufferedAmount=${a}`)}else if(g){const v=t;m.warn("aibot",`${e} ws send failed seq=${o} session=${v.session_id??""} event=${v.event_id??""} client_msg_id=${v.client_msg_id??""} readyState=${c} bufferedAmount=${a} err=${g.message}`)}const O=this.pendingOutputWrites.get(o),k=!!(O&&O.socket===l&&O.connectionGeneration===d);if(!this.isConnectionCurrent(l,d)){k&&(this.settlePendingOutputWrite(o,!1,!1,l,d),this.settlePendingAcceptedOutput(o,"retry",!1,l,d));return}if(!(i&&_&&!k)){if(g)this.removeSeqFromEventCorrelation(o),_?(this.settlePendingOutputWrite(o,!1,!0,l,d),this.settlePendingAcceptedOutput(o,"retry",!1,l,d)):i&&this.bufferOutboundEntry(i),n!==void 0?this.rejectPendingRequestAfterWriteFailure(o,e,g):i&&this.reconnectAfterOutboundWriteFailure(l,`${e} callback failed`);else if(_){const v=k;this.settlePendingOutputWrite(o,!0,!1,l,d),v&&this.trackNonReplayableOutput(l,e,t,o)}}}),h=!0,!b}catch(c){return this.isConnectionCurrent(l,d)?(this.removeSeqFromEventCorrelation(o),_?(this.settlePendingOutputWrite(o,!1,!0,l,d),this.settlePendingAcceptedOutput(o,"retry",!1,l,d)):i&&this.bufferOutboundEntry(i),this.emitClientError(new Error(`sendPacket failed: ${c}`)),i&&this.reconnectAfterOutboundWriteFailure(l,`${e} send threw`),!1):(this.settlePendingOutputWrite(o,!1,!1,l,d),this.settlePendingAcceptedOutput(o,"retry",!1,l,d),!1)}}}if(p.DROPPABLE_COMMANDS.has(e))return this.packetLog?.logOutboundPacket(e,n??0,t,"dropped"),!1;if(n!==void 0)return this.packetLog?.logOutboundPacket(e,n,t,"dropped"),!1;const r=this.bufferOutboundEntry(i??this.createOutboundEntry(e,t));if(this.packetLog?.logOutboundPacket(e,n??0,t,r?"buffered":"dropped"),r&&e==="client_stream_chunk"){const u=t;m.info("aibot",`stream_chunk buffered (ws not open) event=${u.event_id??""} session=${u.session_id??""} chunk_seq=${u.chunk_seq??""} is_finish=${u.is_finish??""} ws=${this.ws?`state=${this.ws.readyState}`:"null"}`)}return!1}replayTerminalOutbox(){for(const e of this.terminalOutbox.listPending())this.scheduleTerminalDelivery(e.payload.event_id,{ignoreNotBefore:!0})}outboundEventId(e,t){if(!p.ORDERED_OUTPUT_COMMANDS.has(e)||!t||typeof t!="object")return"";const n=t.event_id;return typeof n=="string"?n.trim():""}blockEventOutput(e){const t=e.trim();if(t)for(this.blockedOutputEvents.delete(t),this.blockedOutputEvents.set(t,Date.now()),this.outboundBuffer=this.outboundBuffer.filter(n=>this.outboundEventId(n.cmd,n.payload)!==t);this.blockedOutputEvents.size>p.MAX_BLOCKED_OUTPUT_EVENTS;){const n=this.blockedOutputEvents.keys().next().value;if(!n)break;this.blockedOutputEvents.delete(n)}}isAckRequiredOutput(e,t){return e==="send_msg"?!0:e==="client_stream_chunk"&&!!t&&typeof t=="object"&&t.is_finish===!0}isNonReplayableOutput(e,t){return e==="codex_event"?!0:e==="client_stream_chunk"&&!!t&&typeof t=="object"&&t.is_finish!==!0}trackNonReplayableOutput(e,t,n,s){if(!this.isNonReplayableOutput(t,n))return;const i=this.outboundEventId(t,n);if(!i||this.outputIntegrityFailed.has(i)||this.blockedOutputEvents.has(i))return;let r=this.nonReplayableOutputEventsBySocket.get(e);r||(r=new Map,this.nonReplayableOutputEventsBySocket.set(e,r)),r.set(i,Math.max(r.get(i)??0,s))}clearNonReplayableOutputThrough(e,t,n){if(!e||n<=0)return;const s=this.nonReplayableOutputEventsBySocket.get(e);if(!s)return;const i=s.get(t);i===void 0||i>n||(s.delete(t),s.size===0&&this.nonReplayableOutputEventsBySocket.delete(e))}clearNonReplayableOutputEvent(e){for(const[t,n]of this.nonReplayableOutputEventsBySocket)n.delete(e),n.size===0&&this.nonReplayableOutputEventsBySocket.delete(t)}failNonReplayableOutputsForSocket(e,t){const n=this.nonReplayableOutputEventsBySocket.get(e);if(n){this.nonReplayableOutputEventsBySocket.delete(e);for(const s of n.keys())this.markOutputIntegrityFailed(s,`connection lost before a complete output acceptance fence: ${t}`)}}hasNonReplayableOutput(e){for(const t of this.nonReplayableOutputEventsBySocket.values())if(t.has(e))return!0;return!1}hasPendingOutputAcceptanceFence(e){return!!(this.pendingAcceptedOutputSeqsByEvent.get(e)?.size||this.outboundBuffer.some(t=>this.outboundEventId(t.cmd,t.payload)===e&&this.isAckRequiredOutput(t.cmd,t.payload)))}hasUnconfirmedEventOutput(e){return!!(this.pendingOutputSeqsByEvent.get(e)?.size||this.pendingAcceptedOutputSeqsByEvent.get(e)?.size||this.outboundBuffer.some(t=>this.outboundEventId(t.cmd,t.payload)===e))}trackOutboundEventCorrelation(e,t,n){const s=this.outboundEventId(e,n);if(!s)return;const i=n,r=typeof i.client_msg_id=="string"?i.client_msg_id.trim():"";this.pruneExpiredEventCorrelations();const u=Date.now();let o=this.eventCorrelations.get(s);if(!o){if(this.eventCorrelations.size>=p.MAX_EVENT_CORRELATIONS){let l;for(const d of this.eventCorrelations.values())this.eventHasCriticalDeliveryState(d.eventId)||(!l||d.touchedAt<l.touchedAt)&&(l=d);l&&this.releaseEventCorrelations(l.eventId)}o={eventId:s,clientMsgIds:new Set,seqRanges:[],touchedAt:u,expiresAt:u+p.EVENT_CORRELATION_TTL_MS},this.eventCorrelations.set(s,o)}o.touchedAt=u,o.expiresAt=u+p.EVENT_CORRELATION_TTL_MS;const f=o.seqRanges[o.seqRanges.length-1];if(f&&t===f.end+1?f.end=t:(!f||t<f.start||t>f.end)&&o.seqRanges.push({start:t,end:t}),r){o.clientMsgIds.add(r);let l=this.clientMsgEventMap.get(r);l||(l=new Set,this.clientMsgEventMap.set(r,l)),l.add(s)}this.scheduleEventCorrelationCleanup()}uniqueEventIdForClientMsgId(e){const t=this.clientMsgEventMap.get(e);if(t?.size===1)return t.values().next().value}eventHasCriticalDeliveryState(e){return!!(this.terminalOutbox.get(e)||this.pendingAcceptedOutputSeqsByEvent.get(e)?.size)}eventIdForSeq(e){if(!(e<=0)){for(const t of this.eventCorrelations.values())if(t.seqRanges.some(n=>e>=n.start&&e<=n.end))return t.eventId}}removeSeqFromEventCorrelation(e){if(!(e<=0))for(const t of this.eventCorrelations.values()){const n=t.seqRanges.findIndex(i=>e>=i.start&&e<=i.end);if(n<0)continue;const s=t.seqRanges[n];s.start===s.end?t.seqRanges.splice(n,1):e===s.start?s.start++:e===s.end?s.end--:t.seqRanges.splice(n,1,{start:s.start,end:e-1},{start:e+1,end:s.end});return}}releaseEventCorrelations(e){const t=e.trim();if(!t)return;const n=this.eventCorrelations.get(t);if(n){for(const s of n.clientMsgIds){const i=this.clientMsgEventMap.get(s);i?.delete(t),i?.size===0&&this.clientMsgEventMap.delete(s)}this.eventCorrelations.delete(t)}this.cancelEventCorrelationCleanupIfEmpty()}clearAllEventCorrelations(){this.eventCorrelationCleanupTimer&&(clearTimeout(this.eventCorrelationCleanupTimer),this.eventCorrelationCleanupTimer=null),this.eventCorrelations.clear(),this.clientMsgEventMap.clear(),this.lastCorrelationPruneAt=0}pruneExpiredEventCorrelations(e=Date.now(),t=!1){if(!(!t&&e-this.lastCorrelationPruneAt<1e3)){this.lastCorrelationPruneAt=e;for(const n of[...this.eventCorrelations.values()])n.expiresAt<=e&&!this.eventHasCriticalDeliveryState(n.eventId)&&this.releaseEventCorrelations(n.eventId);this.cancelEventCorrelationCleanupIfEmpty()}}scheduleEventCorrelationCleanup(){if(this.eventCorrelationCleanupTimer||this.eventCorrelations.size===0)return;let e=Number.POSITIVE_INFINITY;for(const t of this.eventCorrelations.values())this.eventHasCriticalDeliveryState(t.eventId)||(e=Math.min(e,t.expiresAt));Number.isFinite(e)&&(this.eventCorrelationCleanupTimer=setTimeout(()=>{this.eventCorrelationCleanupTimer=null,this.pruneExpiredEventCorrelations(Date.now(),!0),this.scheduleEventCorrelationCleanup()},Math.max(1,e-Date.now())),this.eventCorrelationCleanupTimer.unref?.())}cancelEventCorrelationCleanupIfEmpty(){!this.eventCorrelationCleanupTimer||this.eventCorrelations.size>0||(clearTimeout(this.eventCorrelationCleanupTimer),this.eventCorrelationCleanupTimer=null)}handleCorrelatedSendResponse(e){if(e.cmd==="send_ack"){const f=e.payload,l=typeof f.client_msg_id=="string"?f.client_msg_id.trim():"";let d=e.seq>0&&this.pendingAcceptedOutputs.has(e.seq)?e.seq:void 0,_;if(d===void 0&&l){const a=this.pendingAcceptedSeqsByClientMsgId.get(l);if(a?.size===1){const h=a.values().next().value,g=this.pendingAcceptedOutputs.get(h),O=this.uniqueEventIdForClientMsgId(l);g&&O&&g.eventId===O&&(d=h,_=O)}}const b=(d!==void 0?this.pendingAcceptedOutputs.get(d)?.eventId:void 0)??_??this.eventIdForSeq(e.seq),c=d??e.seq;if(d!==void 0){const a=this.pendingOutputWrites.get(d);a&&this.settlePendingOutputWrite(d,!0,!1,a.socket,a.connectionGeneration),this.settlePendingAcceptedOutput(d,"accepted",!1)}else if(e.seq>0){const a=this.pendingOutputWrites.get(e.seq);a&&this.settlePendingOutputWrite(e.seq,!0,!1,a.socket,a.connectionGeneration)}b&&this.clearNonReplayableOutputThrough(this.ws,b,c);return}if(e.cmd!=="send_nack"&&e.cmd!=="error")return;this.pruneExpiredEventCorrelations();const t=e.payload,n=typeof t.client_msg_id=="string"?t.client_msg_id.trim():"",s=e.cmd==="error"&&typeof t.ref_id=="string"?t.ref_id.trim():"",i=e.cmd==="error"&&typeof t.ref_cmd=="string"?t.ref_cmd.trim():"",r=this.eventIdForSeq(e.seq),u=s&&(!i||p.ORDERED_OUTPUT_COMMANDS.has(i))?this.uniqueEventIdForClientMsgId(s)??(this.eventCorrelations.has(s)?s:void 0):void 0,o=r??(n?this.uniqueEventIdForClientMsgId(n):void 0)??u;o&&(this.blockEventOutput(o),this.settlePendingOutputWritesForEvent(o,!0),this.clearNonReplayableOutputEvent(o),e.seq>0&&this.pendingAcceptedOutputs.get(e.seq)?.eventId===o?this.settlePendingAcceptedOutput(e.seq,"rejected",!1):this.settlePendingAcceptedOutputsForEvent(o,"rejected",!1),t.code===4003&&(this.purgeBufferedStreamChunks(o),m.warn("aibot",`event payload rejected (4003), purging buffered chunks for event=${o}`)),this.promotePendingTerminalAfterOutputRejection(o,Number(t.code??0),t.msg),this.emit("streamRejected",o,Number(t.code??0)))}promotePendingTerminalAfterOutputRejection(e,t,n){this.provisionalRespondedTerminals.delete(e);const s=this.terminalOutbox.get(e);if(!(!s||s.payload.status!=="responded"&&s.payload.code!=="agent_output_unconfirmed"||s.deliveryStartedAt))try{const i=this.terminalOutbox.enqueue({...s.payload,status:"failed",code:"agent_output_rejected",msg:n?`required agent output was rejected: ${n}`:`required agent output was rejected by the server (code=${t})`,updated_at:Date.now()});this.scheduleTerminalDelivery(i.payload.event_id,{ignoreNotBefore:!0})}catch(i){this.emitClientError(new Error(`failed to persist output-rejection terminal promotion: event=${e} code=${t}: ${i instanceof Error?i.message:i}`))}}scheduleTerminalDelivery(e,t){if(!this.connected||!this.ws||this.ws.readyState!==y.OPEN)return;if(this.terminalInFlight.has(e)){t?.ignoreNotBefore&&this.terminalReplayAfterFlight.add(e);return}const n=this.terminalOutbox.get(e);if(!n)return;const s=this.terminalRetryTimers.get(e);s&&(clearTimeout(s),this.terminalRetryTimers.delete(e));const i=t?.ignoreNotBefore?0:Math.max(0,n.nextAttemptAt-Date.now()),r=Math.max(i,t?.minimumDelayMs??0);if(r>0){const u=setTimeout(()=>{this.terminalRetryTimers.delete(e),this.scheduleTerminalDelivery(e,{ignoreNotBefore:!0})},r);this.terminalRetryTimers.set(e,u);return}this.deliverTerminalEntry(n)}async deliverTerminalEntry(e){const t=e.payload.event_id;if(this.terminalInFlight.has(t)||!this.terminalOutbox.isCurrent(e))return;this.terminalInFlight.add(t);let n=0;try{n=await this.sendEventResultReliable(e)}finally{this.terminalInFlight.delete(t);const s=this.terminalReplayAfterFlight.delete(t);this.terminalOutbox.get(t)&&this.scheduleTerminalDelivery(t,s?{ignoreNotBefore:!0}:{minimumDelayMs:n})}}clearTerminalRetryTimers(){for(const e of this.terminalRetryTimers.values())clearTimeout(e);this.terminalRetryTimers.clear()}async sendEventResultReliable(e){const t=e.payload.event_id,n=e.payload.terminal_commit_token?.trim();if(n&&!this.negotiatedCapabilities.has("terminal_commit_v1")){const d=this.ws;return this.emitClientError(new Error(`refusing tokenized event_result on an unnegotiated connection: event=${t}`)),d&&this.reconnectAfterOutboundWriteFailure(d,"terminal_commit_v1 not negotiated"),p.TERMINAL_RETRY_DELAY_MS}if(!await this.awaitOutputDeliveryBarrier(t))return p.TERMINAL_RETRY_DELAY_MS;const s=this.provisionalRespondedTerminals.get(t);if(s&&this.terminalOutbox.isCurrent(e))if(!this.canReplaceUnsentOutputGuard(e))this.provisionalRespondedTerminals.delete(t);else try{return this.terminalOutbox.enqueue(s),this.provisionalRespondedTerminals.delete(t),0}catch(d){return this.emitClientError(new Error(`failed to promote confirmed output terminal to responded: event=${t}: ${d instanceof Error?d.message:d}`)),p.TERMINAL_RETRY_DELAY_MS}const i=e.payload;try{if(!this.terminalOutbox.markDeliveryStarted(e))return 0}catch(d){return this.emitClientError(new Error(`event_result delivery-start persist failed: event=${t}: ${d instanceof Error?d.message:d}`)),p.TERMINAL_RETRY_DELAY_MS}const r=Math.max(1,this.ackPolicy?.max_retries??3),u=this.ackPolicy?.push_ack_timeout_ms??5e3,o=750;let f="unknown delivery failure";for(let d=1;d<=r;d++){if(!this.terminalOutbox.isCurrent(e))return 0;const _=this.ws?.readyState??-1,b=this.ws?.bufferedAmount??0;m.info("aibot",`event_result send attempt event=${i.event_id} status=${i.status} attempt=${d}/${r} readyState=${_} bufferedAmount=${b}`);try{const c=await this.sendEventResultRequest(i,u);if(!this.terminalOutbox.isCurrent(e))return 0;if(c.cmd==="send_ack"){const h=c.payload;if(m.info("aibot",`event_result ack event=${i.event_id} status=${i.status} attempt=${d}/${r} ack_event=${h.event_id??""} ack_status=${h.status??""}`),h.event_id!==i.event_id||h.status!==i.status)throw new Error(`event_result ACK mismatch: expected event=${i.event_id} status=${i.status}, got event=${h.event_id??""} status=${h.status??""}`);if(n&&(h.terminal_commit_token?.trim()!==n||h.terminal_committed!==!0))throw new Error(`event_result terminal commit ACK mismatch: event=${i.event_id}`);if(n)try{this.terminalCommitTokens.remove(i.event_id,n)}catch(g){throw new Error(`terminal commit token cleanup failed: event=${i.event_id}: ${g instanceof Error?g.message:g}`)}return this.terminalOutbox.acknowledge(e,h.event_id,h.status,h.terminal_commit_token,h.terminal_committed)&&(this.rememberCommittedTerminalEvent(i.event_id),this.tokenizedTerminalRejections.delete(i.event_id),this.blockEventOutput(i.event_id),this.releaseEventCorrelations(i.event_id),this.clearNonReplayableOutputEvent(i.event_id),this.settleOutputIntegrityTerminal(i.event_id),this.scheduleStopResultsForEvent(i.event_id)),0}const a=c.payload;if(m.warn("aibot",`event_result rejected event=${i.event_id} status=${i.status} attempt=${d}/${r} cmd=${c.cmd} code=${a.code??""} msg=${a.msg??""}${a.ref_cmd?` ref_cmd=${a.ref_cmd}`:""}${a.ref_id?` ref_id=${a.ref_id}`:""}`),n){f=`tokenized terminal rejected: cmd=${c.cmd} code=${a.code??""} msg=${a.msg??""}`;const h=(this.tokenizedTerminalRejections.get(i.event_id)??0)+1;if(this.tokenizedTerminalRejections.set(i.event_id,h),h<p.MAX_TOKENIZED_TERMINAL_REJECTIONS)break;this.tokenizedTerminalRejections.delete(i.event_id);try{this.terminalCommitTokens.remove(i.event_id,n),this.terminalOutbox.moveToDeadLetter(e,{responseCmd:c.cmd,code:a.code,message:a.msg})&&(this.blockEventOutput(i.event_id),this.releaseEventCorrelations(i.event_id),this.clearNonReplayableOutputEvent(i.event_id),this.settleOutputIntegrityTerminal(i.event_id),this.emitClientError(new Error(`tokenized event_result permanently rejected after ${h} rejections, moved to dead-letter: event=${i.event_id} status=${i.status} cmd=${c.cmd} code=${a.code??""} msg=${a.msg??""}`)))}catch(g){f=`dead-letter persist failed: ${g instanceof Error?g.message:g}`,this.emitClientError(new Error(`tokenized event_result dead-letter persist failed: event=${i.event_id} status=${i.status}: ${f}`));break}return 0}try{return this.terminalOutbox.moveToDeadLetter(e,{responseCmd:c.cmd,code:a.code,message:a.msg})&&(this.tokenizedTerminalRejections.delete(i.event_id),this.blockEventOutput(i.event_id),this.releaseEventCorrelations(i.event_id),this.clearNonReplayableOutputEvent(i.event_id),this.settleOutputIntegrityTerminal(i.event_id),this.emitClientError(new Error(`event_result rejected and moved to dead-letter: event=${i.event_id} status=${i.status} cmd=${c.cmd} code=${a.code??""} msg=${a.msg??""}`))),0}catch(h){f=`dead-letter persist failed: ${h instanceof Error?h.message:h}`,this.emitClientError(new Error(`event_result dead-letter persist failed: event=${i.event_id} status=${i.status}: ${f}`));break}}catch(c){const a=c instanceof Error?c.message:String(c);if(f=a,m.warn("aibot",`event_result attempt failed event=${i.event_id} status=${i.status} attempt=${d}/${r} err=${a}`),d===r)break;await new Promise(h=>setTimeout(h,o*d))}}const l=Date.now()+p.TERMINAL_RETRY_DELAY_MS;try{this.terminalOutbox.recordRetry(e,l,f)}catch(d){this.emitClientError(new Error(`event_result retry state persist failed: event=${i.event_id} status=${i.status}: ${d instanceof Error?d.message:d}`))}return this.emitClientError(new Error(`event_result ack failed after ${r} attempts; retained for retry: event=${i.event_id} status=${i.status} err=${f}`)),p.TERMINAL_RETRY_DELAY_MS}purgeBufferedStreamChunks(e){const t=this.outboundBuffer.length;this.outboundBuffer=this.outboundBuffer.filter(n=>n.cmd!=="client_stream_chunk"?!0:n.payload?.event_id!==e),this.outboundBuffer.length<t&&m.info("aibot",`purged ${t-this.outboundBuffer.length} buffered stream chunks for event=${e}`)}emitClientError(e){if(this.listenerCount("error")===0){m.warn("aibot",`Client error (no listeners): ${e.message}`);return}this.emit("error",e)}createOutboundEntry(e,t){const n={cmd:e,payload:t};return this.outboundEntryOrders.set(n,++this.nextOutboundEntryOrder),n}makeOutputIntegrityFailurePayload(e,t){return this.withTerminalCommitToken({event_id:e,status:"failed",code:"agent_output_integrity_failed",msg:`required agent output was discarded before delivery: ${t}`,updated_at:Date.now()})}canReplaceUnsentOutputGuard(e){return e.payload.code==="agent_output_unconfirmed"&&!e.deliveryStartedAt}rememberCommittedTerminalEvent(e){for(this.committedTerminalEvents.delete(e),this.committedTerminalEvents.add(e);this.committedTerminalEvents.size>p.MAX_COMMITTED_TERMINAL_EVENTS;){const t=this.committedTerminalEvents.values().next().value;if(!t)break;this.committedTerminalEvents.delete(t)}}replayStopResultOutbox(){for(const e of this.stopResultOutbox.listPending())this.scheduleStopResultDelivery(e,{ignoreNotBefore:!0})}scheduleStopResultsForEvent(e){for(const t of this.stopResultOutbox.listPending())t.payload.event_id===e&&this.scheduleStopResultDelivery(t,{ignoreNotBefore:!0})}scheduleStopResultDelivery(e,t){if(!this.connected||!this.ws||this.ws.readyState!==y.OPEN||!this.stopResultOutbox.isCurrent(e))return;const n=e.payload.event_id;if(this.terminalOutbox.get(n)||this.terminalCommitTokens.get(n))return;const s=this.stopResultRetryTimers.get(e.key);s&&(clearTimeout(s),this.stopResultRetryTimers.delete(e.key));const i=t?.ignoreNotBefore?0:Math.max(0,e.nextAttemptAt-Date.now());if(i>0){const r=setTimeout(()=>{this.stopResultRetryTimers.delete(e.key),this.scheduleStopResultDelivery(e,{ignoreNotBefore:!0})},i);this.stopResultRetryTimers.set(e.key,r);return}this.deliverStopResultEntry(e)}async deliverStopResultEntry(e){if(this.stopResultInFlight.has(e.key)||!this.stopResultOutbox.isCurrent(e))return;this.stopResultInFlight.add(e.key);let t=p.TERMINAL_RETRY_DELAY_MS;try{t=await this.sendStopResultReliable(e)}finally{if(this.stopResultInFlight.delete(e.key),this.stopResultOutbox.isCurrent(e)){const n=setTimeout(()=>{this.stopResultRetryTimers.delete(e.key),this.scheduleStopResultDelivery(e,{ignoreNotBefore:!0})},t);this.stopResultRetryTimers.set(e.key,n)}}}async sendStopResultReliable(e){const t=this.ackPolicy?.push_ack_timeout_ms??5e3;let n="unknown delivery failure";try{const i=await this.request("event_stop_result",e.payload,{expected:["send_ack","send_nack","error"],timeoutMs:t});if(!this.stopResultOutbox.isCurrent(e))return 0;if(i.cmd==="send_ack"){const r=i.payload;if(this.stopResultOutbox.acknowledge(e,r.event_id,r.terminal_commit_token,r.terminal_committed))return 0;n=`event_stop_result ACK mismatch event=${e.payload.event_id}`}else{const r=i.payload;if(n=`event_stop_result rejected: cmd=${i.cmd} code=${r.code??""} msg=${r.msg??""}`,r.code===4001||r.code===4003){try{this.stopResultOutbox.discard(e)&&this.emitClientError(new Error(`event_stop_result permanently rejected and discarded: event=${e.payload.event_id} stop=${e.payload.stop_id} code=${r.code} msg=${r.msg??""}`))}catch(u){this.emitClientError(new Error(`event_stop_result discard persist failed: event=${e.payload.event_id} stop=${e.payload.stop_id}: ${u instanceof Error?u.message:u}`))}return 0}}}catch(i){n=i instanceof Error?i.message:String(i)}const s=Date.now()+p.TERMINAL_RETRY_DELAY_MS;try{this.stopResultOutbox.recordRetry(e,s,n)}catch(i){this.emitClientError(new Error(`event_stop_result retry state persist failed: event=${e.payload.event_id} stop=${e.payload.stop_id}: ${i instanceof Error?i.message:i}`))}return this.emitClientError(new Error(`event_stop_result retained for retry: event=${e.payload.event_id} stop=${e.payload.stop_id} err=${n}`)),p.TERMINAL_RETRY_DELAY_MS}clearStopResultRetryTimers(){for(const e of this.stopResultRetryTimers.values())clearTimeout(e);this.stopResultRetryTimers.clear()}markOutputIntegrityFailed(e,t){if(this.blockEventOutput(e),this.outputIntegrityFailed.has(e))return;const n={reason:t,terminalObserved:!!(this.provisionalRespondedTerminals.has(e)||this.terminalOutbox.get(e)),terminalSettled:!1};this.outputIntegrityFailed.set(e,n),this.provisionalRespondedTerminals.delete(e),this.clearNonReplayableOutputEvent(e);const s=this.outboundBuffer.length;this.outboundBuffer=this.outboundBuffer.filter(u=>this.outboundEventId(u.cmd,u.payload)!==e);const i=this.terminalOutbox.get(e),r=i?this.canReplaceUnsentOutputGuard(i):!1;if(i&&!r)n.terminalObserved=!0,this.scheduleTerminalDelivery(e,{ignoreNotBefore:!0});else if(!this.committedTerminalEvents.has(e))try{const u=this.terminalOutbox.enqueue(this.makeOutputIntegrityFailurePayload(e,t));this.scheduleTerminalDelivery(u.payload.event_id,{ignoreNotBefore:!0})}catch(u){this.emitClientError(new Error(`output-integrity terminal persist failed: event=${e}: ${u instanceof Error?u.message:u}`))}for(const u of[...this.pendingOutputSeqsByEvent.get(e)??[]])this.settlePendingOutputWrite(u,!1,!1);this.settlePendingAcceptedOutputsForEvent(e,"rejected",!1),this.emit("streamRejected",e,0),this.emitClientError(new Error(`required output integrity failed: event=${e} removed=${s-this.outboundBuffer.length} reason=${t}`))}settleOutputIntegrityTerminal(e){const t=this.outputIntegrityFailed.get(e);t&&(t.terminalSettled=!0,t.terminalObserved&&this.outputIntegrityFailed.delete(e))}bufferOutboundEntry(e){if(this.outboundEntryOrders.has(e)||this.outboundEntryOrders.set(e,++this.nextOutboundEntryOrder),this.outboundBuffer.includes(e))return!0;const t=this.outboundEventId(e.cmd,e.payload);return t&&(this.outputIntegrityFailed.has(t)||this.blockedOutputEvents.has(t))?!1:(this.outboundBuffer.length>=p.MAX_OUTBOUND_BUFFER_SIZE&&(this.outboundBuffer=this.outboundBuffer.filter(n=>p.BUFFER_OVERFLOW_RETAIN_COMMANDS.has(n.cmd)),this.outboundBuffer.length>=p.MAX_OUTBOUND_BUFFER_SIZE&&m.warn("aibot",`outbound buffer soft limit exceeded by ${this.outboundBuffer.length} retained packet(s); preserving required output`)),this.outboundBuffer.length>=p.MAX_OUTBOUND_BUFFER_HARD_SIZE?(t?this.markOutputIntegrityFailed(t,`outbound buffer hard limit ${p.MAX_OUTBOUND_BUFFER_HARD_SIZE} reached`):this.emitClientError(new Error(`outbound buffer hard limit ${p.MAX_OUTBOUND_BUFFER_HARD_SIZE} reached; rejected non-event packet cmd=${e.cmd}`)),!1):(this.outboundBuffer.push(e),this.outboundBuffer.sort((n,s)=>(this.outboundEntryOrders.get(n)??Number.MAX_SAFE_INTEGER)-(this.outboundEntryOrders.get(s)??Number.MAX_SAFE_INTEGER)),!0))}registerPendingOutputWrite(e,t,n,s,i){let r;const u=new Promise(l=>{r=l}),o=setTimeout(()=>{const l=this.pendingOutputWrites.get(e);if(!l||l.socket!==s||l.connectionGeneration!==i)return;if(!this.isConnectionCurrent(s,i)){this.settlePendingOutputWrite(e,!1,!1,s,i);return}this.removeSeqFromEventCorrelation(e);const d=this.isNonReplayableOutput(n.cmd,n.payload);this.settlePendingOutputWrite(e,!1,!d,s,i),d&&this.markOutputIntegrityFailed(t,`websocket write callback timed out for non-replayable ${n.cmd} seq=${e}`),this.emitClientError(new Error(`outbound websocket write callback timeout: cmd=${n.cmd} event=${t} seq=${e} delivery_unknown=${d}`)),this.reconnectAfterOutboundWriteFailure(s,`${n.cmd} callback timeout`)},p.OUTBOUND_WRITE_CALLBACK_TIMEOUT_MS);o.unref?.(),this.pendingOutputWrites.set(e,{eventId:t,entry:n,socket:s,connectionGeneration:i,promise:u,resolve:r,timer:o});let f=this.pendingOutputSeqsByEvent.get(t);f||(f=new Set,this.pendingOutputSeqsByEvent.set(t,f)),f.add(e)}settlePendingOutputWrite(e,t,n,s,i){const r=this.pendingOutputWrites.get(e);if(!r||s&&(r.socket!==s||r.connectionGeneration!==i))return;this.pendingOutputWrites.delete(e),clearTimeout(r.timer);const u=this.pendingOutputSeqsByEvent.get(r.eventId);u?.delete(e),u?.size===0&&this.pendingOutputSeqsByEvent.delete(r.eventId),n&&this.bufferOutboundEntry(r.entry),r.resolve(t)}settlePendingOutputWritesForEvent(e,t){const n=this.pendingOutputSeqsByEvent.get(e);if(n)for(const s of[...n]){const i=this.pendingOutputWrites.get(s);i&&this.settlePendingOutputWrite(s,t,!1,i.socket,i.connectionGeneration)}}abortPendingOutputWrites(e,t,n,s){for(const[i,r]of[...this.pendingOutputWrites.entries()])if(!(t&&(r.socket!==t||r.connectionGeneration!==n))){if(this.removeSeqFromEventCorrelation(i),s&&this.isNonReplayableOutput(r.entry.cmd,r.entry.payload)){this.settlePendingOutputWrite(i,!1,!1,t,n),this.markOutputIntegrityFailed(r.eventId,`connection lost while websocket write completion was unknown: ${s}`);continue}this.settlePendingOutputWrite(i,!1,e,t,n)}}registerPendingAcceptedOutput(e,t,n,s,i){let r;const u=new Promise(h=>{r=h}),o=Math.max(1,this.ackPolicy?.push_ack_timeout_ms??5e3),f=Math.max(1,this.ackPolicy?.max_retries??3),l=Math.max(1e3,Math.ceil(o*.25)),d=Math.max(p.MIN_REQUIRED_OUTPUT_ACK_TIMEOUT_MS,o*f+l),_=setTimeout(()=>{const h=this.pendingAcceptedOutputs.get(e);if(!(!h||h.socket!==s||h.connectionGeneration!==i)){if(!this.isConnectionCurrent(s,i)){this.settlePendingAcceptedOutput(e,"retry",!1,s,i);return}this.settlePendingAcceptedOutput(e,"retry",!0,s,i),this.emitClientError(new Error(`required output ACK timeout; retained for reconnect retry: event=${t} seq=${e}`)),this.reconnectAfterOutboundWriteFailure(s,"required output ACK timeout")}},d);_.unref?.(),this.pendingAcceptedOutputs.set(e,{eventId:t,entry:n,socket:s,connectionGeneration:i,promise:u,resolve:r,timer:_});const b=n.payload,c=typeof b.client_msg_id=="string"?b.client_msg_id.trim():"";if(c){let h=this.pendingAcceptedSeqsByClientMsgId.get(c);h||(h=new Set,this.pendingAcceptedSeqsByClientMsgId.set(c,h)),h.add(e)}let a=this.pendingAcceptedOutputSeqsByEvent.get(t);a||(a=new Set,this.pendingAcceptedOutputSeqsByEvent.set(t,a)),a.add(e)}settlePendingAcceptedOutput(e,t,n,s,i){const r=this.pendingAcceptedOutputs.get(e);if(!r||s&&(r.socket!==s||r.connectionGeneration!==i))return;this.pendingAcceptedOutputs.delete(e),clearTimeout(r.timer);const u=this.pendingAcceptedOutputSeqsByEvent.get(r.eventId);u?.delete(e),u?.size===0&&this.pendingAcceptedOutputSeqsByEvent.delete(r.eventId);const o=r.entry.payload,f=typeof o.client_msg_id=="string"?o.client_msg_id.trim():"";if(f){const l=this.pendingAcceptedSeqsByClientMsgId.get(f);l?.delete(e),l?.size===0&&this.pendingAcceptedSeqsByClientMsgId.delete(f)}n&&this.bufferOutboundEntry(r.entry),r.resolve(t)}settlePendingAcceptedOutputsForEvent(e,t,n){const s=this.pendingAcceptedOutputSeqsByEvent.get(e);if(s)for(const i of[...s])this.settlePendingAcceptedOutput(i,t,n)}abortPendingAcceptedOutputs(e,t,n){for(const[s,i]of[...this.pendingAcceptedOutputs.entries()])t&&(i.socket!==t||i.connectionGeneration!==n)||this.settlePendingAcceptedOutput(s,"retry",e,t,n)}rejectPendingRequestAfterWriteFailure(e,t,n){const s=this.pendingRequests.get(e);s&&(this.pendingRequests.delete(e),clearTimeout(s.timer),s.reject(new Error(`websocket write failed: ${t}: ${n.message}`)))}async awaitOutputDeliveryBarrier(e){const t=this.outboundFlushInFlight;if(t&&!await t.promise)return!1;for(;;){const n=this.pendingOutputSeqsByEvent.get(e);if(!n||n.size===0)break;const s=[...n].map(r=>this.pendingOutputWrites.get(r)?.promise).filter(r=>!!r);if(s.length===0)break;if((await Promise.all(s)).some(r=>!r))return!1}for(;;){const n=this.pendingAcceptedOutputSeqsByEvent.get(e);if(!n||n.size===0)break;const s=[...n].map(r=>this.pendingAcceptedOutputs.get(r)?.promise).filter(r=>!!r);if(s.length===0)break;const i=await Promise.all(s);if(i.includes("retry"))return!1;if(i.includes("rejected")){const r=this.terminalOutbox.get(e);if(!r||r.payload.status==="responded")return!1}}return this.outboundBuffer.some(n=>this.outboundEventId(n.cmd,n.payload)===e)?!1:this.hasNonReplayableOutput(e)?(this.markOutputIntegrityFailed(e,"output flush completed without a complete acceptance fence"),!1):!!(this.connected&&this.ws&&this.ws.readyState===y.OPEN)}isConnectionCurrent(e,t){return this.ws===e&&this.connectionGeneration===t}isOutboundFlushCurrent(e){return!e.finalized&&this.outboundFlushInFlight===e&&this.isConnectionCurrent(e.socket,e.connectionGeneration)}flushOutboundBuffer(e=this.ws,t=this.connectionGeneration){if(!e||!this.isConnectionCurrent(e,t))return Promise.resolve(!1);const n=this.outboundFlushInFlight;if(n){if(!n.finalized&&n.socket===e&&n.connectionGeneration===t)return n.promise;this.cancelOutboundFlush(n,"discard","superseded stale flush")}if(this.outboundBuffer.length===0)return Promise.resolve(!0);const s={connectionGeneration:t,flushGeneration:++this.nextOutboundFlushGeneration,socket:e,promise:Promise.resolve(!1),finalized:!1,activeBatch:null,nextIndex:0,currentWritePending:!1,currentWriteOutcome:null,cancelCurrentWrite:null,remainderHandled:!1};return this.outboundFlushInFlight=s,s.promise=this.performOutboundBufferFlush(s),s.promise.finally(()=>{s.finalized=!0,s.cancelCurrentWrite?.(),s.cancelCurrentWrite=null,this.outboundFlushInFlight===s&&(this.outboundFlushInFlight=null)}),s.promise}async performOutboundBufferFlush(e){for(;this.isOutboundFlushCurrent(e)&&this.outboundBuffer.length>0;){const{socket:t}=e;if(t.readyState!==y.OPEN)return!1;const n=this.outboundBuffer;this.outboundBuffer=[],e.activeBatch=n,e.nextIndex=0,e.remainderHandled=!1;for(let s=0;s<n.length;s++){if(!this.isOutboundFlushCurrent(e))return!1;const i=n[s],{cmd:r,payload:u}=i,o=this.outboundEventId(r,u);if(o&&this.blockedOutputEvents.has(o)){this.packetLog?.logOutboundPacket(r,0,u,"dropped"),e.nextIndex=s+1,e.currentWritePending=!1,e.currentWriteOutcome=null;continue}const f=++this.seq,l={cmd:r,seq:f,payload:u};e.nextIndex=s,e.currentWritePending=!0,e.currentWriteOutcome=null,o&&this.isAckRequiredOutput(r,u)&&this.registerPendingAcceptedOutput(f,o,i,t,e.connectionGeneration);const d=await new Promise(_=>{let b=!1;const c=h=>{b||(b=!0,clearTimeout(a),e.currentWriteOutcome=h,_(h))},a=setTimeout(()=>c("uncertain"),p.OUTBOUND_WRITE_CALLBACK_TIMEOUT_MS);a.unref?.(),e.cancelCurrentWrite=()=>c("canceled");try{this.trackOutboundEventCorrelation(r,f,u),t.send(JSON.stringify(l),h=>{c(h?"retry":"written")})}catch{c("retry")}});if(e.cancelCurrentWrite=null,!this.isOutboundFlushCurrent(e))return!1;if(d!=="written"||t.readyState!==y.OPEN){d==="retry"&&this.removeSeqFromEventCorrelation(f),this.settlePendingAcceptedOutput(f,"retry",!1);const _=this.retainOutboundFlushRemainder(e,d,`auth flush write failed for ${r} seq=${f} outcome=${d}`);return this.emitClientError(new Error(`outbound buffer flush failed at cmd=${r} seq=${f} outcome=${d}; ${_} packet(s) retained`)),!1}this.trackNonReplayableOutput(t,r,u,f),e.currentWritePending=!1,e.currentWriteOutcome="written",e.nextIndex=s+1}e.activeBatch=null,e.currentWritePending=!1,e.currentWriteOutcome=null,e.remainderHandled=!0}return this.isOutboundFlushCurrent(e)}retainOutboundFlushRemainder(e,t,n){if(e.remainderHandled||!e.activeBatch)return 0;e.remainderHandled=!0;const s=e.activeBatch,i=Math.min(e.nextIndex,s.length);let r=i;if(e.currentWritePending&&i<s.length){const o=s[i],f=this.outboundEventId(o.cmd,o.payload);t!=="retry"&&this.isNonReplayableOutput(o.cmd,o.payload)&&f&&(this.markOutputIntegrityFailed(f,n),r=i+1)}let u=0;for(const o of s.slice(r))this.bufferOutboundEntry(o)&&u++;return e.activeBatch=null,e.nextIndex=s.length,e.currentWritePending=!1,u}cancelOutboundFlush(e,t,n){if(e.finalized){this.outboundFlushInFlight===e&&(this.outboundFlushInFlight=null);return}e.finalized=!0,this.outboundFlushInFlight===e&&(this.outboundFlushInFlight=null),t==="connection_lost"?this.retainOutboundFlushRemainder(e,e.currentWriteOutcome,`connection lost during auth flush: ${n}`):(e.remainderHandled=!0,e.activeBatch=null,e.currentWritePending=!1);const s=e.cancelCurrentWrite;e.cancelCurrentWrite=null,s?.()}reconnectAfterOutboundFlushFailure(e,t){this.agentDeleted||!this.isConnectionCurrent(e,t)||this.reconnectAfterOutboundWriteFailure(e,"outbound flush failed")}reconnectAfterOutboundWriteFailure(e,t){if(this.agentDeleted||this.ws!==e)return;const n=this.connectionGeneration;this.connected=!1,this.negotiatedCapabilities.clear(),this.stopHeartbeat(),this.clearTerminalRetryTimers(),this.clearStopResultRetryTimers(),this.rejectAllPendingRequests(t);const s=this.outboundFlushInFlight;s&&s.socket===e&&s.connectionGeneration===n&&this.cancelOutboundFlush(s,"connection_lost",t),this.failNonReplayableOutputsForSocket(e,t),this.abortPendingOutputWrites(!0,e,n,t),this.abortPendingAcceptedOutputs(!0,e,n),this.clearAllEventCorrelations(),this.connectionGeneration++;try{e.close(1011,t)}catch{}this.ws===e&&(this.ws=null),this.attemptReconnect()}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(e=this.ws,t=this.connectionGeneration){if(!(!e||!this.isConnectionCurrent(e,t)))try{e.close()}catch{}}startHeartbeat(){this.stopHeartbeat(),this.heartbeatFailures=0,this.heartbeatTimer=setInterval(()=>{const e=this.ws,t=this.connectionGeneration;!this.connected||!e||this.request("ping",{ts:Date.now()},{expected:["pong"],timeoutMs:5e3}).then(()=>{this.isConnectionCurrent(e,t)&&(this.heartbeatFailures=0)}).catch(()=>{!this.connected||!this.isConnectionCurrent(e,t)||(this.heartbeatFailures++,!(this.heartbeatFailures<p.HEARTBEAT_MAX_FAILURES)&&(this.cleanupSocket(e,t),this.attemptReconnect()))})},this.heartbeatSec*1e3)}stopHeartbeat(){this.heartbeatTimer&&(clearInterval(this.heartbeatTimer),this.heartbeatTimer=null)}}export{p as AibotClient,D as preprocessLargeIntegers,x as withRequiredCapabilities};
@@ -1 +1 @@
1
- import y from"node:http";import{randomBytes as O}from"node:crypto";import{log as c}from"../log/index.js";import{PHASE1_INVOKE_ACTIONS as S}from"./tools.js";import{WebSocketServer as w,WebSocket as k}from"ws";const f=1024*1024,v=32*1024*1024,H=50,b=1e3,g=6e4,N=256,J=new Set([...S,"event_tool_call"]);class E{server=null;port=0;authToken;invokeHandler=null;statusLineHandler=null;otelHandler=null;activeInvokes=0;wss=null;bridgeWs=null;mcpBridgeUpHandler=null;downstreamOutbox=[];constructor(){this.authToken=O(32).toString("hex")}setInvokeHandler(e){this.invokeHandler=e}setStatusLineHandler(e){this.statusLineHandler=e}setOtelHandler(e){this.otelHandler=e}async start(e=0){return new Promise((t,r)=>{this.server=y.createServer((o,i)=>this.handleRequest(o,i)),this.wss=new w({noServer:!0}),this.server.on("upgrade",(o,i,n)=>{const s=new URL(o.url??"/",`http://127.0.0.1:${this.port}`);if(s.pathname!=="/mcp-bridge"||s.searchParams.get("token")!==this.authToken){i.destroy();return}this.wss.handleUpgrade(o,i,n,d=>{if(this.bridgeWs=d,c.info("internal-api","mcp bridge connected"),this.downstreamOutbox.length>0){const a=this.downstreamOutbox;this.downstreamOutbox=[];for(const l of a)d.send(l);c.info("internal-api",`mcp bridge flushed ${a.length} buffered downstream frame(s)`)}d.on("message",a=>{let l;try{l=JSON.parse(a.toString("utf8"))}catch{return}this.mcpBridgeUpHandler?.(l)}),d.on("close",()=>{this.bridgeWs===d&&(this.bridgeWs=null),c.info("internal-api","mcp bridge disconnected")}),d.on("error",()=>{})})}),this.server.on("error",r),this.server.listen(e,"127.0.0.1",()=>{const o=this.server.address();typeof o=="object"&&o&&(this.port=o.port),this.server.removeListener("error",r),t(this.port)})})}get url(){return`http://127.0.0.1:${this.port}?token=${this.authToken}`}get baseUrl(){return`http://127.0.0.1:${this.port}`}get mcpBridgeWsUrl(){return`ws://127.0.0.1:${this.port}/mcp-bridge?token=${this.authToken}`}setMcpBridgeUpHandler(e){this.mcpBridgeUpHandler=e}sendMcpFrameToBridge(e){const t=JSON.stringify(e);if(this.bridgeWs&&this.bridgeWs.readyState===k.OPEN){this.bridgeWs.send(t);return}this.downstreamOutbox.length>=N&&(this.downstreamOutbox.shift(),c.warn("internal-api","mcp downstream outbox full, dropping oldest frame")),this.downstreamOutbox.push(t)}get token(){return this.authToken}async stop(){if(this.server)return new Promise(e=>{this.server.close(()=>{this.server=null,e()})})}checkAuth(e){return e.headers.authorization===`Bearer ${this.authToken}`||new URL(e.url??"/",`http://127.0.0.1:${this.port}`).searchParams.get("token")===this.authToken}handleRequest(e,t){if(e.method!=="POST"){t.writeHead(405),t.end(JSON.stringify({error:"method not allowed"}));return}if(!this.checkAuth(e)){t.writeHead(401),t.end(JSON.stringify({ok:!1,error:"unauthorized"}));return}const r=new URL(e.url??"/",`http://127.0.0.1:${this.port}`);if(r.pathname==="/api/status-line"){this.handleStatusLine(e,t);return}if(r.pathname==="/v1/logs"||r.pathname==="/v1/traces"){this.handleOtel(r.pathname==="/v1/logs"?"logs":"traces",e,t);return}if(!r.pathname.startsWith("/api/invoke")){t.writeHead(404),t.end(JSON.stringify({error:"not found"}));return}const o=[];let i=0;e.on("data",n=>{if(i+=n.length,i>f){t.writeHead(413),t.end(JSON.stringify({ok:!1,error:"request body too large"})),e.destroy();return}o.push(n)}),e.on("end",async()=>{let n;try{n=JSON.parse(Buffer.concat(o).toString("utf8"))}catch{t.writeHead(400),t.end(JSON.stringify({ok:!1,error:"invalid JSON"}));return}const s=n.action,d=n.params??{};if(!s||!this.invokeHandler){t.writeHead(400),t.end(JSON.stringify({ok:!1,error:"missing action or no handler"}));return}if(!J.has(s)){t.writeHead(400),t.end(JSON.stringify({ok:!1,error:`action '${s}' is not allowed`}));return}if(this.activeInvokes>=H){t.writeHead(429),t.end(JSON.stringify({ok:!1,error:"too many concurrent invokes"}));return}const a=n.timeout_ms!=null?Math.max(b,Math.min(n.timeout_ms,g)):g,l=Date.now();this.activeInvokes++;const u=new AbortController;try{const h=await Promise.race([this.invokeHandler(s,d,u.signal,a),new Promise((p,m)=>setTimeout(()=>{u.abort(),m(new Error("invoke timeout"))},a))]);t.writeHead(200,{"content-type":"application/json"}),t.end(JSON.stringify({ok:!0,data:h})),c.info("internal-api",`invoke ok action=${s} tool=${d.tool_name??"-"} ms=${Date.now()-l}`)}catch(h){u.abort();const p=h instanceof Error?h.message:String(h);t.writeHead(500),t.end(JSON.stringify({ok:!1,error:p})),c.error("internal-api",`invoke error action=${s} tool=${d.tool_name??"-"} ms=${Date.now()-l} err=${p}`)}finally{this.activeInvokes--}})}handleStatusLine(e,t){const r=[];let o=0;e.on("data",i=>{if(o+=i.length,o>f){t.writeHead(413),t.end(JSON.stringify({ok:!1,error:"request body too large"})),e.destroy();return}r.push(i)}),e.on("end",async()=>{let i;try{i=JSON.parse(Buffer.concat(r).toString("utf8"))}catch{t.writeHead(400),t.end(JSON.stringify({ok:!1,error:"invalid JSON"}));return}if(!this.statusLineHandler){t.writeHead(200),t.end(JSON.stringify({ok:!0}));return}try{await this.statusLineHandler(i),t.writeHead(200,{"content-type":"application/json"}),t.end(JSON.stringify({ok:!0}))}catch(n){console.error("[internal-api] statusLine handler error:",n instanceof Error?n.message:n),t.writeHead(200,{"content-type":"application/json"}),t.end(JSON.stringify({ok:!0}))}})}handleOtel(e,t,r){const o=[];let i=0,n=!1;t.on("data",s=>{if(!n){if(i+=s.length,i>v){n=!0,r.writeHead(413,{"content-type":"application/json"}),r.end(JSON.stringify({error:"OTLP request body too large"}));return}o.push(s)}}),t.on("end",async()=>{if(n)return;const s=Buffer.concat(o).toString("utf8");let d;try{const a=JSON.parse(s);if(!a||typeof a!="object"||Array.isArray(a))throw new Error("OTLP body is not an object");d=a}catch{r.writeHead(400,{"content-type":"application/json"}),r.end(JSON.stringify({error:"invalid OTLP JSON"}));return}try{await this.otelHandler?.(e,d,s)}catch(a){c.warn("internal-api",`OTLP ${e} evidence persistence failed: ${a instanceof Error?a.message:String(a)}`)}r.writeHead(200,{"content-type":"application/json"}),r.end("{}")})}}export{E as InternalApiServer};
1
+ import y from"node:http";import{randomBytes as O}from"node:crypto";import{log as c}from"../log/index.js";import{PHASE1_INVOKE_ACTIONS as S}from"./tools.js";import{WebSocketServer as w,WebSocket as k}from"ws";const f=1024*1024,v=32*1024*1024,H=50,b=1e3,g=75e3,N=256,J=new Set([...S,"event_tool_call"]);class E{server=null;port=0;authToken;invokeHandler=null;statusLineHandler=null;otelHandler=null;activeInvokes=0;wss=null;bridgeWs=null;mcpBridgeUpHandler=null;downstreamOutbox=[];constructor(){this.authToken=O(32).toString("hex")}setInvokeHandler(e){this.invokeHandler=e}setStatusLineHandler(e){this.statusLineHandler=e}setOtelHandler(e){this.otelHandler=e}async start(e=0){return new Promise((t,r)=>{this.server=y.createServer((o,i)=>this.handleRequest(o,i)),this.wss=new w({noServer:!0}),this.server.on("upgrade",(o,i,n)=>{const s=new URL(o.url??"/",`http://127.0.0.1:${this.port}`);if(s.pathname!=="/mcp-bridge"||s.searchParams.get("token")!==this.authToken){i.destroy();return}this.wss.handleUpgrade(o,i,n,d=>{if(this.bridgeWs=d,c.info("internal-api","mcp bridge connected"),this.downstreamOutbox.length>0){const a=this.downstreamOutbox;this.downstreamOutbox=[];for(const l of a)d.send(l);c.info("internal-api",`mcp bridge flushed ${a.length} buffered downstream frame(s)`)}d.on("message",a=>{let l;try{l=JSON.parse(a.toString("utf8"))}catch{return}this.mcpBridgeUpHandler?.(l)}),d.on("close",()=>{this.bridgeWs===d&&(this.bridgeWs=null),c.info("internal-api","mcp bridge disconnected")}),d.on("error",()=>{})})}),this.server.on("error",r),this.server.listen(e,"127.0.0.1",()=>{const o=this.server.address();typeof o=="object"&&o&&(this.port=o.port),this.server.removeListener("error",r),t(this.port)})})}get url(){return`http://127.0.0.1:${this.port}?token=${this.authToken}`}get baseUrl(){return`http://127.0.0.1:${this.port}`}get mcpBridgeWsUrl(){return`ws://127.0.0.1:${this.port}/mcp-bridge?token=${this.authToken}`}setMcpBridgeUpHandler(e){this.mcpBridgeUpHandler=e}sendMcpFrameToBridge(e){const t=JSON.stringify(e);if(this.bridgeWs&&this.bridgeWs.readyState===k.OPEN){this.bridgeWs.send(t);return}this.downstreamOutbox.length>=N&&(this.downstreamOutbox.shift(),c.warn("internal-api","mcp downstream outbox full, dropping oldest frame")),this.downstreamOutbox.push(t)}get token(){return this.authToken}async stop(){if(this.server)return new Promise(e=>{this.server.close(()=>{this.server=null,e()})})}checkAuth(e){return e.headers.authorization===`Bearer ${this.authToken}`||new URL(e.url??"/",`http://127.0.0.1:${this.port}`).searchParams.get("token")===this.authToken}handleRequest(e,t){if(e.method!=="POST"){t.writeHead(405),t.end(JSON.stringify({error:"method not allowed"}));return}if(!this.checkAuth(e)){t.writeHead(401),t.end(JSON.stringify({ok:!1,error:"unauthorized"}));return}const r=new URL(e.url??"/",`http://127.0.0.1:${this.port}`);if(r.pathname==="/api/status-line"){this.handleStatusLine(e,t);return}if(r.pathname==="/v1/logs"||r.pathname==="/v1/traces"){this.handleOtel(r.pathname==="/v1/logs"?"logs":"traces",e,t);return}if(!r.pathname.startsWith("/api/invoke")){t.writeHead(404),t.end(JSON.stringify({error:"not found"}));return}const o=[];let i=0;e.on("data",n=>{if(i+=n.length,i>f){t.writeHead(413),t.end(JSON.stringify({ok:!1,error:"request body too large"})),e.destroy();return}o.push(n)}),e.on("end",async()=>{let n;try{n=JSON.parse(Buffer.concat(o).toString("utf8"))}catch{t.writeHead(400),t.end(JSON.stringify({ok:!1,error:"invalid JSON"}));return}const s=n.action,d=n.params??{};if(!s||!this.invokeHandler){t.writeHead(400),t.end(JSON.stringify({ok:!1,error:"missing action or no handler"}));return}if(!J.has(s)){t.writeHead(400),t.end(JSON.stringify({ok:!1,error:`action '${s}' is not allowed`}));return}if(this.activeInvokes>=H){t.writeHead(429),t.end(JSON.stringify({ok:!1,error:"too many concurrent invokes"}));return}const a=n.timeout_ms!=null?Math.max(b,Math.min(n.timeout_ms,g)):g,l=Date.now();this.activeInvokes++;const u=new AbortController;try{const h=await Promise.race([this.invokeHandler(s,d,u.signal,a),new Promise((p,m)=>setTimeout(()=>{u.abort(),m(new Error("invoke timeout"))},a))]);t.writeHead(200,{"content-type":"application/json"}),t.end(JSON.stringify({ok:!0,data:h})),c.info("internal-api",`invoke ok action=${s} tool=${d.tool_name??"-"} ms=${Date.now()-l}`)}catch(h){u.abort();const p=h instanceof Error?h.message:String(h);t.writeHead(500),t.end(JSON.stringify({ok:!1,error:p})),c.error("internal-api",`invoke error action=${s} tool=${d.tool_name??"-"} ms=${Date.now()-l} err=${p}`)}finally{this.activeInvokes--}})}handleStatusLine(e,t){const r=[];let o=0;e.on("data",i=>{if(o+=i.length,o>f){t.writeHead(413),t.end(JSON.stringify({ok:!1,error:"request body too large"})),e.destroy();return}r.push(i)}),e.on("end",async()=>{let i;try{i=JSON.parse(Buffer.concat(r).toString("utf8"))}catch{t.writeHead(400),t.end(JSON.stringify({ok:!1,error:"invalid JSON"}));return}if(!this.statusLineHandler){t.writeHead(200),t.end(JSON.stringify({ok:!0}));return}try{await this.statusLineHandler(i),t.writeHead(200,{"content-type":"application/json"}),t.end(JSON.stringify({ok:!0}))}catch(n){console.error("[internal-api] statusLine handler error:",n instanceof Error?n.message:n),t.writeHead(200,{"content-type":"application/json"}),t.end(JSON.stringify({ok:!0}))}})}handleOtel(e,t,r){const o=[];let i=0,n=!1;t.on("data",s=>{if(!n){if(i+=s.length,i>v){n=!0,r.writeHead(413,{"content-type":"application/json"}),r.end(JSON.stringify({error:"OTLP request body too large"}));return}o.push(s)}}),t.on("end",async()=>{if(n)return;const s=Buffer.concat(o).toString("utf8");let d;try{const a=JSON.parse(s);if(!a||typeof a!="object"||Array.isArray(a))throw new Error("OTLP body is not an object");d=a}catch{r.writeHead(400,{"content-type":"application/json"}),r.end(JSON.stringify({error:"invalid OTLP JSON"}));return}try{await this.otelHandler?.(e,d,s)}catch(a){c.warn("internal-api",`OTLP ${e} evidence persistence failed: ${a instanceof Error?a.message:String(a)}`)}r.writeHead(200,{"content-type":"application/json"}),r.end("{}")})}}export{E as InternalApiServer};
@@ -1 +1 @@
1
- const r=[{name:"grix_query",description:"Search contacts, sessions, message history, messages, or favorited sessions in the Grix/AIBot platform. message_history defaults to the newest one clean text/approval-card message and excludes process/tool cards.",inputSchema:{type:"object",properties:{action:{type:"string",enum:["contact_search","session_search","message_history","message_search","search_favorite_sessions"],description:"Query action type. search_favorite_sessions returns the owner's favorited sessions (supports keyword filter)."},id:{type:"string",description:"Contact ID (contact_search) or Session ID (session_search)."},keyword:{type:"string",description:"Search keyword."},sessionType:{type:"integer",enum:[1,2],description:"Filter by session type (session_search only): 1=private chat, 2=group chat. Omit to return all."},limit:{type:"integer",description:"Max results. message_history defaults to 1 when omitted."},offset:{type:"integer",description:"Result offset."},sessionId:{type:"string",description:"Session ID (message_history, message_search)."},beforeId:{type:"string",description:"Pagination cursor (message_history, message_search)."}},required:["action"]},validation:{required:["action"],properties:{action:{type:"string",enum:["contact_search","session_search","message_history","message_search","search_favorite_sessions"]},id:{type:"string"},keyword:{type:"string",maxLength:200},sessionType:{type:"integer",enum:[1,2]},limit:{type:"integer",minimum:1,maximum:100},offset:{type:"integer",minimum:0},sessionId:{type:"string"},beforeId:{type:"string"}}}},{name:"grix_audit_data",description:"Read connector-backed conversation audit replay data from Grix/AIBot: manifest, span pages, and content chunks.",inputSchema:{type:"object",properties:{action:{type:"string",enum:["get_manifest","list_spans","get_content_chunk"],description:"Audit read action. Start with get_manifest, then call list_spans and/or get_content_chunk based on the manifest."},auditId:{type:"string",description:"Audit ID returned by the audited turn metadata."},revision:{type:"integer",description:"Optional manifest revision to read. Usually use the revision returned by get_manifest."},cursor:{type:"string",description:"Opaque pagination cursor returned by list_spans or get_content_chunk."},limit:{type:"integer",description:"Max span items for list_spans."},contentId:{type:"string",description:"Content reference ID from manifest.content_refs (get_content_chunk only)."},maxBytes:{type:"integer",description:"Max UTF-8 bytes to return for get_content_chunk."}},required:["action","auditId"]},validation:{required:["action","auditId"],properties:{action:{type:"string",enum:["get_manifest","list_spans","get_content_chunk"]},auditId:{type:"string"},revision:{type:"integer",minimum:1},cursor:{type:"string",maxLength:4096},limit:{type:"integer",minimum:1,maximum:200},contentId:{type:"string"},maxBytes:{type:"integer",minimum:1,maximum:131072}}}},{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"],description:"Group action type."},sessionId:{type:"string",description:"Group session ID."},name:{type:"string",description:"Group name (create)."},memberIds:{type:"array",items:{type:"string"},description:"Member IDs to add/remove."},memberTypes:{type:"array",items:{type:"integer",enum:[1,2]},description:"Member types (1=user, 2=agent)."},memberId:{type:"string",description:"Target member ID."},role:{type:"integer",enum:[1,2],description:"New role (1=admin, 2=member)."},memberType:{type:"integer",description:"Member type."},allMembersMuted:{type:"boolean",description:"Whether to mute all members."},isSpeakMuted:{type:"boolean",description:"Whether member is muted."},canSpeakWhenAllMuted:{type:"boolean",description:"Allow speaking when all muted."}},required:["action"]},validation:{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"}}}},{name:"grix_message_send",description:"Send a message to a session in the Grix/AIBot platform.",inputSchema:{type:"object",properties:{sessionId:{type:"string",description:"Target session ID"},content:{type:"string",description:"Message content"},msgType:{type:"integer",description:"Message type (1=text, default 1)"},quotedMessageId:{type:"string",description:"Message ID to reply to"},threadId:{type:"string",description:"Thread ID for threaded reply"}},required:["sessionId","content"]},validation:{required:["sessionId","content"],properties:{sessionId:{type:"string"},content:{type:"string",maxLength:1e4},msgType:{type:"integer"},quotedMessageId:{type:"string"},threadId:{type:"string"}}}},{name:"grix_message_unsend",description:"Recall/unsend a message in the Grix/AIBot platform.",inputSchema:{type:"object",properties:{sessionId:{type:"string",description:"Session ID"},msgId:{type:"string",description:"Message ID to unsend"}},required:["sessionId","msgId"]},validation:{required:["sessionId","msgId"],properties:{sessionId:{type:"string"},msgId:{type:"string"}}}},{name:"grix_file_link",description:"Create a direct, tailnet-only download link for a local file on this host. Use this whenever the user asks you to send, share, give, or deliver a file that exists on the machine where you run (a report, log, build artifact, export, or any local path). It returns a ready-to-use Markdown link in the `markdown` field \u2014 include that exact Markdown link in your reply so the user can click and download the file directly over the shared Tailscale network. The link is reachable only inside the tailnet, so just send it as-is \u2014 no need to worry about or mention any link lifetime. The download link is HTTPS, served by a built-in self-signed CA. The result also returns `ca_install_url`: the first time you share a link with a user (or whenever their browser warns the cert is untrusted), also give them this CA install link so they can install and trust it once \u2014 after that all download links work without warnings. Requires this host to be on a tailnet (Tailscale running).",inputSchema:{type:"object",properties:{file_path:{type:"string",description:"Absolute path to a local file on this host to share with the user."},ttl_ms:{type:"integer",description:"Optional link lifetime in milliseconds. Leave unset to use the long default; set only if you deliberately want a short-lived link."}},required:["file_path"]},validation:{required:["file_path"],properties:{file_path:{type:"string",maxLength:4096},ttl_ms:{type:"integer",minimum:1e4,maximum:864e5}}}},{name:"grix_file_upload",description:"Upload a local file to the Grix platform and send it as a media message in the target session. Supports images (jpg/png/webp/gif/bmp/heic/heif), videos (mp4/mov/m4v/webm/mkv/avi), documents (pdf/doc/docx/xls/xlsx/ppt/pptx/txt/md/csv/json/xml), and archives (zip/rar/7z/tar/gz). Max 50 MB. Use this instead of grix_file_link when the file should appear as a native attachment in the chat (visible inline for images/videos), rather than a tailnet download link.",inputSchema:{type:"object",properties:{file_path:{type:"string",description:"Absolute path to a local file to upload."},session_id:{type:"string",description:"Target session ID to send the file to."},caption:{type:"string",description:"Optional text caption for the media message."},reply_to_message_id:{type:"string",description:"Optional message ID to quote/reply to."}},required:["file_path","session_id"]},validation:{required:["file_path","session_id"],properties:{file_path:{type:"string",maxLength:4096},session_id:{type:"string"},caption:{type:"string",maxLength:2e3},reply_to_message_id:{type:"string"}}}},{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"],description:"Admin action type."},agentName:{type:"string",description:"Agent name (create_agent)."},introduction:{type:"string",description:"Professional behavioral introduction describing the Agent purpose, responsibilities, operating expectations, and boundaries (create_agent)."},isMain:{type:"boolean",description:"Set as main agent (create_agent)."},agentId:{type:"string",description:"Agent ID (assign_category, rotate_api_key)."},categoryId:{type:"string",description:"Category ID (create_agent, update_category, assign_category)."},name:{type:"string",description:"Category name (create_category, update_category)."},parentId:{type:"string",description:"Parent category ID (create_category, update_category)."},sortOrder:{type:"integer",description:"Sort order (create_category, update_category)."}},required:["action"]},validation:{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"}}}},{name:"grix_call_owner",description:"Call your owner into this session to talk by voice. Use this when, during your work, you need to reach your owner \u2014 to discuss something or to get an approval/review. It sends the owner an offline notification; when they tap it they land directly in this conversation and a voice-brain call is started automatically. Requires the owner to have configured a voice brain. Rate-limited per session.",inputSchema:{type:"object",properties:{session_id:{type:"string",description:"The session ID to call the owner into."}},required:["session_id"]},validation:{required:["session_id"],properties:{session_id:{type:"string"}}}},{name:"grix_agent_update",description:"Update the display name and/or text introduction of one of your owner's agents, identified by its numeric agent ID. Provide agent_name, introduction, or both \u2014 at least one is required.",inputSchema:{type:"object",properties:{agent_id:{type:"string",description:"Target agent's numeric ID, passed as a string."},agent_name:{type:"string",description:"New display name (max 100 characters, must be unique among the owner's agents)."},introduction:{type:"string",description:"New text introduction (max 300 characters)."}},required:["agent_id"]},validation:{required:["agent_id"],properties:{agent_id:{type:"string"},agent_name:{type:"string",maxLength:100},introduction:{type:"string",maxLength:300}}}},{name:"grix_dispatch_agent",description:`Dispatch one of your owner's agents to do work in a given working directory. Provide the target agent numeric ID, the working directory, and a text description of the task. The backend creates a NEW private session between the owner and that agent for each dispatch (it does not reuse past sessions), binds the working directory when the agent type requires it (claude/codex/etc.), and sends the task into the session AS THE OWNER so the agent starts working. Because the task is delivered as the owner, write it in the owner's first-person voice and tone \u2014 phrase it the way the owner would speak directly to the agent (e.g. "\u5E2E\u6211\u2026", "\u4F60\u53BB\u2026"), NOT as a third-person relay or as yourself narrating on the owner's behalf. Provide a short title summarizing the core of the task \u2014 it becomes the new session's title; if omitted the backend derives one from the task text.`,inputSchema:{type:"object",properties:{agent_id:{type:"string",description:"Target agent's numeric ID, passed as a string."},cwd:{type:"string",description:"Absolute working directory where the agent should do the work."},task:{type:"string",description:"Text description of the task to perform, written in the owner's first-person voice and tone \u2014 it is delivered into the session as the owner, so phrase it as the owner speaking directly to the agent, not as a third-person relay."},title:{type:"string",description:"Optional short title (a few words) summarizing the core of the task; used as the new session's title. If omitted, the backend derives a title from the task text."}},required:["agent_id","cwd","task"]},validation:{required:["agent_id","cwd","task"],properties:{agent_id:{type:"string"},cwd:{type:"string",maxLength:4096},task:{type:"string",maxLength:1e4},title:{type:"string",maxLength:255}}}},{name:"grix_session_send",description:"Send a message into a session AS THE OWNER \u2014 it appears as if the owner sent it, NOT as you (the agent). Dispatch callbacks (report_dispatch_result) MUST use this tool, even when you are a member of the callback session: a message sent as yourself cannot quote-wake the dispatcher agent, and your membership is not checked. NEVER use this for your own ordinary replies in a conversation \u2014 that would make your words show up as the owner's message; reply normally (or use grix_message_send to send as yourself) for those. The owner must be a member of the target session. Requires the Send as Owner permission scope \u2014 if rejected for missing permission, surface the error as-is so the owner can grant it; do not fall back to grix_message_send. Optional quoted_message_id quotes a message in the same session (used by dispatch callbacks to wake the dispatcher agent).",inputSchema:{type:"object",properties:{session_id:{type:"string",description:"Target session ID."},content:{type:"string",description:"Message content to send as the owner."},quoted_message_id:{type:"string",description:"Optional message ID in the target session to quote/reply to.",pattern:"^[1-9]\\d*$"}},required:["session_id","content"]},validation:{required:["session_id","content"],properties:{session_id:{type:"string"},content:{type:"string",maxLength:1e4},quoted_message_id:{type:"string",pattern:"^[1-9]\\d*$"}}}},{name:"grix_chat_state_query",description:"Query the chat-level task states across all of your owner's sessions (including direct and group chats). Returns one entry per session with a single mutually-exclusive state: running (working), waiting_approval (blocked on your owner to approve/deny), waiting_question (asked the owner a question, awaiting their reply), completed, failed, or idle (no task / stopped). When session_id identifies one completed session, the entry also includes exactly one clean final_result; non-completed and list queries never include message content. Also returns the session title (task_title) for easy identification. Supports pagination (page/page_size) and optional state filtering. Use this to see at a glance which chats are done, still running, or waiting on the owner.",inputSchema:{type:"object",properties:{session_id:{type:"string",description:"(Optional) Query a single session by its ID. Omit to return all sessions."},page:{type:"number",description:"(Optional) Page number, starting from 1. Defaults to 1 if omitted."},page_size:{type:"number",description:"(Optional) Number of items per page, max 100. Defaults to 10 if omitted."},state:{type:"string",description:"(Optional) Filter by a specific state: running, waiting_approval, waiting_question, completed, failed, or idle. Omit to return all states."}}},validation:{required:[],properties:{}}},{name:"grix_chat_state_update",description:"Manually update the task state of a specific chat session. Use this to mark a chat as completed, failed, idle, or any other state when you need to override it manually. The reason is written to stop_reason and is optional.",inputSchema:{type:"object",properties:{session_id:{type:"string",description:"The session ID whose state to update."},state:{type:"string",enum:["running","waiting_approval","waiting_question","completed","failed","idle"],description:"The new state to set. Must be one of: running, waiting_approval, waiting_question, completed, failed, idle."},reason:{type:"string",description:"(Optional) Reason for the state change, written to stop_reason."}}},validation:{required:["session_id","state"],properties:{}}},{name:"grix_access_control",description:"Manage sender access control: pair approval, allow/remove senders, set policy.",inputSchema:{type:"object",properties:{action:{type:"string",enum:["pair_approve","pair_deny","allow_sender","remove_sender","set_policy"],description:"Access control action type."},code:{type:"string",description:"Pairing code (required for pair_approve/pair_deny)."},sender_id:{type:"string",description:"Sender ID (required for allow_sender/remove_sender)."},policy:{type:"string",enum:["allowlist","open","disabled"],description:"Access policy (required for set_policy)."}},required:["action"]},validation:{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"]}}}},{name:"grix_widget_visitor_ban",description:"Ban a widget visitor session: blocks the session and adds its most recent init IP to the owner's global IP ban list (7-day expiry).",inputSchema:{type:"object",properties:{session_id:{type:"string",description:"Widget visitor session ID (required)."}},required:["session_id"]},validation:{required:["session_id"],properties:{session_id:{type:"string"}}}},{name:"grix_skill_set",description:"Create, update, or delete one of your owner's custom skills in the platform skill library. The skill library auto-syncs to every machine the owner runs an agent on (a per-machine `grix/skills` directory), so defining a skill here makes it available on all of them. A skill is a plain SKILL.md package (frontmatter + body), same standard as other Grix skills. Provide `name` and the full `content` (the SKILL.md text) to create or overwrite by name. To delete a skill, pass its `name` with an empty `content` string. How and when any agent actually loads/uses a synced skill is arranged by the owner (in a system prompt or in conversation); this tool only manages the library.",inputSchema:{type:"object",properties:{name:{type:"string",description:"Skill name (max 100 characters); unique within the owner. Used as the identifier and the synced directory name."},content:{type:"string",description:"Full SKILL.md text. Pass an empty string to delete the skill by name."}},required:["name","content"]},validation:{required:["name","content"],properties:{name:{type:"string",maxLength:100},content:{type:"string",maxLength:262144}}}},{name:"grix_skill_get",description:"Read one of your owner's custom skills by name (returns its full SKILL.md content), or list the owner's skill library when no name is given. Use this before editing to fetch current content, then call grix_skill_set with the revised text.",inputSchema:{type:"object",properties:{name:{type:"string",description:"Skill name to fetch. Omit to list all skills (names + versions) instead."}}},validation:{required:[],properties:{name:{type:"string",maxLength:100}}}}],a=[{name:"grix_reply",description:"Send your final reply \u2014 the conclusion the user is waiting for \u2014 to the specified session. This is the message the user treats as your answer; deliver your final result here, and use plain text only for brief progress notes while you work. Supports streaming in chunks; the frontend automatically aggregates them into one complete message. The connector quotes the message being answered automatically, so quoted_message_id is optional (set it only to quote a different earlier message).",inputSchema:{type:"object",properties:{event_id:{type:"string",description:"Associated event ID from the inbound event."},session_id:{type:"string",description:"Target session ID."},text:{type:"string",description:"Reply text content."},quoted_message_id:{type:"string",description:"Quoted message ID (optional)."},is_final:{type:"boolean",description:"Whether this is a stage-final reply. Advisory only \u2014 does not trigger event completion; completion is handled by the complete tool or Stop hook."}},required:["session_id","text"]},validation:{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"}}}},{name:"grix_complete",description:"Mark event processing as complete, notifying the backend that no more replies are expected.",inputSchema:{type:"object",properties:{event_id:{type:"string",description:"The event ID to complete."},status:{type:"string",enum:["responded","canceled","failed"],description:"Completion status."},msg:{type:"string",description:"Additional note (optional)."}},required:["event_id","status"]},validation:{required:["event_id","status"],properties:{event_id:{type:"string"},status:{type:"string",enum:["responded","canceled","failed"]},msg:{type:"string",maxLength:500}}}},{name:"grix_event_ack",description:"Acknowledge event receipt (usually done automatically by the Dispatcher; agents typically do not need to call this manually).",inputSchema:{type:"object",properties:{event_id:{type:"string",description:"The event ID to acknowledge."},session_id:{type:"string",description:"Session ID."}},required:["event_id"]},validation:{required:["event_id"],properties:{event_id:{type:"string"},session_id:{type:"string"}}}},{name:"grix_composing",description:'Set the "typing" indicator status for a session.',inputSchema:{type:"object",properties:{session_id:{type:"string",description:"Session ID."},active:{type:"boolean",description:"true = typing, false = stopped."},event_id:{type:"string",description:"Associated event ID (optional)."}},required:["session_id","active"]},validation:{required:["session_id","active"],properties:{session_id:{type:"string"},active:{type:"boolean"},event_id:{type:"string"}}}},{name:"grix_status",description:"Query the Grix connection status of the current MCP session.",inputSchema:{type:"object",properties:{}},validation:{required:[],properties:{}}}],v={grix_dispatch_agent:"grix-agent-dispatch",grix_agent_update:"grix-agent-dispatch",grix_query:"grix-query",grix_audit_data:"grix-audit-data",grix_group:"grix-group",grix_message_send:"message-send",grix_message_unsend:"message-unsend",grix_admin:"grix-admin",grix_call_owner:"grix-owner-relay",grix_session_send:"grix-owner-relay",grix_chat_state_query:"grix-chat-state",grix_chat_state_update:"grix-chat-state",grix_status:"grix-chat-state",grix_access_control:"grix-access-control",grix_widget_visitor_ban:"grix-widget-visitor-ban",grix_file_link:"tailnet-file-share"};function w(e){return` Before calling this tool, follow the \`${e}\` skill's procedure first; do not invoke this tool directly without going through that skill's guidance.`}for(const e of[...r,...a]){const t=v[e.name];t&&(e.description+=w(t))}const p=[{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."},final:{type:"boolean",description:"Advisory flag only. It does not complete the event; completion is handled by complete tool or Stop hook."}},required:["chat_id","event_id","text"]}},{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"]},msg:{type:"string"},code:{type:"string"}},required:["event_id","status"]}}],c=new Set(p.map(e=>e.name)),x=new Set(r.map(e=>e.name)),k=new Set(a.map(e=>e.name)),I=/([A-Za-z0-9._-]+:[A-Za-z0-9._-]+:[A-Za-z0-9._-]+:[A-Za-z0-9._-]+)/,S=/[A-Za-z0-9._-]+/;function H(e){return!!(x.has(e)||k.has(e)||c.has(e)||e.startsWith("mcp__grix"))}const q=[...r,...a],A=[...q,...p],Z=new Map(A.map(e=>[e.name,e]));function K(e,t){return e==="reply"?{name:"grix_reply",args:m("grix_reply",{event_id:t.event_id,session_id:t.chat_id,text:t.text,quoted_message_id:t.reply_to,is_final:t.final})}:e==="complete"?{name:"grix_complete",args:m("grix_complete",{event_id:t.event_id,status:t.status,msg:t.msg,code:t.code})}:{name:e,args:t}}function l(e){const t=String(e??"").trim();return t?t.match(I)?.[1]:void 0}function u(e){const t=String(e??"").trim();if(!t)return;const n=l(t);if(n)return _(n);const i=t.match(/(?:chat_id|session_id)\s*=\s*"([A-Za-z0-9._-]+)"/)?.[1];if(i)return i;const o=t.match(/[A-Za-z0-9._-]+/g)??[];for(const s of o)if(!(s==="event_id"||s==="chat_id"||s==="session_id")&&s.length>0)return s;return t.match(S)?.[0]}function _(e){if(!e)return;const t=e.split(":",1)[0]?.trim();if(t)return u(t)}function m(e,t){if(e!=="grix_reply"&&e!=="grix_complete")return t;const n={...t},i=l(n.event_id);if(i&&(n.event_id=i),e==="grix_reply"){const o=_(i),d=String(n.session_id??""),s=u(n.session_id),b=/\bevent_id\b|["'<>\s]/.test(d);s&&!(b&&o)?n.session_id=s:o&&(n.session_id=o)}return n}function V(e){return c.has(e)}function X(e,t){switch(e){case"grix_query":return T(t);case"grix_audit_data":return M(t);case"grix_group":return O(t);case"grix_message_send":return D(t);case"grix_message_unsend":return L(t);case"grix_file_link":return E(t);case"grix_file_upload":return j(t);case"grix_admin":return $(t);case"grix_call_owner":return N(t);case"grix_agent_update":return P(t);case"grix_dispatch_agent":return C(t);case"grix_session_send":return U(t);case"grix_chat_state_query":return z(t);case"grix_chat_state_update":return B(t);case"grix_access_control":return R(t);case"grix_widget_visitor_ban":return G(t);case"grix_skill_set":return W(t);case"grix_skill_get":return F(t);default:throw new Error(`Unknown tool: ${e}`)}}const g={contact_search:"contact_search",session_search:"session_search",message_history:"message_history",message_search:"message_search",search_favorite_sessions:"search_favorite_sessions"};function T(e){const t=String(e.action??""),n=g[t];if(!n)throw new Error(`Unknown grix_query action: ${t}`);const i={};return e.id!=null&&(i.id=e.id),e.keyword!=null&&(i.keyword=e.keyword),e.sessionType!=null&&(i.session_type=e.sessionType),e.limit!=null?i.limit=e.limit:t==="message_history"&&(i.limit=1),e.offset!=null&&(i.offset=e.offset),e.sessionId!=null&&(i.session_id=e.sessionId),e.beforeId!=null&&(i.before_id=e.beforeId),{action:n,params:i}}const y={get_manifest:"audit_get_manifest",list_spans:"audit_list_spans",get_content_chunk:"audit_get_content_chunk"};function M(e){const t=String(e.action??""),n=y[t];if(!n)throw new Error(`Unknown grix_audit_data action: ${t}`);const i={audit_id:e.auditId};return e.revision!=null&&(i.revision=e.revision),e.cursor!=null&&(i.cursor=e.cursor),e.limit!=null&&(i.limit=e.limit),e.contentId!=null&&(i.content_id=e.contentId),e.maxBytes!=null&&(i.max_bytes=e.maxBytes),{action:n,params:i}}const h={create:"group_create",detail:"group_detail_read",leave:"group_leave_self",add_members:"group_member_add",remove_members:"group_member_remove",update_member_role:"group_member_role_update",update_all_members_muted:"group_all_members_muted_update",update_member_speaking:"group_member_speaking_update",dissolve:"group_dissolve"};function O(e){const t=String(e.action??""),n=h[t];if(!n)throw new Error(`Unknown grix_group action: ${t}`);const i={};return e.sessionId!=null&&(i.session_id=e.sessionId),e.name!=null&&(i.name=e.name),e.memberIds!=null&&(i.member_ids=e.memberIds),e.memberTypes!=null&&(i.member_types=e.memberTypes),e.memberId!=null&&(i.member_id=e.memberId),e.role!=null&&(i.role=e.role),e.memberType!=null&&(i.member_type=e.memberType),e.allMembersMuted!=null&&(i.all_members_muted=e.allMembersMuted),e.isSpeakMuted!=null&&(i.is_speak_muted=e.isSpeakMuted),e.canSpeakWhenAllMuted!=null&&(i.can_speak_when_all_muted=e.canSpeakWhenAllMuted),{action:n,params:i}}function D(e){const t={session_id:e.sessionId,msg_type:e.msgType??1,content:e.content};return e.quotedMessageId!=null&&(t.quoted_message_id=e.quotedMessageId),e.threadId!=null&&(t.thread_id=e.threadId),{action:"send_msg",params:t}}function L(e){return{action:"delete_msg",params:{session_id:e.sessionId,msg_id:e.msgId}}}function E(e){const t={file_path:e.file_path};return e.ttl_ms!=null&&(t.ttl_ms=e.ttl_ms),{action:"file_link",params:t}}function j(e){const t={file_path:e.file_path,session_id:e.session_id};return e.caption!=null&&(t.caption=e.caption),e.reply_to_message_id!=null&&(t.reply_to_message_id=e.reply_to_message_id),{action:"file_upload",params:t,timeoutMs:6e4}}const f={create_agent:"agent_api_create",list_categories:"agent_category_list",create_category:"agent_category_create",update_category:"agent_category_update",assign_category:"agent_category_assign",rotate_api_key:"agent_api_key_rotate"};function N(e){return{action:"call_owner",params:{session_id:e.session_id}}}function P(e){if(e.agent_name==null&&e.introduction==null)throw new Error("grix_agent_update requires agent_name or introduction");const t={agent_id:e.agent_id};return e.agent_name!=null&&(t.agent_name=e.agent_name),e.introduction!=null&&(t.introduction=e.introduction),{action:"agent_introduction_update",params:t}}function C(e){return{action:"dispatch_agent",params:{agent_id:e.agent_id,cwd:e.cwd,task:e.task,title:e.title}}}function U(e){const t={session_id:e.session_id,content:e.content};return e.quoted_message_id!=null&&String(e.quoted_message_id).trim()!==""&&(t.quoted_message_id=e.quoted_message_id),{action:"session_send",params:t}}function z(e){const t={};return e.session_id!=null&&(t.session_id=e.session_id),e.page!=null&&(t.page=e.page),e.page_size!=null&&(t.page_size=e.page_size),e.state!=null&&(t.state=e.state),{action:"chat_state_query",params:t}}function R(e){const t=String(e.action??""),n=Q[t];if(!n)throw new Error(`Unknown grix_access_control action: ${t}`);const i={};return e.code!=null&&(i.code=e.code),e.sender_id!=null&&(i.sender_id=e.sender_id),e.policy!=null&&(i.policy=e.policy),{action:"claude_access_control",params:{verb:n,payload:i},timeoutMs:3e4}}function G(e){return{action:"widget_visitor_ban",params:{session_id:e.session_id}}}function B(e){const t={session_id:e.session_id,state:e.state};return e.reason!=null&&(t.reason=e.reason),{action:"chat_state_update",params:t}}function W(e){return{action:"skill_set",params:{name:e.name,content:e.content},timeoutMs:3e4}}function F(e){const t={};return e.name!=null&&(t.name=e.name),{action:"skill_get",params:t}}function $(e){const t=String(e.action??""),n=f[t];if(!n)throw new Error(`Unknown grix_admin action: ${t}`);const i={};return e.agentName!=null&&(i.agent_name=e.agentName),e.introduction!=null&&(i.introduction=e.introduction),e.isMain!=null&&(i.is_main=e.isMain),e.agentId!=null&&(i.agent_id=e.agentId),e.categoryId!=null&&(i.category_id=e.categoryId),e.name!=null&&(i.name=e.name),e.parentId!=null&&(i.parent_id=e.parentId),e.sortOrder!=null&&(i.sort_order=e.sortOrder),{action:n,params:i}}const Y=new Set([...Object.values(g),...Object.values(y),...Object.values(h),...Object.values(f),"send_msg","delete_msg","file_link","file_upload","call_owner","agent_introduction_update","dispatch_agent","session_send","chat_state_query","chat_state_update","claude_access_control","widget_visitor_ban","skill_set","skill_get"]),Q={pair_approve:"pair_approve",pair_deny:"pair_deny",allow_sender:"sender_allow",remove_sender:"sender_remove",set_policy:"policy_set"};export{Q as ACCESS_CONTROL_ACTION_MAP,q as ALL_TOOLS,a as EVENT_TOOLS,A as EXPOSED_TOOLS,Y as PHASE1_INVOKE_ACTIONS,x as PHASE1_TOOL_NAMES,k as PHASE2_TOOL_NAMES,r as TOOLS,p as TOOL_ALIASES,Z as TOOL_MAP,V as isAlias,H as isGrixInternalToolName,K as mapToolAlias,m as normalizeEventToolArgs,X as toolCallToInvoke};
1
+ const r=[{name:"grix_query",description:"Search contacts, sessions, message history, messages, or favorited sessions in the Grix/AIBot platform. message_history defaults to the newest one clean text/approval-card message and excludes process/tool cards.",inputSchema:{type:"object",properties:{action:{type:"string",enum:["contact_search","session_search","message_history","message_search","search_favorite_sessions"],description:"Query action type. search_favorite_sessions returns the owner's favorited sessions (supports keyword filter)."},id:{type:"string",description:"Contact ID (contact_search) or Session ID (session_search)."},keyword:{type:"string",description:"Search keyword."},sessionType:{type:"integer",enum:[1,2],description:"Filter by session type (session_search only): 1=private chat, 2=group chat. Omit to return all."},limit:{type:"integer",description:"Max results. message_history defaults to 1 when omitted."},offset:{type:"integer",description:"Result offset."},sessionId:{type:"string",description:"Session ID (message_history, message_search)."},beforeId:{type:"string",description:"Pagination cursor (message_history, message_search)."}},required:["action"]},validation:{required:["action"],properties:{action:{type:"string",enum:["contact_search","session_search","message_history","message_search","search_favorite_sessions"]},id:{type:"string"},keyword:{type:"string",maxLength:200},sessionType:{type:"integer",enum:[1,2]},limit:{type:"integer",minimum:1,maximum:100},offset:{type:"integer",minimum:0},sessionId:{type:"string"},beforeId:{type:"string"}}}},{name:"grix_audit_data",description:"Read connector-backed conversation audit replay data from Grix/AIBot: manifest, span pages, and content chunks.",inputSchema:{type:"object",properties:{action:{type:"string",enum:["get_manifest","list_spans","get_content_chunk"],description:"Audit read action. Start with get_manifest, then call list_spans and/or get_content_chunk based on the manifest."},auditId:{type:"string",description:"Audit ID returned by the audited turn metadata."},revision:{type:"integer",description:"Optional manifest revision to read. Usually use the revision returned by get_manifest."},cursor:{type:"string",description:"Opaque pagination cursor returned by list_spans or get_content_chunk."},limit:{type:"integer",description:"Max span items for list_spans."},contentId:{type:"string",description:"Content reference ID from manifest.content_refs (get_content_chunk only)."},maxBytes:{type:"integer",description:"Max UTF-8 bytes to return for get_content_chunk."}},required:["action","auditId"]},validation:{required:["action","auditId"],properties:{action:{type:"string",enum:["get_manifest","list_spans","get_content_chunk"]},auditId:{type:"string"},revision:{type:"integer",minimum:1},cursor:{type:"string",maxLength:4096},limit:{type:"integer",minimum:1,maximum:200},contentId:{type:"string"},maxBytes:{type:"integer",minimum:1,maximum:131072}}}},{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"],description:"Group action type."},sessionId:{type:"string",description:"Group session ID."},name:{type:"string",description:"Group name (create)."},memberIds:{type:"array",items:{type:"string"},description:"Member IDs to add/remove."},memberTypes:{type:"array",items:{type:"integer",enum:[1,2]},description:"Member types (1=user, 2=agent)."},memberId:{type:"string",description:"Target member ID."},role:{type:"integer",enum:[1,2],description:"New role (1=admin, 2=member)."},memberType:{type:"integer",description:"Member type."},allMembersMuted:{type:"boolean",description:"Whether to mute all members."},isSpeakMuted:{type:"boolean",description:"Whether member is muted."},canSpeakWhenAllMuted:{type:"boolean",description:"Allow speaking when all muted."}},required:["action"]},validation:{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"}}}},{name:"grix_message_send",description:"Send a message to a session in the Grix/AIBot platform.",inputSchema:{type:"object",properties:{sessionId:{type:"string",description:"Target session ID"},content:{type:"string",description:"Message content"},msgType:{type:"integer",description:"Message type (1=text, default 1)"},quotedMessageId:{type:"string",description:"Message ID to reply to"},threadId:{type:"string",description:"Thread ID for threaded reply"}},required:["sessionId","content"]},validation:{required:["sessionId","content"],properties:{sessionId:{type:"string"},content:{type:"string",maxLength:1e4},msgType:{type:"integer"},quotedMessageId:{type:"string"},threadId:{type:"string"}}}},{name:"grix_message_unsend",description:"Recall/unsend a message in the Grix/AIBot platform.",inputSchema:{type:"object",properties:{sessionId:{type:"string",description:"Session ID"},msgId:{type:"string",description:"Message ID to unsend"}},required:["sessionId","msgId"]},validation:{required:["sessionId","msgId"],properties:{sessionId:{type:"string"},msgId:{type:"string"}}}},{name:"grix_file_link",description:"Create a direct, tailnet-only download link for a local file on this host. Use this whenever the user asks you to send, share, give, or deliver a file that exists on the machine where you run (a report, log, build artifact, export, or any local path). It returns a ready-to-use Markdown link in the `markdown` field \u2014 include that exact Markdown link in your reply so the user can click and download the file directly over the shared Tailscale network. The link is reachable only inside the tailnet, so just send it as-is \u2014 no need to worry about or mention any link lifetime. The download link is HTTPS, served by a built-in self-signed CA. The result also returns `ca_install_url`: the first time you share a link with a user (or whenever their browser warns the cert is untrusted), also give them this CA install link so they can install and trust it once \u2014 after that all download links work without warnings. Requires this host to be on a tailnet (Tailscale running).",inputSchema:{type:"object",properties:{file_path:{type:"string",description:"Absolute path to a local file on this host to share with the user."},ttl_ms:{type:"integer",description:"Optional link lifetime in milliseconds. Leave unset to use the long default; set only if you deliberately want a short-lived link."}},required:["file_path"]},validation:{required:["file_path"],properties:{file_path:{type:"string",maxLength:4096},ttl_ms:{type:"integer",minimum:1e4,maximum:864e5}}}},{name:"grix_file_upload",description:"Upload a local file to the Grix platform and send it as a media message in the target session. Supports images (jpg/png/webp/gif/bmp/heic/heif), videos (mp4/mov/m4v/webm/mkv/avi), documents (pdf/doc/docx/xls/xlsx/ppt/pptx/txt/md/csv/json/xml), and archives (zip/rar/7z/tar/gz). Max 50 MB. Use this instead of grix_file_link when the file should appear as a native attachment in the chat (visible inline for images/videos), rather than a tailnet download link.",inputSchema:{type:"object",properties:{file_path:{type:"string",description:"Absolute path to a local file to upload."},session_id:{type:"string",description:"Target session ID to send the file to."},caption:{type:"string",description:"Optional text caption for the media message."},reply_to_message_id:{type:"string",description:"Optional message ID to quote/reply to."}},required:["file_path","session_id"]},validation:{required:["file_path","session_id"],properties:{file_path:{type:"string",maxLength:4096},session_id:{type:"string"},caption:{type:"string",maxLength:2e3},reply_to_message_id:{type:"string"}}}},{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"],description:"Admin action type."},agentName:{type:"string",description:"Agent name (create_agent)."},introduction:{type:"string",description:"Professional behavioral introduction describing the Agent purpose, responsibilities, operating expectations, and boundaries (create_agent)."},isMain:{type:"boolean",description:"Set as main agent (create_agent)."},agentId:{type:"string",description:"Agent ID (assign_category, rotate_api_key)."},categoryId:{type:"string",description:"Category ID (create_agent, update_category, assign_category)."},name:{type:"string",description:"Category name (create_category, update_category)."},parentId:{type:"string",description:"Parent category ID (create_category, update_category)."},sortOrder:{type:"integer",description:"Sort order (create_category, update_category)."}},required:["action"]},validation:{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"}}}},{name:"grix_call_owner",description:"Call your owner into this session to talk by voice. Use this when, during your work, you need to reach your owner \u2014 to discuss something or to get an approval/review. It sends the owner an offline notification; when they tap it they land directly in this conversation and a voice-brain call is started automatically. Requires the owner to have configured a voice brain. Rate-limited per session.",inputSchema:{type:"object",properties:{session_id:{type:"string",description:"The session ID to call the owner into."}},required:["session_id"]},validation:{required:["session_id"],properties:{session_id:{type:"string"}}}},{name:"grix_agent_update",description:"Update the display name and/or text introduction of one of your owner's agents, identified by its numeric agent ID. Provide agent_name, introduction, or both \u2014 at least one is required.",inputSchema:{type:"object",properties:{agent_id:{type:"string",description:"Target agent's numeric ID, passed as a string."},agent_name:{type:"string",description:"New display name (max 100 characters, must be unique among the owner's agents)."},introduction:{type:"string",description:"New text introduction (max 300 characters)."}},required:["agent_id"]},validation:{required:["agent_id"],properties:{agent_id:{type:"string"},agent_name:{type:"string",maxLength:100},introduction:{type:"string",maxLength:300}}}},{name:"grix_dispatch_agent",description:`Dispatch one of your owner's agents to do work in a given working directory. Provide the target agent numeric ID, the working directory, and a text description of the task. The backend creates a NEW private session between the owner and that agent for each dispatch (it does not reuse past sessions), binds the working directory when the agent type requires it (claude/codex/etc.), and sends the task into the session AS THE OWNER so the agent starts working. Because the task is delivered as the owner, write it in the owner's first-person voice and tone \u2014 phrase it the way the owner would speak directly to the agent (e.g. "\u5E2E\u6211\u2026", "\u4F60\u53BB\u2026"), NOT as a third-person relay or as yourself narrating on the owner's behalf. Provide a short title summarizing the core of the task \u2014 it becomes the new session's title; if omitted the backend derives one from the task text.`,inputSchema:{type:"object",properties:{agent_id:{type:"string",description:"Target agent's numeric ID, passed as a string."},cwd:{type:"string",description:"Absolute working directory where the agent should do the work."},task:{type:"string",description:"Text description of the task to perform, written in the owner's first-person voice and tone \u2014 it is delivered into the session as the owner, so phrase it as the owner speaking directly to the agent, not as a third-person relay."},title:{type:"string",description:"Optional short title (a few words) summarizing the core of the task; used as the new session's title. If omitted, the backend derives a title from the task text."}},required:["agent_id","cwd","task"]},validation:{required:["agent_id","cwd","task"],properties:{agent_id:{type:"string"},cwd:{type:"string",maxLength:4096},task:{type:"string",maxLength:1e4},title:{type:"string",maxLength:255}}}},{name:"grix_session_send",description:"Send a message into a session AS THE OWNER \u2014 it appears as if the owner sent it, NOT as you (the agent). Dispatch callbacks (report_dispatch_result) MUST use this tool, even when you are a member of the callback session: a message sent as yourself cannot quote-wake the dispatcher agent, and your membership is not checked. NEVER use this for your own ordinary replies in a conversation \u2014 that would make your words show up as the owner's message; reply normally (or use grix_message_send to send as yourself) for those. The owner must be a member of the target session. Requires the Send as Owner permission scope \u2014 if rejected for missing permission, surface the error as-is so the owner can grant it; do not fall back to grix_message_send. Optional quoted_message_id quotes a message in the same session (used by dispatch callbacks to wake the dispatcher agent).",inputSchema:{type:"object",properties:{session_id:{type:"string",description:"Target session ID."},content:{type:"string",description:"Message content to send as the owner."},quoted_message_id:{type:"string",description:"Optional message ID in the target session to quote/reply to.",pattern:"^[1-9]\\d*$"}},required:["session_id","content"]},validation:{required:["session_id","content"],properties:{session_id:{type:"string"},content:{type:"string",maxLength:1e4},quoted_message_id:{type:"string",pattern:"^[1-9]\\d*$"}}}},{name:"grix_chat_state_query",description:"Query the chat-level task states across all of your owner's sessions (including direct and group chats). Returns one entry per session with a single mutually-exclusive state: running (working), waiting_approval (blocked on your owner to approve/deny), waiting_question (asked the owner a question, awaiting their reply), completed, failed, or idle (no task / stopped). When session_id identifies one completed session, the entry also includes exactly one clean final_result; non-completed and list queries never include message content. Also returns the session title (task_title) for easy identification. Supports pagination (page/page_size) and optional state filtering. Use this to see at a glance which chats are done, still running, or waiting on the owner.",inputSchema:{type:"object",properties:{session_id:{type:"string",description:"(Optional) Query a single session by its ID. Omit to return all sessions."},page:{type:"number",description:"(Optional) Page number, starting from 1. Defaults to 1 if omitted."},page_size:{type:"number",description:"(Optional) Number of items per page, max 100. Defaults to 10 if omitted."},state:{type:"string",description:"(Optional) Filter by a specific state: running, waiting_approval, waiting_question, completed, failed, or idle. Omit to return all states."}}},validation:{required:[],properties:{}}},{name:"grix_chat_state_update",description:"Manually update the task state of a specific chat session. Use this to mark a chat as completed, failed, idle, or any other state when you need to override it manually. The reason is written to stop_reason and is optional.",inputSchema:{type:"object",properties:{session_id:{type:"string",description:"The session ID whose state to update."},state:{type:"string",enum:["running","waiting_approval","waiting_question","completed","failed","idle"],description:"The new state to set. Must be one of: running, waiting_approval, waiting_question, completed, failed, idle."},reason:{type:"string",description:"(Optional) Reason for the state change, written to stop_reason."}}},validation:{required:["session_id","state"],properties:{}}},{name:"grix_access_control",description:"Manage sender access control: pair approval, allow/remove senders, set policy.",inputSchema:{type:"object",properties:{action:{type:"string",enum:["pair_approve","pair_deny","allow_sender","remove_sender","set_policy"],description:"Access control action type."},code:{type:"string",description:"Pairing code (required for pair_approve/pair_deny)."},sender_id:{type:"string",description:"Sender ID (required for allow_sender/remove_sender)."},policy:{type:"string",enum:["allowlist","open","disabled"],description:"Access policy (required for set_policy)."}},required:["action"]},validation:{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"]}}}},{name:"grix_widget_visitor_ban",description:"Ban a widget visitor session: blocks the session and adds its most recent init IP to the owner's global IP ban list (7-day expiry).",inputSchema:{type:"object",properties:{session_id:{type:"string",description:"Widget visitor session ID (required)."}},required:["session_id"]},validation:{required:["session_id"],properties:{session_id:{type:"string"}}}},{name:"grix_skill_set",description:"Create, update, or delete one of your owner's custom skills in the platform skill library. The skill library auto-syncs to every machine the owner runs an agent on (a per-machine `grix/skills` directory), so defining a skill here makes it available on all of them. A skill is a plain SKILL.md package (frontmatter + body), same standard as other Grix skills. Provide `name` and the full `content` (the SKILL.md text) to create or overwrite by name. To delete a skill, pass its `name` with an empty `content` string. How and when any agent actually loads/uses a synced skill is arranged by the owner (in a system prompt or in conversation); this tool only manages the library.",inputSchema:{type:"object",properties:{name:{type:"string",description:"Skill name (max 100 characters); unique within the owner. Used as the identifier and the synced directory name."},content:{type:"string",description:"Full SKILL.md text. Pass an empty string to delete the skill by name."}},required:["name","content"]},validation:{required:["name","content"],properties:{name:{type:"string",maxLength:100},content:{type:"string",maxLength:262144}}}},{name:"grix_skill_get",description:"Read one of your owner's custom skills by name (returns its full SKILL.md content), or list the owner's skill library when no name is given. Use this before editing to fetch current content, then call grix_skill_set with the revised text.",inputSchema:{type:"object",properties:{name:{type:"string",description:"Skill name to fetch. Omit to list all skills (names + versions) instead."}}},validation:{required:[],properties:{name:{type:"string",maxLength:100}}}}],a=[{name:"grix_reply",description:"Send your final reply \u2014 the conclusion the user is waiting for \u2014 to the specified session. This is the message the user treats as your answer; deliver your final result here, and use plain text only for brief progress notes while you work. Supports streaming in chunks; the frontend automatically aggregates them into one complete message. The connector quotes the message being answered automatically, so quoted_message_id is optional (set it only to quote a different earlier message).",inputSchema:{type:"object",properties:{event_id:{type:"string",description:"Associated event ID from the inbound event."},session_id:{type:"string",description:"Target session ID."},text:{type:"string",description:"Reply text content."},quoted_message_id:{type:"string",description:"Quoted message ID (optional)."},is_final:{type:"boolean",description:"Whether this is a stage-final reply. Advisory only \u2014 does not trigger event completion; completion is handled by the complete tool or Stop hook."}},required:["session_id","text"]},validation:{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"}}}},{name:"grix_complete",description:"Mark event processing as complete, notifying the backend that no more replies are expected.",inputSchema:{type:"object",properties:{event_id:{type:"string",description:"The event ID to complete."},status:{type:"string",enum:["responded","canceled","failed"],description:"Completion status."},msg:{type:"string",description:"Additional note (optional)."}},required:["event_id","status"]},validation:{required:["event_id","status"],properties:{event_id:{type:"string"},status:{type:"string",enum:["responded","canceled","failed"]},msg:{type:"string",maxLength:500}}}},{name:"grix_event_ack",description:"Acknowledge event receipt (usually done automatically by the Dispatcher; agents typically do not need to call this manually).",inputSchema:{type:"object",properties:{event_id:{type:"string",description:"The event ID to acknowledge."},session_id:{type:"string",description:"Session ID."}},required:["event_id"]},validation:{required:["event_id"],properties:{event_id:{type:"string"},session_id:{type:"string"}}}},{name:"grix_composing",description:'Set the "typing" indicator status for a session.',inputSchema:{type:"object",properties:{session_id:{type:"string",description:"Session ID."},active:{type:"boolean",description:"true = typing, false = stopped."},event_id:{type:"string",description:"Associated event ID (optional)."}},required:["session_id","active"]},validation:{required:["session_id","active"],properties:{session_id:{type:"string"},active:{type:"boolean"},event_id:{type:"string"}}}},{name:"grix_status",description:"Query the Grix connection status of the current MCP session.",inputSchema:{type:"object",properties:{}},validation:{required:[],properties:{}}}],v={grix_dispatch_agent:"grix-agent-dispatch",grix_agent_update:"grix-agent-dispatch",grix_query:"grix-query",grix_audit_data:"grix-audit-data",grix_group:"grix-group",grix_message_send:"message-send",grix_message_unsend:"message-unsend",grix_admin:"grix-admin",grix_call_owner:"grix-owner-relay",grix_session_send:"grix-owner-relay",grix_chat_state_query:"grix-chat-state",grix_chat_state_update:"grix-chat-state",grix_status:"grix-chat-state",grix_access_control:"grix-access-control",grix_widget_visitor_ban:"grix-widget-visitor-ban",grix_file_link:"tailnet-file-share"};function w(e){return` Before calling this tool, follow the \`${e}\` skill's procedure first; do not invoke this tool directly without going through that skill's guidance.`}for(const e of[...r,...a]){const t=v[e.name];t&&(e.description+=w(t))}const p=[{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."},final:{type:"boolean",description:"Advisory flag only. It does not complete the event; completion is handled by complete tool or Stop hook."}},required:["chat_id","event_id","text"]}},{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"]},msg:{type:"string"},code:{type:"string"}},required:["event_id","status"]}}],c=new Set(p.map(e=>e.name)),x=new Set(r.map(e=>e.name)),k=new Set(a.map(e=>e.name)),I=/([A-Za-z0-9._-]+:[A-Za-z0-9._-]+:[A-Za-z0-9._-]+:[A-Za-z0-9._-]+)/,S=/[A-Za-z0-9._-]+/;function H(e){return!!(x.has(e)||k.has(e)||c.has(e)||e.startsWith("mcp__grix"))}const q=[...r,...a],A=[...q,...p],Z=new Map(A.map(e=>[e.name,e]));function K(e,t){return e==="reply"?{name:"grix_reply",args:m("grix_reply",{event_id:t.event_id,session_id:t.chat_id,text:t.text,quoted_message_id:t.reply_to,is_final:t.final})}:e==="complete"?{name:"grix_complete",args:m("grix_complete",{event_id:t.event_id,status:t.status,msg:t.msg,code:t.code})}:{name:e,args:t}}function l(e){const t=String(e??"").trim();return t?t.match(I)?.[1]:void 0}function u(e){const t=String(e??"").trim();if(!t)return;const n=l(t);if(n)return _(n);const i=t.match(/(?:chat_id|session_id)\s*=\s*"([A-Za-z0-9._-]+)"/)?.[1];if(i)return i;const o=t.match(/[A-Za-z0-9._-]+/g)??[];for(const s of o)if(!(s==="event_id"||s==="chat_id"||s==="session_id")&&s.length>0)return s;return t.match(S)?.[0]}function _(e){if(!e)return;const t=e.split(":",1)[0]?.trim();if(t)return u(t)}function m(e,t){if(e!=="grix_reply"&&e!=="grix_complete")return t;const n={...t},i=l(n.event_id);if(i&&(n.event_id=i),e==="grix_reply"){const o=_(i),d=String(n.session_id??""),s=u(n.session_id),b=/\bevent_id\b|["'<>\s]/.test(d);s&&!(b&&o)?n.session_id=s:o&&(n.session_id=o)}return n}function V(e){return c.has(e)}function X(e,t){switch(e){case"grix_query":return T(t);case"grix_audit_data":return M(t);case"grix_group":return O(t);case"grix_message_send":return D(t);case"grix_message_unsend":return L(t);case"grix_file_link":return E(t);case"grix_file_upload":return j(t);case"grix_admin":return $(t);case"grix_call_owner":return N(t);case"grix_agent_update":return P(t);case"grix_dispatch_agent":return C(t);case"grix_session_send":return U(t);case"grix_chat_state_query":return z(t);case"grix_chat_state_update":return B(t);case"grix_access_control":return R(t);case"grix_widget_visitor_ban":return G(t);case"grix_skill_set":return W(t);case"grix_skill_get":return F(t);default:throw new Error(`Unknown tool: ${e}`)}}const g={contact_search:"contact_search",session_search:"session_search",message_history:"message_history",message_search:"message_search",search_favorite_sessions:"search_favorite_sessions"};function T(e){const t=String(e.action??""),n=g[t];if(!n)throw new Error(`Unknown grix_query action: ${t}`);const i={};return e.id!=null&&(i.id=e.id),e.keyword!=null&&(i.keyword=e.keyword),e.sessionType!=null&&(i.session_type=e.sessionType),e.limit!=null?i.limit=e.limit:t==="message_history"&&(i.limit=1),e.offset!=null&&(i.offset=e.offset),e.sessionId!=null&&(i.session_id=e.sessionId),e.beforeId!=null&&(i.before_id=e.beforeId),{action:n,params:i}}const y={get_manifest:"audit_get_manifest",list_spans:"audit_list_spans",get_content_chunk:"audit_get_content_chunk"};function M(e){const t=String(e.action??""),n=y[t];if(!n)throw new Error(`Unknown grix_audit_data action: ${t}`);const i={audit_id:e.auditId};return e.revision!=null&&(i.revision=e.revision),e.cursor!=null&&(i.cursor=e.cursor),e.limit!=null&&(i.limit=e.limit),e.contentId!=null&&(i.content_id=e.contentId),e.maxBytes!=null&&(i.max_bytes=e.maxBytes),{action:n,params:i}}const h={create:"group_create",detail:"group_detail_read",leave:"group_leave_self",add_members:"group_member_add",remove_members:"group_member_remove",update_member_role:"group_member_role_update",update_all_members_muted:"group_all_members_muted_update",update_member_speaking:"group_member_speaking_update",dissolve:"group_dissolve"};function O(e){const t=String(e.action??""),n=h[t];if(!n)throw new Error(`Unknown grix_group action: ${t}`);const i={};return e.sessionId!=null&&(i.session_id=e.sessionId),e.name!=null&&(i.name=e.name),e.memberIds!=null&&(i.member_ids=e.memberIds),e.memberTypes!=null&&(i.member_types=e.memberTypes),e.memberId!=null&&(i.member_id=e.memberId),e.role!=null&&(i.role=e.role),e.memberType!=null&&(i.member_type=e.memberType),e.allMembersMuted!=null&&(i.all_members_muted=e.allMembersMuted),e.isSpeakMuted!=null&&(i.is_speak_muted=e.isSpeakMuted),e.canSpeakWhenAllMuted!=null&&(i.can_speak_when_all_muted=e.canSpeakWhenAllMuted),{action:n,params:i}}function D(e){const t={session_id:e.sessionId,msg_type:e.msgType??1,content:e.content};return e.quotedMessageId!=null&&(t.quoted_message_id=e.quotedMessageId),e.threadId!=null&&(t.thread_id=e.threadId),{action:"send_msg",params:t}}function L(e){return{action:"delete_msg",params:{session_id:e.sessionId,msg_id:e.msgId}}}function E(e){const t={file_path:e.file_path};return e.ttl_ms!=null&&(t.ttl_ms=e.ttl_ms),{action:"file_link",params:t}}function j(e){const t={file_path:e.file_path,session_id:e.session_id};return e.caption!=null&&(t.caption=e.caption),e.reply_to_message_id!=null&&(t.reply_to_message_id=e.reply_to_message_id),{action:"file_upload",params:t,timeoutMs:6e4}}const f={create_agent:"agent_api_create",list_categories:"agent_category_list",create_category:"agent_category_create",update_category:"agent_category_update",assign_category:"agent_category_assign",rotate_api_key:"agent_api_key_rotate"};function N(e){return{action:"call_owner",params:{session_id:e.session_id}}}function P(e){if(e.agent_name==null&&e.introduction==null)throw new Error("grix_agent_update requires agent_name or introduction");const t={agent_id:e.agent_id};return e.agent_name!=null&&(t.agent_name=e.agent_name),e.introduction!=null&&(t.introduction=e.introduction),{action:"agent_introduction_update",params:t}}function C(e){return{action:"dispatch_agent",params:{agent_id:e.agent_id,cwd:e.cwd,task:e.task,title:e.title},timeoutMs:75e3}}function U(e){const t={session_id:e.session_id,content:e.content};return e.quoted_message_id!=null&&String(e.quoted_message_id).trim()!==""&&(t.quoted_message_id=e.quoted_message_id),{action:"session_send",params:t}}function z(e){const t={};return e.session_id!=null&&(t.session_id=e.session_id),e.page!=null&&(t.page=e.page),e.page_size!=null&&(t.page_size=e.page_size),e.state!=null&&(t.state=e.state),{action:"chat_state_query",params:t}}function R(e){const t=String(e.action??""),n=Q[t];if(!n)throw new Error(`Unknown grix_access_control action: ${t}`);const i={};return e.code!=null&&(i.code=e.code),e.sender_id!=null&&(i.sender_id=e.sender_id),e.policy!=null&&(i.policy=e.policy),{action:"claude_access_control",params:{verb:n,payload:i},timeoutMs:3e4}}function G(e){return{action:"widget_visitor_ban",params:{session_id:e.session_id}}}function B(e){const t={session_id:e.session_id,state:e.state};return e.reason!=null&&(t.reason=e.reason),{action:"chat_state_update",params:t}}function W(e){return{action:"skill_set",params:{name:e.name,content:e.content},timeoutMs:3e4}}function F(e){const t={};return e.name!=null&&(t.name=e.name),{action:"skill_get",params:t}}function $(e){const t=String(e.action??""),n=f[t];if(!n)throw new Error(`Unknown grix_admin action: ${t}`);const i={};return e.agentName!=null&&(i.agent_name=e.agentName),e.introduction!=null&&(i.introduction=e.introduction),e.isMain!=null&&(i.is_main=e.isMain),e.agentId!=null&&(i.agent_id=e.agentId),e.categoryId!=null&&(i.category_id=e.categoryId),e.name!=null&&(i.name=e.name),e.parentId!=null&&(i.parent_id=e.parentId),e.sortOrder!=null&&(i.sort_order=e.sortOrder),{action:n,params:i}}const Y=new Set([...Object.values(g),...Object.values(y),...Object.values(h),...Object.values(f),"send_msg","delete_msg","file_link","file_upload","call_owner","agent_introduction_update","dispatch_agent","session_send","chat_state_query","chat_state_update","claude_access_control","widget_visitor_ban","skill_set","skill_get"]),Q={pair_approve:"pair_approve",pair_deny:"pair_deny",allow_sender:"sender_allow",remove_sender:"sender_remove",set_policy:"policy_set"};export{Q as ACCESS_CONTROL_ACTION_MAP,q as ALL_TOOLS,a as EVENT_TOOLS,A as EXPOSED_TOOLS,Y as PHASE1_INVOKE_ACTIONS,x as PHASE1_TOOL_NAMES,k as PHASE2_TOOL_NAMES,r as TOOLS,p as TOOL_ALIASES,Z as TOOL_MAP,V as isAlias,H as isGrixInternalToolName,K as mapToolAlias,m as normalizeEventToolArgs,X as toolCallToInvoke};
@@ -1 +1 @@
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
+ 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};
@@ -10058,7 +10058,10 @@ function buildAgentUpdateInvoke(args) {
10058
10058
  function buildDispatchAgentInvoke(args) {
10059
10059
  return {
10060
10060
  action: "dispatch_agent",
10061
- params: { agent_id: args.agent_id, cwd: args.cwd, task: args.task, title: args.title }
10061
+ params: { agent_id: args.agent_id, cwd: args.cwd, task: args.task, title: args.title },
10062
+ // Backend session_bind waits 60s (cold slot start). Leave headroom for
10063
+ // session create + send-as-owner after bind succeeds.
10064
+ timeoutMs: 75e3
10062
10065
  };
10063
10066
  }
10064
10067
  function buildSessionSendInvoke(args) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "grix-connector",
3
- "version": "3.29.0",
3
+ "version": "3.29.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",