grix-connector 3.1.12 → 3.1.13

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 +1 @@
1
- import{randomUUID as x}from"node:crypto";import{CallToolRequestSchema as S,ListToolsRequestSchema as w}from"@modelcontextprotocol/sdk/types.js";import{log as f}from"../../core/log/index.js";import{toolCallToInvoke as k}from"../../core/mcp/tools.js";const E=new Set(["contact_search","session_search","message_history","message_search","group_create","group_detail_read","group_leave_self","group_member_add","group_member_remove","group_member_role_update","group_all_members_muted_update","group_member_speaking_update","group_dissolve","send_msg","delete_msg","agent_api_create","agent_category_list","agent_category_create","agent_category_update","agent_category_assign","agent_api_key_rotate"]),y=3e4,I=[{name:"reply",description:"Send a visible message back to the chat for this grix-claude event.",inputSchema:{type:"object",properties:{text:{type:"string",description:"The visible reply text to send."},chat_id:{type:"string",description:"The target chat/session id from the <channel> tag."},event_id:{type:"string",description:"The Aibot event_id from the <channel> tag."},reply_to:{type:"string",description:"Optional message_id to quote instead of the inbound trigger message."},files:{type:"array",items:{type:"string"},description:"Optional absolute local file paths. Each file is uploaded through Agent API OSS presign before sending."},final:{type:"boolean",description:"Whether this is the final reply for the event. Defaults to false \u2014 the event stays open while Claude continues working, and auto-completes after inactivity. Set true only when this is definitively the last message for the event."}},required:["chat_id","event_id"]}},{name:"complete",description:"Finish an event without sending a visible reply so the backend does not time out.",inputSchema:{type:"object",properties:{event_id:{type:"string",description:"The Aibot event_id from the <channel> tag."},status:{type:"string",enum:["responded","canceled","failed"]},code:{type:"string"},msg:{type:"string"}},required:["event_id","status"]}},{name:"delete_message",description:"Delete a previously sent message in the same grix-claude chat.",inputSchema:{type:"object",properties:{chat_id:{type:"string"},message_id:{type:"string"}},required:["chat_id","message_id"]}},{name:"status",description:"Show grix-claude runtime status, upstream access state, bridge health, and startup hints.",inputSchema:{type:"object",properties:{}}},{name:"send",description:"Send a message to a chat session proactively, without requiring an inbound event. Use for notifications or scheduled reports.",inputSchema:{type:"object",properties:{chat_id:{type:"string",description:"The target chat/session id."},text:{type:"string",description:"The message text to send."}},required:["chat_id","text"]}},{name:"access_pair",description:"Forward a sender pairing approval code to upstream access control.",inputSchema:{type:"object",properties:{code:{type:"string"}},required:["code"]}},{name:"access_deny",description:"Forward a sender pairing denial code to upstream access control.",inputSchema:{type:"object",properties:{code:{type:"string"}},required:["code"]}},{name:"allow_sender",description:"Ask upstream access control to allow a sender_id.",inputSchema:{type:"object",properties:{sender_id:{type:"string"}},required:["sender_id"]}},{name:"remove_sender",description:"Ask upstream access control to remove a sender_id.",inputSchema:{type:"object",properties:{sender_id:{type:"string"}},required:["sender_id"]}},{name:"access_policy",description:"Ask upstream access control to update the sender access policy.",inputSchema:{type:"object",properties:{policy:{type:"string",enum:["allowlist","open","disabled"]}},required:["policy"]}},{name:"grix_query",description:"Search contacts, sessions, message history, or messages by keyword in the Grix/AIBot platform.",inputSchema:{type:"object",properties:{action:{type:"string",enum:["contact_search","session_search","message_history","message_search"]},keyword:{type:"string"},id:{type:"string"},sessionId:{type:"string"},limit:{type:"number"},offset:{type:"number"},beforeId:{type:"string"}},required:["action"]}},{name:"grix_group",description:"Manage groups in the Grix/AIBot platform: create, get details, leave, dissolve, manage members and permissions.",inputSchema:{type:"object",properties:{action:{type:"string",enum:["create","detail","leave","add_members","remove_members","update_member_role","update_all_members_muted","update_member_speaking","dissolve"]},sessionId:{type:"string"},name:{type:"string"},memberIds:{type:"array",items:{type:"string"}},memberTypes:{type:"array",items:{type:"integer",enum:[1,2]}},memberId:{type:"string"},role:{type:"integer",enum:[1,2]},memberType:{type:"integer",description:"Member type (for update_member_role / update_member_speaking)."},allMembersMuted:{type:"boolean"},isSpeakMuted:{type:"boolean"},canSpeakWhenAllMuted:{type:"boolean",description:"Allow speaking when all muted (for update_member_speaking)."}},required:["action"]}},{name:"grix_message_send",description:"Send a message to a session in the Grix/AIBot platform.",inputSchema:{type:"object",properties:{sessionId:{type:"string"},content:{type:"string"},msgType:{type:"number"},quotedMessageId:{type:"string"},threadId:{type:"string"}},required:["sessionId","content"]}},{name:"grix_message_unsend",description:"Recall/unsend a message in the Grix/AIBot platform.",inputSchema:{type:"object",properties:{sessionId:{type:"string"},msgId:{type:"string"}},required:["sessionId","msgId"]}},{name:"grix_admin",description:"Agent and category management in the Grix/AIBot platform: create agents, manage categories, rotate API keys.",inputSchema:{type:"object",properties:{action:{type:"string",enum:["create_agent","list_categories","create_category","update_category","assign_category","rotate_api_key"]},agentId:{type:"string"},agentName:{type:"string"},introduction:{type:"string"},isMain:{type:"boolean"},categoryId:{type:"string"},name:{type:"string"},parentId:{type:"string"},sortOrder:{type:"number"}},required:["action"]}}];function q(n,e){n.setRequestHandler(w,async()=>({tools:I})),n.setRequestHandler(S,async i=>{const{name:r,arguments:t}=i.params,s=t??{};try{switch(r){case"reply":return await A(s,e);case"complete":return await $(s,e);case"delete_message":return await T(s,e);case"status":return j(e);case"send":return await M(s,e);case"access_pair":case"access_deny":case"allow_sender":case"remove_sender":case"access_policy":return await R(r,s,e);case"grix_query":case"grix_group":case"grix_message_send":case"grix_message_unsend":case"grix_admin":return await O(r,s,e);default:return{content:[{type:"text",text:`Unknown tool: ${r}`}],isError:!0}}}catch(a){return f.error("claude-tools",`Tool ${r} error: ${a}`),{content:[{type:"text",text:`Error: ${a instanceof Error?a.message:String(a)}`}],isError:!0}}})}async function A(n,e){const i=e.getActiveEvent();if(!i)return{content:[{type:"text",text:"No active event to reply to"}],isError:!0};const r=String(n.text??""),t=String(n.chat_id??""),s=String(n.event_id??i.eventId),a=n.reply_to,d=n.files,_=n.final===!0;if(!t||!s)return{content:[{type:"text",text:"reply requires chat_id and event_id"}],isError:!0};if(!r.trim()&&(!d||d.length===0))return{content:[{type:"text",text:"reply requires at least one of text or files"}],isError:!0};const{text:g,quotedMessageId:v}=e.resolveQuotedMessageId(a,r),b=[];let m=0;const h=`reply_${s}_${Date.now()}`;try{if(g){const p=e.splitText(g);for(let o=0;o<p.length;o++){if(!e.isEventActive(s))return c("ignored: event no longer active");m++,e.bridge.sendStreamChunk(s,t,p[o],++i.chunkSeq,!1,h)}}if(d&&d.length>0)for(const p of d){if(!e.isEventActive(s))return c("ignored: event no longer active");m++;const o=await e.uploadFile(p,t),l=`${x()}_${m}`;e.bridge.sendMedia(s,t,o.access_url,o.file_name,v,l,o.extra),f.info("claude-tools",`File sent: ${o.file_name}`)}e.bridge.sendStreamChunk(s,t,"",++i.chunkSeq,!0,h)}catch(p){if(g&&b.length===0)try{const o=`fallback_${s}_${Date.now()}`,l=e.splitText(g);for(let u=0;u<l.length;u++)e.bridge.sendStreamChunk("",t,l[u],u+1,!1,o);return e.bridge.sendStreamChunk("",t,"",l.length+1,!0,o),e.markReplySent(s),_&&e.finalizeEvent(s,"responded"),c("sent via fallback")}catch{}if(!e.isEventActive(s))return c("ignored: event no longer active");throw e.bridge.sendEventResult(s,"failed",String(p),"send_msg_failed"),p}return e.markReplySent(s),_?e.finalizeEvent(s,"responded"):(i.responded=!0,e.clearActiveEvent("completed")),c("Reply sent")}async function $(n,e){const i=e.getActiveEvent(),r=String(n.event_id??""),t=n.status??"",s=n.code,a=n.msg;if(!r||!t)return{content:[{type:"text",text:"complete requires event_id and status"}],isError:!0};const d=["responded","canceled","failed"];return d.includes(t)?e.isEventActive(r)?(e.bridge.sendEventResult(r,t,a,s),e.clearActiveEvent(t),c("Event completed")):c("ignored: event no longer active"):{content:[{type:"text",text:`status must be one of: ${d.join(", ")}`}],isError:!0}}async function T(n,e){const i=String(n.chat_id??""),r=String(n.message_id??"");if(!i||!r)return{content:[{type:"text",text:"chat_id and message_id are required"}],isError:!0};try{return await e.bridge.agentInvoke("grix_message_unsend",{sessionId:i,msgId:r},y),c(`deleted (${r})`)}catch(t){return{content:[{type:"text",text:`Delete failed: ${t}`}],isError:!0}}}function j(n){const e=n.getStatusInfo();return{content:[{type:"text",text:JSON.stringify({alive:e.alive,active_event:e.activeEvent,pending_approvals:e.pendingPermissions,pending_questions:e.pendingElicitations})}]}}async function M(n,e){const i=String(n.chat_id??""),r=String(n.text??"");if(!i||!r)return{content:[{type:"text",text:"chat_id and text are required"}],isError:!0};try{const t=e.splitText(r),s=`send_${i}_${Date.now()}`;for(let a=0;a<t.length;a++)e.bridge.sendStreamChunk("",i,t[a],a+1,!1,s);return e.bridge.sendStreamChunk("",i,"",t.length+1,!0,s),c("sent")}catch(t){return{content:[{type:"text",text:`Send failed: ${t}`}],isError:!0}}}const C={access_pair:{verb:"pair_approve",payloadKey:"code"},access_deny:{verb:"pair_deny",payloadKey:"code"},allow_sender:{verb:"sender_allow",payloadKey:"sender_id"},remove_sender:{verb:"sender_remove",payloadKey:"sender_id"},access_policy:{verb:"policy_set",payloadKey:"policy"}};async function R(n,e,i){try{const r=C[n];if(!r)throw new Error(`Unknown access control tool: ${n}`);const t={};e.code!=null&&(t.code=e.code),e.sender_id!=null&&(t.sender_id=e.sender_id),e.policy!=null&&(t.policy=e.policy);const s=await i.bridge.agentInvoke("claude_access_control",{verb:r.verb,payload:t},y);return{content:[{type:"text",text:typeof s=="string"?s:JSON.stringify(s)}]}}catch(r){return{content:[{type:"text",text:`${n} failed: ${r}`}],isError:!0}}}async function O(n,e,i){try{const r=k(n,e);if(!E.has(r.action))throw new Error(`Action not allowed: ${r.action}`);const t=await i.bridge.agentInvoke(r.action,r.params,y);if(t&&Number(t.code??0)!==0)throw new Error(String(t.msg??"invoke failed"));return{content:[{type:"text",text:t?.data!=null?typeof t.data=="string"?t.data:JSON.stringify(t.data):JSON.stringify(t)}]}}catch(r){return{content:[{type:"text",text:`${n} failed: ${r}`}],isError:!0}}}function c(n){return{content:[{type:"text",text:n}]}}export{q as registerClaudeTools};
1
+ import{randomUUID as x}from"node:crypto";import{CallToolRequestSchema as S,ListToolsRequestSchema as w}from"@modelcontextprotocol/sdk/types.js";import{log as f}from"../../core/log/index.js";import{toolCallToInvoke as k}from"../../core/mcp/tools.js";const E=new Set(["contact_search","session_search","message_history","message_search","group_create","group_detail_read","group_leave_self","group_member_add","group_member_remove","group_member_role_update","group_all_members_muted_update","group_member_speaking_update","group_dissolve","send_msg","delete_msg","agent_api_create","agent_category_list","agent_category_create","agent_category_update","agent_category_assign","agent_api_key_rotate"]),y=3e4,I=[{name:"reply",description:"Send a visible message back to the chat for this grix-claude event.",inputSchema:{type:"object",properties:{text:{type:"string",description:"The visible reply text to send."},chat_id:{type:"string",description:"The target chat/session id from the <channel> tag."},event_id:{type:"string",description:"The Aibot event_id from the <channel> tag."},reply_to:{type:"string",description:"Optional message_id to quote instead of the inbound trigger message."},files:{type:"array",items:{type:"string"},description:"Optional absolute local file paths. Each file is uploaded through Agent API OSS presign before sending."},final:{type:"boolean",description:"Whether this is the final reply for the event. Defaults to false \u2014 the event stays open while Claude continues working, and auto-completes after inactivity. Set true only when this is definitively the last message for the event."}},required:["chat_id","event_id"]}},{name:"complete",description:"Finish an event without sending a visible reply so the backend does not time out.",inputSchema:{type:"object",properties:{event_id:{type:"string",description:"The Aibot event_id from the <channel> tag."},status:{type:"string",enum:["responded","canceled","failed"]},code:{type:"string"},msg:{type:"string"}},required:["event_id","status"]}},{name:"delete_message",description:"Delete a previously sent message in the same grix-claude chat.",inputSchema:{type:"object",properties:{chat_id:{type:"string"},message_id:{type:"string"}},required:["chat_id","message_id"]}},{name:"status",description:"Show grix-claude runtime status, upstream access state, bridge health, and startup hints.",inputSchema:{type:"object",properties:{}}},{name:"send",description:"Send a message to a chat session proactively, without requiring an inbound event. Use for notifications or scheduled reports.",inputSchema:{type:"object",properties:{chat_id:{type:"string",description:"The target chat/session id."},text:{type:"string",description:"The message text to send."}},required:["chat_id","text"]}},{name:"access_pair",description:"Forward a sender pairing approval code to upstream access control.",inputSchema:{type:"object",properties:{code:{type:"string"}},required:["code"]}},{name:"access_deny",description:"Forward a sender pairing denial code to upstream access control.",inputSchema:{type:"object",properties:{code:{type:"string"}},required:["code"]}},{name:"allow_sender",description:"Ask upstream access control to allow a sender_id.",inputSchema:{type:"object",properties:{sender_id:{type:"string"}},required:["sender_id"]}},{name:"remove_sender",description:"Ask upstream access control to remove a sender_id.",inputSchema:{type:"object",properties:{sender_id:{type:"string"}},required:["sender_id"]}},{name:"access_policy",description:"Ask upstream access control to update the sender access policy.",inputSchema:{type:"object",properties:{policy:{type:"string",enum:["allowlist","open","disabled"]}},required:["policy"]}},{name:"grix_query",description:"Search contacts, sessions, message history, or messages by keyword in the Grix/AIBot platform.",inputSchema:{type:"object",properties:{action:{type:"string",enum:["contact_search","session_search","message_history","message_search"]},keyword:{type:"string"},id:{type:"string"},sessionId:{type:"string"},limit:{type:"number"},offset:{type:"number"},beforeId:{type:"string"}},required:["action"]}},{name:"grix_group",description:"Manage groups in the Grix/AIBot platform: create, get details, leave, dissolve, manage members and permissions.",inputSchema:{type:"object",properties:{action:{type:"string",enum:["create","detail","leave","add_members","remove_members","update_member_role","update_all_members_muted","update_member_speaking","dissolve"]},sessionId:{type:"string"},name:{type:"string"},memberIds:{type:"array",items:{type:"string"}},memberTypes:{type:"array",items:{type:"integer",enum:[1,2]}},memberId:{type:"string"},role:{type:"integer",enum:[1,2]},memberType:{type:"integer",description:"Member type (for update_member_role / update_member_speaking)."},allMembersMuted:{type:"boolean"},isSpeakMuted:{type:"boolean"},canSpeakWhenAllMuted:{type:"boolean",description:"Allow speaking when all muted (for update_member_speaking)."}},required:["action"]}},{name:"grix_message_send",description:"Send a message to a session in the Grix/AIBot platform.",inputSchema:{type:"object",properties:{sessionId:{type:"string"},content:{type:"string"},msgType:{type:"number"},quotedMessageId:{type:"string"},threadId:{type:"string"}},required:["sessionId","content"]}},{name:"grix_message_unsend",description:"Recall/unsend a message in the Grix/AIBot platform.",inputSchema:{type:"object",properties:{sessionId:{type:"string"},msgId:{type:"string"}},required:["sessionId","msgId"]}},{name:"grix_admin",description:"Agent and category management in the Grix/AIBot platform: create agents, manage categories, rotate API keys.",inputSchema:{type:"object",properties:{action:{type:"string",enum:["create_agent","list_categories","create_category","update_category","assign_category","rotate_api_key"]},agentId:{type:"string"},agentName:{type:"string"},introduction:{type:"string"},isMain:{type:"boolean"},categoryId:{type:"string"},name:{type:"string"},parentId:{type:"string"},sortOrder:{type:"number"}},required:["action"]}}];function q(r,e){r.setRequestHandler(w,async()=>({tools:I})),r.setRequestHandler(S,async i=>{const{name:n,arguments:t}=i.params,s=t??{};try{switch(n){case"reply":return await A(s,e);case"complete":return await $(s,e);case"delete_message":return await T(s,e);case"status":return j(e);case"send":return await M(s,e);case"access_pair":case"access_deny":case"allow_sender":case"remove_sender":case"access_policy":return await R(n,s,e);case"grix_query":case"grix_group":case"grix_message_send":case"grix_message_unsend":case"grix_admin":return await O(n,s,e);default:return{content:[{type:"text",text:`Unknown tool: ${n}`}],isError:!0}}}catch(a){return f.error("claude-tools",`Tool ${n} error: ${a}`),{content:[{type:"text",text:`Error: ${a instanceof Error?a.message:String(a)}`}],isError:!0}}})}async function A(r,e){const i=e.getActiveEvent();if(!i)return{content:[{type:"text",text:"No active event to reply to"}],isError:!0};const n=String(r.text??""),t=String(r.chat_id??""),s=String(r.event_id??i.eventId),a=r.reply_to,d=r.files,_=r.final===!0;if(!t||!s)return{content:[{type:"text",text:"reply requires chat_id and event_id"}],isError:!0};if(!n.trim()&&(!d||d.length===0))return{content:[{type:"text",text:"reply requires at least one of text or files"}],isError:!0};const{text:g,quotedMessageId:v}=e.resolveQuotedMessageId(a,n),b=[];let m=0;const h=`reply_${s}_${Date.now()}`;try{if(g){const p=e.splitText(g);for(let o=0;o<p.length;o++){if(!e.isEventActive(s))return c("ignored: event no longer active");m++,e.bridge.sendStreamChunk(s,t,p[o],++i.chunkSeq,!1,h)}}if(d&&d.length>0)for(const p of d){if(!e.isEventActive(s))return c("ignored: event no longer active");m++;const o=await e.uploadFile(p,t),l=`${x()}_${m}`;e.bridge.sendMedia(s,t,o.access_url,o.file_name,v,l,o.extra),f.info("claude-tools",`File sent: ${o.file_name}`)}e.bridge.sendStreamChunk(s,t,"",++i.chunkSeq,!0,h)}catch(p){if(g&&b.length===0)try{const o=`fallback_${s}_${Date.now()}`,l=e.splitText(g);for(let u=0;u<l.length;u++)e.bridge.sendStreamChunk("",t,l[u],u+1,!1,o);return e.bridge.sendStreamChunk("",t,"",l.length+1,!0,o),e.markReplySent(s),_&&e.finalizeEvent(s,"responded"),c("sent via fallback")}catch{}if(!e.isEventActive(s))return c("ignored: event no longer active");throw e.bridge.sendEventResult(s,"failed",String(p),"send_msg_failed"),p}return e.markReplySent(s),_?e.finalizeEvent(s,"responded"):(i.responded=!0,e.clearActiveEvent("completed")),c("Reply sent")}async function $(r,e){const i=e.getActiveEvent(),n=String(r.event_id??""),t=r.status??"",s=r.code,a=r.msg;if(!n||!t)return{content:[{type:"text",text:"complete requires event_id and status"}],isError:!0};const d=["responded","canceled","failed"];return d.includes(t)?e.isEventActive(n)?(e.bridge.sendEventResult(n,t,a,s),e.clearActiveEvent(t),c("Event completed")):c("ignored: event no longer active"):{content:[{type:"text",text:`status must be one of: ${d.join(", ")}`}],isError:!0}}async function T(r,e){const i=String(r.chat_id??""),n=String(r.message_id??"");if(!i||!n)return{content:[{type:"text",text:"chat_id and message_id are required"}],isError:!0};try{return await e.bridge.agentInvoke("grix_message_unsend",{sessionId:i,msgId:n},y),c(`deleted (${n})`)}catch(t){return{content:[{type:"text",text:`Delete failed: ${t}`}],isError:!0}}}function j(r){const e=r.getStatusInfo();return{content:[{type:"text",text:JSON.stringify({alive:e.alive,active_event:e.activeEvent,pending_approvals:e.pendingPermissions,pending_questions:e.pendingElicitations})}]}}async function M(r,e){const i=String(r.chat_id??""),n=String(r.text??"");if(!i||!n)return{content:[{type:"text",text:"chat_id and text are required"}],isError:!0};try{const t=e.splitText(n),s=`send_${i}_${Date.now()}`;for(let a=0;a<t.length;a++)e.bridge.sendStreamChunk("",i,t[a],a+1,!1,s);return e.bridge.sendStreamChunk("",i,"",t.length+1,!0,s),c("sent")}catch(t){return{content:[{type:"text",text:`Send failed: ${t}`}],isError:!0}}}const C={access_pair:{verb:"pair_approve",payloadKey:"code"},access_deny:{verb:"pair_deny",payloadKey:"code"},allow_sender:{verb:"sender_allow",payloadKey:"sender_id"},remove_sender:{verb:"sender_remove",payloadKey:"sender_id"},access_policy:{verb:"policy_set",payloadKey:"policy"}};async function R(r,e,i){try{const n=C[r];if(!n)throw new Error(`Unknown access control tool: ${r}`);const t={};e.code!=null&&(t.code=e.code),e.sender_id!=null&&(t.sender_id=e.sender_id),e.policy!=null&&(t.policy=e.policy);const s=await i.bridge.agentInvoke("claude_access_control",{verb:n.verb,payload:t},y);return{content:[{type:"text",text:typeof s=="string"?s:JSON.stringify(s)}]}}catch(n){return{content:[{type:"text",text:`${r} failed: ${n}`}],isError:!0}}}async function O(r,e,i){try{const n=k(r,e);if(!E.has(n.action))throw new Error(`Action not allowed: ${n.action}`);const t=await i.bridge.agentInvoke(n.action,n.params,y);if(t&&Number(t.code??0)!==0)throw new Error(String(t.msg??"invoke failed"));return{content:[{type:"text",text:t?.data!=null?typeof t.data=="string"?t.data:JSON.stringify(t.data):JSON.stringify(t)}]}}catch(n){return{content:[{type:"text",text:`${r} failed: ${n}`}],isError:!0}}}function c(r){return{content:[{type:"text",text:r}]}}export{q as registerClaudeTools};
@@ -1 +1 @@
1
- import{log as l}from"../../core/log/index.js";class c{controlURL="";token="";isConfigured(){return!!(this.controlURL&&this.token)}configure(t,e){this.controlURL=t.replace(/\/+$/,""),this.token=e.trim(),l.info("claude-worker-client",`Configured with control URL: ${this.controlURL}`)}async post(t,e,s){if(!this.isConfigured())throw new Error("worker control not configured");const i=new AbortController,o=setTimeout(()=>i.abort(),s);try{const r=await fetch(`${this.controlURL}${t}`,{method:"POST",headers:{"content-type":"application/json",authorization:`Bearer ${this.token}`},body:JSON.stringify(e),signal:i.signal}),n=await r.text(),a=n.trim()?JSON.parse(n):{};if(!r.ok)throw new Error(a.error||`worker control failed ${r.status}`);return a}finally{clearTimeout(o)}}isRetryableError(t){const e=t instanceof Error?t.message:String(t);return/fetch failed|network|ECONNRESET|ETIMEDOUT|EAI_AGAIN|socket hang up|aborted/i.test(e)}async postWithRetry(t,e,s,i=1){let o;for(let r=0;r<=i;r++)try{return r>0&&l.info("claude-worker-client",`Retrying ${t} attempt=${r+1}`),await this.post(t,e,s)}catch(n){if(o=n,r>=i||!this.isRetryableError(n))break;await new Promise(a=>setTimeout(a,150))}throw o instanceof Error?o:new Error(String(o))}async deliverEvent(t){return this.postWithRetry("/v1/worker/deliver-event",{payload:t},1e4,1)}async deliverStop(t){return this.postWithRetry("/v1/worker/deliver-stop",{payload:t},1e4,1)}async deliverLocalAction(t){return this.postWithRetry("/v1/worker/deliver-local-action",{payload:t},1e4,1)}async ping(){try{return await this.postWithRetry("/v1/worker/ping",{},5e3,1),!0}catch{return!1}}}export{c as ClaudeWorkerClient};
1
+ import{log as l}from"../../core/log/index.js";class c{controlURL="";token="";isConfigured(){return!!(this.controlURL&&this.token)}configure(r,e){this.controlURL=r.replace(/\/+$/,""),this.token=e.trim(),l.info("claude-worker-client",`Configured with control URL: ${this.controlURL}`)}async post(r,e,s){if(!this.isConfigured())throw new Error("worker control not configured");const i=new AbortController,o=setTimeout(()=>i.abort(),s);try{const t=await fetch(`${this.controlURL}${r}`,{method:"POST",headers:{"content-type":"application/json",authorization:`Bearer ${this.token}`},body:JSON.stringify(e),signal:i.signal}),n=await t.text(),a=n.trim()?JSON.parse(n):{};if(!t.ok)throw new Error(a.error||`worker control failed ${t.status}`);return a}finally{clearTimeout(o)}}isRetryableError(r){const e=r instanceof Error?r.message:String(r);return/fetch failed|network|ECONNRESET|ETIMEDOUT|EAI_AGAIN|socket hang up|aborted/i.test(e)}async postWithRetry(r,e,s,i=1){let o;for(let t=0;t<=i;t++)try{return t>0&&l.info("claude-worker-client",`Retrying ${r} attempt=${t+1}`),await this.post(r,e,s)}catch(n){if(o=n,t>=i||!this.isRetryableError(n))break;await new Promise(a=>setTimeout(a,150))}throw o instanceof Error?o:new Error(String(o))}async deliverEvent(r){return this.postWithRetry("/v1/worker/deliver-event",{payload:r},1e4,1)}async deliverStop(r){return this.postWithRetry("/v1/worker/deliver-stop",{payload:r},1e4,1)}async deliverLocalAction(r){return this.postWithRetry("/v1/worker/deliver-local-action",{payload:r},1e4,1)}async ping(){try{return await this.postWithRetry("/v1/worker/ping",{},5e3,1),!0}catch{return!1}}}export{c as ClaudeWorkerClient};
@@ -1,2 +1,2 @@
1
- import{spawn as x,execSync as y}from"node:child_process";import{randomUUID as v}from"node:crypto";import{mkdir as S}from"node:fs/promises";import{readFileSync as C}from"node:fs";import{join as d}from"node:path";import{homedir as T,tmpdir as I}from"node:os";import{log as o}from"../../core/log/index.js";import{MCP_HTTP_CHANNEL_NAME as l}from"./protocol-contract.js";function P(e){let t=null,r=0,n=!1,i=!1;const a=v(),s=e.gatewayUrl??"http://127.0.0.1:19580/mcp";return{async start(){await F(e.command,s,e.env);const c=E(e.grix),u=[...e.args??[],"--name",`grix-mcp-${e.name}`,"--session-id",a];e.fullAuto&&u.push("--dangerously-skip-permissions"),u.push("--dangerously-load-development-channels",`server:${l}`,"--append-system-prompt",c);const f=d(I(),`grix-mcp-claude-${e.name}`);await S(f,{recursive:!0});const{expectPath:$,pidPath:g}=await M(f,e.command,u),_={...process.env,...e.env??{}};t=x("/usr/bin/expect",[$],{cwd:e.cwd,env:_,stdio:["ignore","pipe","pipe"],detached:!0}),o.info("mcp-http-launcher",`\u542F\u52A8 Claude: name=${e.name} cwd=${e.cwd} pid=${t.pid}`),r=await k(g),n=!0,o.info("mcp-http-launcher",`Claude \u5B50\u8FDB\u7A0B PID: ${r}`),t.on("exit",(m,p)=>{o.info("mcp-http-launcher",`Claude \u9000\u51FA: code=${m} signal=${p}`),n=!1,t=null,r=0,i||(o.info("mcp-http-launcher","3 \u79D2\u540E\u81EA\u52A8\u91CD\u542F..."),setTimeout(()=>{i||this.start().catch(w=>{o.error("mcp-http-launcher",`\u91CD\u542F\u5931\u8D25: ${w}`)})},3e3))}),t.stdout?.on("data",m=>{const p=m.toString().trim();p&&o.info("mcp-http-launcher",`[stdout] ${p.slice(0,300)}`)}),t.stderr?.on("data",m=>{const p=m.toString().trim();p&&o.info("mcp-http-launcher",`[stderr] ${p.slice(0,300)}`)})},async stop(){if(i=!0,n=!1,r>0)try{process.kill(r,"SIGTERM")}catch{}if(t?.pid){try{process.kill(-t.pid,"SIGTERM")}catch{}await new Promise(c=>{const u=setTimeout(()=>{if(r>0)try{process.kill(r,"SIGKILL")}catch{}if(t?.pid)try{process.kill(-t.pid,"SIGKILL")}catch{}c()},5e3);t?.once("exit",()=>{clearTimeout(u),c()})})}t=null,r=0},getStatus(){return{name:e.name,alive:n,pid:r}}}}function E(e){return["You are connected to a chat via the grix MCP server.",`On startup, immediately call grix_authorize with: agentId="${e.agentId}", apiKey="${e.apiKey}", wsUrl="${e.wsUrl}", clientType="${e.clientType}".`,"When you receive a <channel> message, you MUST respond by calling the grix_reply tool (or the grix_complete tool if no response is needed).","Never write your reply as plain text \u2014 it will NOT reach the user. Only the grix_reply tool delivers your response to the chat.","The <channel> message contains event_id and session_id \u2014 pass them to grix_reply."].join(" ")}async function F(e,t,r){const n=d(T(),".claude.json");let i=null;try{const s=C(n,"utf8");i=JSON.parse(s)?.mcpServers?.[l]??null}catch{}if(i&&String(i.type??"").trim()==="http"&&String(i.url??"").trim()===t)return;o.info("mcp-http-launcher",`\u6CE8\u518C MCP Server: ${l} -> ${t}`);const a={...process.env,...r??{}};try{y(`${e} mcp remove -s user ${l}`,{encoding:"utf8",timeout:1e4,env:a,stdio:"pipe"})}catch{}y(`${e} mcp add --scope user --transport http ${l} ${t}`,{encoding:"utf8",timeout:1e4,env:a,stdio:"pipe"})}async function M(e,t,r){const{writeFile:n}=await import("node:fs/promises"),i=d(e,"claude.pid"),a=d(e,"claude.expect"),s=["log_user 1","set timeout -1","set startup_prompt_armed 1",`set claude_command [list {${h(t)}}${r.map(c=>` {${h(c)}}`).join("")}]`,"spawn -noecho {*}$claude_command",`set pid_file [open {${h(i)}} w]`,"puts $pid_file [exp_pid -i $spawn_id]","close $pid_file","expect {"," -re {(?i)(Quick.*safety.*check|trust.*folder)} {",' if {$startup_prompt_armed} { send -- "1\\r"; after 300 }; exp_continue'," }"," -re {(?i)I am using this for local development} {",' if {$startup_prompt_armed} { send -- "1\\r"; after 300 }; exp_continue'," }"," -re {(?i)(Enter.*confirm|Press.*Enter|Hit.*Enter)} {",' if {$startup_prompt_armed} { send -- "\\r"; after 300 }; exp_continue'," }"," -re {Listening for channel} {"," set startup_prompt_armed 0"," after 1000",' send -- "Call grix_authorize now as instructed in your system prompt.\\r"'," }"," -re {bypass permissions} {"," set startup_prompt_armed 0"," after 1000",' send -- "Call grix_authorize now as instructed in your system prompt.\\r"'," }"," eof {}","}","expect eof",""];return await n(i,"","utf8"),await n(a,s.join(`
2
- `),"utf8"),{expectPath:a,pidPath:i}}function h(e){return e.replace(/[\\{}$\[\]"]/g,"\\$&")}async function k(e,t=1e4){const{readFile:r}=await import("node:fs/promises"),n=Math.ceil(t/100);for(let i=0;i<n;i++){try{const a=await r(e,"utf8"),s=parseInt(String(a).trim(),10);if(Number.isFinite(s)&&s>0)return s}catch{}await new Promise(a=>setTimeout(a,100))}return 0}export{P as createMcpHttpLauncher};
1
+ import{spawn as x,execSync as y}from"node:child_process";import{randomUUID as v}from"node:crypto";import{mkdir as S}from"node:fs/promises";import{readFileSync as C}from"node:fs";import{join as d}from"node:path";import{homedir as T,tmpdir as I}from"node:os";import{log as o}from"../../core/log/index.js";import{MCP_HTTP_CHANNEL_NAME as m}from"./protocol-contract.js";function P(t){let e=null,r=0,n=!1,i=!1;const a=v(),s=t.gatewayUrl??"http://127.0.0.1:19580/mcp";return{async start(){await F(t.command,s,t.env);const c=E(t.grix),u=[...t.args??[],"--name",`grix-mcp-${t.name}`,"--session-id",a];t.fullAuto&&u.push("--dangerously-skip-permissions"),u.push("--dangerously-load-development-channels",`server:${m}`,"--append-system-prompt",c);const f=d(I(),`grix-mcp-claude-${t.name}`);await S(f,{recursive:!0});const{expectPath:$,pidPath:g}=await M(f,t.command,u),_={...process.env,...t.env??{}};e=x("/usr/bin/expect",[$],{cwd:t.cwd,env:_,stdio:["ignore","pipe","pipe"],detached:!0}),o.info("mcp-http-launcher",`\u542F\u52A8 Claude: name=${t.name} cwd=${t.cwd} pid=${e.pid}`),r=await k(g),n=!0,o.info("mcp-http-launcher",`Claude \u5B50\u8FDB\u7A0B PID: ${r}`),e.on("exit",(l,p)=>{o.info("mcp-http-launcher",`Claude \u9000\u51FA: code=${l} signal=${p}`),n=!1,e=null,r=0,i||(o.info("mcp-http-launcher","3 \u79D2\u540E\u81EA\u52A8\u91CD\u542F..."),setTimeout(()=>{i||this.start().catch(w=>{o.error("mcp-http-launcher",`\u91CD\u542F\u5931\u8D25: ${w}`)})},3e3))}),e.stdout?.on("data",l=>{const p=l.toString().trim();p&&o.info("mcp-http-launcher",`[stdout] ${p.slice(0,300)}`)}),e.stderr?.on("data",l=>{const p=l.toString().trim();p&&o.info("mcp-http-launcher",`[stderr] ${p.slice(0,300)}`)})},async stop(){if(i=!0,n=!1,r>0)try{process.kill(r,"SIGTERM")}catch{}if(e?.pid){try{process.kill(-e.pid,"SIGTERM")}catch{}await new Promise(c=>{const u=setTimeout(()=>{if(r>0)try{process.kill(r,"SIGKILL")}catch{}if(e?.pid)try{process.kill(-e.pid,"SIGKILL")}catch{}c()},5e3);e?.once("exit",()=>{clearTimeout(u),c()})})}e=null,r=0},getStatus(){return{name:t.name,alive:n,pid:r}}}}function E(t){return["You are connected to a chat via the grix MCP server.",`On startup, immediately call grix_authorize with: agentId="${t.agentId}", apiKey="${t.apiKey}", wsUrl="${t.wsUrl}", clientType="${t.clientType}".`,"When you receive a <channel> message, you MUST respond by calling the grix_reply tool (or the grix_complete tool if no response is needed).","Never write your reply as plain text \u2014 it will NOT reach the user. Only the grix_reply tool delivers your response to the chat.","The <channel> message contains event_id and session_id \u2014 pass them to grix_reply."].join(" ")}async function F(t,e,r){const n=d(T(),".claude.json");let i=null;try{const s=C(n,"utf8");i=JSON.parse(s)?.mcpServers?.[m]??null}catch{}if(i&&String(i.type??"").trim()==="http"&&String(i.url??"").trim()===e)return;o.info("mcp-http-launcher",`\u6CE8\u518C MCP Server: ${m} -> ${e}`);const a={...process.env,...r??{}};try{y(`${t} mcp remove -s user ${m}`,{encoding:"utf8",timeout:1e4,env:a,stdio:"pipe"})}catch{}y(`${t} mcp add --scope user --transport http ${m} ${e}`,{encoding:"utf8",timeout:1e4,env:a,stdio:"pipe"})}async function M(t,e,r){const{writeFile:n}=await import("node:fs/promises"),i=d(t,"claude.pid"),a=d(t,"claude.expect"),s=["log_user 1","set timeout -1","set startup_prompt_armed 1",`set claude_command [list {${h(e)}}${r.map(c=>` {${h(c)}}`).join("")}]`,"spawn -noecho {*}$claude_command",`set pid_file [open {${h(i)}} w]`,"puts $pid_file [exp_pid -i $spawn_id]","close $pid_file","expect {"," -re {(?i)(Quick.*safety.*check|trust.*folder)} {",' if {$startup_prompt_armed} { send -- "1\\r"; after 300 }; exp_continue'," }"," -re {(?i)I am using this for local development} {",' if {$startup_prompt_armed} { send -- "1\\r"; after 300 }; exp_continue'," }"," -re {(?i)(Enter.*confirm|Press.*Enter|Hit.*Enter)} {",' if {$startup_prompt_armed} { send -- "\\r"; after 300 }; exp_continue'," }"," -re {Listening for channel} {"," set startup_prompt_armed 0"," after 1000",' send -- "Call grix_authorize now as instructed in your system prompt.\\r"'," }"," -re {bypass permissions} {"," set startup_prompt_armed 0"," after 1000",' send -- "Call grix_authorize now as instructed in your system prompt.\\r"'," }"," eof {}","}","expect eof",""];return await n(i,"","utf8"),await n(a,s.join(`
2
+ `),"utf8"),{expectPath:a,pidPath:i}}function h(t){return t.replace(/[\\{}$\[\]"]/g,"\\$&")}async function k(t,e=1e4){const{readFile:r}=await import("node:fs/promises"),n=Math.ceil(e/100);for(let i=0;i<n;i++){try{const a=await r(t,"utf8"),s=parseInt(String(a).trim(),10);if(Number.isFinite(s)&&s>0)return s}catch{}await new Promise(a=>setTimeout(a,100))}return 0}export{P as createMcpHttpLauncher};
@@ -1 +1 @@
1
- class m{defaultTimeoutMs;onTimeout;timers=new Map;constructor(t){this.defaultTimeoutMs=t.defaultTimeoutMs??9e4,this.onTimeout=t.onTimeout}arm(t,e){this.cancel(t);const s=e?.timeoutMs??this.defaultTimeoutMs,i=Date.now()+s,o=setTimeout(()=>{this.timers.delete(t),this.onTimeout(t).catch(()=>{})},s);return this.timers.set(t,o),i}cancel(t){const e=this.timers.get(t);e&&(clearTimeout(e),this.timers.delete(t))}has(t){return this.timers.has(t)}close(){for(const t of this.timers.values())clearTimeout(t);this.timers.clear()}}export{m as ResultTimeoutManager};
1
+ class m{defaultTimeoutMs;onTimeout;timers=new Map;constructor(e){this.defaultTimeoutMs=e.defaultTimeoutMs??9e4,this.onTimeout=e.onTimeout}arm(e,t){this.cancel(e);const s=t?.timeoutMs??this.defaultTimeoutMs,i=Date.now()+s,o=setTimeout(()=>{this.timers.delete(e),this.onTimeout(e).catch(()=>{})},s);return this.timers.set(e,o),i}cancel(e){const t=this.timers.get(e);t&&(clearTimeout(t),this.timers.delete(e))}has(e){return this.timers.has(e)}close(){for(const e of this.timers.values())clearTimeout(e);this.timers.clear()}}export{m as ResultTimeoutManager};
@@ -1,6 +1,7 @@
1
- import{closeSync as I,createReadStream as M,openSync as P,readFileSync as k,readSync as L,realpathSync as J,statSync as _}from"node:fs";import{createInterface as B}from"node:readline";import A from"node:path";import O from"node:os";import{log as R}from"../../core/log/index.js";function U(s){let o=s;try{o=J(s)}catch{}return o.replace(/[^a-zA-Z0-9]/g,"-")}function d(s,o){const t=A.join(O.homedir(),".claude"),u=A.join(t,"projects",U(o));return A.join(u,`${s}.jsonl`)}function w(){return{inputTokens:0,outputTokens:0,cacheReadInputTokens:0,cacheCreationInputTokens:0}}function N(s,o){s.inputTokens+=o.input_tokens??0,s.outputTokens+=o.output_tokens??0,s.cacheReadInputTokens+=o.cache_read_input_tokens??0,s.cacheCreationInputTokens+=o.cache_creation_input_tokens??0}async function Q(s,o){const t=d(s,o);R.info("usage-parser",`Parsing session usage from ${t}`);const u=new Map,e=w();let n=0;try{const i=B({input:M(t,"utf8"),crlfDelay:1/0});for await(const c of i)if(c.trim())try{const l=JSON.parse(c);if(l.type!=="assistant")continue;const r=l.message?.usage;if(!r)continue;const p=l.message?.model??"unknown";n++,N(e,r);let f=u.get(p);f||(f={turns:0,usage:w()},u.set(p,f)),f.turns++,N(f.usage,r)}catch{}}catch(i){return i.code==="ENOENT"?(R.info("usage-parser",`Session JSONL not found: ${t}`),null):(R.error("usage-parser",`Failed to parse session usage: ${i}`),null)}return n===0?null:{models:[...u.entries()].map(([i,c])=>({model:i,turns:c.turns,total:c.usage})),total:e,turns:n}}const S=64*1024,z=9e4;function C(s,o,t){const u=d(s,o);try{const e=k(u);let n;t!==void 0&&t>0?n=t<e.length?e.subarray(t):Buffer.alloc(0):n=e.length>S?e.subarray(e.length-S):e;const a=n.toString("utf8").split(`
2
- `);for(let i=a.length-1;i>=0;i--){const c=a[i].trim();if(!c)continue;let l;try{l=JSON.parse(c)}catch{continue}if(l.type!=="assistant")continue;const r=l.message;if(!r?.content)continue;const p=Array.isArray(r.content)?r.content:[{type:"text",text:String(r.content)}],f=[];for(const y of p){const m=y;m.type==="text"&&m.text?.trim()&&f.push(m.text)}if(f.length>0)return{text:f.join(`
3
- `),stopReason:r.stop_reason??null}}return null}catch{return null}}function X(s,o,t){return C(s,o,t)?.text??null}function Y(s,o,t){const u=d(s,o),e=t>0?t:0;let n;try{const a=_(u).size;if(e>=a)return{segments:[],newOffset:e};const i=a-e,c=Buffer.alloc(i);n=P(u,"r");const l=L(n,c,0,i,e),r=c.subarray(0,l);let p=-1;for(let g=r.length-1;g>=0;g--)if(r[g]===10){p=g;break}if(p<0)return{segments:[],newOffset:e};const f=r.subarray(0,p+1),y=e+f.length,m=[];for(const g of f.toString("utf8").split(`
4
- `)){const T=g.trim();if(!T)continue;let x;try{x=JSON.parse(T)}catch{continue}if(x.type!=="assistant")continue;const h=x.message;if(!h?.content)continue;const j=Array.isArray(h.content)?h.content:[{type:"text",text:String(h.content)}];for(const E of j){const b=E;b.type==="text"&&b.text?.trim()&&m.push(b.text)}}return{segments:m,newOffset:y}}catch{return{segments:[],newOffset:e}}finally{if(n!==void 0)try{I(n)}catch{}}}function Z(s,o,t){const u=d(s,o);let e=null,n;try{const c=_(u);e=Date.now()-c.mtimeMs,n=k(u)}catch{return{lastStopReason:null,freshMs:null}}let a;t!==void 0&&t>0?a=t<n.length?n.subarray(t):Buffer.alloc(0):a=n.length>S?n.subarray(n.length-S):n;const i=a.toString("utf8").split(`
5
- `);for(let c=i.length-1;c>=0;c--){const l=i[c].trim();if(!l)continue;let r;try{r=JSON.parse(l)}catch{continue}if(r.type!=="assistant")continue;const p=r.message;if(p)return{lastStopReason:p.stop_reason??null,freshMs:e}}return{lastStopReason:null,freshMs:e}}function q(s,o,t,u=!1){const e=d(s,o);try{const n=_(e);if(u&&Date.now()-n.mtimeMs<z)return!1;const a=k(e),c=(t>0&&t<a.length?a.subarray(t):a.length>S?a.subarray(a.length-S):a).toString("utf8").split(`
6
- `);let l=!1,r=!1;for(const p of c){const f=p.trim();if(!f)continue;let y;try{y=JSON.parse(f)}catch{continue}if(y.type==="user"){const g=y.message?.content??y.content;(Array.isArray(g)?g:[]).some(h=>typeof h=="object"&&h!==null&&h.type==="tool_result")&&(l=!0,r=!1)}else y.type==="assistant"&&l&&(r=!0)}return l&&!r}catch{return!1}}export{C as extractLastAssistantEntry,X as extractLastAssistantText,Y as extractTextBlocksAfterOffset,q as hasTerminalToolResultAfterOffset,Q as parseClaudeSessionUsage,Z as probeSessionTurnState,d as resolveSessionJsonlPath};
1
+ import{closeSync as j,createReadStream as E,openSync as M,readFileSync as x,readSync as J,realpathSync as L,statSync as _}from"node:fs";import{createInterface as B}from"node:readline";import R from"node:path";import O from"node:os";import{log as A}from"../../core/log/index.js";function U(o){let r=o;try{r=L(o)}catch{}return r.replace(/[^a-zA-Z0-9]/g,"-")}function S(o,r){const t=R.join(O.homedir(),".claude"),a=R.join(t,"projects",U(r));return R.join(a,`${o}.jsonl`)}function w(){return{inputTokens:0,outputTokens:0,cacheReadInputTokens:0,cacheCreationInputTokens:0}}function I(o,r){o.inputTokens+=r.input_tokens??0,o.outputTokens+=r.output_tokens??0,o.cacheReadInputTokens+=r.cache_read_input_tokens??0,o.cacheCreationInputTokens+=r.cache_creation_input_tokens??0}async function Q(o,r){const t=S(o,r);A.info("usage-parser",`Parsing session usage from ${t}`);const a=new Map,e=w();let s=0;try{const l=B({input:E(t,"utf8"),crlfDelay:1/0});for await(const c of l)if(c.trim())try{const u=JSON.parse(c);if(u.type!=="assistant")continue;const n=u.message?.usage;if(!n)continue;const f=u.message?.model??"unknown";s++,I(e,n);let p=a.get(f);p||(p={turns:0,usage:w()},a.set(f,p)),p.turns++,I(p.usage,n)}catch{}}catch(l){return l.code==="ENOENT"?(A.info("usage-parser",`Session JSONL not found: ${t}`),null):(A.error("usage-parser",`Failed to parse session usage: ${l}`),null)}return s===0?null:{models:[...a.entries()].map(([l,c])=>({model:l,turns:c.turns,total:c.usage})),total:e,turns:s}}const m=64*1024,z=9e4;function C(o,r,t){const a=S(o,r);try{const e=x(a);let s;t!==void 0&&t>0?s=t<e.length?e.subarray(t):Buffer.alloc(0):s=e.length>m?e.subarray(e.length-m):e;const i=s.toString("utf8").split(`
2
+ `);for(let l=i.length-1;l>=0;l--){const c=i[l].trim();if(!c)continue;let u;try{u=JSON.parse(c)}catch{continue}if(u.type!=="assistant")continue;const n=u.message;if(!n?.content)continue;const f=Array.isArray(n.content)?n.content:[{type:"text",text:String(n.content)}],p=[];for(const g of f){const y=g;y.type==="text"&&y.text?.trim()&&p.push(y.text)}if(p.length>0)return{text:p.join(`
3
+ `),stopReason:n.stop_reason??null}}return null}catch{return null}}function X(o,r,t){return C(o,r,t)?.text??null}function Y(o,r,t){const a=S(o,r),e=t>0?t:0;let s;try{const i=_(a).size;if(e>=i)return{segments:[],newOffset:e};const l=i-e,c=Buffer.alloc(l);s=M(a,"r");const u=J(s,c,0,l,e),n=c.subarray(0,u);let f=-1;for(let h=n.length-1;h>=0;h--)if(n[h]===10){f=h;break}if(f<0)return{segments:[],newOffset:e};const p=n.subarray(0,f+1),g=e+p.length,y=[];for(const h of p.toString("utf8").split(`
4
+ `)){const T=h.trim();if(!T)continue;let k;try{k=JSON.parse(T)}catch{continue}if(k.type!=="assistant")continue;const d=k.message;if(!d?.content)continue;const N=Array.isArray(d.content)?d.content:[{type:"text",text:String(d.content)}];for(const P of N){const b=P;b.type==="text"&&b.text?.trim()&&y.push(b.text)}}return{segments:y,newOffset:g}}catch{return{segments:[],newOffset:e}}finally{if(s!==void 0)try{j(s)}catch{}}}function Z(o,r,t){const a=S(o,r);let e=null,s;try{const c=_(a);e=Date.now()-c.mtimeMs,s=x(a)}catch{return{lastStopReason:null,freshMs:null}}let i;t!==void 0&&t>0?i=t<s.length?s.subarray(t):Buffer.alloc(0):i=s.length>m?s.subarray(s.length-m):s;const l=i.toString("utf8").split(`
5
+ `);for(let c=l.length-1;c>=0;c--){const u=l[c].trim();if(!u)continue;let n;try{n=JSON.parse(u)}catch{continue}if(n.type!=="assistant")continue;const f=n.message;if(f)return{lastStopReason:f.stop_reason??null,freshMs:e}}return{lastStopReason:null,freshMs:e}}function q(o,r){const t=S(o,r);let a;try{a=x(t)}catch{return 0}const s=(a.length>m?a.subarray(a.length-m):a).toString("utf8").split(`
6
+ `),i=new Set,l=new Set,c=/<task-id>\s*([^<\s]+)\s*<\/task-id>/;for(const n of s){const f=n.trim();if(!f)continue;if(f.includes("task-notification")){const y=c.exec(f);y&&l.add(y[1]);continue}let p;try{p=JSON.parse(f)}catch{continue}const g=p.toolUseResult;g&&typeof g.backgroundTaskId=="string"&&i.add(g.backgroundTaskId)}let u=0;for(const n of i)l.has(n)||u++;return u}function G(o,r,t,a=!1){const e=S(o,r);try{const s=_(e);if(a&&Date.now()-s.mtimeMs<z)return!1;const i=x(e),c=(t>0&&t<i.length?i.subarray(t):i.length>m?i.subarray(i.length-m):i).toString("utf8").split(`
7
+ `);let u=!1,n=!1;for(const f of c){const p=f.trim();if(!p)continue;let g;try{g=JSON.parse(p)}catch{continue}if(g.type==="user"){const h=g.message?.content??g.content;(Array.isArray(h)?h:[]).some(d=>typeof d=="object"&&d!==null&&d.type==="tool_result")&&(u=!0,n=!1)}else g.type==="assistant"&&u&&(n=!0)}return u&&!n}catch{return!1}}export{q as countPendingBackgroundTasks,C as extractLastAssistantEntry,X as extractLastAssistantText,Y as extractTextBlocksAfterOffset,G as hasTerminalToolResultAfterOffset,Q as parseClaudeSessionUsage,Z as probeSessionTurnState,S as resolveSessionJsonlPath};
@@ -1 +1 @@
1
- import{log as o}from"../core/log/index.js";import{RespawnManager as u}from"./respawn-manager.js";import{EventQueue as p}from"./event-queue.js";class c extends Error{constructor(e){super(`adapter pool full (maxSize=${e})`),this.name="PoolFullError"}}const h=6e4;class g{slots=new Map;config;factory;sendAck;onEventState=null;onQueueComposing=null;onInternalError=null;onEventStarted=null;onEventDone=null;onSessionActivity=null;stopped=!1;sweepTimer=null;constructor(e,t,s){this.config=e,this.factory=t,this.sendAck=s}setEventStateHandler(e){this.onEventState=e}setQueueComposingHandler(e){this.onQueueComposing=e}setInternalErrorHandler(e){this.onInternalError=e}setEventStartedHandler(e){this.onEventStarted=e}setEventDoneHandler(e){this.onEventDone=e}setSessionActivityHandler(e){this.onSessionActivity=e}async deliverInboundEvent(e){const t=e.session_id,s=this.slots.get(t);if(s){s.startPromise&&await s.startPromise,s.lastActivityAt=Date.now(),this.sendAck(e.event_id,e.session_id),s.state==="stopped"&&o.info("adapter-pool",`Holding event ${e.event_id} in paused queue for restarting slot ${t}`),this.submitToSlotQueue(s,e);return}if(this.slots.size>=this.config.maxPoolSize&&!this.evictDeadSlot()&&!this.evictIdleSlot())throw new c(this.config.maxPoolSize);this.sendAck(e.event_id,e.session_id);const r=this.createSlot(t);try{await r.startPromise}catch(n){throw n}this.submitToSlotQueue(r,e)}submitToSlotQueue(e,t){e.eventQueue.submit(t)==="rejected"&&o.info("adapter-pool",`Event ${t.event_id} rejected by EventQueue (queue full)`)}eventComplete(e,t){const s=this.slots.get(t);s&&s.eventQueue.complete(e)}cancelEvent(e,t){const s=this.slots.get(t);return s?s.eventQueue.cancel(e):!1}removeQueuedEvent(e,t){const s=this.slots.get(e);return s?s.eventQueue.removeQueued(t):!1}clearQueue(e){const t=this.slots.get(e);return t?t.eventQueue.clear(e):[]}drainAllQueuedEvents(){const e=[];for(const t of this.slots.values())e.push(...t.eventQueue.drainQueuedForSession(t.sessionId));return e}drainQueuedForSession(e){if(!this.config.eventQueue)return[];const t=this.slots.get(e);return t?t.eventQueue.drainQueuedForSession(e):[]}getQueueSnapshot(e){if(!this.config.eventQueue)return null;const t=this.slots.get(e);return t?t.eventQueue.snapshot(e):null}deliverStopEvent(e,t){if(t){const s=this.slots.get(t);o.info("adapter-pool",`[stop-trace] pool.deliverStopEvent session=${t} event=${e} slotExists=${!!s} -> adapter.deliverStopEvent`),s?.adapter.deliverStopEvent(e,t);return}o.info("adapter-pool",`[stop-trace] pool.deliverStopEvent (broadcast, no session) event=${e} slots=${this.slots.size}`);for(const s of this.slots.values())s.adapter.deliverStopEvent(e)}collectActiveEventIds(){const e=[];for(const t of this.slots.values()){const s=t.adapter.getActiveEventIds;typeof s=="function"&&e.push(...s.call(t.adapter))}return e}clearActiveEventsForShutdown(){for(const e of this.slots.values()){const t=e.adapter.clearActiveEventForShutdown;typeof t=="function"&&t.call(e.adapter)}}async deliverLocalAction(e,t){const s=String((e.params??{}).session_id??"");let r=s?this.slots.get(s):void 0;if(!r&&s&&t?.autoCreateSlot&&!this.stopped&&(this.slots.size>=this.config.maxPoolSize&&(this.evictDeadSlot()||this.evictIdleSlotSync()),this.slots.size<this.config.maxPoolSize)){o.info("adapter-pool",`Auto-creating slot for session ${s} (local_action trigger)`);const i=this.createSlot(s);try{await i.startPromise,r=i}catch(d){o.error("adapter-pool",`Failed to auto-create slot for ${s}: ${d}`)}}return r?.adapter?.handleLocalAction?(r.lastActivityAt=Date.now(),r.adapter.handleLocalAction(e)):(await Promise.allSettled([...this.slots.values()].map(i=>i.adapter.handleLocalAction?.(e)??Promise.resolve({handled:!1,kind:""})))).find(i=>i.status==="fulfilled"&&i.value.handled)?.value??{handled:!1,kind:""}}getOrCreateSlot(e){const t=this.slots.get(e);if(t)return t;if(!this.stopped)return this.createSlot(e)}getSlot(e){return this.slots.get(e)}getAllSlots(){return[...this.slots.values()]}async removeSlot(e){const t=this.slots.get(e);t&&(this.onSessionActivity?.(e,!1),t.eventQueue.destroy(),t.respawn.stopAll(),this.detachSlotEventListenersExceptDone(t),await t.adapter.stop().catch(()=>{}),t.adapter.removeAllListeners("eventDone"),this.slots.delete(e))}async stop(){this.stopped=!0,this.stopIdleSweep();const e=[...this.slots.values()];this.slots.clear(),await Promise.allSettled(e.map(async t=>{t.eventQueue.destroy(),t.respawn.stopAll(),this.detachAllSlotEventListeners(t),await t.adapter.stop().catch(()=>{})}))}startIdleSweep(){this.stopIdleSweep(),this.sweepTimer=setInterval(()=>this.sweepIdle(),h)}stopIdleSweep(){this.sweepTimer&&(clearInterval(this.sweepTimer),this.sweepTimer=null)}sweepIdle(){const e=Date.now();for(const[t,s]of this.slots){if(s.state==="stopped"&&s.respawn.exhausted){o.info("adapter-pool",`Removing dead slot for session ${t} (sweep)`),this.removeSlot(t).catch(()=>{});continue}if(s.state==="ready"){if(s.adapter.getStatus().busy){s.lastActivityAt=e;continue}e-s.lastActivityAt<=this.config.idleTimeoutMs||(s.adapter.hasBackgroundWork?s.adapter.hasBackgroundWork().then(r=>{r?(o.info("adapter-pool",`Deferred eviction for session ${t}: background grandchild processes detected`),s.lastActivityAt=Date.now()):(o.info("adapter-pool",`Evicting idle slot for session ${t}`),this.removeSlot(t).catch(n=>{o.error("adapter-pool",`Failed to evict slot ${t}: ${n}`)}))}).catch(()=>{o.info("adapter-pool",`Evicting idle slot for session ${t} (background probe failed)`),this.removeSlot(t).catch(r=>{o.error("adapter-pool",`Failed to evict slot ${t}: ${r}`)})}):(o.info("adapter-pool",`Evicting idle slot for session ${t}`),this.removeSlot(t).catch(r=>{o.error("adapter-pool",`Failed to evict slot ${t}: ${r}`)})))}}}evictDeadSlot(){for(const[e,t]of this.slots)if(t.state==="stopped"&&t.respawn.exhausted)return o.info("adapter-pool",`Removing dead slot for session ${e} (exhausted respawn)`),this.removeSlot(e).catch(()=>{}),!0;return!1}evictIdleSlot(){let e=null;for(const t of this.slots.values())t.state==="ready"&&(t.adapter.getStatus().busy||(!e||t.lastActivityAt<e.lastActivityAt)&&(e=t));return e?(o.info("adapter-pool",`Evicting idle slot for session ${e.sessionId} (pool full)`),this.removeSlot(e.sessionId).catch(()=>{}),!0):!1}evictIdleSlotSync(){let e=null,t="";for(const[s,r]of this.slots)r.state==="ready"&&(r.adapter.getStatus().busy||(!e||r.lastActivityAt<e.lastActivityAt)&&(e=r,t=s));return!e||!t?!1:(o.info("adapter-pool",`Evicting idle slot for session ${t} (pool full, sync)`),this.slots.delete(t),e.respawn.stopAll(),this.detachAllSlotEventListeners(e),e.adapter.stop().catch(()=>{}),!0)}getStatus(){let e=0,t=0;for(const s of this.slots.values())s.state==="ready"&&(e++,s.adapter.getStatus().busy&&t++);return{total:this.slots.size,ready:e,busy:t}}createSlot(e){const t=this.factory(e),s=new u,r=this.createEventQueue(e),n={sessionId:e,adapter:t,respawn:s,state:"starting",lastActivityAt:Date.now(),startPromise:null,eventQueue:r};this.slots.set(e,n);const a=(async()=>{try{this.wireSlotEvents(n),await t.start(),n.state="ready"}catch(i){throw this.detachAllSlotEventListeners(n),this.slots.delete(e),i}finally{n.startPromise=null}})();return n.startPromise=a,s.startHealthCheck(this.slotRespawnCtx(n)),n}wireSlotEvents(e){e.adapter.on("exit",t=>{this.stopped||(o.error("adapter-pool",`Slot ${e.sessionId} adapter exited (code=${t})`),e.state="stopped",e.eventQueue.pause("restart"),e.respawn.scheduleRespawn(this.slotRespawnCtx(e)))}),e.adapter.on("stuck",()=>{this.stopped||this.slots.get(e.sessionId)===e&&(o.error("adapter-pool",`Slot ${e.sessionId} adapter stuck, triggering respawn`),e.state="stopped",e.eventQueue.pause("restart"),e.respawn.scheduleRespawn(this.slotRespawnCtx(e)))}),e.adapter.on?.("error",t=>{if(this.stopped)return;const s=t instanceof Error?t.message:String(t);o.error("adapter-pool",`Slot ${e.sessionId} adapter error: ${s}`)}),e.adapter.on("internalError",t=>{if(!this.stopped){if(this.slots.get(e.sessionId)!==e){o.warn("adapter-pool",`Ignored internalError from detached slot session=${e.sessionId} event=${t.eventId}`);return}if(!this.onInternalError){o.warn("adapter-pool",`Slot ${e.sessionId} emitted internalError but no handler registered: event=${t.eventId} msg=${t.errorMsg}`);return}this.onInternalError(t)}}),e.adapter.on("eventStarted",(t,s)=>{this.stopped||this.slots.get(e.sessionId)===e&&this.onEventStarted?.(t,s)}),e.adapter.on("eventDone",t=>{this.stopped||this.slots.get(e.sessionId)===e&&this.onEventDone?.(t,e.sessionId)}),e.adapter.on("sessionActivity",(t,s)=>{this.stopped||this.slots.get(e.sessionId)===e&&this.onSessionActivity?.(t||e.sessionId,s)}),e.adapter.on("pauseIntake",t=>{this.stopped||this.slots.get(e.sessionId)===e&&e.eventQueue.pause(t)}),e.adapter.on("resumeIntake",t=>{this.stopped||this.slots.get(e.sessionId)===e&&e.eventQueue.resume(t)})}detachAllSlotEventListeners(e){e.adapter.removeAllListeners("exit"),e.adapter.removeAllListeners("error"),e.adapter.removeAllListeners("stuck"),e.adapter.removeAllListeners("internalError"),e.adapter.removeAllListeners("eventStarted"),e.adapter.removeAllListeners("eventDone"),e.adapter.removeAllListeners("sessionActivity"),e.adapter.removeAllListeners("pauseIntake"),e.adapter.removeAllListeners("resumeIntake")}detachSlotEventListenersExceptDone(e){e.adapter.removeAllListeners("exit"),e.adapter.removeAllListeners("error"),e.adapter.removeAllListeners("stuck"),e.adapter.removeAllListeners("internalError"),e.adapter.removeAllListeners("eventStarted"),e.adapter.removeAllListeners("sessionActivity"),e.adapter.removeAllListeners("pauseIntake"),e.adapter.removeAllListeners("resumeIntake")}slotRespawnCtx(e){const t=this,s=e.sessionId;return{name:`slot-${s}`,get stopped(){return t.stopped||!t.slots.has(s)},get agentAlive(){return e.adapter.isAlive()},pingAgent:r=>e.adapter.ping(r),onRespawnNeeded:async()=>{e.eventQueue.pause("restart"),this.detachAllSlotEventListeners(e),await e.adapter.stop().catch(()=>{}),e.adapter=t.factory(s),e.state="starting",t.wireSlotEvents(e),await e.adapter.start(),e.state="ready",e.respawn.resetAttempts(),e.respawn.resetHealthFailures(),e.respawn.stopSlowRetry(),e.respawn.startHealthCheck(t.slotRespawnCtx(e)),e.eventQueue.resume("compaction"),e.eventQueue.resume("barrier"),e.eventQueue.resume("restart")},onCleanupAgent:()=>{"cleanup"in e.adapter&&e.adapter.cleanup()},onAbortActiveRun:r=>{}}}createEventQueue(e){const t=this.config.eventQueue??{maxConcurrent:1,maxQueued:5,queueTimeoutMs:3e5,cancelableQueued:!0,cancelableRunning:!0},s={onDeliver:r=>{const n=this.slots.get(e);n&&n.adapter.deliverInboundEvent(r)},onStateChange:(r,n,a,i)=>{this.onEventState?.(r,n,a,i)},onCancelRunning:r=>{this.deliverStopEvent(r,e)},onRejected:(r,n)=>{this.onEventState?.(r.event_id,r.session_id,"failed",{reason:n})},onComposing:(r,n,a)=>{this.onQueueComposing?.(r,n,a)}};return new p(t,s)}}export{g as AdapterPool,c as PoolFullError};
1
+ import{log as o}from"../core/log/index.js";import{RespawnManager as u}from"./respawn-manager.js";import{EventQueue as p}from"./event-queue.js";class c extends Error{constructor(e){super(`adapter pool full (maxSize=${e})`),this.name="PoolFullError"}}const h=6e4;class g{slots=new Map;config;factory;sendAck;onEventState=null;onQueueComposing=null;onInternalError=null;onEventStarted=null;onEventDone=null;onSessionActivity=null;stopped=!1;sweepTimer=null;constructor(e,t,s){this.config=e,this.factory=t,this.sendAck=s}setEventStateHandler(e){this.onEventState=e}setQueueComposingHandler(e){this.onQueueComposing=e}setInternalErrorHandler(e){this.onInternalError=e}setEventStartedHandler(e){this.onEventStarted=e}setEventDoneHandler(e){this.onEventDone=e}setSessionActivityHandler(e){this.onSessionActivity=e}async deliverInboundEvent(e){const t=e.session_id,s=this.slots.get(t);if(s){s.startPromise&&await s.startPromise,s.lastActivityAt=Date.now(),this.sendAck(e.event_id,e.session_id),s.state==="stopped"&&o.info("adapter-pool",`Holding event ${e.event_id} in paused queue for restarting slot ${t}`),this.submitToSlotQueue(s,e);return}if(this.slots.size>=this.config.maxPoolSize&&!this.evictDeadSlot()&&!this.evictIdleSlot())throw new c(this.config.maxPoolSize);this.sendAck(e.event_id,e.session_id);const r=this.createSlot(t);try{await r.startPromise}catch(n){throw n}this.submitToSlotQueue(r,e)}submitToSlotQueue(e,t){e.eventQueue.submit(t)==="rejected"&&o.info("adapter-pool",`Event ${t.event_id} rejected by EventQueue (queue full)`)}eventComplete(e,t){const s=this.slots.get(t);s&&s.eventQueue.complete(e)}cancelEvent(e,t){const s=this.slots.get(t);return s?s.eventQueue.cancel(e):!1}removeQueuedEvent(e,t){const s=this.slots.get(e);return s?s.eventQueue.removeQueued(t):!1}clearQueue(e){const t=this.slots.get(e);return t?t.eventQueue.clear(e):[]}drainAllQueuedEvents(){const e=[];for(const t of this.slots.values())e.push(...t.eventQueue.drainQueuedForSession(t.sessionId));return e}drainQueuedForSession(e){if(!this.config.eventQueue)return[];const t=this.slots.get(e);return t?t.eventQueue.drainQueuedForSession(e):[]}getQueueSnapshot(e){if(!this.config.eventQueue)return null;const t=this.slots.get(e);return t?t.eventQueue.snapshot(e):null}deliverStopEvent(e,t){if(t){const s=this.slots.get(t);o.info("adapter-pool",`[stop-trace] pool.deliverStopEvent session=${t} event=${e} slotExists=${!!s} -> adapter.deliverStopEvent`),s?.adapter.deliverStopEvent(e,t);return}o.info("adapter-pool",`[stop-trace] pool.deliverStopEvent (broadcast, no session) event=${e} slots=${this.slots.size}`);for(const s of this.slots.values())s.adapter.deliverStopEvent(e)}collectActiveEventIds(){const e=[];for(const t of this.slots.values()){const s=t.adapter.getActiveEventIds;typeof s=="function"&&e.push(...s.call(t.adapter))}return e}clearActiveEventsForShutdown(){for(const e of this.slots.values()){const t=e.adapter.clearActiveEventForShutdown;typeof t=="function"&&t.call(e.adapter)}}async deliverLocalAction(e,t){const s=String((e.params??{}).session_id??"");let r=s?this.slots.get(s):void 0;if(!r&&s&&t?.autoCreateSlot&&!this.stopped&&(this.slots.size>=this.config.maxPoolSize&&(this.evictDeadSlot()||this.evictIdleSlotSync()),this.slots.size<this.config.maxPoolSize)){o.info("adapter-pool",`Auto-creating slot for session ${s} (local_action trigger)`);const i=this.createSlot(s);try{await i.startPromise,r=i}catch(d){o.error("adapter-pool",`Failed to auto-create slot for ${s}: ${d}`)}}return r?.adapter?.handleLocalAction?(r.lastActivityAt=Date.now(),r.adapter.handleLocalAction(e)):(await Promise.allSettled([...this.slots.values()].map(i=>i.adapter.handleLocalAction?.(e)??Promise.resolve({handled:!1,kind:""})))).find(i=>i.status==="fulfilled"&&i.value.handled)?.value??{handled:!1,kind:""}}getOrCreateSlot(e){const t=this.slots.get(e);if(t)return t;if(!this.stopped)return this.createSlot(e)}getSlot(e){return this.slots.get(e)}getAllSlots(){return[...this.slots.values()]}async removeSlot(e){const t=this.slots.get(e);t&&(this.onSessionActivity?.(e,!1),t.eventQueue.destroy(),t.respawn.stopAll(),this.detachSlotEventListenersExceptDone(t),await t.adapter.stop().catch(()=>{}),t.adapter.removeAllListeners("eventDone"),this.slots.delete(e))}async stop(){this.stopped=!0,this.stopIdleSweep();const e=[...this.slots.values()];this.slots.clear(),await Promise.allSettled(e.map(async t=>{t.eventQueue.destroy(),t.respawn.stopAll(),this.detachAllSlotEventListeners(t),await t.adapter.stop().catch(()=>{})}))}startIdleSweep(){this.stopIdleSweep(),this.sweepTimer=setInterval(()=>this.sweepIdle(),h)}stopIdleSweep(){this.sweepTimer&&(clearInterval(this.sweepTimer),this.sweepTimer=null)}sweepIdle(){const e=Date.now();for(const[t,s]of this.slots){if(s.state==="stopped"&&s.respawn.exhausted){o.info("adapter-pool",`Removing dead slot for session ${t} (sweep)`),this.removeSlot(t).catch(()=>{});continue}if(s.state==="ready"){if(s.adapter.getStatus().busy){s.lastActivityAt=e;continue}e-s.lastActivityAt<=this.config.idleTimeoutMs||(s.adapter.hasBackgroundWork?s.adapter.hasBackgroundWork().then(r=>{r?(o.info("adapter-pool",`Deferred eviction for session ${t}: background grandchild processes detected`),s.lastActivityAt=Date.now()):(o.info("adapter-pool",`Evicting idle slot for session ${t}`),this.removeSlot(t).catch(n=>{o.error("adapter-pool",`Failed to evict slot ${t}: ${n}`)}))}).catch(()=>{o.info("adapter-pool",`Evicting idle slot for session ${t} (background probe failed)`),this.removeSlot(t).catch(r=>{o.error("adapter-pool",`Failed to evict slot ${t}: ${r}`)})}):(o.info("adapter-pool",`Evicting idle slot for session ${t}`),this.removeSlot(t).catch(r=>{o.error("adapter-pool",`Failed to evict slot ${t}: ${r}`)})))}}}evictDeadSlot(){for(const[e,t]of this.slots)if(t.state==="stopped"&&t.respawn.exhausted)return o.info("adapter-pool",`Removing dead slot for session ${e} (exhausted respawn)`),this.removeSlot(e).catch(()=>{}),!0;return!1}evictIdleSlot(){let e=null;for(const t of this.slots.values())t.state==="ready"&&(t.adapter.getStatus().busy||(!e||t.lastActivityAt<e.lastActivityAt)&&(e=t));return e?(o.info("adapter-pool",`Evicting idle slot for session ${e.sessionId} (pool full)`),this.removeSlot(e.sessionId).catch(()=>{}),!0):!1}evictIdleSlotSync(){let e=null,t="";for(const[s,r]of this.slots)r.state==="ready"&&(r.adapter.getStatus().busy||(!e||r.lastActivityAt<e.lastActivityAt)&&(e=r,t=s));return!e||!t?!1:(o.info("adapter-pool",`Evicting idle slot for session ${t} (pool full, sync)`),this.slots.delete(t),e.respawn.stopAll(),this.detachAllSlotEventListeners(e),e.adapter.stop().catch(()=>{}),!0)}getStatus(){let e=0,t=0;for(const s of this.slots.values())s.state==="ready"&&(e++,s.adapter.getStatus().busy&&t++);return{total:this.slots.size,ready:e,busy:t}}createSlot(e){const t=this.factory(e),s=new u,r=this.createEventQueue(e),n={sessionId:e,adapter:t,respawn:s,state:"starting",lastActivityAt:Date.now(),startPromise:null,eventQueue:r};this.slots.set(e,n);const a=(async()=>{try{this.wireSlotEvents(n),await t.start(),n.state="ready"}catch(i){throw this.detachAllSlotEventListeners(n),this.slots.delete(e),i}finally{n.startPromise=null}})();return n.startPromise=a,s.startHealthCheck(this.slotRespawnCtx(n)),n}wireSlotEvents(e){e.adapter.on("exit",t=>{this.stopped||(o.error("adapter-pool",`Slot ${e.sessionId} adapter exited (code=${t})`),e.state="stopped",e.eventQueue.pause("restart"),e.respawn.scheduleRespawn(this.slotRespawnCtx(e)))}),e.adapter.on("stuck",()=>{this.stopped||this.slots.get(e.sessionId)===e&&(o.error("adapter-pool",`Slot ${e.sessionId} adapter stuck, triggering respawn`),e.state="stopped",e.eventQueue.pause("restart"),e.respawn.scheduleRespawn(this.slotRespawnCtx(e)))}),e.adapter.on?.("error",t=>{if(this.stopped)return;const s=t instanceof Error?t.message:String(t);o.error("adapter-pool",`Slot ${e.sessionId} adapter error: ${s}`)}),e.adapter.on("internalError",t=>{if(!this.stopped){if(this.slots.get(e.sessionId)!==e){o.warn("adapter-pool",`Ignored internalError from detached slot session=${e.sessionId} event=${t.eventId}`);return}if(!this.onInternalError){o.warn("adapter-pool",`Slot ${e.sessionId} emitted internalError but no handler registered: event=${t.eventId} msg=${t.errorMsg}`);return}this.onInternalError(t)}}),e.adapter.on("eventStarted",(t,s)=>{this.stopped||this.slots.get(e.sessionId)===e&&this.onEventStarted?.(t,s)}),e.adapter.on("eventDone",t=>{this.stopped||this.slots.get(e.sessionId)===e&&this.onEventDone?.(t,e.sessionId)}),e.adapter.on("sessionActivity",(t,s,r)=>{this.stopped||this.slots.get(e.sessionId)===e&&this.onSessionActivity?.(t||e.sessionId,s,r)}),e.adapter.on("pauseIntake",t=>{this.stopped||this.slots.get(e.sessionId)===e&&e.eventQueue.pause(t)}),e.adapter.on("resumeIntake",t=>{this.stopped||this.slots.get(e.sessionId)===e&&e.eventQueue.resume(t)})}detachAllSlotEventListeners(e){e.adapter.removeAllListeners("exit"),e.adapter.removeAllListeners("error"),e.adapter.removeAllListeners("stuck"),e.adapter.removeAllListeners("internalError"),e.adapter.removeAllListeners("eventStarted"),e.adapter.removeAllListeners("eventDone"),e.adapter.removeAllListeners("sessionActivity"),e.adapter.removeAllListeners("pauseIntake"),e.adapter.removeAllListeners("resumeIntake")}detachSlotEventListenersExceptDone(e){e.adapter.removeAllListeners("exit"),e.adapter.removeAllListeners("error"),e.adapter.removeAllListeners("stuck"),e.adapter.removeAllListeners("internalError"),e.adapter.removeAllListeners("eventStarted"),e.adapter.removeAllListeners("sessionActivity"),e.adapter.removeAllListeners("pauseIntake"),e.adapter.removeAllListeners("resumeIntake")}slotRespawnCtx(e){const t=this,s=e.sessionId;return{name:`slot-${s}`,get stopped(){return t.stopped||!t.slots.has(s)},get agentAlive(){return e.adapter.isAlive()},pingAgent:r=>e.adapter.ping(r),onRespawnNeeded:async()=>{e.eventQueue.pause("restart"),this.detachAllSlotEventListeners(e),await e.adapter.stop().catch(()=>{}),e.adapter=t.factory(s),e.state="starting",t.wireSlotEvents(e),await e.adapter.start(),e.state="ready",e.respawn.resetAttempts(),e.respawn.resetHealthFailures(),e.respawn.stopSlowRetry(),e.respawn.startHealthCheck(t.slotRespawnCtx(e)),e.eventQueue.resume("compaction"),e.eventQueue.resume("barrier"),e.eventQueue.resume("restart")},onCleanupAgent:()=>{"cleanup"in e.adapter&&e.adapter.cleanup()},onAbortActiveRun:r=>{}}}createEventQueue(e){const t=this.config.eventQueue??{maxConcurrent:1,maxQueued:5,queueTimeoutMs:3e5,cancelableQueued:!0,cancelableRunning:!0},s={onDeliver:r=>{const n=this.slots.get(e);n&&n.adapter.deliverInboundEvent(r)},onStateChange:(r,n,a,i)=>{this.onEventState?.(r,n,a,i)},onCancelRunning:r=>{this.deliverStopEvent(r,e)},onRejected:(r,n)=>{this.onEventState?.(r.event_id,r.session_id,"failed",{reason:n})},onComposing:(r,n,a)=>{this.onQueueComposing?.(r,n,a)}};return new p(t,s)}}export{g as AdapterPool,c as PoolFullError};
@@ -1,4 +1,4 @@
1
- import T 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 y}from"../adapter/claude/index.js";import{CodexAdapter as Y}from"../adapter/codex/index.js";import{PiAdapter as X}from"../adapter/pi/index.js";import{AcpAdapter as A}from"../adapter/acp/index.js";import{OpenHumanAdapter as Z}from"../adapter/openhuman/index.js";import{CursorAdapter as M}from"../adapter/cursor/index.js";import{CodeWhaleAdapter as O}from"../adapter/codewhale/index.js";import{OpenCodeAdapter as ee}from"../adapter/opencode/index.js";import{AgyAdapter as q}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 x,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 U,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{getMachineName as ye}from"../core/util/index.js";import{AllowlistGate as xe}from"../core/access/allowlist-gate.js";import{ActiveEventStore as Te}from"../core/persistence/active-event-store.js";import{DEFAULT_CONNECTOR_RUNTIME_CONFIG as Le,applyConnectorRuntimeConfigPatch as Pe,extractConnectorRuntimeConfigPatch as He}from"./runtime-config.js";import{SendController as Me}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 Ie}from"./tool-card-utils.js";import{DeferredEventManager as De}from"./deferred-events.js";import{buildAgentProbeResult as Qe,PROBE_CACHE_TTL_STATIC_MS as Be,PROBE_CACHE_TTL_FULL_MS as Fe}from"./probe-helper.js";const Oe=600*1e3,qe=60*1e3,j=30*1e3,Ue=10*1e3,P=new Set(["claude","acp","agy","cursor","codex"]),We=new Set(["claude","codex","cursor","codewhale","opencode","pi","openhuman","agy","acp"]),B=3;class It{config;name;aibotHandle;aibotConfig;pool;stopped=!1;revokeHandler=new ve;sessionBindings=new Map;deferredMgr;sendCtrl=new Me(Le);bindingStore;globalConfigStore;upgradeTrigger=null;shareSetHandler=null;agentProfile={agentName:"",introduction:""};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 o=Number(e);return o>0&&Date.now()-o<=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,o=this.config.providerApiKey;if((!e||!o)&&(this.config.adapterType??"acp")==="claude"){const t=se();e||(e=(t.ANTHROPIC_BASE_URL??"").trim()||void 0),o||(o=(t.ANTHROPIC_API_KEY??"").trim()||(t.ANTHROPIC_AUTH_TOKEN??"").trim()||void 0)}if((!e||!o)&&(this.config.adapterType??"acp")==="codex"){const t=ae();!e&&t.baseUrl&&(e=t.baseUrl),!o&&t.apiKey&&(o=t.apiKey)}if(!e||!o)return null;if(this.isRateLimitsCacheFresh(this.cachedProviderQuotaSampledAtMs)&&this.cachedProviderQuota)return this.cachedProviderQuota;try{const t=await K(e,o);return this.cachedProviderQuota=t,this.cachedProviderQuotaSampledAtMs=Date.now(),u.info(this.name,`[provider-quota] queried: provider=${t.provider} success=${t.success}`+(t.tiers.length>0?` tiers=${t.tiers.map(n=>`${n.name}=${n.usedPercent}%`).join(",")}`:"")+(t.balance?` balance=${t.balance.remaining} ${t.balance.unit}`:"")+(t.error?` error=${t.error}`:"")),t}catch(t){return u.warn(this.name,`[provider-quota] query failed: ${t instanceof Error?t.message:String(t)}`),null}}startKiroQuotaTimer(){this.kiroQuotaTimer||(this.kiroQuotaTimer=setInterval(()=>{if(this.stopped){this.stopKiroQuotaTimer();return}this.refreshAndPushKiroQuota().catch(()=>{})},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 o=this.providerQuotaToRateLimits(e);for(const[t,n]of this.sessionBindings.entries()){if(!n)continue;const i={provider_quota:e};o&&(i.rate_limits=o);const s=this.cachedAcpContextWindow;s&&("usedPercentage"in s?i.context_window={usedPercentage:s.usedPercentage,remainingPercentage:100-s.usedPercentage}:i.context_window=s),this.aibotHandle.sendUpdateBindingCard({session_id:t,worker_status:"ready",cwd:n,meta:i})}}getFreshClaudeRateLimitState(){const e=this.cachedClaudeRateLimitState;return e&&this.isRateLimitsCacheFresh(e.sampledAt)?e:null}getFreshCodexGlobalRateLimitCache(){const e=this.cachedRateLimits,o=this.cachedCodexContextWindow,t=this.cachedCodexTokenUsage;return{sampledAt:Math.max(this.cachedRateLimitsSampledAtMs??0,this.cachedCodexUsageSampledAtMs??0)||null,rateLimits:e,contextWindow:o,tokenUsage:t,hasData:!!(e||o||t)}}getStatus(){const e=this.pool?.getStatus()??{total:0,ready:0,busy:0};return{name:this.name,alive:!this.stopped,busy:e.busy>0,exhausted:this.pool?[...this.pool.getAllSlots()].some(o=>o.respawn.exhausted):!1,adapterType:this.config.adapterType??"acp",clientType:this.config.aibot.clientType,pool:e}}async probe(e={}){const o=e.conversation?"full":"static",t=e.conversation?Fe:Be;if(!e.fresh){const a=this.probeCache.get(o);if(a&&Date.now()-a.sampledAt<t)return{...a.result,cached:!0}}const n=this.config.adapterType??"acp",i=this.createAdapter(n,"__probe__"),s=e.conversation&&n==="acp"?()=>this.runAcpConversationProbe(i,e.timeoutMs??1e4):void 0;let r;try{r=await Qe({adapter:i,agentName:this.name,clientType:this.config.aibot.clientType,adapterType:n,providerBaseUrl:this.config.providerBaseUrl??null,opts:e,launchConversationProbe:s})}finally{i.stop().catch(()=>{})}return this.probeCache.set(o,{result:r,sampledAt:r.probed_at}),r}async runAcpConversationProbe(e,o){const t=Date.now(),n="__probe__",i=`probe-${Date.now()}-${Math.random().toString(36).slice(2,8)}`,s=this.config.agent.cwd||V(),r=()=>Date.now()-t;try{await e.start()}catch(h){return{attempted:!0,ok:!1,latency_ms:r(),error:{code:"conversation_failed",message:`start failed: ${h instanceof Error?h.message:String(h)}`}}}if(!e.isAlive())return{attempted:!0,ok:!1,latency_ms:r(),error:{code:"process_not_started",message:"agent process not alive"}};if(e instanceof A)try{await e.bindSession(n,s)}catch{}let a=null;const d=new Promise(h=>{a=m=>{m===i&&h()},e.on("eventDone",a)});e.deliverInboundEvent({event_id:i,session_id:n,content:"ping",msg_id:i}),e.deliverStopEvent(i,n);let c=null;const l=await Promise.race([d.then(()=>!1),new Promise(h=>{c=setTimeout(()=>h(!0),o)})]);return c&&clearTimeout(c),a&&e.removeListener("eventDone",a),l?{attempted:!0,ok:!1,latency_ms:r(),error:{code:"conversation_timeout",message:`no eventDone within ${o}ms`}}:{attempted:!0,ok:!0,latency_ms:r()}}constructor(e,o){this.config=e,this.name=e.name;const t=e.adapterType??"acp";this.aibotConfig={...e.aibot,...t==="claude"?{localActions:e.aibot.localActions??["session_control","claude_interaction_reply","get_session_usage","get_rate_limits","set_model","set_mode","thread_compact","get_agent_global_config"]}:{}},e.eventQueue&&(this.aibotConfig.concurrency={max_concurrent:e.eventQueue.maxConcurrent,max_queued:e.eventQueue.maxQueued,queue_timeout_ms:e.eventQueue.queueTimeoutMs,cancelable_queued:e.eventQueue.cancelableQueued,cancelable_running:e.eventQueue.cancelableRunning}),this.conversationLog=e.logDir?new pe(e.logDir):null,this.packetLog=e.logDir?new fe(e.logDir):null,this.bindingStore=new ke(e.bindingsPath),this.bindingStore.load(),this.globalConfigStore=o??null,this.deferredMgr=new De(this.name),this.allowlistGate=e.allowlistPath?new xe(e.allowlistPath):null,this.activeEventStore=e.activeEventStorePath?new Te(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},o=>{const t=this.createAdapter(e,o);return t instanceof A&&t.on("acpSessionReady",n=>{this.bindingStore.setAcpSessionId(o,n),this.sessionScanCache.invalidate()}),t},(o,t)=>{this.aibotHandle.sendEventAck({event_id:o,session_id:t,received_at:Date.now()})}),this.pool.setEventStateHandler((o,t,n,i)=>{u.info(this.name,`[queue-debug] send event_state session=${t} event=${o} state=${n} queue_pos=${i?.queue_position??""} queue_total=${i?.queue_total??""}`),this.aibotHandle.sendEventState({event_id:o,session_id:t,state:n,content_preview:i?.content_preview,queue_position:i?.queue_position,queue_total:i?.queue_total,actions:i?.actions,reason:i?.reason,updated_at:Date.now()}),this.pushQueueSnapshotForSession(t),(n==="canceled"||n==="failed")&&this.aibotHandle.sendEventResult({event_id:o,status:n==="canceled"?"canceled":"failed",msg:i?.reason,updated_at:Date.now()})}),this.pool.setQueueComposingHandler((o,t,n)=>{this.aibotHandle.sendSessionActivitySet({session_id:o,kind:"composing",active:t,...t?{ttl_ms:3e4,ref_event_id:n}:{}})}),this.pool.setInternalErrorHandler(o=>{this.handleSessionInternalError(o).catch(t=>{u.error(this.name,`[recovery] handleSessionInternalError failed event=${o.eventId} session=${o.sessionId}: ${t instanceof Error?t.message:String(t)}`)})}),this.pool.setEventStartedHandler((o,t)=>{if(this.config.adapterType!=="claude")return;this.claudeWorkerStatus.set(t,"busy");const n=this.bindingStore.get(t);n?.cwd&&this.aibotHandle.sendUpdateBindingCard({session_id:t,worker_status:"busy",cwd:n.cwd,meta:this.buildClaudeToolbarMeta(t)})}),this.pool.setEventDoneHandler((o,t)=>{const n=this.config.adapterType??"acp";if(n==="claude"){this.claudeWorkerStatus.set(t,"ready");const i=this.bindingStore.get(t);i?.cwd&&this.aibotHandle.sendUpdateBindingCard({session_id:t,worker_status:"ready",cwd:i.cwd,meta:this.buildClaudeToolbarMeta(t)})}else if(n==="agy"){const i=this.bindingStore.get(t);if(i?.cwd){const s=this.buildAgyToolbarMeta(t),r=s?.available_models??[];u.info(this.name,`[agy-toolbar-diag] eventDone push binding card session=${t} model_id=${String(s?.model_id??"")} available_models=${r.length} meta_keys=${s?Object.keys(s).join(","):"<none>"}`),this.aibotHandle.sendUpdateBindingCard({session_id:t,worker_status:"ready",cwd:i.cwd,meta:s})}else u.info(this.name,`[agy-toolbar-diag] eventDone skip binding card: no binding cwd session=${t}`)}}),this.pool.setSessionActivityHandler((o,t)=>{const n=this.selfDrivenSessions.has(o);t?(this.selfDrivenSessions.add(o),this.aibotHandle.sendSessionActivitySet({session_id:o,kind:"composing",active:!0,ttl_ms:9e4})):(this.selfDrivenSessions.delete(o),n&&this.aibotHandle.sendSessionActivitySet({session_id:o,kind:"composing",active:!1})),n!==t&&this.pushQueueSnapshotForSession(o)}),this.pool.startIdleSweep(),u.info(this.name,`Ready (adapter: ${e}, poolMax: ${this.config.poolMaxSize??20})`)}async stop(){this.stopped=!0,this.pool?.stopIdleSweep(),this.stopKiroQuotaTimer();const e=this.pool?.collectActiveEventIds()??[];e.length>0&&this.activeEventStore&&await this.activeEventStore.save(e);for(const n of e)u.info(this.name,`Canceling active event on shutdown: ${n}`),this.sendEventResultWithCleanup(n,"canceled","process shutting down");e.length>0&&await new Promise(n=>setTimeout(n,100)),this.pool?.clearActiveEventsForShutdown();const o=this.deferredMgr.getAllDeferredEvents();for(const n of o)u.info(this.name,`Failing deferred event on shutdown: ${n.event_id}`),this.sendEventResultWithCleanup(n.event_id,"failed","process shutting down");const t=this.pool?.drainAllQueuedEvents()??[];for(const n of t)u.info(this.name,`Failing queued event on shutdown: ${n.event_id}`),this.sendEventResultWithCleanup(n.event_id,"failed","process shutting down");this.deferredMgr.clearAll(),await this.pool?.stop(),this.aibotHandle?.disconnect(),e.length>0&&this.activeEventStore&&await this.activeEventStore.save([]),this.eventSessionIndex.clear(),this.inflightEvents.clear(),this.restartCount.clear()}createAdapter(e,o){switch(e){case"claude":return this.createClaudeAdapter(o);case"codex":return this.createCodexAdapter(o);case"pi":return this.createPiAdapter(o);case"openhuman":return this.createOpenHumanAdapter(o);case"codewhale":return this.createCodeWhaleAdapter(o);case"cursor":return this.createCursorAdapter(o);case"opencode":return this.createOpenCodeAdapter(o);case"agy":return this.createAgyAdapter(o);default:return this.createAcpAdapter(o)}}createCursorAdapter(e){const o={...this.config.adapterOptions??{}};o.bindingStore=this.bindingStore,o.aibotSessionId=e;const t=this.bindingStore.get(e),n=t?.modelId??this.globalConfigStore?.get(this.name)?.modelId;n&&(o.model=n),t?.modeId&&(o.mode=t.modeId);const i={sendStreamChunk:(s,r,a,d,c)=>{this.sendStreamChunkByRuntimeConfig(s,r,a,d,c)},sendEventResult:(s,r,a)=>{this.sendEventResultWithCleanup(s,r,a)},sendEventAck:(s,r)=>{this.aibotHandle.sendEventAck({event_id:s,session_id:r,received_at:Date.now()})},sendRawEventEnvelope:(s,r,a)=>{this.aibotHandle.sendMsg({event_id:s,session_id:r,msg_type:1,content:"[cursor] raw_event",extra:{channel_data:{cursor:{raw_event:a}},agent_api_origin:!0}})},agentInvoke:async(s,r)=>this.platformInvoke(s,r),sendLocalActionResult:(s,r,a,d,c)=>{this.aibotHandle.sendLocalActionResult({action_id:s,status:r,...a!==void 0?{result:a}:{},...d?{error_code:d}:{},...c?{error_msg:c}:{}})}};return new M({command:this.config.agent.command,args:this.config.agent.args,env:this.config.agent.env,options:o},i)}createClaudeAdapter(e){const o={sendReply:(i,s,r,a,d)=>{this.sendReplyByRuntimeConfig(i,s,r,a,d)},sendStreamChunk:(i,s,r,a,d,c,l)=>{this.sendStreamChunkByRuntimeConfig(i,s,r,a,d,c,l)},sendMedia:(i,s,r,a,d,c,l)=>{this.aibotHandle.sendMedia({event_id:i,session_id:s,content:r,msg_type:2,quoted_message_id:d||void 0,client_msg_id:c||void 0,extra:l?{media_caption:a,...l}:{media_caption:a}})},sendEventResult:(i,s,r,a)=>{this.sendEventResultWithCleanup(i,s,r,a)},sendEventAck:(i,s)=>{this.aibotHandle.sendEventAck({event_id:i,session_id:s,received_at:Date.now()})},agentInvoke:async(i,s,r)=>this.platformInvoke(i,s,r),sendLocalActionResult:(i,s,r,a,d)=>{this.aibotHandle.sendLocalActionResult({action_id:i,status:s,...r!==void 0?{result:r}:{},...a?{error_code:a}:{},...d?{error_msg:d}:{}})},sendToolUse:(i,s,r,a)=>{this.sendToolExecutionCard(i,s,E(r,a))},sendToolResult:(i,s,r,a)=>{this.sendToolExecutionCard(i,s,$(r,a))},getWsUrl:()=>this.config.aibot.url,getAgentId:()=>this.config.aibot.agentId,getAgentProfile:()=>this.agentProfile,getApiKey:()=>this.config.aibot.apiKey,getActiveEventCount:()=>0,getPendingPermissionCount:()=>0,getPendingElicitationCount:()=>0,sendAgentQuestionCard:(i,s,r)=>{const a=r.questions.map(c=>c.header).join(", "),d=Ie(`[Agent Question] ${r.request_id}`,"agent_question",r);this.aibotHandle.sendText({event_id:i,session_id:s,content:d,msg_type:1,extra:{card_type:"agent_question",summary_text:a}})},sendPermissionCard:i=>{this.aibotHandle.sendMsg({event_id:i.eventId,session_id:i.sessionId,client_msg_id:`perm_${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 s=this.bindingStore.get(e);s?.cwd&&this.aibotHandle.sendUpdateBindingCard({session_id:e,worker_status:this.claudeWorkerStatus.get(e)??"ready",cwd:s.cwd,meta:this.buildClaudeToolbarMeta(e)})},sendMcpFrame:i=>{this.aibotHandle.sendMcpFrame(e,i)}},t=this.config.adapterOptions??{},n={...t,sessionRuntimeResolver:()=>{const i=this.bindingStore.get(e);return{cwd:i?.cwd,modeId:i?.modeId??C.fullAuto,modelId:i?.modelId??this.globalConfigStore?.get(this.name)?.modelId,pluginDir:t.pluginDir,claudeSessionId:i?.claudeSessionId,onSessionIdAssigned:s=>{this.bindingStore.setClaudeSessionId(e,s),this.sessionScanCache.invalidate()}}}};return new y({command:this.config.agent.command,args:this.config.agent.args,env:this.config.agent.env,options:n},o)}createCodexAdapter(e){let o=null;const t={sendEventResult:(s,r,a)=>{this.sendEventResultWithCleanup(s,r,a)},sendEventAck:(s,r)=>this.aibotHandle.sendEventAck({event_id:s,session_id:r,received_at:Date.now()}),sendCodexEvent:s=>{this.shouldDropCodexDisplayEvent(s.event_id,s.codex_method)||(this.aibotHandle.sendCodexEvent(s),this.logCodexEventToConversation(s))},sendCodexEventReliable:async s=>{if(!this.shouldDropCodexDisplayEvent(s.event_id,s.codex_method)){try{await this.aibotHandle.sendCodexEventReliable(s)}catch(r){u.warn("bridge",`[codex] sendCodexEventReliable ACK failed event=${s.event_id}: ${r}`)}u.info("bridge",`[codex] sendCodexEventReliable done event=${s.event_id} method=${s.codex_method}`),this.logCodexEventToConversation(s)}},sendRunError:(s,r,a)=>{this.sendStreamChunkByRuntimeConfig(s,r,`
1
+ import T 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 y}from"../adapter/claude/index.js";import{CodexAdapter as Y}from"../adapter/codex/index.js";import{PiAdapter as X}from"../adapter/pi/index.js";import{AcpAdapter as A}from"../adapter/acp/index.js";import{OpenHumanAdapter as Z}from"../adapter/openhuman/index.js";import{CursorAdapter as M}from"../adapter/cursor/index.js";import{CodeWhaleAdapter as O}from"../adapter/codewhale/index.js";import{OpenCodeAdapter as ee}from"../adapter/opencode/index.js";import{AgyAdapter as q}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 x,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 U,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{getMachineName as ye}from"../core/util/index.js";import{AllowlistGate as xe}from"../core/access/allowlist-gate.js";import{ActiveEventStore as Te}from"../core/persistence/active-event-store.js";import{DEFAULT_CONNECTOR_RUNTIME_CONFIG as Le,applyConnectorRuntimeConfigPatch as Pe,extractConnectorRuntimeConfigPatch as He}from"./runtime-config.js";import{SendController as Me}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 Ie}from"./tool-card-utils.js";import{DeferredEventManager as De}from"./deferred-events.js";import{buildAgentProbeResult as Qe,PROBE_CACHE_TTL_STATIC_MS as Be,PROBE_CACHE_TTL_FULL_MS as Fe}from"./probe-helper.js";const Oe=600*1e3,qe=60*1e3,j=30*1e3,Ue=10*1e3,P=new Set(["claude","acp","agy","cursor","codex"]),We=new Set(["claude","codex","cursor","codewhale","opencode","pi","openhuman","agy","acp"]),B=3;class It{config;name;aibotHandle;aibotConfig;pool;stopped=!1;revokeHandler=new ve;sessionBindings=new Map;deferredMgr;sendCtrl=new Me(Le);bindingStore;globalConfigStore;upgradeTrigger=null;shareSetHandler=null;agentProfile={agentName:"",introduction:""};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;selfDrivenLabels=new Map;probeCache=new Map;sessionScanCache;isRateLimitsCacheFresh(e){if(!Number.isFinite(e))return!1;const o=Number(e);return o>0&&Date.now()-o<=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,o=this.config.providerApiKey;if((!e||!o)&&(this.config.adapterType??"acp")==="claude"){const t=se();e||(e=(t.ANTHROPIC_BASE_URL??"").trim()||void 0),o||(o=(t.ANTHROPIC_API_KEY??"").trim()||(t.ANTHROPIC_AUTH_TOKEN??"").trim()||void 0)}if((!e||!o)&&(this.config.adapterType??"acp")==="codex"){const t=ae();!e&&t.baseUrl&&(e=t.baseUrl),!o&&t.apiKey&&(o=t.apiKey)}if(!e||!o)return null;if(this.isRateLimitsCacheFresh(this.cachedProviderQuotaSampledAtMs)&&this.cachedProviderQuota)return this.cachedProviderQuota;try{const t=await K(e,o);return this.cachedProviderQuota=t,this.cachedProviderQuotaSampledAtMs=Date.now(),u.info(this.name,`[provider-quota] queried: provider=${t.provider} success=${t.success}`+(t.tiers.length>0?` tiers=${t.tiers.map(n=>`${n.name}=${n.usedPercent}%`).join(",")}`:"")+(t.balance?` balance=${t.balance.remaining} ${t.balance.unit}`:"")+(t.error?` error=${t.error}`:"")),t}catch(t){return u.warn(this.name,`[provider-quota] query failed: ${t instanceof Error?t.message:String(t)}`),null}}startKiroQuotaTimer(){this.kiroQuotaTimer||(this.kiroQuotaTimer=setInterval(()=>{if(this.stopped){this.stopKiroQuotaTimer();return}this.refreshAndPushKiroQuota().catch(()=>{})},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 o=this.providerQuotaToRateLimits(e);for(const[t,n]of this.sessionBindings.entries()){if(!n)continue;const i={provider_quota:e};o&&(i.rate_limits=o);const s=this.cachedAcpContextWindow;s&&("usedPercentage"in s?i.context_window={usedPercentage:s.usedPercentage,remainingPercentage:100-s.usedPercentage}:i.context_window=s),this.aibotHandle.sendUpdateBindingCard({session_id:t,worker_status:"ready",cwd:n,meta:i})}}getFreshClaudeRateLimitState(){const e=this.cachedClaudeRateLimitState;return e&&this.isRateLimitsCacheFresh(e.sampledAt)?e:null}getFreshCodexGlobalRateLimitCache(){const e=this.cachedRateLimits,o=this.cachedCodexContextWindow,t=this.cachedCodexTokenUsage;return{sampledAt:Math.max(this.cachedRateLimitsSampledAtMs??0,this.cachedCodexUsageSampledAtMs??0)||null,rateLimits:e,contextWindow:o,tokenUsage:t,hasData:!!(e||o||t)}}getStatus(){const e=this.pool?.getStatus()??{total:0,ready:0,busy:0};return{name:this.name,alive:!this.stopped,busy:e.busy>0,exhausted:this.pool?[...this.pool.getAllSlots()].some(o=>o.respawn.exhausted):!1,adapterType:this.config.adapterType??"acp",clientType:this.config.aibot.clientType,pool:e}}async probe(e={}){const o=e.conversation?"full":"static",t=e.conversation?Fe:Be;if(!e.fresh){const a=this.probeCache.get(o);if(a&&Date.now()-a.sampledAt<t)return{...a.result,cached:!0}}const n=this.config.adapterType??"acp",i=this.createAdapter(n,"__probe__"),s=e.conversation&&n==="acp"?()=>this.runAcpConversationProbe(i,e.timeoutMs??1e4):void 0;let r;try{r=await Qe({adapter:i,agentName:this.name,clientType:this.config.aibot.clientType,adapterType:n,providerBaseUrl:this.config.providerBaseUrl??null,opts:e,launchConversationProbe:s})}finally{i.stop().catch(()=>{})}return this.probeCache.set(o,{result:r,sampledAt:r.probed_at}),r}async runAcpConversationProbe(e,o){const t=Date.now(),n="__probe__",i=`probe-${Date.now()}-${Math.random().toString(36).slice(2,8)}`,s=this.config.agent.cwd||V(),r=()=>Date.now()-t;try{await e.start()}catch(h){return{attempted:!0,ok:!1,latency_ms:r(),error:{code:"conversation_failed",message:`start failed: ${h instanceof Error?h.message:String(h)}`}}}if(!e.isAlive())return{attempted:!0,ok:!1,latency_ms:r(),error:{code:"process_not_started",message:"agent process not alive"}};if(e instanceof A)try{await e.bindSession(n,s)}catch{}let a=null;const d=new Promise(h=>{a=m=>{m===i&&h()},e.on("eventDone",a)});e.deliverInboundEvent({event_id:i,session_id:n,content:"ping",msg_id:i}),e.deliverStopEvent(i,n);let c=null;const l=await Promise.race([d.then(()=>!1),new Promise(h=>{c=setTimeout(()=>h(!0),o)})]);return c&&clearTimeout(c),a&&e.removeListener("eventDone",a),l?{attempted:!0,ok:!1,latency_ms:r(),error:{code:"conversation_timeout",message:`no eventDone within ${o}ms`}}:{attempted:!0,ok:!0,latency_ms:r()}}constructor(e,o){this.config=e,this.name=e.name;const t=e.adapterType??"acp";this.aibotConfig={...e.aibot,...t==="claude"?{localActions:e.aibot.localActions??["session_control","claude_interaction_reply","get_session_usage","get_rate_limits","set_model","set_mode","thread_compact","get_agent_global_config"]}:{}},e.eventQueue&&(this.aibotConfig.concurrency={max_concurrent:e.eventQueue.maxConcurrent,max_queued:e.eventQueue.maxQueued,queue_timeout_ms:e.eventQueue.queueTimeoutMs,cancelable_queued:e.eventQueue.cancelableQueued,cancelable_running:e.eventQueue.cancelableRunning}),this.conversationLog=e.logDir?new pe(e.logDir):null,this.packetLog=e.logDir?new fe(e.logDir):null,this.bindingStore=new ke(e.bindingsPath),this.bindingStore.load(),this.globalConfigStore=o??null,this.deferredMgr=new De(this.name),this.allowlistGate=e.allowlistPath?new xe(e.allowlistPath):null,this.activeEventStore=e.activeEventStorePath?new Te(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},o=>{const t=this.createAdapter(e,o);return t instanceof A&&t.on("acpSessionReady",n=>{this.bindingStore.setAcpSessionId(o,n),this.sessionScanCache.invalidate()}),t},(o,t)=>{this.aibotHandle.sendEventAck({event_id:o,session_id:t,received_at:Date.now()})}),this.pool.setEventStateHandler((o,t,n,i)=>{u.info(this.name,`[queue-debug] send event_state session=${t} event=${o} state=${n} queue_pos=${i?.queue_position??""} queue_total=${i?.queue_total??""}`),this.aibotHandle.sendEventState({event_id:o,session_id:t,state:n,content_preview:i?.content_preview,queue_position:i?.queue_position,queue_total:i?.queue_total,actions:i?.actions,reason:i?.reason,updated_at:Date.now()}),this.pushQueueSnapshotForSession(t),(n==="canceled"||n==="failed")&&this.aibotHandle.sendEventResult({event_id:o,status:n==="canceled"?"canceled":"failed",msg:i?.reason,updated_at:Date.now()})}),this.pool.setQueueComposingHandler((o,t,n)=>{this.aibotHandle.sendSessionActivitySet({session_id:o,kind:"composing",active:t,...t?{ttl_ms:3e4,ref_event_id:n}:{}})}),this.pool.setInternalErrorHandler(o=>{this.handleSessionInternalError(o).catch(t=>{u.error(this.name,`[recovery] handleSessionInternalError failed event=${o.eventId} session=${o.sessionId}: ${t instanceof Error?t.message:String(t)}`)})}),this.pool.setEventStartedHandler((o,t)=>{if(this.config.adapterType!=="claude")return;this.claudeWorkerStatus.set(t,"busy");const n=this.bindingStore.get(t);n?.cwd&&this.aibotHandle.sendUpdateBindingCard({session_id:t,worker_status:"busy",cwd:n.cwd,meta:this.buildClaudeToolbarMeta(t)})}),this.pool.setEventDoneHandler((o,t)=>{const n=this.config.adapterType??"acp";if(n==="claude"){this.claudeWorkerStatus.set(t,"ready");const i=this.bindingStore.get(t);i?.cwd&&this.aibotHandle.sendUpdateBindingCard({session_id:t,worker_status:"ready",cwd:i.cwd,meta:this.buildClaudeToolbarMeta(t)})}else if(n==="agy"){const i=this.bindingStore.get(t);if(i?.cwd){const s=this.buildAgyToolbarMeta(t),r=s?.available_models??[];u.info(this.name,`[agy-toolbar-diag] eventDone push binding card session=${t} model_id=${String(s?.model_id??"")} available_models=${r.length} meta_keys=${s?Object.keys(s).join(","):"<none>"}`),this.aibotHandle.sendUpdateBindingCard({session_id:t,worker_status:"ready",cwd:i.cwd,meta:s})}else u.info(this.name,`[agy-toolbar-diag] eventDone skip binding card: no binding cwd session=${t}`)}}),this.pool.setSessionActivityHandler((o,t,n)=>{const i=this.selfDrivenSessions.has(o),s=this.selfDrivenLabels.get(o);t?(this.selfDrivenSessions.add(o),n&&this.selfDrivenLabels.set(o,n),this.aibotHandle.sendSessionActivitySet({session_id:o,kind:"composing",active:!0,ttl_ms:9e4})):(this.selfDrivenSessions.delete(o),this.selfDrivenLabels.delete(o),i&&this.aibotHandle.sendSessionActivitySet({session_id:o,kind:"composing",active:!1})),(i!==t||t&&n!==void 0&&n!==s)&&this.pushQueueSnapshotForSession(o)}),this.pool.startIdleSweep(),u.info(this.name,`Ready (adapter: ${e}, poolMax: ${this.config.poolMaxSize??20})`)}async stop(){this.stopped=!0,this.pool?.stopIdleSweep(),this.stopKiroQuotaTimer();const e=this.pool?.collectActiveEventIds()??[];e.length>0&&this.activeEventStore&&await this.activeEventStore.save(e);for(const n of e)u.info(this.name,`Canceling active event on shutdown: ${n}`),this.sendEventResultWithCleanup(n,"canceled","process shutting down");e.length>0&&await new Promise(n=>setTimeout(n,100)),this.pool?.clearActiveEventsForShutdown();const o=this.deferredMgr.getAllDeferredEvents();for(const n of o)u.info(this.name,`Failing deferred event on shutdown: ${n.event_id}`),this.sendEventResultWithCleanup(n.event_id,"failed","process shutting down");const t=this.pool?.drainAllQueuedEvents()??[];for(const n of t)u.info(this.name,`Failing queued event on shutdown: ${n.event_id}`),this.sendEventResultWithCleanup(n.event_id,"failed","process shutting down");this.deferredMgr.clearAll(),await this.pool?.stop(),this.aibotHandle?.disconnect(),e.length>0&&this.activeEventStore&&await this.activeEventStore.save([]),this.eventSessionIndex.clear(),this.inflightEvents.clear(),this.restartCount.clear()}createAdapter(e,o){switch(e){case"claude":return this.createClaudeAdapter(o);case"codex":return this.createCodexAdapter(o);case"pi":return this.createPiAdapter(o);case"openhuman":return this.createOpenHumanAdapter(o);case"codewhale":return this.createCodeWhaleAdapter(o);case"cursor":return this.createCursorAdapter(o);case"opencode":return this.createOpenCodeAdapter(o);case"agy":return this.createAgyAdapter(o);default:return this.createAcpAdapter(o)}}createCursorAdapter(e){const o={...this.config.adapterOptions??{}};o.bindingStore=this.bindingStore,o.aibotSessionId=e;const t=this.bindingStore.get(e),n=t?.modelId??this.globalConfigStore?.get(this.name)?.modelId;n&&(o.model=n),t?.modeId&&(o.mode=t.modeId);const i={sendStreamChunk:(s,r,a,d,c)=>{this.sendStreamChunkByRuntimeConfig(s,r,a,d,c)},sendEventResult:(s,r,a)=>{this.sendEventResultWithCleanup(s,r,a)},sendEventAck:(s,r)=>{this.aibotHandle.sendEventAck({event_id:s,session_id:r,received_at:Date.now()})},sendRawEventEnvelope:(s,r,a)=>{this.aibotHandle.sendMsg({event_id:s,session_id:r,msg_type:1,content:"[cursor] raw_event",extra:{channel_data:{cursor:{raw_event:a}},agent_api_origin:!0}})},agentInvoke:async(s,r)=>this.platformInvoke(s,r),sendLocalActionResult:(s,r,a,d,c)=>{this.aibotHandle.sendLocalActionResult({action_id:s,status:r,...a!==void 0?{result:a}:{},...d?{error_code:d}:{},...c?{error_msg:c}:{}})}};return new M({command:this.config.agent.command,args:this.config.agent.args,env:this.config.agent.env,options:o},i)}createClaudeAdapter(e){const o={sendReply:(i,s,r,a,d)=>{this.sendReplyByRuntimeConfig(i,s,r,a,d)},sendStreamChunk:(i,s,r,a,d,c,l)=>{this.sendStreamChunkByRuntimeConfig(i,s,r,a,d,c,l)},sendMedia:(i,s,r,a,d,c,l)=>{this.aibotHandle.sendMedia({event_id:i,session_id:s,content:r,msg_type:2,quoted_message_id:d||void 0,client_msg_id:c||void 0,extra:l?{media_caption:a,...l}:{media_caption:a}})},sendEventResult:(i,s,r,a)=>{this.sendEventResultWithCleanup(i,s,r,a)},sendEventAck:(i,s)=>{this.aibotHandle.sendEventAck({event_id:i,session_id:s,received_at:Date.now()})},agentInvoke:async(i,s,r)=>this.platformInvoke(i,s,r),sendLocalActionResult:(i,s,r,a,d)=>{this.aibotHandle.sendLocalActionResult({action_id:i,status:s,...r!==void 0?{result:r}:{},...a?{error_code:a}:{},...d?{error_msg:d}:{}})},sendToolUse:(i,s,r,a)=>{this.sendToolExecutionCard(i,s,E(r,a))},sendToolResult:(i,s,r,a)=>{this.sendToolExecutionCard(i,s,$(r,a))},getWsUrl:()=>this.config.aibot.url,getAgentId:()=>this.config.aibot.agentId,getAgentProfile:()=>this.agentProfile,getApiKey:()=>this.config.aibot.apiKey,getActiveEventCount:()=>0,getPendingPermissionCount:()=>0,getPendingElicitationCount:()=>0,sendAgentQuestionCard:(i,s,r)=>{const a=r.questions.map(c=>c.header).join(", "),d=Ie(`[Agent Question] ${r.request_id}`,"agent_question",r);this.aibotHandle.sendText({event_id:i,session_id:s,content:d,msg_type:1,extra:{card_type:"agent_question",summary_text:a}})},sendPermissionCard:i=>{this.aibotHandle.sendMsg({event_id:i.eventId,session_id:i.sessionId,client_msg_id:`perm_${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 s=this.bindingStore.get(e);s?.cwd&&this.aibotHandle.sendUpdateBindingCard({session_id:e,worker_status:this.claudeWorkerStatus.get(e)??"ready",cwd:s.cwd,meta:this.buildClaudeToolbarMeta(e)})},sendMcpFrame:i=>{this.aibotHandle.sendMcpFrame(e,i)}},t=this.config.adapterOptions??{},n={...t,sessionRuntimeResolver:()=>{const i=this.bindingStore.get(e);return{cwd:i?.cwd,modeId:i?.modeId??C.fullAuto,modelId:i?.modelId??this.globalConfigStore?.get(this.name)?.modelId,pluginDir:t.pluginDir,claudeSessionId:i?.claudeSessionId,onSessionIdAssigned:s=>{this.bindingStore.setClaudeSessionId(e,s),this.sessionScanCache.invalidate()}}}};return new y({command:this.config.agent.command,args:this.config.agent.args,env:this.config.agent.env,options:n},o)}createCodexAdapter(e){let o=null;const t={sendEventResult:(s,r,a)=>{this.sendEventResultWithCleanup(s,r,a)},sendEventAck:(s,r)=>this.aibotHandle.sendEventAck({event_id:s,session_id:r,received_at:Date.now()}),sendCodexEvent:s=>{this.shouldDropCodexDisplayEvent(s.event_id,s.codex_method)||(this.aibotHandle.sendCodexEvent(s),this.logCodexEventToConversation(s))},sendCodexEventReliable:async s=>{if(!this.shouldDropCodexDisplayEvent(s.event_id,s.codex_method)){try{await this.aibotHandle.sendCodexEventReliable(s)}catch(r){u.warn("bridge",`[codex] sendCodexEventReliable ACK failed event=${s.event_id}: ${r}`)}u.info("bridge",`[codex] sendCodexEventReliable done event=${s.event_id} method=${s.codex_method}`),this.logCodexEventToConversation(s)}},sendRunError:(s,r,a)=>{this.sendStreamChunkByRuntimeConfig(s,r,`
2
2
 
3
3
  Error: ${a}`,1,!1)},sendUpdateBindingCard:(s,r,a,d)=>{const c={...d??{}};if(!c.rate_limits&&this.cachedProviderQuota?.success){this.isRateLimitsCacheFresh(this.cachedProviderQuotaSampledAtMs)||this.maybeQueryProviderQuota().catch(()=>{});const l=this.providerQuotaToCodexRateLimits(this.cachedProviderQuota);l&&(c.rate_limits=l.rateLimits,c.rate_limit_primary_percent=l.primaryPercent,c.rate_limit_secondary_percent=l.secondaryPercent,c.rate_limit_primary_window_min=l.primaryWindowMin,c.rate_limit_secondary_window_min=l.secondaryWindowMin)}!c.provider_quota&&this.cachedProviderQuota?.success&&(c.provider_quota=this.cachedProviderQuota),this.aibotHandle.sendUpdateBindingCard({session_id:s,worker_status:r,cwd:a,...Object.keys(c).length>0?{meta:c}:{}})},agentInvoke:async(s,r)=>this.platformInvoke(s,r),sendLocalActionResult:(s,r,a,d,c)=>this.aibotHandle.sendLocalActionResult({action_id:s,status:r,...a!==void 0?{result:a}:{},...d?{error_code:d}:{},...c?{error_msg:c}:{}}),sendSessionActivitySet:(s,r,a,d)=>{this.aibotHandle.sendSessionActivitySet({session_id:s,kind:r,active:a,...d??{}})},getConversationLog:()=>this.conversationLog,getAgentProfile:()=>this.agentProfile,onRateLimitsUpdated:s=>{this.cachedRateLimits=s,this.cachedRateLimitsSampledAtMs=Date.now(),this.isRateLimitsCacheFresh(this.cachedProviderQuotaSampledAtMs)||this.maybeQueryProviderQuota().catch(()=>{});const r=this.bindingStore.get(e);if(r?.cwd){const a=this.cachedRateLimitsSampledAtMs,d={rate_limits:{primary:s.primary,secondary:s.secondary,sampledAt:a},rate_limit_primary_percent:s.primary.usedPercent,rate_limit_secondary_percent:s.secondary.usedPercent,rate_limit_primary_window_min:s.primary.windowMinutes,rate_limit_secondary_window_min:s.secondary.windowMinutes,...o?.getEffortMeta()};this.cachedProviderQuota?.success&&(d.provider_quota=this.cachedProviderQuota),this.aibotHandle.sendUpdateBindingCard({session_id:e,worker_status:"ready",cwd:r.cwd,meta:d})}},onContextWindowUpdated:s=>{if(!s)return;this.cachedCodexContextWindow=s,this.cachedCodexUsageSampledAtMs=Date.now();const r=this.bindingStore.get(e);r?.cwd&&this.aibotHandle.sendUpdateBindingCard({session_id:e,worker_status:"ready",cwd:r.cwd,meta:{context_window:s,...o?.getEffortMeta()}})},onTokenUsageUpdated:s=>{s&&(this.cachedCodexTokenUsage=s,this.cachedCodexUsageSampledAtMs=Date.now())}},n=this.config.adapterOptions??{},i=this.globalConfigStore?.get(this.name);return o=new Y({command:this.config.agent.command,args:this.config.agent.args,env:this.config.agent.env,options:{...n,model:this.bindingStore.getCodexModelId(e)??n.model,collaborationMode:this.bindingStore.getCodexModeId(e)??n.collaborationMode,reasoningEffort:i?.codexReasoningEffort??n.reasoningEffort,sandboxMode:i?.codexSandboxMode??n.sandboxMode,aibotSessionId:e,bindingStore:this.bindingStore}},t),o}createCodeWhaleAdapter(e){const o={sendEventResult:(n,i,s)=>{this.sendEventResultWithCleanup(n,i,s)},sendEventAck:(n,i)=>this.aibotHandle.sendEventAck({event_id:n,session_id:i,received_at:Date.now()}),sendStreamChunk:(n,i,s,r,a,d)=>{this.sendStreamChunkByRuntimeConfig(n,i,s,r,a,d)},sendUpdateBindingCard:(n,i,s,r)=>this.aibotHandle.sendUpdateBindingCard({session_id:n,worker_status:i,cwd:s,...r?{meta:r}:{}}),sendLocalActionResult:(n,i,s,r,a)=>this.aibotHandle.sendLocalActionResult({action_id:n,status:i,...s!==void 0?{result:s}:{},...r?{error_code:r}:{},...a?{error_msg:a}:{}}),sendSessionActivitySet:(n,i,s,r)=>{this.aibotHandle.sendSessionActivitySet({session_id:n,kind:i,active:s,...r??{}})},sendToolUse:(n,i,s,r)=>{this.sendToolExecutionCard(n,i,E(s,r))},sendToolResult:(n,i,s,r)=>{this.sendToolExecutionCard(n,i,$(s,r))},agentInvoke:async(n,i)=>this.platformInvoke(n,i),getConversationLog:()=>this.conversationLog,getAgentProfile:()=>this.agentProfile},t=this.config.adapterOptions??{};return new O({command:this.config.agent.command,args:this.config.agent.args,env:this.config.agent.env,options:{...t,aibotSessionId:e,bindingStore:this.bindingStore}},o)}createPiAdapter(e){const o={sendEventResult:(n,i,s)=>{this.sendEventResultWithCleanup(n,i,s),u.info("bridge",`[pi] sendEventResult event=${n} status=${i}`)},sendEventAck:(n,i)=>this.aibotHandle.sendEventAck({event_id:n,session_id:i,received_at:Date.now()}),sendUpdateBindingCard:(n,i,s,r)=>this.aibotHandle.sendUpdateBindingCard({session_id:n,worker_status:i,cwd:s,...r?{meta:r}:{}}),agentInvoke:async(n,i)=>this.platformInvoke(n,i),sendLocalActionResult:(n,i,s,r,a)=>{this.aibotHandle.sendLocalActionResult({action_id:n,status:i,...s!==void 0?{result:s}:{},...r?{error_code:r}:{},...a?{error_msg:a}:{}})},sendSessionActivitySet:(n,i,s,r)=>{this.aibotHandle.sendSessionActivitySet({session_id:n,kind:i,active:s,...r??{}})},sendToolUse:(n,i,s,r)=>{this.sendToolExecutionCard(n,i,E(s,r))},sendToolResult:(n,i,s,r)=>{this.sendToolExecutionCard(n,i,$(s,r))},sendStreamChunk:(n,i,s,r,a,d)=>{this.sendStreamChunkByRuntimeConfig(n,i,s,r,a,d),a&&u.info("bridge",`[pi] sendFinalStreamChunk event=${n} seq=${r}`)},sendFinalStreamChunkReliable:async(n,i,s,r)=>{if(this.finalizeThinking(n,i),this.resolveEventRuntimeConfig(n).responseDelivery==="single_message"&&n){this.bufferStreamChunk(n,i,"",!0);return}try{await this.aibotHandle.sendStreamChunkRequest({event_id:n,session_id:i,delta_content:"",chunk_seq:s,is_finish:!0,...r?{client_msg_id:r}:{}})}catch(d){u.warn("bridge",`[pi] sendFinalStreamChunkReliable ACK failed event=${n}: ${d}`)}u.info("bridge",`[pi] sendFinalStreamChunkReliable done event=${n} seq=${s}`)},sendThinking:(n,i,s)=>{this.sendThinkingByRuntimeConfig(n,i,s)},sendRunError:(n,i,s,r,a)=>{this.sendStreamChunkByRuntimeConfig(n,i,`
4
4
 
@@ -8,7 +8,7 @@ Error: ${s}`,0,!1)},sendUpdateBindingCard:(n,i,s)=>this.aibotHandle.sendUpdateBi
8
8
 
9
9
  Error: ${s}`,0,!1)},sendUpdateBindingCard:(n,i,s)=>this.aibotHandle.sendUpdateBindingCard({session_id:n,worker_status:i,cwd:s}),agentInvoke:async(n,i)=>this.platformInvoke(n,i),sendLocalActionResult:(n,i,s,r,a)=>{this.aibotHandle.sendLocalActionResult({action_id:n,status:i,...s!==void 0?{result:s}:{},...r?{error_code:r}:{},...a?{error_msg:a}:{}})},getAgentProfile:()=>this.agentProfile},t=this.config.adapterOptions??{};return new ee({command:this.config.agent.command,args:this.config.agent.args,env:this.config.agent.env,options:t},o,{port:t.port,hostname:t.hostname,model:t.model,agent:t.agent,permissionPolicy:t.permission_policy,enableSessionBinding:!0,aibotSessionId:e,bindingStore:this.bindingStore})}createAgyAdapter(e){const o={sendStreamChunk:(n,i,s,r,a)=>{this.sendStreamChunkByRuntimeConfig(n,i,s,r,a)},sendEventResult:(n,i,s)=>{this.sendEventResultWithCleanup(n,i,s)},sendEventAck:(n,i)=>{this.aibotHandle.sendEventAck({event_id:n,session_id:i,received_at:Date.now()})},agentInvoke:async(n,i,s)=>this.platformInvoke(n,i,s),forceCompleteInternalEvent:(n,i)=>{this.pool.eventComplete(n,i),this.pushQueueSnapshotForSession(i)},persistConversationId:(n,i)=>{this.bindingStore.setAgyConversationId(n,i)},getAgentProfile:()=>this.agentProfile},t=n=>{const i=this.bindingStore.get(n);return{cwd:i?.cwd,modelId:i?.modelId??this.globalConfigStore?.get(this.name)?.modelId,conversationId:i?.agyConversationId}};return new q({command:this.config.agent.command,args:this.config.agent.args,env:this.config.agent.env,options:this.config.adapterOptions??{}},o,t)}createAcpAdapter(e){const o=this.isAcpRawTransportEnabled(),t={sendStreamChunk:(a,d,c,l,h,m,v)=>{this.finalizeThinking(a,d),this.sendStreamChunkByRuntimeConfig(a,d,c,l,h,m,v)},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(o){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,`
10
10
 
11
- Error: ${c}`,1,!1)},sendPermissionCard:a=>{if(o){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)},getAgentProfile:()=>this.agentProfile},n=e?this.bindingStore.get(e):void 0,i=this.globalConfigStore?.get(this.name),{initialModel:s,initialMode:r}=Ae({sessionBinding:n,globalDefaults:i,configInitialMode:this.config.acpInitialMode});return(s||r)&&u.info(this.name,`[toolbar] hydrate from binding: session=${e} model=${s??"<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:s,acpMcpTools:this.config.acpMcpTools,rawTransport:o,eventResultsPath:this.config.eventResultsPath,approvalMode:this.config.approvalMode,bindingStore:this.config.enableSessionBinding?this.bindingStore:void 0,aibotSessionId:e,autoInjectArgs:this.config.autoInjectArgs,bridgeLog:this.config.logDir?new _e(this.config.logDir,`${this.name}-${e}`):null})}async connectAibot(){const e=new J;this.aibotHandle=await e.connect(this.aibotConfig,{aborted:()=>this.stopped,label:this.name,packetLog:this.packetLog,maxRetries:this.config.connectMaxRetries});const o=this.aibotHandle.authAck;if(this.applyAgentProfile(o?.agent_name,o?.introduction,{source:"auth_ack",respawnOnChange:!1}),this.aibotHandle.onEvent(t=>{this.handleAibotEvent(t).catch(n=>{u.error(this.name,`handleAibotEvent failed: ${n}`),this.aibotHandle.sendEventAck({event_id:t.event_id,session_id:t.session_id,received_at:Date.now()});const i=n instanceof Error?n.message:String(n);if(/CWD must be|Bound directory does not exist|Bound path is not a directory/i.test(i)&&t.session_id){this.bindingStore.delete(t.session_id),this.sessionBindings.delete(t.session_id);const r=this.pool.getSlot(t.session_id);r?.adapter instanceof A&&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:g.invalidCwd}}}},quoted_message_id:t.msg_id});return}this.aibotHandle.sendEventResult({event_id:t.event_id,status:"failed",msg:i,updated_at:Date.now()})})}),this.aibotHandle.onStop(t=>{try{this.handleAibotStop(t)}catch(n){u.error(this.name,`handleAibotStop failed: ${n}`)}}),this.aibotHandle.onShareSet(t=>{try{this.shareSetHandler?.(Array.isArray(t?.shared_to)?t.shared_to:[])}catch(n){u.error(this.name,`onShareSet failed: ${n}`)}}),this.aibotHandle.onProfilePush(t=>{try{this.applyAgentProfile(t?.agent_name,t?.introduction,{source:"profile_push",respawnOnChange:!0})}catch(n){u.error(this.name,`onProfilePush failed: ${n}`)}}),this.aibotHandle.onRevoke(t=>{try{this.handleAibotRevoke(t)}catch(n){u.error(this.name,`handleAibotRevoke failed: ${n}`)}}),this.aibotHandle.onLocalAction(t=>{this.handleAibotLocalAction(t).catch(n=>{u.error(this.name,`handleAibotLocalAction failed: ${n}`)})}),this.aibotHandle.onEventCancel(t=>{u.info(this.name,`recv event_cancel event_id=${t.event_id} session_id=${t.session_id}`),this.handleEventCancel(t).catch(n=>{u.error(this.name,`handleEventCancel failed: ${n}`)})}),this.aibotHandle.onMcpFrame((t,n)=>{const i=this.pool.getSlot(t)?.adapter;i?.deliverMcpFrameToAgent?i.deliverMcpFrameToAgent(n):u.warn(this.name,`mcp_frame: no adapter for session=${t}`)}),this.aibotHandle.onQueueClear(t=>{const n=this.pool.clearQueue(t.session_id);this.aibotHandle.sendQueueClearResult({session_id:t.session_id,canceled_event_ids:n}),this.pushQueueSnapshotForSession(t.session_id)}),this.aibotHandle.onQueueSnapshotQuery(t=>{this.replyQueueSnapshotForSession(t.session_id)}),u.info(this.name,"Connected to aibot"),this.activeEventStore){const t=await this.activeEventStore.drain();if(t.length>0){u.warn(this.name,`Recovering ${t.length} stale event(s) from previous run`);for(const n of t)u.info(this.name,`Failing stale event on startup: ${n}`),this.aibotHandle.sendEventResult({event_id:n,status:"failed",msg:"process restarted, event lost",updated_at:Date.now()})}}this.pushQueueSnapshots(),this.aibotHandle.onReconnected(()=>{this.pushQueueSnapshots();const t=this.aibotHandle.authAck;this.applyAgentProfile(t?.agent_name,t?.introduction,{source:"reconnect",respawnOnChange:!0})}),this.aibotHandle.onStreamRejected((t,n)=>{this.sendCtrl.markEventRejected(t)})}applyAgentProfile(e,o,t){const n=String(e??"").trim(),i=String(o??"").trim(),s=n!==this.agentProfile.agentName||i!==this.agentProfile.introduction;this.agentProfile={agentName:n,introduction:i},n||i?u.info(this.name,`agent profile (${t.source}): GOT name="${n}" intro_len=${i.length} changed=${s}`):u.warn(this.name,`agent profile (${t.source}): EMPTY \u2014 \u670D\u52A1\u7AEF\u672A\u4E0B\u53D1 agent_name/introduction\uFF08auth_ack \u5B57\u6BB5\u7F3A\u5931\u6216\u503C\u4E3A\u7A7A\uFF09`),s&&t.respawnOnChange&&this.applyProfileChangeToAdapters("agent_profile_changed")}applyProfileChangeToAdapters(e){if(!this.pool)return;const o=this.pool.getAllSlots();let t=0;for(const r of o)if(r.adapter.onAgentProfileChanged)try{r.adapter.onAgentProfileChanged(),t++}catch(a){u.warn(this.name,`onAgentProfileChanged failed for session=${r.sessionId}: ${a}`)}const n=o.filter(r=>r.adapter instanceof y),i=n.filter(r=>r.state==="ready"&&!r.adapter.getStatus().busy),s=n.length-i.length;u.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=>{u.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 o=this.pool?.getQueueSnapshot(e)??null,t=o?[...o.running]:[],n=o?o.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=o?o.queued.map(s=>({event_id:s.event_id,position:s.position,...s.content_preview?{content_preview:s.content_preview}:{},...s.title?{title:s.title}:{},...s.summary?{summary:s.summary}:{},actions:[{type:"cancel"}]})):[];if(t.length===0&&this.selfDrivenSessions.has(e)&&this.pool?.getSlot(e)){const s=`selfdrive_${e}`,r="Background task in progress";t.push(s),n.push({event_id:s,content_preview:r,title:r,summary:r,actions:[]})}return{session_id:e,running:t,running_items:n,queued:i}}pushQueueSnapshotForSession(e){if(!this.config.eventQueue||!this.pool)return;const o=this.buildQueueSnapshotPayload(e);u.info(this.name,`[queue-debug] push snapshot session=${e} running=${o.running.length} queued=${o.queued.length} running_ids=[${o.running.join(",")}]`),this.aibotHandle.sendQueueSnapshot(o)}replyQueueSnapshotForSession(e){!this.config.eventQueue||!this.pool||this.aibotHandle.sendQueueSnapshot(this.buildQueueSnapshotPayload(e))}async platformInvoke(e,o,t){return e==="file_link"?$e(o):this.aibotHandle.agentInvoke(e,o,t)}sendReplyByRuntimeConfig(e,o,t,n,i){this.indexEventSession(e,o),this.sendCtrl.sendReply(e,o,t,n,i),t&&this.conversationLog?.logOutbound?.(o,e,"reply",t)}sendStreamChunkByRuntimeConfig(e,o,t,n,i,s,r){this.indexEventSession(e,o),this.sendCtrl.sendStreamChunk(e,o,t,n,i,s,r),(t||i)&&this.conversationLog?.logOutbound?.(o,e,i?"stream_chunk_finish":"stream_chunk",t)}sendRunErrorAsChunk(e,o,t){this.sendStreamChunkByRuntimeConfig(e,o,`
11
+ Error: ${c}`,1,!1)},sendPermissionCard:a=>{if(o){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)},getAgentProfile:()=>this.agentProfile},n=e?this.bindingStore.get(e):void 0,i=this.globalConfigStore?.get(this.name),{initialModel:s,initialMode:r}=Ae({sessionBinding:n,globalDefaults:i,configInitialMode:this.config.acpInitialMode});return(s||r)&&u.info(this.name,`[toolbar] hydrate from binding: session=${e} model=${s??"<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:s,acpMcpTools:this.config.acpMcpTools,rawTransport:o,eventResultsPath:this.config.eventResultsPath,approvalMode:this.config.approvalMode,bindingStore:this.config.enableSessionBinding?this.bindingStore:void 0,aibotSessionId:e,autoInjectArgs:this.config.autoInjectArgs,bridgeLog:this.config.logDir?new _e(this.config.logDir,`${this.name}-${e}`):null})}async connectAibot(){const e=new J;this.aibotHandle=await e.connect(this.aibotConfig,{aborted:()=>this.stopped,label:this.name,packetLog:this.packetLog,maxRetries:this.config.connectMaxRetries});const o=this.aibotHandle.authAck;if(this.applyAgentProfile(o?.agent_name,o?.introduction,{source:"auth_ack",respawnOnChange:!1}),this.aibotHandle.onEvent(t=>{this.handleAibotEvent(t).catch(n=>{u.error(this.name,`handleAibotEvent failed: ${n}`),this.aibotHandle.sendEventAck({event_id:t.event_id,session_id:t.session_id,received_at:Date.now()});const i=n instanceof Error?n.message:String(n);if(/CWD must be|Bound directory does not exist|Bound path is not a directory/i.test(i)&&t.session_id){this.bindingStore.delete(t.session_id),this.sessionBindings.delete(t.session_id);const r=this.pool.getSlot(t.session_id);r?.adapter instanceof A&&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:g.invalidCwd}}}},quoted_message_id:t.msg_id});return}this.aibotHandle.sendEventResult({event_id:t.event_id,status:"failed",msg:i,updated_at:Date.now()})})}),this.aibotHandle.onStop(t=>{try{this.handleAibotStop(t)}catch(n){u.error(this.name,`handleAibotStop failed: ${n}`)}}),this.aibotHandle.onShareSet(t=>{try{this.shareSetHandler?.(Array.isArray(t?.shared_to)?t.shared_to:[])}catch(n){u.error(this.name,`onShareSet failed: ${n}`)}}),this.aibotHandle.onProfilePush(t=>{try{this.applyAgentProfile(t?.agent_name,t?.introduction,{source:"profile_push",respawnOnChange:!0})}catch(n){u.error(this.name,`onProfilePush failed: ${n}`)}}),this.aibotHandle.onRevoke(t=>{try{this.handleAibotRevoke(t)}catch(n){u.error(this.name,`handleAibotRevoke failed: ${n}`)}}),this.aibotHandle.onLocalAction(t=>{this.handleAibotLocalAction(t).catch(n=>{u.error(this.name,`handleAibotLocalAction failed: ${n}`)})}),this.aibotHandle.onEventCancel(t=>{u.info(this.name,`recv event_cancel event_id=${t.event_id} session_id=${t.session_id}`),this.handleEventCancel(t).catch(n=>{u.error(this.name,`handleEventCancel failed: ${n}`)})}),this.aibotHandle.onMcpFrame((t,n)=>{const i=this.pool.getSlot(t)?.adapter;i?.deliverMcpFrameToAgent?i.deliverMcpFrameToAgent(n):u.warn(this.name,`mcp_frame: no adapter for session=${t}`)}),this.aibotHandle.onQueueClear(t=>{const n=this.pool.clearQueue(t.session_id);this.aibotHandle.sendQueueClearResult({session_id:t.session_id,canceled_event_ids:n}),this.pushQueueSnapshotForSession(t.session_id)}),this.aibotHandle.onQueueSnapshotQuery(t=>{this.replyQueueSnapshotForSession(t.session_id)}),u.info(this.name,"Connected to aibot"),this.activeEventStore){const t=await this.activeEventStore.drain();if(t.length>0){u.warn(this.name,`Recovering ${t.length} stale event(s) from previous run`);for(const n of t)u.info(this.name,`Failing stale event on startup: ${n}`),this.aibotHandle.sendEventResult({event_id:n,status:"failed",msg:"process restarted, event lost",updated_at:Date.now()})}}this.pushQueueSnapshots(),this.aibotHandle.onReconnected(()=>{this.pushQueueSnapshots();const t=this.aibotHandle.authAck;this.applyAgentProfile(t?.agent_name,t?.introduction,{source:"reconnect",respawnOnChange:!0})}),this.aibotHandle.onStreamRejected((t,n)=>{this.sendCtrl.markEventRejected(t)})}applyAgentProfile(e,o,t){const n=String(e??"").trim(),i=String(o??"").trim(),s=n!==this.agentProfile.agentName||i!==this.agentProfile.introduction;this.agentProfile={agentName:n,introduction:i},n||i?u.info(this.name,`agent profile (${t.source}): GOT name="${n}" intro_len=${i.length} changed=${s}`):u.warn(this.name,`agent profile (${t.source}): EMPTY \u2014 \u670D\u52A1\u7AEF\u672A\u4E0B\u53D1 agent_name/introduction\uFF08auth_ack \u5B57\u6BB5\u7F3A\u5931\u6216\u503C\u4E3A\u7A7A\uFF09`),s&&t.respawnOnChange&&this.applyProfileChangeToAdapters("agent_profile_changed")}applyProfileChangeToAdapters(e){if(!this.pool)return;const o=this.pool.getAllSlots();let t=0;for(const r of o)if(r.adapter.onAgentProfileChanged)try{r.adapter.onAgentProfileChanged(),t++}catch(a){u.warn(this.name,`onAgentProfileChanged failed for session=${r.sessionId}: ${a}`)}const n=o.filter(r=>r.adapter instanceof y),i=n.filter(r=>r.state==="ready"&&!r.adapter.getStatus().busy),s=n.length-i.length;u.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=>{u.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 o=this.pool?.getQueueSnapshot(e)??null,t=o?[...o.running]:[],n=o?o.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=o?o.queued.map(s=>({event_id:s.event_id,position:s.position,...s.content_preview?{content_preview:s.content_preview}:{},...s.title?{title:s.title}:{},...s.summary?{summary:s.summary}:{},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),n.push({event_id:s,content_preview:r,title:r,summary:r,actions:[]})}return{session_id:e,running:t,running_items:n,queued:i}}pushQueueSnapshotForSession(e){if(!this.config.eventQueue||!this.pool)return;const o=this.buildQueueSnapshotPayload(e);u.info(this.name,`[queue-debug] push snapshot session=${e} running=${o.running.length} queued=${o.queued.length} running_ids=[${o.running.join(",")}]`),this.aibotHandle.sendQueueSnapshot(o)}replyQueueSnapshotForSession(e){!this.config.eventQueue||!this.pool||this.aibotHandle.sendQueueSnapshot(this.buildQueueSnapshotPayload(e))}async platformInvoke(e,o,t){return e==="file_link"?$e(o):this.aibotHandle.agentInvoke(e,o,t)}sendReplyByRuntimeConfig(e,o,t,n,i){this.indexEventSession(e,o),this.sendCtrl.sendReply(e,o,t,n,i),t&&this.conversationLog?.logOutbound?.(o,e,"reply",t)}sendStreamChunkByRuntimeConfig(e,o,t,n,i,s,r){this.indexEventSession(e,o),this.sendCtrl.sendStreamChunk(e,o,t,n,i,s,r),(t||i)&&this.conversationLog?.logOutbound?.(o,e,i?"stream_chunk_finish":"stream_chunk",t)}sendRunErrorAsChunk(e,o,t){this.sendStreamChunkByRuntimeConfig(e,o,`
12
12
 
13
13
  Error: ${t}`,1,!1)}sendEventResultWithCleanup(e,o,t,n){this.sendCtrl.sendEventResult(e,o,t,n);const i=this.eventSessionIndex.get(e);i&&(this.pool.eventComplete(e,i),this.pushQueueSnapshotForSession(i),this.conversationLog?.logResult?.(i,e,o,t),this.eventSessionIndex.delete(e)),this.inflightEvents.delete(e),this.restartCount.delete(e),o==="responded"&&(this.cachedProviderQuotaSampledAtMs=null,this.maybeQueryProviderQuota().catch(()=>{}))}async handleSessionInternalError(e){const{eventId:o,sessionId:t,errorMsg:n}=e;if(this.stopped)return;const i=this.inflightEvents.get(o);if(!i){u.warn(this.name,`[recovery] no inflight event for internalError event=${o} session=${t}; surface failure directly`),this.sendRunErrorAsChunk(o,t,n),this.sendEventResultWithCleanup(o,"failed",n,"agent_stop_failure");return}const s=(this.restartCount.get(o)??0)+1;this.restartCount.set(o,s);const r=this.config.adapterType??"acp";if(s>B){u.error(this.name,`[recovery] adapter=${r} session=${t} event=${o} restart=${s}/${B} outcome=give-up err=${n}`),this.sendRunErrorAsChunk(o,t,n),this.sendEventResultWithCleanup(o,"failed",n,"agent_stop_failure");return}u.info(this.name,`[recovery] adapter=${r} session=${t} event=${o} restart=${s}/${B} outcome=restarting err=${n}`);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=${o} session=${t}: ${l instanceof Error?l.message:String(l)}`),this.sendEventResultWithCleanup(o,"failed",l instanceof Error?l.message:String(l));return}for(const l of 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,o){return e==="acp"?"continue":o.content}sendThinkingByRuntimeConfig(e,o,t){this.sendCtrl.sendThinking(e,o,t)}bufferStreamChunk(e,o,t,n,i){this.sendCtrl.bufferOnly(e,o,t,n,i)}flushBufferedStreamText(e){}resolveEventRuntimeConfig(e){return this.sendCtrl.resolveEventRuntimeConfig(e)}captureEventRuntimeConfig(e){this.sendCtrl.captureEventRuntimeConfig(e),this.indexEventSession(e.event_id,e.session_id),e.event_id&&this.inflightEvents.set(e.event_id,e)}indexEventSession(e,o){!e||!o||this.eventSessionIndex.set(e,o)}shouldDropToolDisplayEvent(e){return this.sendCtrl.shouldDropToolDisplayEvent(e)}shouldDropThinkingDisplayEvent(e){return this.sendCtrl.shouldDropThinkingDisplayEvent(e)}shouldDropCodexDisplayEvent(e,o){return this.sendCtrl.shouldDropCodexDisplayEvent(e,o)}logCodexEventToConversation(e){if(!this.conversationLog||e.codex_method!=="item/agentMessage/delta")return;const t=e.codex_payload?.params?.delta;if(!t)return;const n=this.eventSessionIndex.get(e.event_id)??e.session_id;this.conversationLog.append(n,{ts:Date.now(),dir:"outbound",event_id:e.event_id,kind:"codex_delta",text_len:t.length,content:t})}isAcpRawTransportEnabled(){return(this.config.adapterOptions??{}).raw_transport===!0}shouldDropAcpRawDisplayEvent(e,o){return this.sendCtrl.shouldDropAcpRawDisplayEvent(e,o)}sendAcpRawEventEnvelope(e,o,t){this.shouldDropAcpRawDisplayEvent(e,t.type)||this.aibotHandle.sendMsg({event_id:e,session_id:o,msg_type:1,content:this.buildAcpRawEventFallbackText(t),extra:{channel_data:{acp:{raw_event:t}},agent_api_origin:!0}})}buildAcpRawEventFallbackText(e){const o=String(e.type??"").trim();if(!o)return"[acp] event";switch(o){case"permission_request":return`Permission required: ${String(e.payload?.tool_title??e.payload?.tool_name??"permission request")}`;case"tool_use":return`[tool] ${String(e.payload?.tool_name??"tool")}`;case"tool_result":return"[tool result]";case"thinking":return"[thinking]";case"error":return`[error] ${String(e.payload?.message??"agent error")}`;case"result":return"[result]";default:return`[acp] ${o}`}}sendToolExecutionCard(e,o,t,n){this.sendCtrl.sendToolExecutionCard(e,o,t,n)}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 o=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=He(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 n=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(o==="claude"){await this.handleSessionControlCommand(i,e);return}if(o==="codex"&&i.verb===p.open){await this.handleCodexSessionControlOpen(i,e);return}if(o==="pi"&&i.verb===p.open){await this.handlePiSessionControlOpen(i,e);return}if(o==="pi"&&i.verb===p.restart){await this.handlePiSessionControlRestart(e);return}if((o==="openhuman"||o==="opencode")&&i.verb===p.open){await this.handleOpenHumanSessionControlOpen(i,e);return}if(o==="codewhale"&&i.verb===p.open){await this.handleCodeWhaleSessionControlOpen(i,e);return}if(this.aibotHandle.sendEventAck({event_id:e.event_id,session_id:e.session_id,received_at:Date.now()}),i.verb===p.open){const 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=T.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: ${T.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),o==="acp"&&i.verb===p.stop){const d=this.bindingStore.get(e.session_id)?.cwd??"";await this.pool.removeSlot(e.session_id).catch(()=>{}),this.sessionBindings.delete(e.session_id),this.aibotHandle.sendEventResult({event_id:e.event_id,status:"responded",msg:`Session worker stopped for ${d}`,updated_at:Date.now()});return}if(U(i,e,this.sessionControlCtx(e.session_id),{...this.sessionControlSenders(),sendEventAck:()=>{}}),i.verb===p.open&&(await this.deferredMgr.release(e.session_id,this.deferredCallbacks()),o==="agy")){const 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(We.has(o)&&!this.bindingStore.get(e.session_id)?.cwd){const d=o;u.info(this.name,`[${d}] binding missing session_id=${e.session_id} event_id=${e.event_id}`),this.deferredMgr.defer(d,String(e.session_id??"").trim(),this.buildInboundEvent(e,n));const c=this.resolveBindingChannelKey(o);this.aibotHandle.sendEventAck({event_id:e.event_id,session_id:e.session_id,received_at:Date.now()}),this.aibotHandle.sendMsg({event_id:e.event_id,session_id:e.session_id,msg_type:1,content:"Session binding missing.",extra:{channel_data:{[c]:{sessionBinding:{status:"missing",reason:"binding_missing",error_code:g.bindingMissing}}}},quoted_message_id:e.msg_id});return}if((this.config.adapterType??"acp")==="acp"){const d=String(e.content??"").trim().match(/^\/(\S+)\s*(.*)/);if(d){const[,c,l]=d,m=this.pool.getSlot(e.session_id)?.adapter;if(m?.execCommand&&(m.getSupportedCommands?.()??[]).some(_=>_.name===c||_.name===`/${c}`)){this.aibotHandle.sendEventAck({event_id:e.event_id,session_id:e.session_id,received_at:Date.now()});try{const _=await m.execCommand(c,l.trim(),e.session_id);_.status==="options"&&_.data&&this.handleExecCommandOptions(e.session_id,c,_.data),this.aibotHandle.sendEventResult({event_id:e.event_id,status:_.status==="failed"?"failed":"responded",msg:_.message,updated_at:Date.now()})}catch(_){this.aibotHandle.sendEventResult({event_id:e.event_id,status:"failed",msg:_ instanceof Error?_.message:String(_),updated_at:Date.now()})}return}}}const r=this.buildInboundEvent(e,n);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({event_id:e.event_id,status:"failed",msg:a.message,updated_at:Date.now()});else throw a}}async handleCodexSessionControlOpen(e,o){const t=o.session_id,n=e.args.trim(),i=()=>this.aibotHandle.sendEventAck({event_id:o.event_id,session_id:t,received_at:Date.now()}),s=(r,a)=>this.aibotHandle.sendEventResult({event_id:o.event_id,status:r,...a,updated_at:Date.now()});if(!n){i(),s("failed",{msg:"Usage: /grix open <working-directory>",code:g.cwdRequired});return}try{const r=await this.resolveCwdForBinding(n),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,u.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:g.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:g.invalidCwd,msg:r instanceof Error?r.message:String(r)})}}async handleSessionControlForPool(e,o){e.verb===p.open&&await this.bindSessionForPool(o.session_id,e.args.trim())}async replayDeferredEventsForSession(e){await this.deferredMgr.release(e,this.deferredCallbacks())}async handleSessionControlLocalActionForPool(e){if(String(e.action_type??"")!==S.sessionControl)return;const o=e.params??{};if(String(o.verb??"").trim().toLowerCase()!==p.open)return;const n=String(o.session_id??"").trim(),i=String(o.cwd??"").trim();!n||!i||await this.bindSessionForPool(n,i)}async handleCodexSessionControlLocalActionOpen(e){const o=e.params??{},t=String(o.session_id??"").trim(),n=String(o.cwd??"").trim(),i=String(o.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||!n){s("failed",void 0,g.cwdRequired,"session cwd is required");return}try{const r=await this.resolveCwdForBinding(n);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,u.info("bridge",`Stale codex binding for session ${t}: ${a.cwd} no longer exists, allowing rebind`)}if(!d){s("failed",void 0,g.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??g.invalidCwd,r instanceof Error?r.message:String(r))}}async handleCursorSessionControlLocalActionOpen(e){const o=e.params??{},t=String(o.session_id??"").trim(),n=String(o.cwd??"").trim(),i=String(o.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||!n){s("failed",void 0,g.cwdRequired,"session cwd is required");return}try{const r=await this.resolveCwdForBinding(n);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,u.info("bridge",`Stale cursor binding for session ${t}: ${a.cwd} no longer exists, allowing rebind`)}if(!d){s("failed",void 0,g.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}),s("ok",{outcome:"opened",binding:this.buildOpenedBindingResult(t,r)})}catch(r){s("failed",void 0,r?.sessionControlErrorCode??g.invalidCwd,r instanceof Error?r.message:String(r))}}async handlePiSessionControlOpen(e,o){const t=o.session_id,n=e.args.trim(),i=()=>this.aibotHandle.sendEventAck({event_id:o.event_id,session_id:t,received_at:Date.now()}),s=(r,a)=>this.aibotHandle.sendEventResult({event_id:o.event_id,status:r,...a,updated_at:Date.now()});if(!n){i(),s("failed",{msg:"Usage: /grix open <working-directory>",code:g.cwdRequired});return}try{const r=await this.resolveCwdForBinding(n),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:o.event_id,session_id:t,msg_type:1,content:`\u2705 Session already bound to \`${a.cwd}\``,quoted_message_id:o.msg_id});return}}catch{d=!0,u.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:g.rebindForbidden});return}}this.bindingStore.set(t,r),this.sessionBindings.set(t,r),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:o.event_id,session_id:t,msg_type:1,content:`\u2705 Working directory bound: \`${r}\``,quoted_message_id:o.msg_id})}catch(r){i(),s("failed",{code:g.invalidCwd,msg:r instanceof Error?r.message:String(r)})}}async handlePiSessionControlRestart(e){const o=e.session_id,t=()=>this.aibotHandle.sendEventAck({event_id:e.event_id,session_id:o,received_at:Date.now()}),n=(r,a)=>this.aibotHandle.sendEventResult({event_id:e.event_id,status:r,...a,updated_at:Date.now()}),s=this.bindingStore.get(o)?.cwd??"";if(!s){t(),n("failed",{msg:"session binding was not found",code:g.bindingMissing});return}t(),await this.pool.removeSlot(o).catch(()=>{}),this.aibotHandle.sendUpdateBindingCard({session_id:o,worker_status:"ready",cwd:s}),n("responded",{msg:`Session worker restarted for ${s}`})}async handlePiSessionControlRestartLocalAction(e){const o=e.params??{},t=String(o.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,g.bindingMissing,"Session binding missing. Open a workspace first.");return}await this.pool.removeSlot(t).catch(()=>{}),this.aibotHandle.sendUpdateBindingCard({session_id:t,worker_status:"ready",cwd:i}),s("ok",{outcome:"restarted",binding:{aibotSessionId:t,cwd:i,workerStatus:"ready"}})}async bindSessionForPool(e,o){const t=String(o??"").trim();if(!e||!t)return;const n=this.pool.getOrCreateSlot(e);if(!n||(n.startPromise&&await n.startPromise,!n.adapter))return;const i=n.adapter instanceof A?n.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,o,t)=>this.sendEventResultWithCleanup(e,o,t),sendSessionComposing:(e,o,t)=>{const n={};o&&(n.ttl_ms=t?.ttlMs??3e4,t?.activity&&(n.activity=t.activity)),this.aibotHandle.sendSessionActivitySet({session_id:e,kind:"composing",active:o,...n})}}}async handleOpenHumanSessionControlOpen(e,o){const t=()=>this.aibotHandle.sendEventAck({event_id:o.event_id,session_id:o.session_id,received_at:Date.now()});try{await this.resolveCwdForBinding(e.args.trim()),await this.handleSessionControlForPool(e,o),U(e,o,this.sessionControlCtx(o.session_id),this.sessionControlSenders()),await this.deferredMgr.release(o.session_id,this.deferredCallbacks())}catch(n){t(),this.aibotHandle.sendEventResult({event_id:o.event_id,status:"failed",code:n?.cwdErrorCode,msg:n instanceof Error?n.message:String(n),updated_at:Date.now()})}}async handleCodeWhaleSessionControlOpen(e,o){const t=o.session_id,n=e.args.trim(),i=()=>this.aibotHandle.sendEventAck({event_id:o.event_id,session_id:t,received_at:Date.now()}),s=(r,a)=>this.aibotHandle.sendEventResult({event_id:o.event_id,status:r,...a,updated_at:Date.now()});if(!n){i(),s("failed",{msg:"Usage: /grix open <working-directory>",code:g.cwdRequired});return}try{const r=await this.resolveCwdForBinding(n),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:g.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:g.invalidCwd,msg:r instanceof Error?r.message:String(r)})}}async handleCodeWhaleSessionControlLocalActionOpen(e){const o=e.params??{},t=String(o.session_id??"").trim(),n=String(o.cwd??"").trim(),i=String(o.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||!n){s("failed",void 0,g.cwdRequired,"session cwd is required");return}try{const r=await this.resolveCwdForBinding(n);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,g.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??g.invalidCwd,r instanceof Error?r.message:String(r))}}normalizeClaudeModeId(e){return String(e??"").trim().toLowerCase()===C.approval?C.approval:C.fullAuto}handleExecCommandOptions(e,o,t){const n=Array.isArray(t?.options)?t.options:[];if(n.length===0)return;const s=this.bindingStore.get(e)?.cwd??"",r={};if(o==="model"){r.available_models=n.map(d=>({id:d.id,displayName:d.label}));const a=n.find(d=>d.current);a&&(r.model_id=a.id)}else if(o==="mode"){r.available_modes=n.map(d=>({id:d.id,displayName:d.label}));const a=n.find(d=>d.current);a&&(r.mode_id=a.id)}else r[`${o}_options`]=n;this.aibotHandle.sendUpdateBindingCard({session_id:e,worker_status:"ready",cwd:s,meta:r})}buildAgyToolbarMeta(e){const o=this.bindingStore.get(e);if(!o)return;const t=te(this.config.agent.command),n=t.length>0?t[0].id:"";let i=o.modelId||this.globalConfigStore?.get(this.name)?.modelId||n;t.length>0&&!t.some(r=>r.id===i)&&(i=n);const s=ie();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,o){const t=e.params??{},n=String(t.model_id??t.modelId??"").trim();if(!o){this.aibotHandle.sendLocalActionResult({action_id:e.action_id,status:"failed",error_code:"session_id_required",error_msg:"session_id is required for set_model"});return}if(!n){this.aibotHandle.sendLocalActionResult({action_id:e.action_id,status:"failed",error_code:"set_model_invalid",error_msg:"model_id is required"});return}const i=this.bindingStore.get(o);if(!i?.cwd){this.aibotHandle.sendLocalActionResult({action_id:e.action_id,status:"failed",error_code:g.bindingMissing,error_msg:"session binding was not found"});return}i.modelId!==n&&(this.bindingStore.setModelId(o,n),this.globalConfigStore?.set(this.name,{modelId:n})),this.aibotHandle.sendUpdateBindingCard({session_id:o,worker_status:"ready",cwd:i.cwd,meta:this.buildAgyToolbarMeta(o)}),this.aibotHandle.sendLocalActionResult({action_id:e.action_id,status:"ok",result:{outcome:"model_set",model_id:n,binding:{cwd:i.cwd,model_id:n}}})}buildClaudeToolbarMeta(e){const o=this.bindingStore.get(e);if(!o)return;const t=D(),n=t.length>0?t[0].id:"";let i=o.modelId||this.globalConfigStore?.get(this.name)?.modelId||n;t.length>0&&!t.some(l=>l.id===i)&&(i=n);const s=this.normalizeClaudeModeId(o.modeId),r={model_id:i,mode_id:s,currentModelId:i,currentModeId:s,available_models:t.map(l=>({id:l.id,displayName:l.displayName}))},a=this.pool.getSlot(e),d=a?.adapter instanceof y?a.adapter.getSessionState():null;(d?.rateLimits?.fiveHour||d?.rateLimits?.sevenDay)&&(this.cachedClaudeRateLimitState=d);const c=d??this.getFreshClaudeRateLimitState();if(c){const l=c.rateLimits;l&&(l.fiveHour||l.sevenDay)&&(r.rate_limits={...l.fiveHour?{fiveHour:l.fiveHour}:{},...l.sevenDay?{sevenDay:l.sevenDay}:{},sampledAt:c.sampledAt||Date.now()})}if(d){const l=d.contextWindow;l.usedPercentage!=null&&(r.context_window={usedPercentage:l.usedPercentage,remainingPercentage:l.remainingPercentage})}if(!r.rate_limits&&this.cachedProviderQuota?.success){this.isRateLimitsCacheFresh(this.cachedProviderQuotaSampledAtMs)||this.maybeQueryProviderQuota().catch(()=>{});const l=this.providerQuotaToRateLimits(this.cachedProviderQuota);l&&(r.rate_limits=l),u.info(this.name,`[toolbar-meta] provider quota fallback: hasCached=${!!this.cachedProviderQuota} fromProvider=${JSON.stringify(l)}`)}return r.rate_limits&&u.info(this.name,`[toolbar-meta] rate_limits included: ${JSON.stringify(r.rate_limits)}`),r}providerQuotaToCodexRateLimits(e){const o=e.tiers.find(i=>i.name==="five_hour"),t=e.tiers.find(i=>i.name==="weekly_limit"),n=i=>i||null;if(o||t){const i={credits:{hasCredits:!0,unlimited:!1,balance:null},sampledAt:this.cachedProviderQuotaSampledAtMs??Date.now()};let s=0,r=0,a=0,d=0;return o&&(i.primary={usedPercent:o.usedPercent,windowMinutes:300,resetsAt:n(o.resetsAt)},s=o.usedPercent,r=300),t&&(i.secondary={usedPercent:t.usedPercent,windowMinutes:10080,resetsAt:n(t.resetsAt)},a=t.usedPercent,d=10080),{rateLimits:i,primaryPercent:s,secondaryPercent:a,primaryWindowMin:r,secondaryWindowMin:d}}if(e.balance){const i=e.balance,s=i.total&&i.total>0?Math.min(100,(i.used??i.total-i.remaining)/i.total*100):0;return{rateLimits:{primary:{usedPercent:s,windowMinutes:0,resetsAt:null},secondary:{usedPercent:0,windowMinutes:0,resetsAt:null},credits:{hasCredits:!0,unlimited:!1,balance:i.remaining},sampledAt:this.cachedProviderQuotaSampledAtMs??Date.now()},primaryPercent:s,secondaryPercent:0,primaryWindowMin:0,secondaryWindowMin:0}}return null}providerQuotaToRateLimits(e){const o=i=>{if(!i)return 0;const s=Date.parse(i);return Number.isFinite(s)?Math.floor(s/1e3):0},t=e.tiers.find(i=>i.name==="five_hour"),n=e.tiers.find(i=>i.name==="weekly_limit");if(t||n)return{...t?{fiveHour:{usedPercentage:t.usedPercent,resetsAt:o(t.resetsAt)}}:{},...n?{sevenDay:{usedPercentage:n.usedPercent,resetsAt:o(n.resetsAt)}}:{},sampledAt:this.cachedProviderQuotaSampledAtMs??Date.now()};if(e.balance){const i=e.balance;return{credit:{remaining:i.remaining,total:i.total,used:i.used,unit:i.unit,resetsAt:i.resetsAt?o(i.resetsAt):0},planName:e.planName,sampledAt:this.cachedProviderQuotaSampledAtMs??Date.now()}}return null}async resolveCwdForBinding(e){const o=String(e??"").trim();if(process.platform!=="win32"&&(/^[a-zA-Z]:[\\/]/.test(o)||/^\\\\/.test(o))){const i=new Error(`Specified path is not valid on this host: ${o}`);throw i.cwdErrorCode=g.invalidCwd,i}const t=T.resolve(o);let n;try{n=await H(t)}catch(i){const s=String(i?.code??"");if(s==="ENOENT"){const r=new Error(`Specified path does not exist: ${t}`);throw r.cwdErrorCode=g.invalidCwd,r}if(s==="EACCES"||s==="EPERM"){const r=new Error("Specified path is not accessible.");throw r.cwdErrorCode=g.invalidCwd,r}throw i}if(!n.isDirectory()){const i=new Error("Specified path is not a directory.");throw i.cwdErrorCode=g.invalidCwd,i}try{return await G(t)}catch{return t}}getClaudeWorkerStatus(e){const o=this.pool.getSlot(e);return o?o.state==="starting"?"starting":o.state==="stopped"?"stopped":o.adapter.getStatus().busy?"busy":"ready":"stopped"}refreshClaudeWorkerStatusCard(e,o){const t=this.getClaudeWorkerStatus(e);return this.claudeWorkerStatus.set(e,t),this.aibotHandle.sendUpdateBindingCard({session_id:e,worker_status:t,cwd:o,meta:this.buildClaudeToolbarMeta(e)}),t}async ensureSlotStarted(e,o=6e4){const t=this.pool.getOrCreateSlot(e);if(!t)throw new Error("Failed to allocate session slot");t.startPromise&&(u.info(this.name,`ensureSlotStarted: awaiting startPromise for session=${e}`),await Promise.race([t.startPromise,new Promise((n,i)=>setTimeout(()=>i(new Error(`ensureSlotStarted timeout (${o}ms) session=${e}`)),o))]),u.info(this.name,`ensureSlotStarted: startPromise resolved for session=${e}`))}resolveAgentSessionId(e){switch(this.config.adapterType??"acp"){case"claude":return e.claudeSessionId;case"codex":return e.codexThreadId;case"pi":return e.piSessionPath;case"codewhale":return e.codewhaleThreadId;case"agy":return e.agyConversationId;default:return e.acpSessionId}}providerKeyForAdapter(){const e=this.config.adapterType??"acp";switch(e){case"claude":case"codex":case"pi":case"codewhale":return e;default:return"acp"}}setResolvedAgentSessionId(e,o){const t=String(e??"").trim(),n=String(o??"").trim();if(!t||!n)return;switch(this.config.adapterType??"acp"){case"claude":this.bindingStore.setClaudeSessionId(t,n);break;case"codex":this.bindingStore.setCodexThreadId(t,n);break;case"pi":this.bindingStore.setPiSessionPath(t,n);break;case"codewhale":this.bindingStore.setCodeWhaleThreadId(t,n);break;case"agy":this.bindingStore.setAgyConversationId(t,n);break;default:this.bindingStore.setAcpSessionId(t,n);break}this.sessionScanCache.invalidate()}normalizePathForCompare(e){const o=String(e??"").trim();if(!o)return"";const t=T.resolve(o);return process.platform==="win32"?t.toLowerCase():t}ensureImportedAgentSession(e,o){const t=String(e??"").trim();if(!t)return;const n=this.normalizePathForCompare(o);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==="acp"&&(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=g.invalidAgentSession,a}const r=this.normalizePathForCompare(i);if(r&&n&&r!==n){const a=new Error(`agent session cwd mismatch: expected ${o}, got ${i}`);throw a.sessionControlErrorCode=g.invalidAgentSession,a}}buildOpenedBindingResult(e,o,t="ready"){const n=this.bindingStore.get(e),i=n?String(this.resolveAgentSessionId(n)??"").trim():"",s={aibotSessionId:e,providerKey:this.providerKeyForAdapter(),cwd:o,workerStatus:t};return i&&(s.bindingId=i,s.agentSessionId=i),s}hasDiskScanner(){const e=this.config.adapterType??"acp";return e==="codex"||e==="claude"||e==="acp"}async handleListSessionsTextCommand(e){this.aibotHandle.sendEventAck({event_id:e.event_id,session_id:e.session_id,received_at:Date.now()});const o=this.config.adapterType??"acp",t=new Map;for(const a of this.pool.getAllSlots())t.set(a.sessionId,a);const n=Array.from(this.bindingStore.entries()),i=new Map;for(const[a,d]of n){const c=this.resolveAgentSessionId(d);if(c){const l=t.get(a),h=l?l.adapter.getStatus().busy?"busy":"ready":"inactive";i.set(c,{aibotSessionId:a,workerStatus:h})}}const s=[],r=new Set;if(o==="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(o==="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(o==="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("---")}}for(const[a,d]of n){const c=this.resolveAgentSessionId(d);if(c&&r.has(c))continue;const l=t.get(a),h=l?l.adapter.getStatus().busy?"busy":"ready":"closed";s.push(` Title: ${c?c.slice(0,8)+"\u2026":a.slice(0,8)+"\u2026"}`),s.push(` AIBot: ${a}`),c&&s.push(` Agent: ${c}`),s.push(` CWD: ${d.cwd??"-"}`),s.push(` State: ${h}`),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}):
14
14
  ${s.join(`
@@ -1 +1 @@
1
- import{createServer as g}from"node:http";import{log as c}from"../log/logger.js";class m{server=null;token;handler=null;upgradeHandler=null;probeHandler=null;installHandler=null;constructor(e){this.token=e}setAgentHandler(e){this.handler=e}setUpgradeHandler(e){this.upgradeHandler=e}setProbeHandler(e){this.probeHandler=e}setInstallHandler(e){this.installHandler=e}async start(e){return new Promise((r,t)=>{this.server=g((n,s)=>this.handleRequest(n,s)),this.server.listen(e,"127.0.0.1",()=>{c.info("admin",`Listening on 127.0.0.1:${e}`),r()}),this.server.on("error",t)})}async stop(){if(this.server)return new Promise(e=>{this.server.close(()=>e())})}handleRequest(e,r){const t=e.method??"",n=e.url??"";if(n==="/api/agents"&&t==="GET")this.handleList(r);else if(n==="/api/agents"&&t==="POST")this.readBody(e).then(s=>this.handleAdd(r,s)).catch(s=>this.error(r,s));else if(t==="DELETE"&&n.startsWith("/api/agents/")){const s=decodeURIComponent(n.slice(12));this.handleRemove(r,s)}else if(t==="POST"&&n.match(/^\/api\/agents\/[^/]+\/restart$/)){const s=decodeURIComponent(n.slice(12,n.lastIndexOf("/restart")));this.handleRestart(r,s)}else if(n==="/api/upgrade"&&t==="GET")this.handleCheckUpgrade(r);else if(n==="/api/upgrade"&&t==="POST")this.handleTriggerUpgrade(r);else if(t==="GET"&&n.startsWith("/api/probe"))this.handleProbe(e,r,n);else if(n==="/api/install"&&t==="GET")this.handleInstallList(r);else if(n==="/api/install"&&t==="POST")this.readBody(e).then(s=>this.handleInstall(r,s)).catch(s=>this.error(r,s));else if(t==="GET"&&n.startsWith("/api/install/")){const s=decodeURIComponent(n.slice(13));this.handleInstallProgress(r,s)}else this.json(r,404,{error:"not_found"})}handleList(e){try{const r=this.handler?.list()??[];this.json(e,200,r)}catch(r){this.error(e,r)}}async handleAdd(e,r){try{const t=await this.handler.add(r);this.json(e,201,t??{ok:!0})}catch(t){this.error(e,t)}}handleRemove(e,r){this.handler.remove(r).then(()=>{e.writeHead(204),e.end()}).catch(t=>this.error(e,t))}handleRestart(e,r){this.handler.restart(r).then(()=>{this.json(e,200,{ok:!0})}).catch(t=>this.error(e,t))}handleCheckUpgrade(e){if(!this.upgradeHandler){this.json(e,501,{error:"upgrade not configured"});return}this.upgradeHandler.check().then(r=>{this.json(e,200,r)}).catch(r=>this.error(e,r))}handleTriggerUpgrade(e){if(!this.upgradeHandler){this.json(e,501,{error:"upgrade not configured"});return}this.upgradeHandler.trigger(),this.json(e,200,{ok:!0,message:"upgrade check triggered"})}error(e,r){const t=r;t.code==="NOT_FOUND"?this.json(e,404,{error:t.message??"not found"}):t.code==="UNKNOWN_AGENT"||t.code==="UNSUPPORTED_OS"?this.json(e,400,{error:t.message,code:t.code}):t.code==="ALREADY_INSTALLED"||t.code==="INSTALL_IN_PROGRESS"?this.json(e,409,{error:t.message,code:t.code}):t.code==="INSTALL_FAILED"||t.code==="INSTALL_TIMEOUT"||t.code==="PREFLIGHT_FAILED"||t.code==="VERIFICATION_FAILED"||t.code==="PREREQ_MISSING"||t.code==="PREREQ_INSTALL_FAILED"||t.code==="FALLBACK_EXHAUSTED"||t.code==="ENVIRONMENT_UNSUPPORTED"?this.json(e,500,{error:t.message,code:t.code}):(c.error("admin",`Handler error: ${t.message??r}`),this.json(e,500,{error:t.message??"internal error"}))}json(e,r,t){const n=JSON.stringify(t);e.writeHead(r,{"Content-Type":"application/json"}),e.end(n)}readBody(e){return new Promise((r,t)=>{let n="";e.setEncoding("utf8"),e.on("data",s=>{n+=s}),e.on("end",()=>{try{r(JSON.parse(n))}catch{t(new Error("invalid JSON body"))}}),e.on("error",t)})}handleProbe(e,r,t){if(!this.probeHandler){this.json(r,501,{error:"probe not configured"});return}const n=t.indexOf("?"),s=n>=0?t.slice(0,n):t,a=n>=0?new URLSearchParams(t.slice(n+1)):new URLSearchParams,i={};a.get("conversation")==="true"&&(i.conversation=!0),a.get("fresh")==="true"&&(i.fresh=!0);const l=Number(a.get("timeoutMs"));Number.isFinite(l)&&l>0&&(i.timeoutMs=l);const d=s.match(/^\/api\/probe\/(.+)$/);if(d){const o=decodeURIComponent(d[1]);this.probeHandler.probeOne(o,i).then(h=>{this.json(r,200,h)}).catch(h=>this.error(r,h));return}this.probeHandler.probeAll(i).then(o=>{this.json(r,200,o)}).catch(o=>this.error(r,o))}handleInstallList(e){if(!this.installHandler){this.json(e,501,{error:"install not configured"});return}try{const r=this.installHandler.listInstallable();this.json(e,200,r)}catch(r){this.error(e,r)}}handleInstallProgress(e,r){if(!this.installHandler){this.json(e,501,{error:"install not configured"});return}const t=this.installHandler.getInstallProgress(r);if(!t){this.json(e,200,{agentType:r,status:"unknown",inProgress:!1,progress:null});return}let n,s,a;switch(t.phase){case"completed":n="done",s="\u5B89\u88C5\u5B8C\u6210";break;case"failed":n="error",a=t.outputTail||"\u5B89\u88C5\u5931\u8D25";break;case"preflight":n="pending",s="\u68C0\u67E5\u524D\u7F6E\u4F9D\u8D56...";break;case"installing_prereq":n="downloading",s=t.currentPrereq?`\u6B63\u5728\u5B89\u88C5\u524D\u7F6E\u4F9D\u8D56: ${t.currentPrereq}`:"\u6B63\u5728\u5B89\u88C5\u524D\u7F6E\u4F9D\u8D56...";break;case"installing":n="installing",s=`\u6B63\u5728\u5B89\u88C5 ${r}...`;break;case"verifying":n="installing",s="\u9A8C\u8BC1\u5B89\u88C5...";break;default:n="unknown"}this.json(e,200,{agentType:r,status:n,inProgress:!0,progress:t.elapsedMs?Math.min(.9,t.elapsedMs/3e4):.1,message:s,error:a})}async handleInstall(e,r){if(!this.installHandler){this.json(e,501,{error:"install not configured"});return}try{const t=r;if(!t||typeof t.agentType!="string"||!t.agentType){this.json(e,400,{error:"agentType is required"});return}const n=await this.installHandler.installAgent(t);if(n.ok)this.json(e,200,n);else{const s=n.error?.code;s==="UNKNOWN_AGENT"||s==="UNSUPPORTED_OS"?this.json(e,400,n):s==="INSTALL_IN_PROGRESS"?this.json(e,409,n):this.json(e,500,n)}}catch(t){this.error(e,t)}}}export{m as AdminServer};
1
+ import{createServer as g}from"node:http";import{log as c}from"../log/logger.js";class m{server=null;token;handler=null;upgradeHandler=null;probeHandler=null;installHandler=null;constructor(e){this.token=e}setAgentHandler(e){this.handler=e}setUpgradeHandler(e){this.upgradeHandler=e}setProbeHandler(e){this.probeHandler=e}setInstallHandler(e){this.installHandler=e}async start(e){return new Promise((r,t)=>{this.server=g((n,s)=>this.handleRequest(n,s)),this.server.listen(e,"127.0.0.1",()=>{c.info("admin",`Listening on 127.0.0.1:${e}`),r()}),this.server.on("error",t)})}async stop(){if(this.server)return new Promise(e=>{this.server.close(()=>e())})}handleRequest(e,r){const t=e.method??"",n=e.url??"";if(n==="/api/agents"&&t==="GET")this.handleList(r);else if(n==="/api/agents"&&t==="POST")this.readBody(e).then(s=>this.handleAdd(r,s)).catch(s=>this.error(r,s));else if(t==="DELETE"&&n.startsWith("/api/agents/")){const s=decodeURIComponent(n.slice(12));this.handleRemove(r,s)}else if(t==="POST"&&n.match(/^\/api\/agents\/[^/]+\/restart$/)){const s=decodeURIComponent(n.slice(12,n.lastIndexOf("/restart")));this.handleRestart(r,s)}else if(n==="/api/reload"&&t==="POST")this.handleReload(r);else if(n==="/api/upgrade"&&t==="GET")this.handleCheckUpgrade(r);else if(n==="/api/upgrade"&&t==="POST")this.handleTriggerUpgrade(r);else if(t==="GET"&&n.startsWith("/api/probe"))this.handleProbe(e,r,n);else if(n==="/api/install"&&t==="GET")this.handleInstallList(r);else if(n==="/api/install"&&t==="POST")this.readBody(e).then(s=>this.handleInstall(r,s)).catch(s=>this.error(r,s));else if(t==="GET"&&n.startsWith("/api/install/")){const s=decodeURIComponent(n.slice(13));this.handleInstallProgress(r,s)}else this.json(r,404,{error:"not_found"})}handleList(e){try{const r=this.handler?.list()??[];this.json(e,200,r)}catch(r){this.error(e,r)}}async handleAdd(e,r){try{const t=await this.handler.add(r);this.json(e,201,t??{ok:!0})}catch(t){this.error(e,t)}}handleRemove(e,r){this.handler.remove(r).then(()=>{e.writeHead(204),e.end()}).catch(t=>this.error(e,t))}handleRestart(e,r){this.handler.restart(r).then(()=>{this.json(e,200,{ok:!0})}).catch(t=>this.error(e,t))}handleReload(e){this.handler.reload().then(r=>{this.json(e,200,{ok:!0,result:r})}).catch(r=>this.error(e,r))}handleCheckUpgrade(e){if(!this.upgradeHandler){this.json(e,501,{error:"upgrade not configured"});return}this.upgradeHandler.check().then(r=>{this.json(e,200,r)}).catch(r=>this.error(e,r))}handleTriggerUpgrade(e){if(!this.upgradeHandler){this.json(e,501,{error:"upgrade not configured"});return}this.upgradeHandler.trigger(),this.json(e,200,{ok:!0,message:"upgrade check triggered"})}error(e,r){const t=r;t.code==="NOT_FOUND"?this.json(e,404,{error:t.message??"not found"}):t.code==="RELOAD_UNSAFE"?this.json(e,409,{error:t.message,code:t.code}):t.code==="UNKNOWN_AGENT"||t.code==="UNSUPPORTED_OS"?this.json(e,400,{error:t.message,code:t.code}):t.code==="ALREADY_INSTALLED"||t.code==="INSTALL_IN_PROGRESS"?this.json(e,409,{error:t.message,code:t.code}):t.code==="INSTALL_FAILED"||t.code==="INSTALL_TIMEOUT"||t.code==="PREFLIGHT_FAILED"||t.code==="VERIFICATION_FAILED"||t.code==="PREREQ_MISSING"||t.code==="PREREQ_INSTALL_FAILED"||t.code==="FALLBACK_EXHAUSTED"||t.code==="ENVIRONMENT_UNSUPPORTED"?this.json(e,500,{error:t.message,code:t.code}):(c.error("admin",`Handler error: ${t.message??r}`),this.json(e,500,{error:t.message??"internal error"}))}json(e,r,t){const n=JSON.stringify(t);e.writeHead(r,{"Content-Type":"application/json"}),e.end(n)}readBody(e){return new Promise((r,t)=>{let n="";e.setEncoding("utf8"),e.on("data",s=>{n+=s}),e.on("end",()=>{try{r(JSON.parse(n))}catch{t(new Error("invalid JSON body"))}}),e.on("error",t)})}handleProbe(e,r,t){if(!this.probeHandler){this.json(r,501,{error:"probe not configured"});return}const n=t.indexOf("?"),s=n>=0?t.slice(0,n):t,a=n>=0?new URLSearchParams(t.slice(n+1)):new URLSearchParams,i={};a.get("conversation")==="true"&&(i.conversation=!0),a.get("fresh")==="true"&&(i.fresh=!0);const l=Number(a.get("timeoutMs"));Number.isFinite(l)&&l>0&&(i.timeoutMs=l);const d=s.match(/^\/api\/probe\/(.+)$/);if(d){const o=decodeURIComponent(d[1]);this.probeHandler.probeOne(o,i).then(h=>{this.json(r,200,h)}).catch(h=>this.error(r,h));return}this.probeHandler.probeAll(i).then(o=>{this.json(r,200,o)}).catch(o=>this.error(r,o))}handleInstallList(e){if(!this.installHandler){this.json(e,501,{error:"install not configured"});return}try{const r=this.installHandler.listInstallable();this.json(e,200,r)}catch(r){this.error(e,r)}}handleInstallProgress(e,r){if(!this.installHandler){this.json(e,501,{error:"install not configured"});return}const t=this.installHandler.getInstallProgress(r);if(!t){this.json(e,200,{agentType:r,status:"unknown",inProgress:!1,progress:null});return}let n,s,a;switch(t.phase){case"completed":n="done",s="\u5B89\u88C5\u5B8C\u6210";break;case"failed":n="error",a=t.outputTail||"\u5B89\u88C5\u5931\u8D25";break;case"preflight":n="pending",s="\u68C0\u67E5\u524D\u7F6E\u4F9D\u8D56...";break;case"installing_prereq":n="downloading",s=t.currentPrereq?`\u6B63\u5728\u5B89\u88C5\u524D\u7F6E\u4F9D\u8D56: ${t.currentPrereq}`:"\u6B63\u5728\u5B89\u88C5\u524D\u7F6E\u4F9D\u8D56...";break;case"installing":n="installing",s=`\u6B63\u5728\u5B89\u88C5 ${r}...`;break;case"verifying":n="installing",s="\u9A8C\u8BC1\u5B89\u88C5...";break;default:n="unknown"}this.json(e,200,{agentType:r,status:n,inProgress:!0,progress:t.elapsedMs?Math.min(.9,t.elapsedMs/3e4):.1,message:s,error:a})}async handleInstall(e,r){if(!this.installHandler){this.json(e,501,{error:"install not configured"});return}try{const t=r;if(!t||typeof t.agentType!="string"||!t.agentType){this.json(e,400,{error:"agentType is required"});return}const n=await this.installHandler.installAgent(t);if(n.ok)this.json(e,200,n);else{const s=n.error?.code;s==="UNKNOWN_AGENT"||s==="UNSUPPORTED_OS"?this.json(e,400,n):s==="INSTALL_IN_PROGRESS"?this.json(e,409,n):this.json(e,500,n)}}catch(t){this.error(e,t)}}}export{m as AdminServer};
@@ -1 +1 @@
1
- import{readdir as r,stat as m}from"node:fs/promises";import{join as l,extname as d}from"node:path";const x={pdf:"application/pdf",doc:"application/msword",docx:"application/vnd.openxmlformats-officedocument.wordprocessingml.document",xls:"application/vnd.ms-excel",xlsx:"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",ppt:"application/vnd.ms-powerpoint",pptx:"application/vnd.openxmlformats-officedocument.presentationml.presentation",txt:"text/plain",md:"text/markdown",csv:"text/csv",json:"application/json",xml:"application/xml",yaml:"text/yaml",yml:"text/yaml",html:"text/html",css:"text/css",js:"text/javascript",ts:"text/typescript",zip:"application/zip",rar:"application/x-rar-compressed","7z":"application/x-7z-compressed",tar:"application/x-tar",gz:"application/gzip",jpg:"image/jpeg",jpeg:"image/jpeg",png:"image/png",gif:"image/gif",webp:"image/webp",svg:"image/svg+xml",mp4:"video/mp4",mov:"video/quicktime",avi:"video/x-msvideo",mkv:"video/x-matroska",webm:"video/webm",mp3:"audio/mpeg",wav:"audio/wav",flac:"audio/flac",aac:"audio/aac"};function n(a){const p=d(a).slice(1).toLowerCase();return x[p]}async function f(a,p=!1){const c=await r(a,{withFileTypes:!0}),s=[];for(const t of c){if(!p&&t.name.startsWith("."))continue;const i=l(a,t.name),e={id:i,name:t.name,is_directory:t.isDirectory()};try{if(t.isDirectory()){const o=await m(i);e.modified_at=o.mtime.toISOString()}else{const o=await m(i);e.size=o.size,e.modified_at=o.mtime.toISOString(),e.mime_type=n(t.name)}}catch{}s.push(e)}return s.sort((t,i)=>t.is_directory!==i.is_directory?t.is_directory?-1:1:t.name.localeCompare(i.name)),s}export{f as listFiles,n as resolveMimeType};
1
+ import{readdir as r,stat as m}from"node:fs/promises";import{join as l,extname as d}from"node:path";const x={pdf:"application/pdf",doc:"application/msword",docx:"application/vnd.openxmlformats-officedocument.wordprocessingml.document",xls:"application/vnd.ms-excel",xlsx:"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",ppt:"application/vnd.ms-powerpoint",pptx:"application/vnd.openxmlformats-officedocument.presentationml.presentation",txt:"text/plain",md:"text/markdown",csv:"text/csv",json:"application/json",xml:"application/xml",yaml:"text/yaml",yml:"text/yaml",html:"text/html",css:"text/css",js:"text/javascript",ts:"text/typescript",zip:"application/zip",rar:"application/x-rar-compressed","7z":"application/x-7z-compressed",tar:"application/x-tar",gz:"application/gzip",jpg:"image/jpeg",jpeg:"image/jpeg",png:"image/png",gif:"image/gif",webp:"image/webp",svg:"image/svg+xml",mp4:"video/mp4",mov:"video/quicktime",avi:"video/x-msvideo",mkv:"video/x-matroska",webm:"video/webm",mp3:"audio/mpeg",wav:"audio/wav",flac:"audio/flac",aac:"audio/aac"};function n(a){const p=d(a).slice(1).toLowerCase();return x[p]}async function f(a,p=!1){const c=await r(a,{withFileTypes:!0}),s=[];for(const i of c){if(!p&&i.name.startsWith("."))continue;const t=l(a,i.name),e={id:t,name:i.name,is_directory:i.isDirectory()};try{if(i.isDirectory()){const o=await m(t);e.modified_at=o.mtime.toISOString()}else{const o=await m(t);e.size=o.size,e.modified_at=o.mtime.toISOString(),e.mime_type=n(i.name)}}catch{}s.push(e)}return s.sort((i,t)=>i.is_directory!==t.is_directory?i.is_directory?-1:1:i.name.localeCompare(t.name)),s}export{f as listFiles,n as resolveMimeType};
@@ -1 +1 @@
1
- import{HealthServer as o}from"./health.js";import{writePidFile as t,removePidFile as l}from"./pidfile.js";import{bindPortOrFail as d}from"./port-bind.js";export{o as HealthServer,d as bindPortOrFail,l as removePidFile,t as writePidFile};
1
+ import{HealthServer as o}from"./health.js";import{writePidFile as t,removePidFile as d,readDaemonPid as m}from"./pidfile.js";import{bindPortOrFail as l}from"./port-bind.js";export{o as HealthServer,l as bindPortOrFail,m as readDaemonPid,d as removePidFile,t as writePidFile};
@@ -1,2 +1,2 @@
1
- import{writeFileSync as c,readFileSync as s,unlinkSync as a,existsSync as l}from"node:fs";import{join as p}from"node:path";import{GRIX_PATHS as w}from"../log/logger.js";const i=p(w.base,"grix-acp.pid");async function m(r,t){const n=Date.now()+t;for(;Date.now()<n;){try{process.kill(r,0)}catch{return!0}await new Promise(e=>setTimeout(e,100))}return!1}function y(){if(l(i)){const r=s(i,"utf-8").trim(),t=parseInt(r,10);if(!Number.isNaN(t)&&t!==process.pid)try{process.kill(t,0),process.kill(t,"SIGTERM");const n=Date.now()+3e3;let e=!0;for(;Date.now()<n;){try{process.kill(t,0)}catch{e=!1;break}const o=Date.now()+100;for(;Date.now()<o;);}if(e)try{process.kill(t,"SIGKILL")}catch{}}catch{}}c(i,`${process.pid}
2
- `,"utf-8")}function h(){try{a(i)}catch{}}export{h as removePidFile,y as writePidFile};
1
+ import{writeFileSync as s,readFileSync as o,unlinkSync as a,existsSync as l}from"node:fs";import{join as p}from"node:path";import{GRIX_PATHS as u}from"../log/logger.js";const i=p(u.base,"grix-acp.pid");async function m(e,t){const n=Date.now()+t;for(;Date.now()<n;){try{process.kill(e,0)}catch{return!0}await new Promise(r=>setTimeout(r,100))}return!1}function y(){if(l(i)){const e=o(i,"utf-8").trim(),t=parseInt(e,10);if(!Number.isNaN(t)&&t!==process.pid)try{process.kill(t,0),process.kill(t,"SIGTERM");const n=Date.now()+3e3;let r=!0;for(;Date.now()<n;){try{process.kill(t,0)}catch{r=!1;break}const c=Date.now()+100;for(;Date.now()<c;);}if(r)try{process.kill(t,"SIGKILL")}catch{}}catch{}}s(i,`${process.pid}
2
+ `,"utf-8")}function h(){try{a(i)}catch{}}function D(){try{const e=parseInt(o(i,"utf-8").trim(),10);return Number.isNaN(e)?null:e}catch{return null}}export{D as readDaemonPid,h as removePidFile,y as writePidFile};