grix-connector
Version:
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.
13 lines (7 loc) • 120 kB
JavaScript
import x from"node:path";import{realpath as G,stat as H}from"node:fs/promises";import{tmpdir as V}from"node:os";import{randomUUID as F}from"node:crypto";import{ConnectionManager as J}from"../core/aibot/index.js";import{ClaudeAdapter as T}from"../adapter/claude/index.js";import{CodexAdapter as X}from"../adapter/codex/index.js";import{PiAdapter as Y}from"../adapter/pi/index.js";import{AcpAdapter as A}from"../adapter/acp/index.js";import{OpenHumanAdapter as Z}from"../adapter/openhuman/index.js";import{CursorAdapter as P}from"../adapter/cursor/index.js";import{CodeWhaleAdapter as q}from"../adapter/codewhale/index.js";import{OpenCodeAdapter as ee}from"../adapter/opencode/index.js";import{AgyAdapter as U}from"../adapter/agy/index.js";import{getCachedAgyModels as te}from"../adapter/agy/model-list.js";import{getCachedAgyQuotaInfo as ie}from"../adapter/agy/quota.js";import{LOCAL_ACTION_ERROR_CODES as y,LOCAL_ACTION_TYPES as S,SESSION_CONTROL_ERROR_CODES as g,SESSION_CONTROL_VERBS as p,SESSION_MODE_IDS as C}from"../adapter/claude/protocol-contract.js";import{parseClaudeSessionUsage as ne}from"../adapter/claude/usage-parser.js";import{fetchAvailableModels as I,getCachedModels as D,readSettingsEnv as se}from"../adapter/claude/model-list.js";import{parseAcpSessionUsage as oe}from"../adapter/acp/usage-parser.js";import{parseCodexSessionUsage as re}from"../adapter/codex/usage-parser.js";import{readCodexProviderSettings as ae}from"../adapter/codex/codex-trust.js";import{scanCodexSessions as de}from"../adapter/codex/session-scanner.js";import{scanClaudeSessions as ce}from"../adapter/claude/session-scanner.js";import{scanAcpSessions as le}from"../adapter/acp/session-scanner.js";import{parsePiSessionUsage as ue}from"../adapter/pi/usage-parser.js";import{SessionScanCache as L,resolveCodexLeafDirs as he,resolveClaudeLeafDirs as ge,resolveAcpLeafDirs as me}from"./session-scan-cache.js";import{log as u,ConversationLog as pe,AgentApiPacketLog as fe,BridgeEventLog as _e}from"../core/log/index.js";import{RevokeHandler as ve}from"./revoke-handler.js";import{AdapterPool as be,PoolFullError as Se}from"./adapter-pool.js";import{parseSessionControlCommand as we,handleSessionControlCommand as O,handleSessionControlLocalAction as Ce}from"./session-controller.js";import{handleAcpSetModel as W,handleAcpSetMode as N,resolveAcpInitialDefaults as Ae}from"./acp-toolbar-persist.js";import{SessionBindingStore as ke}from"../core/persistence/session-binding-store.js";import{handleFileListAction as Re,handleCreateFolderAction as Ee,serveLocalFile as $e,realHomeDir as z}from"../core/files/index.js";import{AllowlistGate as ye}from"../core/access/allowlist-gate.js";import{ActiveEventStore as xe}from"../core/persistence/active-event-store.js";import{DEFAULT_CONNECTOR_RUNTIME_CONFIG as Te,applyConnectorRuntimeConfigPatch as Le,extractConnectorRuntimeConfigPatch as Me}from"./runtime-config.js";import{SendController as He}from"./send-controller.js";import{queryProviderQuota as K}from"../core/provider-quota/index.js";import{queryKiroQuota as Q}from"../core/provider-quota/kiro.js";import{buildToolUseCard as E,buildToolResultCard as $,buildLocalGrixCardLink as Pe}from"./tool-card-utils.js";import{DeferredEventManager as Ie}from"./deferred-events.js";import{buildAgentProbeResult as De,PROBE_CACHE_TTL_STATIC_MS as Qe,PROBE_CACHE_TTL_FULL_MS as Be}from"./probe-helper.js";const Fe=600*1e3,qe=60*1e3,j=30*1e3,Ue=10*1e3,M=new Set(["claude","acp","agy","cursor","codex"]),Oe=new Set(["claude","codex","cursor","codewhale","opencode","pi","openhuman","agy","acp"]),B=3;class Ht{config;name;aibotHandle;aibotConfig;pool;stopped=!1;revokeHandler=new ve;sessionBindings=new Map;deferredMgr;sendCtrl=new He(Te);bindingStore;globalConfigStore;upgradeTrigger=null;allowlistGate;activeEventStore;cachedRateLimits=null;cachedRateLimitsSampledAtMs=null;cachedCodexContextWindow=null;cachedCodexTokenUsage=null;cachedCodexUsageSampledAtMs=null;cachedAcpContextWindow=null;cachedAcpContextWindowSampledAtMs=null;cachedClaudeRateLimitState=null;cachedProviderQuota=null;cachedProviderQuotaSampledAtMs=null;claudeWorkerStatus=new Map;conversationLog=null;packetLog=null;kiroQuotaTimer=null;eventSessionIndex=new Map;inflightEvents=new Map;restartCount=new Map;selfDrivenSessions=new Set;probeCache=new Map;sessionScanCache;isRateLimitsCacheFresh(e){if(!Number.isFinite(e))return!1;const s=Number(e);return s>0&&Date.now()-s<=qe}async maybeQueryProviderQuota(){if(this.config.aibot.clientType==="kiro"){if(this.isRateLimitsCacheFresh(this.cachedProviderQuotaSampledAtMs)&&this.cachedProviderQuota)return this.cachedProviderQuota;try{const t=await Q();return this.cachedProviderQuota=t,this.cachedProviderQuotaSampledAtMs=Date.now(),u.info(this.name,`[provider-quota] kiro queried: success=${t.success}`+(t.balance?` balance=${t.balance.remaining} ${t.balance.unit}`:"")+(t.planName?` plan=${t.planName}`:"")+(t.error?` error=${t.error}`:"")),t}catch(t){return u.warn(this.name,`[provider-quota] kiro query failed: ${t instanceof Error?t.message:String(t)}`),null}}let e=this.config.providerBaseUrl,s=this.config.providerApiKey;if((!e||!s)&&(this.config.adapterType??"acp")==="claude"){const t=se();e||(e=(t.ANTHROPIC_BASE_URL??"").trim()||void 0),s||(s=(t.ANTHROPIC_API_KEY??"").trim()||(t.ANTHROPIC_AUTH_TOKEN??"").trim()||void 0)}if((!e||!s)&&(this.config.adapterType??"acp")==="codex"){const t=ae();!e&&t.baseUrl&&(e=t.baseUrl),!s&&t.apiKey&&(s=t.apiKey)}if(!e||!s)return null;if(this.isRateLimitsCacheFresh(this.cachedProviderQuotaSampledAtMs)&&this.cachedProviderQuota)return this.cachedProviderQuota;try{const t=await K(e,s);return this.cachedProviderQuota=t,this.cachedProviderQuotaSampledAtMs=Date.now(),u.info(this.name,`[provider-quota] queried: provider=${t.provider} success=${t.success}`+(t.tiers.length>0?` tiers=${t.tiers.map(o=>`${o.name}=${o.usedPercent}%`).join(",")}`:"")+(t.balance?` balance=${t.balance.remaining} ${t.balance.unit}`:"")+(t.error?` error=${t.error}`:"")),t}catch(t){return u.warn(this.name,`[provider-quota] query failed: ${t instanceof Error?t.message:String(t)}`),null}}startKiroQuotaTimer(){this.kiroQuotaTimer||(this.kiroQuotaTimer=setInterval(()=>{if(this.stopped){this.stopKiroQuotaTimer();return}this.refreshAndPushKiroQuota().catch(()=>{})},j),u.info(this.name,`[kiro-quota-timer] started (interval=${j}ms)`))}stopKiroQuotaTimer(){this.kiroQuotaTimer&&(clearInterval(this.kiroQuotaTimer),this.kiroQuotaTimer=null,u.info(this.name,"[kiro-quota-timer] stopped"))}async refreshAndPushKiroQuota(){try{const e=await Q();this.cachedProviderQuota=e,this.cachedProviderQuotaSampledAtMs=Date.now(),e.success&&this.pushKiroQuotaToBindings(e)}catch{}}pushKiroQuotaToBindings(e){const s=this.providerQuotaToRateLimits(e);for(const[t,o]of this.sessionBindings.entries()){if(!o)continue;const i={provider_quota:e};s&&(i.rate_limits=s);const n=this.cachedAcpContextWindow;n&&("usedPercentage"in n?i.context_window={usedPercentage:n.usedPercentage,remainingPercentage:100-n.usedPercentage}:i.context_window=n),this.aibotHandle.sendUpdateBindingCard({session_id:t,worker_status:"ready",cwd:o,meta:i})}}getFreshClaudeRateLimitState(){const e=this.cachedClaudeRateLimitState;return e&&this.isRateLimitsCacheFresh(e.sampledAt)?e:null}getFreshCodexGlobalRateLimitCache(){const e=this.cachedRateLimits,s=this.cachedCodexContextWindow,t=this.cachedCodexTokenUsage;return{sampledAt:Math.max(this.cachedRateLimitsSampledAtMs??0,this.cachedCodexUsageSampledAtMs??0)||null,rateLimits:e,contextWindow:s,tokenUsage:t,hasData:!!(e||s||t)}}getStatus(){const e=this.pool?.getStatus()??{total:0,ready:0,busy:0};return{name:this.name,alive:!this.stopped,busy:e.busy>0,exhausted:this.pool?[...this.pool.getAllSlots()].some(s=>s.respawn.exhausted):!1,adapterType:this.config.adapterType??"acp",clientType:this.config.aibot.clientType,pool:e}}async probe(e={}){const s=e.conversation?"full":"static",t=e.conversation?Be:Qe;if(!e.fresh){const a=this.probeCache.get(s);if(a&&Date.now()-a.sampledAt<t)return{...a.result,cached:!0}}const o=this.config.adapterType??"acp",i=this.createAdapter(o,"__probe__"),n=e.conversation&&o==="acp"?()=>this.runAcpConversationProbe(i,e.timeoutMs??1e4):void 0;let r;try{r=await De({adapter:i,agentName:this.name,clientType:this.config.aibot.clientType,adapterType:o,providerBaseUrl:this.config.providerBaseUrl??null,opts:e,launchConversationProbe:n})}finally{i.stop().catch(()=>{})}return this.probeCache.set(s,{result:r,sampledAt:r.probed_at}),r}async runAcpConversationProbe(e,s){const t=Date.now(),o="__probe__",i=`probe-${Date.now()}-${Math.random().toString(36).slice(2,8)}`,n=this.config.agent.cwd||V(),r=()=>Date.now()-t;try{await e.start()}catch(h){return{attempted:!0,ok:!1,latency_ms:r(),error:{code:"conversation_failed",message:`start failed: ${h instanceof Error?h.message:String(h)}`}}}if(!e.isAlive())return{attempted:!0,ok:!1,latency_ms:r(),error:{code:"process_not_started",message:"agent process not alive"}};if(e instanceof A)try{await e.bindSession(o,n)}catch{}let a=null;const d=new Promise(h=>{a=m=>{m===i&&h()},e.on("eventDone",a)});e.deliverInboundEvent({event_id:i,session_id:o,content:"ping",msg_id:i}),e.deliverStopEvent(i,o);let c=null;const l=await Promise.race([d.then(()=>!1),new Promise(h=>{c=setTimeout(()=>h(!0),s)})]);return c&&clearTimeout(c),a&&e.removeListener("eventDone",a),l?{attempted:!0,ok:!1,latency_ms:r(),error:{code:"conversation_timeout",message:`no eventDone within ${s}ms`}}:{attempted:!0,ok:!0,latency_ms:r()}}constructor(e,s){this.config=e,this.name=e.name;const t=e.adapterType??"acp";this.aibotConfig={...e.aibot,...t==="claude"?{localActions:e.aibot.localActions??["session_control","claude_interaction_reply","get_session_usage","get_rate_limits","set_model","set_mode","thread_compact","get_agent_global_config"]}:{}},e.eventQueue&&(this.aibotConfig.concurrency={max_concurrent:e.eventQueue.maxConcurrent,max_queued:e.eventQueue.maxQueued,queue_timeout_ms:e.eventQueue.queueTimeoutMs,cancelable_queued:e.eventQueue.cancelableQueued,cancelable_running:e.eventQueue.cancelableRunning}),this.conversationLog=e.logDir?new pe(e.logDir):null,this.packetLog=e.logDir?new fe(e.logDir):null,this.bindingStore=new ke(e.bindingsPath),this.bindingStore.load(),this.globalConfigStore=s??null,this.deferredMgr=new Ie(this.name),this.allowlistGate=e.allowlistPath?new ye(e.allowlistPath):null,this.activeEventStore=e.activeEventStorePath?new xe(e.activeEventStorePath):null,t==="codex"?this.sessionScanCache=new L(de,he):t==="claude"?this.sessionScanCache=new L(ce,ge):t==="agy"?this.sessionScanCache=new L(()=>[],()=>[]):this.sessionScanCache=new L(le,me)}async start(){(this.config.adapterType??"acp")==="claude"&&(await I().catch(()=>{}),this.maybeQueryProviderQuota().catch(()=>{})),this.config.aibot.clientType==="kiro"&&(this.maybeQueryProviderQuota().catch(()=>{}),this.startKiroQuotaTimer()),(this.config.adapterType??"acp")==="codex"&&this.maybeQueryProviderQuota().catch(()=>{}),await this.connectAibot(),this.sendCtrl.bind(this.aibotHandle);const e=this.config.adapterType??"acp";this.pool=new be({maxPoolSize:this.config.poolMaxSize??20,idleTimeoutMs:this.config.poolIdleTimeoutMs??18e5,eventQueue:this.config.eventQueue},s=>{const t=this.createAdapter(e,s);return t instanceof A&&t.on("acpSessionReady",o=>{this.bindingStore.setAcpSessionId(s,o),this.sessionScanCache.invalidate()}),t},(s,t)=>{this.aibotHandle.sendEventAck({event_id:s,session_id:t,received_at:Date.now()})}),this.pool.setEventStateHandler((s,t,o,i)=>{u.info(this.name,`[queue-debug] send event_state session=${t} event=${s} state=${o} queue_pos=${i?.queue_position??""} queue_total=${i?.queue_total??""}`),this.aibotHandle.sendEventState({event_id:s,session_id:t,state:o,content_preview:i?.content_preview,queue_position:i?.queue_position,queue_total:i?.queue_total,actions:i?.actions,reason:i?.reason,updated_at:Date.now()}),this.pushQueueSnapshotForSession(t),(o==="canceled"||o==="failed")&&this.aibotHandle.sendEventResult({event_id:s,status:o==="canceled"?"canceled":"failed",msg:i?.reason,updated_at:Date.now()})}),this.pool.setQueueComposingHandler((s,t,o)=>{this.aibotHandle.sendSessionActivitySet({session_id:s,kind:"composing",active:t,...t?{ttl_ms:3e4,ref_event_id:o}:{}})}),this.pool.setInternalErrorHandler(s=>{this.handleSessionInternalError(s).catch(t=>{u.error(this.name,`[recovery] handleSessionInternalError failed event=${s.eventId} session=${s.sessionId}: ${t instanceof Error?t.message:String(t)}`)})}),this.pool.setEventStartedHandler((s,t)=>{if(this.config.adapterType!=="claude")return;this.claudeWorkerStatus.set(t,"busy");const o=this.bindingStore.get(t);o?.cwd&&this.aibotHandle.sendUpdateBindingCard({session_id:t,worker_status:"busy",cwd:o.cwd,meta:this.buildClaudeToolbarMeta(t)})}),this.pool.setEventDoneHandler((s,t)=>{const o=this.config.adapterType??"acp";if(o==="claude"){this.claudeWorkerStatus.set(t,"ready");const i=this.bindingStore.get(t);i?.cwd&&this.aibotHandle.sendUpdateBindingCard({session_id:t,worker_status:"ready",cwd:i.cwd,meta:this.buildClaudeToolbarMeta(t)})}else if(o==="agy"){const i=this.bindingStore.get(t);if(i?.cwd){const n=this.buildAgyToolbarMeta(t),r=n?.available_models??[];u.info(this.name,`[agy-toolbar-diag] eventDone push binding card session=${t} model_id=${String(n?.model_id??"")} available_models=${r.length} meta_keys=${n?Object.keys(n).join(","):"<none>"}`),this.aibotHandle.sendUpdateBindingCard({session_id:t,worker_status:"ready",cwd:i.cwd,meta:n})}else u.info(this.name,`[agy-toolbar-diag] eventDone skip binding card: no binding cwd session=${t}`)}}),this.pool.setSessionActivityHandler((s,t)=>{const o=this.selfDrivenSessions.has(s);t?(this.selfDrivenSessions.add(s),this.aibotHandle.sendSessionActivitySet({session_id:s,kind:"composing",active:!0,ttl_ms:9e4})):(this.selfDrivenSessions.delete(s),o&&this.aibotHandle.sendSessionActivitySet({session_id:s,kind:"composing",active:!1})),o!==t&&this.pushQueueSnapshotForSession(s)}),this.pool.startIdleSweep(),u.info(this.name,`Ready (adapter: ${e}, poolMax: ${this.config.poolMaxSize??20})`)}async stop(){this.stopped=!0,this.pool?.stopIdleSweep(),this.stopKiroQuotaTimer();const e=this.pool?.collectActiveEventIds()??[];e.length>0&&this.activeEventStore&&await this.activeEventStore.save(e);for(const o of e)u.info(this.name,`Canceling active event on shutdown: ${o}`),this.sendEventResultWithCleanup(o,"canceled","process shutting down");e.length>0&&await new Promise(o=>setTimeout(o,100)),this.pool?.clearActiveEventsForShutdown();const s=this.deferredMgr.getAllDeferredEvents();for(const o of s)u.info(this.name,`Failing deferred event on shutdown: ${o.event_id}`),this.sendEventResultWithCleanup(o.event_id,"failed","process shutting down");const t=this.pool?.drainAllQueuedEvents()??[];for(const o of t)u.info(this.name,`Failing queued event on shutdown: ${o.event_id}`),this.sendEventResultWithCleanup(o.event_id,"failed","process shutting down");this.deferredMgr.clearAll(),await this.pool?.stop(),this.aibotHandle?.disconnect(),e.length>0&&this.activeEventStore&&await this.activeEventStore.save([]),this.eventSessionIndex.clear(),this.inflightEvents.clear(),this.restartCount.clear()}createAdapter(e,s){switch(e){case"claude":return this.createClaudeAdapter(s);case"codex":return this.createCodexAdapter(s);case"pi":return this.createPiAdapter(s);case"openhuman":return this.createOpenHumanAdapter(s);case"codewhale":return this.createCodeWhaleAdapter(s);case"cursor":return this.createCursorAdapter(s);case"opencode":return this.createOpenCodeAdapter(s);case"agy":return this.createAgyAdapter(s);default:return this.createAcpAdapter(s)}}createCursorAdapter(e){const s={...this.config.adapterOptions??{}},t=this.bindingStore.get(e),o=t?.modelId??this.globalConfigStore?.get(this.name)?.modelId;o&&(s.model=o),t?.modeId&&(s.mode=t.modeId);const i={sendStreamChunk:(n,r,a,d,c)=>{this.sendStreamChunkByRuntimeConfig(n,r,a,d,c)},sendEventResult:(n,r,a)=>{this.sendEventResultWithCleanup(n,r,a)},sendEventAck:(n,r)=>{this.aibotHandle.sendEventAck({event_id:n,session_id:r,received_at:Date.now()})},sendRawEventEnvelope:(n,r,a)=>{this.aibotHandle.sendMsg({event_id:n,session_id:r,msg_type:1,content:"[cursor] raw_event",extra:{channel_data:{cursor:{raw_event:a}},agent_api_origin:!0}})},agentInvoke:async(n,r)=>this.platformInvoke(n,r),sendLocalActionResult:(n,r,a,d,c)=>{this.aibotHandle.sendLocalActionResult({action_id:n,status:r,...a!==void 0?{result:a}:{},...d?{error_code:d}:{},...c?{error_msg:c}:{}})}};return new P({command:this.config.agent.command,args:this.config.agent.args,env:this.config.agent.env,options:s},i)}createClaudeAdapter(e){const s={sendReply:(i,n,r,a,d)=>{this.sendReplyByRuntimeConfig(i,n,r,a,d)},sendStreamChunk:(i,n,r,a,d,c,l)=>{this.sendStreamChunkByRuntimeConfig(i,n,r,a,d,c,l)},sendMedia:(i,n,r,a,d,c,l)=>{this.aibotHandle.sendMedia({event_id:i,session_id:n,content:r,msg_type:2,quoted_message_id:d||void 0,client_msg_id:c||void 0,extra:l?{media_caption:a,...l}:{media_caption:a}})},sendEventResult:(i,n,r,a)=>{this.sendEventResultWithCleanup(i,n,r,a)},sendEventAck:(i,n)=>{this.aibotHandle.sendEventAck({event_id:i,session_id:n,received_at:Date.now()})},agentInvoke:async(i,n,r)=>this.platformInvoke(i,n,r),sendLocalActionResult:(i,n,r,a,d)=>{this.aibotHandle.sendLocalActionResult({action_id:i,status:n,...r!==void 0?{result:r}:{},...a?{error_code:a}:{},...d?{error_msg:d}:{}})},sendToolUse:(i,n,r,a)=>{this.sendToolExecutionCard(i,n,E(r,a))},sendToolResult:(i,n,r,a)=>{this.sendToolExecutionCard(i,n,$(r,a))},getWsUrl:()=>this.config.aibot.url,getAgentId:()=>this.config.aibot.agentId,getApiKey:()=>this.config.aibot.apiKey,getActiveEventCount:()=>0,getPendingPermissionCount:()=>0,getPendingElicitationCount:()=>0,sendAgentQuestionCard:(i,n,r)=>{const a=r.questions.map(c=>c.header).join(", "),d=Pe(`[Agent Question] ${r.request_id}`,"agent_question",r);this.aibotHandle.sendText({event_id:i,session_id:n,content:d,msg_type:1,extra:{card_type:"agent_question",summary_text:a}})},sendPermissionCard:i=>{this.aibotHandle.sendMsg({event_id:i.eventId,session_id:i.sessionId,client_msg_id:`perm_${F()}`,msg_type:1,content:i.toolTitle?`Permission required: ${i.toolTitle}`:"Permission request",extra:{channel_data:{execApproval:{approvalId:i.approvalId,approvalSlug:i.toolName},grix:{execApproval:{approval_command_id:i.approvalId,command:i.toolTitle||i.toolName,host:"claude"}}},agent_api_origin:!0}})},sendDirectMessage:i=>{this.aibotHandle.sendMsg({session_id:i.sessionId,msg_type:1,content:i.content,...i.clientMsgId?{client_msg_id:i.clientMsgId}:{},...i.quotedMessageId?{quoted_message_id:i.quotedMessageId}:{}})},onStatusLineUpdated:i=>{(i.rateLimits?.fiveHour||i.rateLimits?.sevenDay)&&(this.cachedClaudeRateLimitState=i);const n=this.bindingStore.get(e);n?.cwd&&this.aibotHandle.sendUpdateBindingCard({session_id:e,worker_status:this.claudeWorkerStatus.get(e)??"ready",cwd:n.cwd,meta:this.buildClaudeToolbarMeta(e)})},sendMcpFrame:i=>{this.aibotHandle.sendMcpFrame(e,i)}},t=this.config.adapterOptions??{},o={...t,sessionRuntimeResolver:()=>{const i=this.bindingStore.get(e);return{cwd:i?.cwd,modeId:i?.modeId??C.fullAuto,modelId:i?.modelId??this.globalConfigStore?.get(this.name)?.modelId,pluginDir:t.pluginDir,claudeSessionId:i?.claudeSessionId,onSessionIdAssigned:n=>{this.bindingStore.setClaudeSessionId(e,n),this.sessionScanCache.invalidate()}}}};return new T({command:this.config.agent.command,args:this.config.agent.args,env:this.config.agent.env,options:o},s)}createCodexAdapter(e){let s=null;const t={sendEventResult:(n,r,a)=>{this.sendEventResultWithCleanup(n,r,a)},sendEventAck:(n,r)=>this.aibotHandle.sendEventAck({event_id:n,session_id:r,received_at:Date.now()}),sendCodexEvent:n=>{this.shouldDropCodexDisplayEvent(n.event_id,n.codex_method)||(this.aibotHandle.sendCodexEvent(n),this.logCodexEventToConversation(n))},sendCodexEventReliable:async n=>{if(!this.shouldDropCodexDisplayEvent(n.event_id,n.codex_method)){try{await this.aibotHandle.sendCodexEventReliable(n)}catch(r){u.warn("bridge",`[codex] sendCodexEventReliable ACK failed event=${n.event_id}: ${r}`)}u.info("bridge",`[codex] sendCodexEventReliable done event=${n.event_id} method=${n.codex_method}`),this.logCodexEventToConversation(n)}},sendRunError:(n,r,a)=>{this.sendStreamChunkByRuntimeConfig(n,r,`
Error: ${a}`,1,!1)},sendUpdateBindingCard:(n,r,a,d)=>{const c={...d??{}};if(!c.rate_limits&&this.cachedProviderQuota?.success){this.isRateLimitsCacheFresh(this.cachedProviderQuotaSampledAtMs)||this.maybeQueryProviderQuota().catch(()=>{});const l=this.providerQuotaToCodexRateLimits(this.cachedProviderQuota);l&&(c.rate_limits=l.rateLimits,c.rate_limit_primary_percent=l.primaryPercent,c.rate_limit_secondary_percent=l.secondaryPercent,c.rate_limit_primary_window_min=l.primaryWindowMin,c.rate_limit_secondary_window_min=l.secondaryWindowMin)}!c.provider_quota&&this.cachedProviderQuota?.success&&(c.provider_quota=this.cachedProviderQuota),this.aibotHandle.sendUpdateBindingCard({session_id:n,worker_status:r,cwd:a,...Object.keys(c).length>0?{meta:c}:{}})},agentInvoke:async(n,r)=>this.platformInvoke(n,r),sendLocalActionResult:(n,r,a,d,c)=>this.aibotHandle.sendLocalActionResult({action_id:n,status:r,...a!==void 0?{result:a}:{},...d?{error_code:d}:{},...c?{error_msg:c}:{}}),sendSessionActivitySet:(n,r,a,d)=>{this.aibotHandle.sendSessionActivitySet({session_id:n,kind:r,active:a,...d??{}})},getConversationLog:()=>this.conversationLog,onRateLimitsUpdated:n=>{this.cachedRateLimits=n,this.cachedRateLimitsSampledAtMs=Date.now(),this.isRateLimitsCacheFresh(this.cachedProviderQuotaSampledAtMs)||this.maybeQueryProviderQuota().catch(()=>{});const r=this.bindingStore.get(e);if(r?.cwd){const a=this.cachedRateLimitsSampledAtMs,d={rate_limits:{primary:n.primary,secondary:n.secondary,sampledAt:a},rate_limit_primary_percent:n.primary.usedPercent,rate_limit_secondary_percent:n.secondary.usedPercent,rate_limit_primary_window_min:n.primary.windowMinutes,rate_limit_secondary_window_min:n.secondary.windowMinutes,...s?.getEffortMeta()};this.cachedProviderQuota?.success&&(d.provider_quota=this.cachedProviderQuota),this.aibotHandle.sendUpdateBindingCard({session_id:e,worker_status:"ready",cwd:r.cwd,meta:d})}},onContextWindowUpdated:n=>{if(!n)return;this.cachedCodexContextWindow=n,this.cachedCodexUsageSampledAtMs=Date.now();const r=this.bindingStore.get(e);r?.cwd&&this.aibotHandle.sendUpdateBindingCard({session_id:e,worker_status:"ready",cwd:r.cwd,meta:{context_window:n,...s?.getEffortMeta()}})},onTokenUsageUpdated:n=>{n&&(this.cachedCodexTokenUsage=n,this.cachedCodexUsageSampledAtMs=Date.now())}},o=this.config.adapterOptions??{},i=this.globalConfigStore?.get(this.name);return s=new X({command:this.config.agent.command,args:this.config.agent.args,env:this.config.agent.env,options:{...o,model:this.bindingStore.getCodexModelId(e)??o.model,collaborationMode:this.bindingStore.getCodexModeId(e)??o.collaborationMode,reasoningEffort:i?.codexReasoningEffort??o.reasoningEffort,sandboxMode:i?.codexSandboxMode??o.sandboxMode,aibotSessionId:e,bindingStore:this.bindingStore}},t),s}createCodeWhaleAdapter(e){const s={sendEventResult:(o,i,n)=>{this.sendEventResultWithCleanup(o,i,n)},sendEventAck:(o,i)=>this.aibotHandle.sendEventAck({event_id:o,session_id:i,received_at:Date.now()}),sendStreamChunk:(o,i,n,r,a,d)=>{this.sendStreamChunkByRuntimeConfig(o,i,n,r,a,d)},sendUpdateBindingCard:(o,i,n,r)=>this.aibotHandle.sendUpdateBindingCard({session_id:o,worker_status:i,cwd:n,...r?{meta:r}:{}}),sendLocalActionResult:(o,i,n,r,a)=>this.aibotHandle.sendLocalActionResult({action_id:o,status:i,...n!==void 0?{result:n}:{},...r?{error_code:r}:{},...a?{error_msg:a}:{}}),sendSessionActivitySet:(o,i,n,r)=>{this.aibotHandle.sendSessionActivitySet({session_id:o,kind:i,active:n,...r??{}})},sendToolUse:(o,i,n,r)=>{this.sendToolExecutionCard(o,i,E(n,r))},sendToolResult:(o,i,n,r)=>{this.sendToolExecutionCard(o,i,$(n,r))},agentInvoke:async(o,i)=>this.platformInvoke(o,i),getConversationLog:()=>this.conversationLog},t=this.config.adapterOptions??{};return new q({command:this.config.agent.command,args:this.config.agent.args,env:this.config.agent.env,options:{...t,aibotSessionId:e,bindingStore:this.bindingStore}},s)}createPiAdapter(e){const s={sendEventResult:(o,i,n)=>{this.sendEventResultWithCleanup(o,i,n),u.info("bridge",`[pi] sendEventResult event=${o} status=${i}`)},sendEventAck:(o,i)=>this.aibotHandle.sendEventAck({event_id:o,session_id:i,received_at:Date.now()}),sendUpdateBindingCard:(o,i,n,r)=>this.aibotHandle.sendUpdateBindingCard({session_id:o,worker_status:i,cwd:n,...r?{meta:r}:{}}),agentInvoke:async(o,i)=>this.platformInvoke(o,i),sendLocalActionResult:(o,i,n,r,a)=>{this.aibotHandle.sendLocalActionResult({action_id:o,status:i,...n!==void 0?{result:n}:{},...r?{error_code:r}:{},...a?{error_msg:a}:{}})},sendSessionActivitySet:(o,i,n,r)=>{this.aibotHandle.sendSessionActivitySet({session_id:o,kind:i,active:n,...r??{}})},sendToolUse:(o,i,n,r)=>{this.sendToolExecutionCard(o,i,E(n,r))},sendToolResult:(o,i,n,r)=>{this.sendToolExecutionCard(o,i,$(n,r))},sendStreamChunk:(o,i,n,r,a,d)=>{this.sendStreamChunkByRuntimeConfig(o,i,n,r,a,d),a&&u.info("bridge",`[pi] sendFinalStreamChunk event=${o} seq=${r}`)},sendFinalStreamChunkReliable:async(o,i,n,r)=>{if(this.finalizeThinking(o,i),this.resolveEventRuntimeConfig(o).responseDelivery==="single_message"&&o){this.bufferStreamChunk(o,i,"",!0);return}try{await this.aibotHandle.sendStreamChunkRequest({event_id:o,session_id:i,delta_content:"",chunk_seq:n,is_finish:!0,...r?{client_msg_id:r}:{}})}catch(d){u.warn("bridge",`[pi] sendFinalStreamChunkReliable ACK failed event=${o}: ${d}`)}u.info("bridge",`[pi] sendFinalStreamChunkReliable done event=${o} seq=${n}`)},sendThinking:(o,i,n)=>{this.sendThinkingByRuntimeConfig(o,i,n)},sendRunError:(o,i,n,r,a)=>{this.sendStreamChunkByRuntimeConfig(o,i,`
Error: ${n}`,r,!1,a)}},t=this.config.adapterOptions??{};return new Y({command:this.config.agent.command,args:this.config.agent.args,env:this.config.agent.env,options:{...t,aibotSessionId:e,bindingStore:this.bindingStore}},s)}createOpenHumanAdapter(e){const s={sendStreamChunk:(o,i,n,r,a,d)=>{this.finalizeThinking(o,i),this.sendStreamChunkByRuntimeConfig(o,i,n,r,a,d)},sendFinalStreamChunkReliable:async(o,i,n,r)=>{if(this.finalizeThinking(o,i),this.resolveEventRuntimeConfig(o).responseDelivery==="single_message"&&o){this.bufferStreamChunk(o,i,"",!0);return}try{await this.aibotHandle.sendStreamChunkRequest({event_id:o,session_id:i,delta_content:"",chunk_seq:n,is_finish:!0,...r?{client_msg_id:r}:{}})}catch(d){u.warn("bridge",`[openhuman] sendFinalStreamChunkReliable ACK failed event=${o}: ${d}`)}},sendEventResult:(o,i,n)=>{this.sendEventResultWithCleanup(o,i,n),u.info("bridge",`[openhuman] sendEventResult event=${o} status=${i}`)},sendEventAck:(o,i)=>this.aibotHandle.sendEventAck({event_id:o,session_id:i,received_at:Date.now()}),sendToolUse:(o,i,n,r)=>{this.sendToolExecutionCard(o,i,E(n,r))},sendToolResult:(o,i,n,r)=>{this.sendToolExecutionCard(o,i,$(n,r))},sendThinking:(o,i,n)=>{this.sendThinkingByRuntimeConfig(o,i,n)},sendRunError:(o,i,n)=>{this.sendStreamChunkByRuntimeConfig(o,i,`
Error: ${n}`,0,!1)},sendUpdateBindingCard:(o,i,n)=>this.aibotHandle.sendUpdateBindingCard({session_id:o,worker_status:i,cwd:n}),agentInvoke:async(o,i)=>this.platformInvoke(o,i),sendLocalActionResult:(o,i,n,r,a)=>{this.aibotHandle.sendLocalActionResult({action_id:o,status:i,...n!==void 0?{result:n}:{},...r?{error_code:r}:{},...a?{error_msg:a}:{}})}},t=this.config.adapterOptions??{};return new Z({command:this.config.agent.command,args:this.config.agent.args,env:this.config.agent.env,options:t},s,{port:t.port,host:t.host,workspaceDir:t.workspace_dir,sessionToken:t.session_token,enableSessionBinding:!0,aibotSessionId:e})}createOpenCodeAdapter(e){const s={sendStreamChunk:(o,i,n,r,a,d)=>{this.sendStreamChunkByRuntimeConfig(o,i,n,r,a,d)},sendFinalStreamChunkReliable:async(o,i,n,r)=>{if(this.finalizeThinking(o,i),this.resolveEventRuntimeConfig(o).responseDelivery==="single_message"&&o){this.bufferStreamChunk(o,i,"",!0);return}try{await this.aibotHandle.sendStreamChunkRequest({event_id:o,session_id:i,delta_content:"",chunk_seq:n,is_finish:!0,...r?{client_msg_id:r}:{}})}catch(d){u.warn("bridge",`[opencode] sendFinalStreamChunkReliable ACK failed event=${o}: ${d}`)}},sendEventResult:(o,i,n)=>{this.sendEventResultWithCleanup(o,i,n),u.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,n,r)=>{this.sendToolExecutionCard(o,i,E(n,r))},sendToolResult:(o,i,n,r)=>{this.sendToolExecutionCard(o,i,$(n,r))},sendThinking:(o,i,n)=>{this.sendThinkingByRuntimeConfig(o,i,n)},sendRunError:(o,i,n)=>{this.sendStreamChunkByRuntimeConfig(o,i,`
Error: ${n}`,0,!1)},sendUpdateBindingCard:(o,i,n)=>this.aibotHandle.sendUpdateBindingCard({session_id:o,worker_status:i,cwd:n}),agentInvoke:async(o,i)=>this.platformInvoke(o,i),sendLocalActionResult:(o,i,n,r,a)=>{this.aibotHandle.sendLocalActionResult({action_id:o,status:i,...n!==void 0?{result:n}:{},...r?{error_code:r}:{},...a?{error_msg:a}:{}})}},t=this.config.adapterOptions??{};return new ee({command:this.config.agent.command,args:this.config.agent.args,env:this.config.agent.env,options:t},s,{port:t.port,hostname:t.hostname,model:t.model,agent:t.agent,permissionPolicy:t.permission_policy,enableSessionBinding:!0,aibotSessionId:e})}createAgyAdapter(e){const s={sendStreamChunk:(o,i,n,r,a)=>{this.sendStreamChunkByRuntimeConfig(o,i,n,r,a)},sendEventResult:(o,i,n)=>{this.sendEventResultWithCleanup(o,i,n)},sendEventAck:(o,i)=>{this.aibotHandle.sendEventAck({event_id:o,session_id:i,received_at:Date.now()})},agentInvoke:async(o,i,n)=>this.platformInvoke(o,i,n),forceCompleteInternalEvent:(o,i)=>{this.pool.eventComplete(o,i),this.pushQueueSnapshotForSession(i)},persistConversationId:(o,i)=>{this.bindingStore.setAgyConversationId(o,i)}},t=o=>{const i=this.bindingStore.get(o);return{cwd:i?.cwd,modelId:i?.modelId??this.globalConfigStore?.get(this.name)?.modelId,conversationId:i?.agyConversationId}};return new U({command:this.config.agent.command,args:this.config.agent.args,env:this.config.agent.env,options:this.config.adapterOptions??{}},s,t)}createAcpAdapter(e){const s=this.isAcpRawTransportEnabled(),t={sendStreamChunk:(a,d,c,l,h,m)=>{this.finalizeThinking(a,d),this.sendStreamChunkByRuntimeConfig(a,d,c,l,h,m)},sendFinalStreamChunkReliable:async(a,d,c,l)=>{if(this.finalizeThinking(a,d),this.resolveEventRuntimeConfig(a).responseDelivery==="single_message"&&a){this.bufferStreamChunk(a,d,"",!0);return}try{await this.aibotHandle.sendStreamChunkRequest({event_id:a,session_id:d,delta_content:"",chunk_seq:c,is_finish:!0,...l?{client_msg_id:l}:{}})}catch(m){u.warn("bridge",`[acp] sendFinalStreamChunkReliable ACK failed event=${a}: ${m}`)}u.info("bridge",`[acp] sendFinalStreamChunkReliable done event=${a} seq=${c}`)},sendEventResult:(a,d,c)=>{this.sendEventResultWithCleanup(a,d,c)},sendEventAck:(a,d)=>{this.aibotHandle.sendEventAck({event_id:a,session_id:d,received_at:Date.now()})},agentInvoke:async(a,d)=>this.platformInvoke(a,d),sendLocalActionResult:(a,d,c,l,h)=>{this.aibotHandle.sendLocalActionResult({action_id:a,status:d,...c!==void 0?{result:c}:{},...l?{error_code:l}:{},...h?{error_msg:h}:{}})},sendRawEventEnvelope:(a,d,c)=>{this.sendAcpRawEventEnvelope(a,d,c)},sendToolUse:(a,d,c,l)=>{if(s){this.sendAcpRawEventEnvelope(a,d,{type:"tool_use",payload:{tool_name:c,tool_input:l??""}});return}this.sendToolExecutionCard(a,d,E(c,l))},sendToolResult:(a,d,c,l)=>{this.sendToolExecutionCard(a,d,$(c,l))},sendThinking:(a,d,c)=>{this.sendThinkingByRuntimeConfig(a,d,c)},sendRunError:(a,d,c)=>{this.sendStreamChunkByRuntimeConfig(a,d,`
Error: ${c}`,1,!1)},sendPermissionCard:a=>{if(s){this.sendAcpRawEventEnvelope(a.eventId,a.sessionId,{type:"permission_request",payload:{tool_call_id:a.toolCallId,tool_name:a.toolName,tool_title:a.toolTitle,options:a.options}});return}this.aibotHandle.sendMsg({event_id:a.eventId,session_id:a.sessionId,client_msg_id:`perm_${F()}`,msg_type:1,content:a.toolTitle?`Permission required: ${a.toolTitle}`:"Permission request",extra:{channel_data:{execApproval:{approvalId:a.toolCallId,approvalSlug:a.toolName},grix:{execApproval:{approval_command_id:a.toolCallId,command:a.toolTitle||a.toolName,host:"acp"}}},agent_api_origin:!0}})},sendAuthNotification:(a,d)=>{a&&this.aibotHandle.sendMsg({session_id:a,msg_type:1,content:d,extra:{biz_card:{version:1,type:"agent_error",payload:{error:{name:"AuthRequired",message:d}}}}})},sendUpdateBindingCard:(a,d,c,l)=>{const h={...l??{}};if(!h.rate_limits&&this.cachedProviderQuota?.success){this.isRateLimitsCacheFresh(this.cachedProviderQuotaSampledAtMs)||this.maybeQueryProviderQuota().catch(()=>{});const m=this.providerQuotaToRateLimits(this.cachedProviderQuota);m&&(h.rate_limits=m)}!h.provider_quota&&this.cachedProviderQuota?.success&&(h.provider_quota=this.cachedProviderQuota),this.aibotHandle.sendUpdateBindingCard({session_id:a,worker_status:d,cwd:c,...Object.keys(h).length>0?{meta:h}:{}})},onSkillsUpdate:a=>{try{this.aibotHandle.sendSkillsUpdate({skills:a})}catch(d){}},onContextWindowUpdated:a=>{this.cachedAcpContextWindow=a,this.cachedAcpContextWindowSampledAtMs=Date.now();const d="usedPercentage"in a?a.usedPercentage.toFixed(1):(a.used/a.size*100).toFixed(1);u.info(this.name,`[acp] context_window updated: ${d}%`);const c=this.bindingStore.get(e);if(c?.cwd){const l={context_window:"usedPercentage"in a?{usedPercentage:a.usedPercentage,remainingPercentage:100-a.usedPercentage}:a};if(this.config.aibot.clientType==="kiro"&&this.cachedProviderQuota?.success){this.isRateLimitsCacheFresh(this.cachedProviderQuotaSampledAtMs)||this.maybeQueryProviderQuota().catch(()=>{}),l.provider_quota=this.cachedProviderQuota;const h=this.providerQuotaToRateLimits(this.cachedProviderQuota);h&&(l.rate_limits=h)}this.aibotHandle.sendUpdateBindingCard({session_id:e,worker_status:"ready",cwd:c.cwd,meta:l})}},sendMcpFrame:a=>{this.aibotHandle.sendMcpFrame(e,a)}},o=e?this.bindingStore.get(e):void 0,i=this.globalConfigStore?.get(this.name),{initialModel:n,initialMode:r}=Ae({sessionBinding:o,globalDefaults:i,configInitialMode:this.config.acpInitialMode});return(n||r)&&u.info(this.name,`[toolbar] hydrate from binding: session=${e} model=${n??"<none>"} mode=${r??"<none>"}`),new A({command:this.config.agent.command,args:this.config.agent.args,env:this.config.agent.env},t,{acpAuthMethod:this.config.acpAuthMethod,acpInitialMode:r,acpInitialModel:n,acpMcpTools:this.config.acpMcpTools,rawTransport:s,eventResultsPath:this.config.eventResultsPath,approvalMode:this.config.approvalMode,bindingStore:this.config.enableSessionBinding?this.bindingStore:void 0,aibotSessionId:e,autoInjectArgs:this.config.autoInjectArgs,bridgeLog:this.config.logDir?new _e(this.config.logDir,`${this.name}-${e}`):null})}async connectAibot(){const e=new J;if(this.aibotHandle=await e.connect(this.aibotConfig,{aborted:()=>this.stopped,label:this.name,packetLog:this.packetLog}),this.aibotHandle.onEvent(s=>{this.handleAibotEvent(s).catch(t=>{u.error(this.name,`handleAibotEvent failed: ${t}`),this.aibotHandle.sendEventAck({event_id:s.event_id,session_id:s.session_id,received_at:Date.now()});const o=t instanceof Error?t.message:String(t);if(/CWD must be|Bound directory does not exist|Bound path is not a directory/i.test(o)&&s.session_id){this.bindingStore.delete(s.session_id),this.sessionBindings.delete(s.session_id);const n=this.pool.getSlot(s.session_id);n?.adapter instanceof A&&n.adapter.getSessionBindings().delete(s.session_id);const r=this.config.adapterType??"acp",a=this.resolveBindingChannelKey(r);this.aibotHandle.sendMsg({event_id:s.event_id,session_id:s.session_id,msg_type:1,content:o,extra:{channel_data:{[a]:{sessionBinding:{status:"missing",reason:"binding_stale",error_code:g.invalidCwd}}}},quoted_message_id:s.msg_id});return}this.aibotHandle.sendEventResult({event_id:s.event_id,status:"failed",msg:o,updated_at:Date.now()})})}),this.aibotHandle.onStop(s=>{try{this.handleAibotStop(s)}catch(t){u.error(this.name,`handleAibotStop failed: ${t}`)}}),this.aibotHandle.onRevoke(s=>{try{this.handleAibotRevoke(s)}catch(t){u.error(this.name,`handleAibotRevoke failed: ${t}`)}}),this.aibotHandle.onLocalAction(s=>{this.handleAibotLocalAction(s).catch(t=>{u.error(this.name,`handleAibotLocalAction failed: ${t}`)})}),this.aibotHandle.onEventCancel(s=>{u.info(this.name,`recv event_cancel event_id=${s.event_id} session_id=${s.session_id}`),this.handleEventCancel(s).catch(t=>{u.error(this.name,`handleEventCancel failed: ${t}`)})}),this.aibotHandle.onMcpFrame((s,t)=>{const o=this.pool.getSlot(s)?.adapter;o?.deliverMcpFrameToAgent?o.deliverMcpFrameToAgent(t):u.warn(this.name,`mcp_frame: no adapter for session=${s}`)}),this.aibotHandle.onQueueClear(s=>{const t=this.pool.clearQueue(s.session_id);this.aibotHandle.sendQueueClearResult({session_id:s.session_id,canceled_event_ids:t}),this.pushQueueSnapshotForSession(s.session_id)}),this.aibotHandle.onQueueSnapshotQuery(s=>{this.replyQueueSnapshotForSession(s.session_id)}),u.info(this.name,"Connected to aibot"),this.activeEventStore){const s=await this.activeEventStore.drain();if(s.length>0){u.warn(this.name,`Recovering ${s.length} stale event(s) from previous run`);for(const t of s)u.info(this.name,`Failing stale event on startup: ${t}`),this.aibotHandle.sendEventResult({event_id:t,status:"failed",msg:"process restarted, event lost",updated_at:Date.now()})}}this.pushQueueSnapshots(),this.aibotHandle.onReconnected(()=>{this.pushQueueSnapshots()}),this.aibotHandle.onStreamRejected((s,t)=>{this.sendCtrl.markEventRejected(s)})}pushQueueSnapshots(){if(!(!this.config.eventQueue||!this.pool))for(const e of this.pool.getAllSlots())this.pushQueueSnapshotForSession(e.sessionId)}buildQueueSnapshotPayload(e){const s=this.pool?.getQueueSnapshot(e)??null,t=s?[...s.running]:[],o=s?s.running_items.map(n=>({event_id:n.event_id,...n.content_preview?{content_preview:n.content_preview}:{},...n.title?{title:n.title}:{},...n.summary?{summary:n.summary}:{},actions:[{type:"stop"}]})):[],i=s?s.queued.map(n=>({event_id:n.event_id,position:n.position,...n.content_preview?{content_preview:n.content_preview}:{},...n.title?{title:n.title}:{},...n.summary?{summary:n.summary}:{},actions:[{type:"cancel"}]})):[];if(t.length===0&&this.selfDrivenSessions.has(e)&&this.pool?.getSlot(e)){const n=`selfdrive_${e}`,r="Background task in progress";t.push(n),o.push({event_id:n,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 s=this.buildQueueSnapshotPayload(e);u.info(this.name,`[queue-debug] push snapshot session=${e} running=${s.running.length} queued=${s.queued.length} running_ids=[${s.running.join(",")}]`),this.aibotHandle.sendQueueSnapshot(s)}replyQueueSnapshotForSession(e){!this.config.eventQueue||!this.pool||this.aibotHandle.sendQueueSnapshot(this.buildQueueSnapshotPayload(e))}async platformInvoke(e,s,t){return e==="file_link"?$e(s):this.aibotHandle.agentInvoke(e,s,t)}sendReplyByRuntimeConfig(e,s,t,o,i){this.indexEventSession(e,s),this.sendCtrl.sendReply(e,s,t,o,i),t&&this.conversationLog?.logOutbound?.(s,e,"reply",t)}sendStreamChunkByRuntimeConfig(e,s,t,o,i,n,r){this.indexEventSession(e,s),this.sendCtrl.sendStreamChunk(e,s,t,o,i,n,r),(t||i)&&this.conversationLog?.logOutbound?.(s,e,i?"stream_chunk_finish":"stream_chunk",t)}sendRunErrorAsChunk(e,s,t){this.sendStreamChunkByRuntimeConfig(e,s,`
Error: ${t}`,1,!1)}sendEventResultWithCleanup(e,s,t,o){this.sendCtrl.sendEventResult(e,s,t,o);const i=this.eventSessionIndex.get(e);i&&(this.pool.eventComplete(e,i),this.pushQueueSnapshotForSession(i),this.conversationLog?.logResult?.(i,e,s,t),this.eventSessionIndex.delete(e)),this.inflightEvents.delete(e),this.restartCount.delete(e),s==="responded"&&(this.cachedProviderQuotaSampledAtMs=null,this.maybeQueryProviderQuota().catch(()=>{}))}async handleSessionInternalError(e){const{eventId:s,sessionId:t,errorMsg:o}=e;if(this.stopped)return;const i=this.inflightEvents.get(s);if(!i){u.warn(this.name,`[recovery] no inflight event for internalError event=${s} session=${t}; surface failure directly`),this.sendRunErrorAsChunk(s,t,o),this.sendEventResultWithCleanup(s,"failed",o,"agent_stop_failure");return}const n=(this.restartCount.get(s)??0)+1;this.restartCount.set(s,n);const r=this.config.adapterType??"acp";if(n>B){u.error(this.name,`[recovery] adapter=${r} session=${t} event=${s} restart=${n}/${B} outcome=give-up err=${o}`),this.sendRunErrorAsChunk(s,t,o),this.sendEventResultWithCleanup(s,"failed",o,"agent_stop_failure");return}u.info(this.name,`[recovery] adapter=${r} session=${t} event=${s} restart=${n}/${B} outcome=restarting err=${o}`);const a=this.pool.drainQueuedForSession(t);a.length>0&&u.info(this.name,`[recovery] session=${t} preserved ${a.length} queued sibling event(s) across restart`);try{await this.pool.removeSlot(t)}catch(l){u.warn(this.name,`[recovery] removeSlot failed session=${t}: ${l instanceof Error?l.message:String(l)}`)}if(this.stopped)return;const d=this.resolveRecoveryPrompt(r,i),c={...i,content:d};try{await this.pool.deliverInboundEvent(c)}catch(l){u.error(this.name,`[recovery] redeliver failed event=${s} session=${t}: ${l instanceof Error?l.message:String(l)}`),this.sendEventResultWithCleanup(s,"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(h){u.error(this.name,`[recovery] sibling redeliver failed event=${l.event_id} session=${t}: ${h instanceof Error?h.message:String(h)}`),this.sendEventResultWithCleanup(l.event_id,"failed",h instanceof Error?h.message:String(h))}}}resolveRecoveryPrompt(e,s){return e==="acp"?"continue":s.content}sendThinkingByRuntimeConfig(e,s,t){this.sendCtrl.sendThinking(e,s,t)}bufferStreamChunk(e,s,t,o,i){this.sendCtrl.bufferOnly(e,s,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),e.event_id&&this.inflightEvents.set(e.event_id,e)}indexEventSession(e,s){!e||!s||this.eventSessionIndex.set(e,s)}shouldDropToolDisplayEvent(e){return this.sendCtrl.shouldDropToolDisplayEvent(e)}shouldDropThinkingDisplayEvent(e){return this.sendCtrl.shouldDropThinkingDisplayEvent(e)}shouldDropCodexDisplayEvent(e,s){return this.sendCtrl.shouldDropCodexDisplayEvent(e,s)}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,s){return this.sendCtrl.shouldDropAcpRawDisplayEvent(e,s)}sendAcpRawEventEnvelope(e,s,t){this.shouldDropAcpRawDisplayEvent(e,t.type)||this.aibotHandle.sendMsg({event_id:e,session_id:s,msg_type:1,content:this.buildAcpRawEventFallbackText(t),extra:{channel_data:{acp:{raw_event:t}},agent_api_origin:!0}})}buildAcpRawEventFallbackText(e){const s=String(e.type??"").trim();if(!s)return"[acp] event";switch(s){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] ${s}`}}sendToolExecutionCard(e,s,t,o){this.sendCtrl.sendToolExecutionCard(e,s,t,o)}async handleAibotEvent(e){if(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 s=this.config.adapterType??"acp";if(this.allowlistGate&&e.sender_id&&!await this.allowlistGate.checkAccess(e.sender_id)){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",code:"access_denied",msg:`sender ${e.sender_id} is not authorized`,updated_at:Date.now()});return}const t=Me(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=we(e);if(i){if(i.verb===p.exec){await this.handleSessionControlCommand(i,e);return}if(i.verb===p.listSessions){await this.handleListSessionsTextCommand(e);return}if(s==="claude"){await this.handleSessionControlCommand(i,e);return}if(s==="codex"&&i.verb===p.open){await this.handleCodexSessionControlOpen(i,e);return}if(s==="pi"&&i.verb===p.open){await this.handlePiSessionControlOpen(i,e);return}if(s==="pi"&&i.verb===p.restart){await this.handlePiSessionControlRestart(e);return}if((s==="openhuman"||s==="opencode")&&i.verb===p.open){await this.handleOpenHumanSessionControlOpen(i,e);return}if(s==="codewhale"&&i.verb===p.open){await this.handleCodeWhaleSessionControlOpen(i,e);return}if(this.aibotHandle.sendEventAck({event_id:e.event_id,session_id:e.session_id,received_at:Date.now()}),i.verb===p.open){const a=i.args.trim();if(!a){this.aibotHandle.sendEventResult({event_id:e.event_id,status:"failed",code:g.cwdRequired,msg:"cwd is required",updated_at:Date.now()});return}try{const d=x.resolve(a);if(!(await H(d)).isDirectory()){this.aibotHandle.sendEventResult({event_id:e.event_id,status:"failed",code:g.invalidCwd,msg:`Path is not a directory: ${d}`,updated_at:Date.now()});return}}catch(d){const c=String(d?.code??""),l=c==="ENOENT"?`Directory does not exist: ${x.resolve(a)}`:c==="EACCES"||c==="EPERM"?"Directory is not accessible":`Invalid path: ${a}`;this.aibotHandle.sendEventResult({event_id:e.event_id,status:"failed",code:g.invalidCwd,msg:l,updated_at:Date.now()});return}}if(i.verb===p.open){const a=this.bindingStore.get(e.session_id);if(a?.cwd)try{await H(a.cwd)}catch{u.info("bridge",`Stale binding detected for session ${e.session_id}: ${a.cwd} no longer exists, clearing`),this.bindingStore.delete(e.session_id),this.sessionBindings.delete(e.session_id);const d=this.pool.getSlot(e.session_id);d?.adapter instanceof A&&d.adapter.getSessionBindings().delete(e.session_id)}}if(await this.handleSessionControlForPool(i,e),s==="acp"&&i.verb===p.stop){const d=this.bindingStore.get(e.session_id)?.cwd??"";await this.pool.removeSlot(e.session_id).catch(()=>{}),this.sessionBindings.delete(e.session_id),this.aibotHandle.sendEventResult({event_id:e.event_id,status:"responded",msg:`Session worker stopped for ${d}`,updated_at:Date.now()});return}if(O(i,e,this.sessionControlCtx(e.session_id),{...this.sessionControlSenders(),sendEventAck:()=>{}}),i.verb===p.open&&(await this.deferredMgr.release(e.session_id,this.deferredCallbacks()),s==="agy")){const a=this.bindingStore.get(e.session_id)?.cwd??"";a&&this.aibotHandle.sendUpdateBindingCard({session_id:e.session_id,worker_status:"ready",cwd:a,meta:this.buildAgyToolbarMeta(e.session_id)})}return}if(e.mirror_mode==="record_only"){this.aibotHandle.sendEventAck({event_id:e.event_id,session_id:e.session_id,received_at:Date.now()}),this.aibotHandle.sendEventResult({event_id:e.event_id,status:"responded",updated_at:Date.now()});return}if(this.isStaleEvent(e)){this.aibotHandle.sendEventAck({event_id:e.event_id,session_id:e.session_id,received_at:Date.now()}),this.aibotHandle.sendEventResult({event_id:e.event_id,status:"failed",code:"event_stale",msg:"event is stale and will not be processed",updated_at:Date.now()});return}if(Oe.has(s)&&!this.bindingStore.get(e.session_id)?.cwd){const d=s;u.info(this.name,`[${d}] binding missing session_id=${e.session_id} event_id=${e.event_id}`),this.deferredMgr.defer(d,String(e.session_id??"").trim(),this.buildInboundEvent(e,o));const c=this.resolveBindingChannelKey(s);this.aibotHandle.sendEventAck({event_id:e.event_id,session_id:e.session_id,received_at:Date.now()}),this.aibotHandle.sendMsg({event_id:e.event_id,session_id:e.session_id,msg_type:1,content:"Session binding missing.",extra:{channel_data:{[c]:{sessionBinding:{status:"missing",reason:"binding_missing",error_code:g.bindingMissing}}}},quoted_message_id:e.msg_id});return}if((this.config.adapterType??"acp")==="acp"){const d=String(e.content??"").trim().match(/^\/(\S+)\s*(.*)/);if(d){const[,c,l]=d,m=this.pool.getSlot(e.session_id)?.adapter;if(m?.execCommand&&(m.getSupportedCommands?.()??[]).some(_=>_.name===c||_.name===`/${c}`)){this.aibotHandle.sendEventAck({event_id:e.event_id,session_id:e.session_id,received_at:Date.now()});try{const _=await m.execCommand(c,l.trim(),e.session_id);_.status==="options"&&_.data&&this.handleExecCommandOptions(e.session_id,c,_.data),this.aibotHandle.sendEventResult({event_id:e.event_id,status:_.status==="failed"?"failed":"responded",msg:_.message,updated_at:Date.now()})}catch(_){this.aibotHandle.sendEventResult({event_id:e.event_id,status:"failed",msg:_ instanceof Error?_.message:String(_),updated_at:Date.now()})}return}}}const r=this.buildInboundEvent(e,o);try{this.captureEventRuntimeConfig(r),await this.pool.deliverInboundEvent(r)}catch(a){if(this.aibotHandle.sendEventAck({event_id:e.event_id,session_id:e.session_id,received_at:Date.now()}),this.inflightEvents.delete(e.event_id),this.restartCount.delete(e.event_id),this.eventSessionIndex.delete(e.event_id),a instanceof Se)this.aibotHandle.sendEventResult({e