grix-connector 3.2.7 → 3.2.8
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.
- package/dist/adapter/claude/claude-bridge-server.js +1 -1
- package/dist/adapter/claude/claude-tools.js +1 -1
- package/dist/adapter/claude/claude-worker-client.js +1 -1
- package/dist/adapter/claude/mcp-http-launcher.js +2 -2
- package/dist/adapter/claude/result-timeout.js +1 -1
- package/dist/bridge/bridge.js +3 -3
- package/dist/core/aibot/client.js +2 -2
- package/dist/core/aibot/connection-handle.js +1 -1
- package/dist/core/aibot/connection-manager.js +1 -1
- package/dist/core/aibot/errors.js +1 -0
- package/dist/core/aibot/index.js +1 -1
- package/dist/core/file-ops/list-files.js +1 -1
- package/dist/log.js +2 -2
- package/dist/manager.js +2 -2
- package/dist/mcp/stream-http/config.js +1 -1
- package/dist/mcp/stream-http/connection-binding.js +1 -1
- package/dist/mcp/stream-http/security.js +1 -1
- package/dist/mcp/stream-http/tool-executor.js +1 -1
- package/dist/mcp/stream-http/tool-registry.js +1 -1
- package/dist/mcp/stream-http/tool-schemas.js +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
import c from"node:http";import{randomUUID as d}from"node:crypto";import{log as o}from"../../core/log/index.js";function l(t){t.writeHead(401,{"content-type":"application/json"}),t.end(JSON.stringify({error:"unauthorized"}))}function u(t){t.writeHead(404,{"content-type":"application/json"}),t.end(JSON.stringify({error:"not_found"}))}function h(t,e){t.writeHead(400,{"content-type":"application/json"}),t.end(JSON.stringify({error:e}))}function p(t,e={ok:!0}){t.writeHead(200,{"content-type":"application/json"}),t.end(JSON.stringify(e))}async function v(t){const e=[];for await(const
|
|
1
|
+
import c from"node:http";import{randomUUID as d}from"node:crypto";import{log as o}from"../../core/log/index.js";function l(t){t.writeHead(401,{"content-type":"application/json"}),t.end(JSON.stringify({error:"unauthorized"}))}function u(t){t.writeHead(404,{"content-type":"application/json"}),t.end(JSON.stringify({error:"not_found"}))}function h(t,e){t.writeHead(400,{"content-type":"application/json"}),t.end(JSON.stringify({error:e}))}function p(t,e={ok:!0}){t.writeHead(200,{"content-type":"application/json"}),t.end(JSON.stringify(e))}async function v(t){const e=[];for await(const r of t)e.push(r);const n=Buffer.concat(e).toString("utf8").trim();return n?JSON.parse(n):{}}function k(t){const e=(t.headers.authorization??"").trim();return e.toLowerCase().startsWith("bearer ")?e.slice(7).trim():""}class w{host="127.0.0.1";port=0;token;callbacks;server=null;address=null;constructor(e){this.token=d(),this.callbacks=e}getToken(){return this.token}getURL(){return this.address?`http://${this.address.address}:${this.address.port}`:""}async start(){this.server||(this.server=c.createServer(async(e,n)=>{try{await this.handleRequest(e,n)}catch(r){h(n,r instanceof Error?r.message:String(r))}}),await new Promise((e,n)=>{this.server.once("error",n),this.server.listen(this.port,this.host,()=>{this.server.off("error",n),e()})}),this.address=this.server.address(),o.info("claude-bridge",`Bridge server listening on ${this.getURL()}`))}async stop(){if(!this.server)return;const e=this.server;this.server=null,this.address=null,e.closeIdleConnections?.(),e.closeAllConnections?.(),await new Promise((n,r)=>{e.close(s=>s?r(s):n())})}async handleRequest(e,n){if(k(e)!==this.token){l(n);return}if(e.method!=="POST"){n.writeHead(405,{"content-type":"application/json"}),n.end(JSON.stringify({error:"method_not_allowed"}));return}const r=new URL(e.url,"http://localhost").pathname,s=await v(e),i=f.get(r);if(!i){u(n);return}const a=await i(this.callbacks,s);p(n,a??{ok:!0})}}const f=new Map([["/v1/worker/register",async(t,e)=>(o.info("claude-bridge",`Worker registered: ${e.worker_id} (pid=${e.pid})`),t.onRegisterWorker(e))],["/v1/worker/status",async(t,e)=>(o.info("claude-bridge",`Worker status: ${e.status}`),t.onStatusUpdate(e))],["/v1/worker/send-text",async(t,e)=>t.onSendText(e)],["/v1/worker/send-stream-chunk",async(t,e)=>t.onSendStreamChunk(e)],["/v1/worker/send-media",async(t,e)=>t.onSendMedia(e)],["/v1/worker/delete-message",async(t,e)=>t.onDeleteMessage(e)],["/v1/worker/ack-event",async(t,e)=>t.onAckEvent(e)],["/v1/worker/event-result",async(t,e)=>t.onSendEventResult(e)],["/v1/worker/event-stop-ack",async(t,e)=>t.onSendEventStopAck(e)],["/v1/worker/event-stop-result",async(t,e)=>t.onSendEventStopResult(e)],["/v1/worker/session-composing",async(t,e)=>t.onSetSessionComposing(e)],["/v1/worker/agent-invoke",async(t,e)=>t.onAgentInvoke(e)],["/v1/worker/local-action-result",async(t,e)=>t.onLocalActionResult(e)]]);export{w as ClaudeBridgeServer};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{randomUUID as x}from"node:crypto";import{CallToolRequestSchema as S,ListToolsRequestSchema as w}from"@modelcontextprotocol/sdk/types.js";import{log as f}from"../../core/log/index.js";import{toolCallToInvoke as k}from"../../core/mcp/tools.js";const E=new Set(["contact_search","session_search","message_history","message_search","group_create","group_detail_read","group_leave_self","group_member_add","group_member_remove","group_member_role_update","group_all_members_muted_update","group_member_speaking_update","group_dissolve","send_msg","delete_msg","agent_api_create","agent_category_list","agent_category_create","agent_category_update","agent_category_assign","agent_api_key_rotate"]),y=3e4,I=[{name:"reply",description:"Send a visible message back to the chat for this grix-claude event.",inputSchema:{type:"object",properties:{text:{type:"string",description:"The visible reply text to send."},chat_id:{type:"string",description:"The target chat/session id from the <channel> tag."},event_id:{type:"string",description:"The Aibot event_id from the <channel> tag."},reply_to:{type:"string",description:"Optional message_id to quote instead of the inbound trigger message."},files:{type:"array",items:{type:"string"},description:"Optional absolute local file paths. Each file is uploaded through Agent API OSS presign before sending."},final:{type:"boolean",description:"Whether this is the final reply for the event. Defaults to false \u2014 the event stays open while Claude continues working, and auto-completes after inactivity. Set true only when this is definitively the last message for the event."}},required:["chat_id","event_id"]}},{name:"complete",description:"Finish an event without sending a visible reply so the backend does not time out.",inputSchema:{type:"object",properties:{event_id:{type:"string",description:"The Aibot event_id from the <channel> tag."},status:{type:"string",enum:["responded","canceled","failed"]},code:{type:"string"},msg:{type:"string"}},required:["event_id","status"]}},{name:"delete_message",description:"Delete a previously sent message in the same grix-claude chat.",inputSchema:{type:"object",properties:{chat_id:{type:"string"},message_id:{type:"string"}},required:["chat_id","message_id"]}},{name:"status",description:"Show grix-claude runtime status, upstream access state, bridge health, and startup hints.",inputSchema:{type:"object",properties:{}}},{name:"send",description:"Send a message to a chat session proactively, without requiring an inbound event. Use for notifications or scheduled reports.",inputSchema:{type:"object",properties:{chat_id:{type:"string",description:"The target chat/session id."},text:{type:"string",description:"The message text to send."}},required:["chat_id","text"]}},{name:"access_pair",description:"Forward a sender pairing approval code to upstream access control.",inputSchema:{type:"object",properties:{code:{type:"string"}},required:["code"]}},{name:"access_deny",description:"Forward a sender pairing denial code to upstream access control.",inputSchema:{type:"object",properties:{code:{type:"string"}},required:["code"]}},{name:"allow_sender",description:"Ask upstream access control to allow a sender_id.",inputSchema:{type:"object",properties:{sender_id:{type:"string"}},required:["sender_id"]}},{name:"remove_sender",description:"Ask upstream access control to remove a sender_id.",inputSchema:{type:"object",properties:{sender_id:{type:"string"}},required:["sender_id"]}},{name:"access_policy",description:"Ask upstream access control to update the sender access policy.",inputSchema:{type:"object",properties:{policy:{type:"string",enum:["allowlist","open","disabled"]}},required:["policy"]}},{name:"grix_query",description:"Search contacts, sessions, message history, or messages by keyword in the Grix/AIBot platform.",inputSchema:{type:"object",properties:{action:{type:"string",enum:["contact_search","session_search","message_history","message_search"]},keyword:{type:"string"},id:{type:"string"},sessionId:{type:"string"},limit:{type:"number"},offset:{type:"number"},beforeId:{type:"string"}},required:["action"]}},{name:"grix_group",description:"Manage groups in the Grix/AIBot platform: create, get details, leave, dissolve, manage members and permissions.",inputSchema:{type:"object",properties:{action:{type:"string",enum:["create","detail","leave","add_members","remove_members","update_member_role","update_all_members_muted","update_member_speaking","dissolve"]},sessionId:{type:"string"},name:{type:"string"},memberIds:{type:"array",items:{type:"string"}},memberTypes:{type:"array",items:{type:"integer",enum:[1,2]}},memberId:{type:"string"},role:{type:"integer",enum:[1,2]},memberType:{type:"integer",description:"Member type (for update_member_role / update_member_speaking)."},allMembersMuted:{type:"boolean"},isSpeakMuted:{type:"boolean"},canSpeakWhenAllMuted:{type:"boolean",description:"Allow speaking when all muted (for update_member_speaking)."}},required:["action"]}},{name:"grix_message_send",description:"Send a message to a session in the Grix/AIBot platform.",inputSchema:{type:"object",properties:{sessionId:{type:"string"},content:{type:"string"},msgType:{type:"number"},quotedMessageId:{type:"string"},threadId:{type:"string"}},required:["sessionId","content"]}},{name:"grix_message_unsend",description:"Recall/unsend a message in the Grix/AIBot platform.",inputSchema:{type:"object",properties:{sessionId:{type:"string"},msgId:{type:"string"}},required:["sessionId","msgId"]}},{name:"grix_admin",description:"Agent and category management in the Grix/AIBot platform: create agents, manage categories, rotate API keys.",inputSchema:{type:"object",properties:{action:{type:"string",enum:["create_agent","list_categories","create_category","update_category","assign_category","rotate_api_key"]},agentId:{type:"string"},agentName:{type:"string"},introduction:{type:"string"},isMain:{type:"boolean"},categoryId:{type:"string"},name:{type:"string"},parentId:{type:"string"},sortOrder:{type:"number"}},required:["action"]}}];function q(n,e){n.setRequestHandler(w,async()=>({tools:I})),n.setRequestHandler(S,async i=>{const{name:r,arguments:t}=i.params,s=t??{};try{switch(r){case"reply":return await A(s,e);case"complete":return await $(s,e);case"delete_message":return await T(s,e);case"status":return j(e);case"send":return await M(s,e);case"access_pair":case"access_deny":case"allow_sender":case"remove_sender":case"access_policy":return await R(r,s,e);case"grix_query":case"grix_group":case"grix_message_send":case"grix_message_unsend":case"grix_admin":return await O(r,s,e);default:return{content:[{type:"text",text:`Unknown tool: ${r}`}],isError:!0}}}catch(a){return f.error("claude-tools",`Tool ${r} error: ${a}`),{content:[{type:"text",text:`Error: ${a instanceof Error?a.message:String(a)}`}],isError:!0}}})}async function A(n,e){const i=e.getActiveEvent();if(!i)return{content:[{type:"text",text:"No active event to reply to"}],isError:!0};const r=String(n.text??""),t=String(n.chat_id??""),s=String(n.event_id??i.eventId),a=n.reply_to,d=n.files,_=n.final===!0;if(!t||!s)return{content:[{type:"text",text:"reply requires chat_id and event_id"}],isError:!0};if(!r.trim()&&(!d||d.length===0))return{content:[{type:"text",text:"reply requires at least one of text or files"}],isError:!0};const{text:g,quotedMessageId:v}=e.resolveQuotedMessageId(a,r),b=[];let m=0;const h=`reply_${s}_${Date.now()}`;try{if(g){const p=e.splitText(g);for(let o=0;o<p.length;o++){if(!e.isEventActive(s))return c("ignored: event no longer active");m++,e.bridge.sendStreamChunk(s,t,p[o],++i.chunkSeq,!1,h)}}if(d&&d.length>0)for(const p of d){if(!e.isEventActive(s))return c("ignored: event no longer active");m++;const o=await e.uploadFile(p,t),l=`${x()}_${m}`;e.bridge.sendMedia(s,t,o.access_url,o.file_name,v,l,o.extra),f.info("claude-tools",`File sent: ${o.file_name}`)}e.bridge.sendStreamChunk(s,t,"",++i.chunkSeq,!0,h)}catch(p){if(g&&b.length===0)try{const o=`fallback_${s}_${Date.now()}`,l=e.splitText(g);for(let u=0;u<l.length;u++)e.bridge.sendStreamChunk("",t,l[u],u+1,!1,o);return e.bridge.sendStreamChunk("",t,"",l.length+1,!0,o),e.markReplySent(s),_&&e.finalizeEvent(s,"responded"),c("sent via fallback")}catch{}if(!e.isEventActive(s))return c("ignored: event no longer active");throw e.bridge.sendEventResult(s,"failed",String(p),"send_msg_failed"),p}return e.markReplySent(s),_?e.finalizeEvent(s,"responded"):(i.responded=!0,e.clearActiveEvent("completed")),c("Reply sent")}async function $(n,e){const i=e.getActiveEvent(),r=String(n.event_id??""),t=n.status??"",s=n.code,a=n.msg;if(!r||!t)return{content:[{type:"text",text:"complete requires event_id and status"}],isError:!0};const d=["responded","canceled","failed"];return d.includes(t)?e.isEventActive(r)?(e.bridge.sendEventResult(r,t,a,s),e.clearActiveEvent(t),c("Event completed")):c("ignored: event no longer active"):{content:[{type:"text",text:`status must be one of: ${d.join(", ")}`}],isError:!0}}async function T(n,e){const i=String(n.chat_id??""),r=String(n.message_id??"");if(!i||!r)return{content:[{type:"text",text:"chat_id and message_id are required"}],isError:!0};try{return await e.bridge.agentInvoke("grix_message_unsend",{sessionId:i,msgId:r},y),c(`deleted (${r})`)}catch(t){return{content:[{type:"text",text:`Delete failed: ${t}`}],isError:!0}}}function j(n){const e=n.getStatusInfo();return{content:[{type:"text",text:JSON.stringify({alive:e.alive,active_event:e.activeEvent,pending_approvals:e.pendingPermissions,pending_questions:e.pendingElicitations})}]}}async function M(n,e){const i=String(n.chat_id??""),r=String(n.text??"");if(!i||!r)return{content:[{type:"text",text:"chat_id and text are required"}],isError:!0};try{const t=e.splitText(r),s=`send_${i}_${Date.now()}`;for(let a=0;a<t.length;a++)e.bridge.sendStreamChunk("",i,t[a],a+1,!1,s);return e.bridge.sendStreamChunk("",i,"",t.length+1,!0,s),c("sent")}catch(t){return{content:[{type:"text",text:`Send failed: ${t}`}],isError:!0}}}const C={access_pair:{verb:"pair_approve",payloadKey:"code"},access_deny:{verb:"pair_deny",payloadKey:"code"},allow_sender:{verb:"sender_allow",payloadKey:"sender_id"},remove_sender:{verb:"sender_remove",payloadKey:"sender_id"},access_policy:{verb:"policy_set",payloadKey:"policy"}};async function R(n,e,i){try{const r=C[n];if(!r)throw new Error(`Unknown access control tool: ${n}`);const t={};e.code!=null&&(t.code=e.code),e.sender_id!=null&&(t.sender_id=e.sender_id),e.policy!=null&&(t.policy=e.policy);const s=await i.bridge.agentInvoke("claude_access_control",{verb:r.verb,payload:t},y);return{content:[{type:"text",text:typeof s=="string"?s:JSON.stringify(s)}]}}catch(r){return{content:[{type:"text",text:`${n} failed: ${r}`}],isError:!0}}}async function O(n,e,i){try{const r=k(n,e);if(!E.has(r.action))throw new Error(`Action not allowed: ${r.action}`);const t=await i.bridge.agentInvoke(r.action,r.params,y);if(t&&Number(t.code??0)!==0)throw new Error(String(t.msg??"invoke failed"));return{content:[{type:"text",text:t?.data!=null?typeof t.data=="string"?t.data:JSON.stringify(t.data):JSON.stringify(t)}]}}catch(r){return{content:[{type:"text",text:`${n} failed: ${r}`}],isError:!0}}}function c(n){return{content:[{type:"text",text:n}]}}export{q as registerClaudeTools};
|
|
1
|
+
import{randomUUID as x}from"node:crypto";import{CallToolRequestSchema as S,ListToolsRequestSchema as w}from"@modelcontextprotocol/sdk/types.js";import{log as f}from"../../core/log/index.js";import{toolCallToInvoke as k}from"../../core/mcp/tools.js";const E=new Set(["contact_search","session_search","message_history","message_search","group_create","group_detail_read","group_leave_self","group_member_add","group_member_remove","group_member_role_update","group_all_members_muted_update","group_member_speaking_update","group_dissolve","send_msg","delete_msg","agent_api_create","agent_category_list","agent_category_create","agent_category_update","agent_category_assign","agent_api_key_rotate"]),y=3e4,I=[{name:"reply",description:"Send a visible message back to the chat for this grix-claude event.",inputSchema:{type:"object",properties:{text:{type:"string",description:"The visible reply text to send."},chat_id:{type:"string",description:"The target chat/session id from the <channel> tag."},event_id:{type:"string",description:"The Aibot event_id from the <channel> tag."},reply_to:{type:"string",description:"Optional message_id to quote instead of the inbound trigger message."},files:{type:"array",items:{type:"string"},description:"Optional absolute local file paths. Each file is uploaded through Agent API OSS presign before sending."},final:{type:"boolean",description:"Whether this is the final reply for the event. Defaults to false \u2014 the event stays open while Claude continues working, and auto-completes after inactivity. Set true only when this is definitively the last message for the event."}},required:["chat_id","event_id"]}},{name:"complete",description:"Finish an event without sending a visible reply so the backend does not time out.",inputSchema:{type:"object",properties:{event_id:{type:"string",description:"The Aibot event_id from the <channel> tag."},status:{type:"string",enum:["responded","canceled","failed"]},code:{type:"string"},msg:{type:"string"}},required:["event_id","status"]}},{name:"delete_message",description:"Delete a previously sent message in the same grix-claude chat.",inputSchema:{type:"object",properties:{chat_id:{type:"string"},message_id:{type:"string"}},required:["chat_id","message_id"]}},{name:"status",description:"Show grix-claude runtime status, upstream access state, bridge health, and startup hints.",inputSchema:{type:"object",properties:{}}},{name:"send",description:"Send a message to a chat session proactively, without requiring an inbound event. Use for notifications or scheduled reports.",inputSchema:{type:"object",properties:{chat_id:{type:"string",description:"The target chat/session id."},text:{type:"string",description:"The message text to send."}},required:["chat_id","text"]}},{name:"access_pair",description:"Forward a sender pairing approval code to upstream access control.",inputSchema:{type:"object",properties:{code:{type:"string"}},required:["code"]}},{name:"access_deny",description:"Forward a sender pairing denial code to upstream access control.",inputSchema:{type:"object",properties:{code:{type:"string"}},required:["code"]}},{name:"allow_sender",description:"Ask upstream access control to allow a sender_id.",inputSchema:{type:"object",properties:{sender_id:{type:"string"}},required:["sender_id"]}},{name:"remove_sender",description:"Ask upstream access control to remove a sender_id.",inputSchema:{type:"object",properties:{sender_id:{type:"string"}},required:["sender_id"]}},{name:"access_policy",description:"Ask upstream access control to update the sender access policy.",inputSchema:{type:"object",properties:{policy:{type:"string",enum:["allowlist","open","disabled"]}},required:["policy"]}},{name:"grix_query",description:"Search contacts, sessions, message history, or messages by keyword in the Grix/AIBot platform.",inputSchema:{type:"object",properties:{action:{type:"string",enum:["contact_search","session_search","message_history","message_search"]},keyword:{type:"string"},id:{type:"string"},sessionId:{type:"string"},limit:{type:"number"},offset:{type:"number"},beforeId:{type:"string"}},required:["action"]}},{name:"grix_group",description:"Manage groups in the Grix/AIBot platform: create, get details, leave, dissolve, manage members and permissions.",inputSchema:{type:"object",properties:{action:{type:"string",enum:["create","detail","leave","add_members","remove_members","update_member_role","update_all_members_muted","update_member_speaking","dissolve"]},sessionId:{type:"string"},name:{type:"string"},memberIds:{type:"array",items:{type:"string"}},memberTypes:{type:"array",items:{type:"integer",enum:[1,2]}},memberId:{type:"string"},role:{type:"integer",enum:[1,2]},memberType:{type:"integer",description:"Member type (for update_member_role / update_member_speaking)."},allMembersMuted:{type:"boolean"},isSpeakMuted:{type:"boolean"},canSpeakWhenAllMuted:{type:"boolean",description:"Allow speaking when all muted (for update_member_speaking)."}},required:["action"]}},{name:"grix_message_send",description:"Send a message to a session in the Grix/AIBot platform.",inputSchema:{type:"object",properties:{sessionId:{type:"string"},content:{type:"string"},msgType:{type:"number"},quotedMessageId:{type:"string"},threadId:{type:"string"}},required:["sessionId","content"]}},{name:"grix_message_unsend",description:"Recall/unsend a message in the Grix/AIBot platform.",inputSchema:{type:"object",properties:{sessionId:{type:"string"},msgId:{type:"string"}},required:["sessionId","msgId"]}},{name:"grix_admin",description:"Agent and category management in the Grix/AIBot platform: create agents, manage categories, rotate API keys.",inputSchema:{type:"object",properties:{action:{type:"string",enum:["create_agent","list_categories","create_category","update_category","assign_category","rotate_api_key"]},agentId:{type:"string"},agentName:{type:"string"},introduction:{type:"string"},isMain:{type:"boolean"},categoryId:{type:"string"},name:{type:"string"},parentId:{type:"string"},sortOrder:{type:"number"}},required:["action"]}}];function q(r,e){r.setRequestHandler(w,async()=>({tools:I})),r.setRequestHandler(S,async i=>{const{name:n,arguments:t}=i.params,s=t??{};try{switch(n){case"reply":return await A(s,e);case"complete":return await $(s,e);case"delete_message":return await T(s,e);case"status":return j(e);case"send":return await M(s,e);case"access_pair":case"access_deny":case"allow_sender":case"remove_sender":case"access_policy":return await R(n,s,e);case"grix_query":case"grix_group":case"grix_message_send":case"grix_message_unsend":case"grix_admin":return await O(n,s,e);default:return{content:[{type:"text",text:`Unknown tool: ${n}`}],isError:!0}}}catch(a){return f.error("claude-tools",`Tool ${n} error: ${a}`),{content:[{type:"text",text:`Error: ${a instanceof Error?a.message:String(a)}`}],isError:!0}}})}async function A(r,e){const i=e.getActiveEvent();if(!i)return{content:[{type:"text",text:"No active event to reply to"}],isError:!0};const n=String(r.text??""),t=String(r.chat_id??""),s=String(r.event_id??i.eventId),a=r.reply_to,d=r.files,_=r.final===!0;if(!t||!s)return{content:[{type:"text",text:"reply requires chat_id and event_id"}],isError:!0};if(!n.trim()&&(!d||d.length===0))return{content:[{type:"text",text:"reply requires at least one of text or files"}],isError:!0};const{text:g,quotedMessageId:v}=e.resolveQuotedMessageId(a,n),b=[];let m=0;const h=`reply_${s}_${Date.now()}`;try{if(g){const p=e.splitText(g);for(let o=0;o<p.length;o++){if(!e.isEventActive(s))return c("ignored: event no longer active");m++,e.bridge.sendStreamChunk(s,t,p[o],++i.chunkSeq,!1,h)}}if(d&&d.length>0)for(const p of d){if(!e.isEventActive(s))return c("ignored: event no longer active");m++;const o=await e.uploadFile(p,t),l=`${x()}_${m}`;e.bridge.sendMedia(s,t,o.access_url,o.file_name,v,l,o.extra),f.info("claude-tools",`File sent: ${o.file_name}`)}e.bridge.sendStreamChunk(s,t,"",++i.chunkSeq,!0,h)}catch(p){if(g&&b.length===0)try{const o=`fallback_${s}_${Date.now()}`,l=e.splitText(g);for(let u=0;u<l.length;u++)e.bridge.sendStreamChunk("",t,l[u],u+1,!1,o);return e.bridge.sendStreamChunk("",t,"",l.length+1,!0,o),e.markReplySent(s),_&&e.finalizeEvent(s,"responded"),c("sent via fallback")}catch{}if(!e.isEventActive(s))return c("ignored: event no longer active");throw e.bridge.sendEventResult(s,"failed",String(p),"send_msg_failed"),p}return e.markReplySent(s),_?e.finalizeEvent(s,"responded"):(i.responded=!0,e.clearActiveEvent("completed")),c("Reply sent")}async function $(r,e){const i=e.getActiveEvent(),n=String(r.event_id??""),t=r.status??"",s=r.code,a=r.msg;if(!n||!t)return{content:[{type:"text",text:"complete requires event_id and status"}],isError:!0};const d=["responded","canceled","failed"];return d.includes(t)?e.isEventActive(n)?(e.bridge.sendEventResult(n,t,a,s),e.clearActiveEvent(t),c("Event completed")):c("ignored: event no longer active"):{content:[{type:"text",text:`status must be one of: ${d.join(", ")}`}],isError:!0}}async function T(r,e){const i=String(r.chat_id??""),n=String(r.message_id??"");if(!i||!n)return{content:[{type:"text",text:"chat_id and message_id are required"}],isError:!0};try{return await e.bridge.agentInvoke("grix_message_unsend",{sessionId:i,msgId:n},y),c(`deleted (${n})`)}catch(t){return{content:[{type:"text",text:`Delete failed: ${t}`}],isError:!0}}}function j(r){const e=r.getStatusInfo();return{content:[{type:"text",text:JSON.stringify({alive:e.alive,active_event:e.activeEvent,pending_approvals:e.pendingPermissions,pending_questions:e.pendingElicitations})}]}}async function M(r,e){const i=String(r.chat_id??""),n=String(r.text??"");if(!i||!n)return{content:[{type:"text",text:"chat_id and text are required"}],isError:!0};try{const t=e.splitText(n),s=`send_${i}_${Date.now()}`;for(let a=0;a<t.length;a++)e.bridge.sendStreamChunk("",i,t[a],a+1,!1,s);return e.bridge.sendStreamChunk("",i,"",t.length+1,!0,s),c("sent")}catch(t){return{content:[{type:"text",text:`Send failed: ${t}`}],isError:!0}}}const C={access_pair:{verb:"pair_approve",payloadKey:"code"},access_deny:{verb:"pair_deny",payloadKey:"code"},allow_sender:{verb:"sender_allow",payloadKey:"sender_id"},remove_sender:{verb:"sender_remove",payloadKey:"sender_id"},access_policy:{verb:"policy_set",payloadKey:"policy"}};async function R(r,e,i){try{const n=C[r];if(!n)throw new Error(`Unknown access control tool: ${r}`);const t={};e.code!=null&&(t.code=e.code),e.sender_id!=null&&(t.sender_id=e.sender_id),e.policy!=null&&(t.policy=e.policy);const s=await i.bridge.agentInvoke("claude_access_control",{verb:n.verb,payload:t},y);return{content:[{type:"text",text:typeof s=="string"?s:JSON.stringify(s)}]}}catch(n){return{content:[{type:"text",text:`${r} failed: ${n}`}],isError:!0}}}async function O(r,e,i){try{const n=k(r,e);if(!E.has(n.action))throw new Error(`Action not allowed: ${n.action}`);const t=await i.bridge.agentInvoke(n.action,n.params,y);if(t&&Number(t.code??0)!==0)throw new Error(String(t.msg??"invoke failed"));return{content:[{type:"text",text:t?.data!=null?typeof t.data=="string"?t.data:JSON.stringify(t.data):JSON.stringify(t)}]}}catch(n){return{content:[{type:"text",text:`${r} failed: ${n}`}],isError:!0}}}function c(r){return{content:[{type:"text",text:r}]}}export{q as registerClaudeTools};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{log as l}from"../../core/log/index.js";class c{controlURL="";token="";isConfigured(){return!!(this.controlURL&&this.token)}configure(
|
|
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
|
|
2
|
-
`),"utf8"),{expectPath:a,pidPath:i}}function h(
|
|
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(
|
|
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};
|
package/dist/bridge/bridge.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import T from"node:path";import{realpath as j,stat as H}from"node:fs/promises";import{tmpdir as V}from"node:os";import{randomUUID as M}from"node:crypto";import{ConnectionManager as Y}from"../core/aibot/index.js";import{ClaudeAdapter as y}from"../adapter/claude/index.js";import{CodexAdapter as J}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 I}from"../adapter/cursor/index.js";import{CodeWhaleAdapter as U}from"../adapter/codewhale/index.js";import{OpenCodeAdapter as ee}from"../adapter/opencode/index.js";import{AgyAdapter as O}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{resolveSkillScanMode as se}from"../adapter/claude/skill-scanner.js";import{buildReportedSkills as oe}from"../default-skills/index.js";import{fetchAvailableModels as D,getCachedModels as Q,readSettingsEnv as re}from"../adapter/claude/model-list.js";import{parseAcpSessionUsage as ae}from"../adapter/acp/usage-parser.js";import{applyProxyEnv as de,getProxyManager as ce}from"../core/proxy/index.js";import{buildProviderEnv as le}from"../core/config/provider-env.js";import{parseCodexSessionUsage as ue}from"../adapter/codex/usage-parser.js";import{readCodexProviderSettings as he}from"../adapter/codex/codex-trust.js";import{scanCodexSessions as ge}from"../adapter/codex/session-scanner.js";import{scanClaudeSessions as me}from"../adapter/claude/session-scanner.js";import{scanAcpSessions as pe}from"../adapter/acp/session-scanner.js";import{parsePiSessionUsage as fe}from"../adapter/pi/usage-parser.js";import{SessionScanCache as L,resolveCodexLeafDirs as _e,resolveClaudeLeafDirs as ve,resolveAcpLeafDirs as be}from"./session-scan-cache.js";import{log as u,ConversationLog as Se,AgentApiPacketLog as we,BridgeEventLog as Ce}from"../core/log/index.js";import{RevokeHandler as Ae}from"./revoke-handler.js";import{AdapterPool as ke,PoolFullError as Re}from"./adapter-pool.js";import{parseSessionControlCommand as Ee,handleSessionControlCommand as q,handleSessionControlLocalAction as $e}from"./session-controller.js";import{handleAcpSetModel as W,handleAcpSetMode as N,resolveAcpInitialDefaults as ye}from"./acp-toolbar-persist.js";import{SessionBindingStore as xe}from"../core/persistence/session-binding-store.js";import{handleFileListAction as Te,handleCreateFolderAction as Le,serveLocalFile as Pe,realHomeDir as K}from"../core/files/index.js";import{getMachineName as He}from"../core/util/index.js";import{uploadReplyFileToAgentMedia as Me}from"../core/protocol/agent-api-media.js";import{AllowlistGate as Ie}from"../core/access/allowlist-gate.js";import{ActiveEventStore as De}from"../core/persistence/active-event-store.js";import{DEFAULT_CONNECTOR_RUNTIME_CONFIG as Qe,applyConnectorRuntimeConfigPatch as Be,extractConnectorRuntimeConfigPatch as Fe}from"./runtime-config.js";import{SendController as Ue}from"./send-controller.js";import{queryProviderQuota as z}from"../core/provider-quota/index.js";import{queryKiroQuota as B}from"../core/provider-quota/kiro.js";import{buildToolUseCard as E,buildToolResultCard as $,buildLocalGrixCardLink as Oe}from"./tool-card-utils.js";import{DeferredEventManager as qe}from"./deferred-events.js";import{buildAgentProbeResult as We,PROBE_CACHE_TTL_STATIC_MS as Ne,PROBE_CACHE_TTL_FULL_MS as Ke}from"./probe-helper.js";const ze=600*1e3,Ge=60*1e3,G=30*1e3,je=10*1e3,P=new Set(["claude","acp","agy","cursor","codex"]),Ve=new Set(["claude","codex","cursor","codewhale","opencode","pi","openhuman","agy","acp"]),F=3;class zt{config;name;aibotHandle;aibotConfig;pool;stopped=!1;revokeHandler=new Ae;sessionBindings=new Map;deferredMgr;sendCtrl=new Ue(Qe);bindingStore;globalConfigStore;upgradeTrigger=null;shareSetHandler=null;providerConfigHandler=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;lastReportedSkillsHash="";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<=Ge}async maybeQueryProviderQuota(){if(this.config.aibot.clientType==="kiro"){if(this.isRateLimitsCacheFresh(this.cachedProviderQuotaSampledAtMs)&&this.cachedProviderQuota)return this.cachedProviderQuota;try{const t=await B();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=re();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=he();!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 z(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(()=>{})},G),u.info(this.name,`[kiro-quota-timer] started (interval=${G}ms)`))}stopKiroQuotaTimer(){this.kiroQuotaTimer&&(clearInterval(this.kiroQuotaTimer),this.kiroQuotaTimer=null,u.info(this.name,"[kiro-quota-timer] stopped"))}async refreshAndPushKiroQuota(){try{const e=await B();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?Ke:Ne;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 We({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 Se(e.logDir):null,this.packetLog=e.logDir?new we(e.logDir):null,this.bindingStore=new xe(e.bindingsPath),this.bindingStore.load(),this.globalConfigStore=o??null,this.deferredMgr=new qe(this.name),this.allowlistGate=e.allowlistPath?new Ie(e.allowlistPath):null,this.activeEventStore=e.activeEventStorePath?new De(e.activeEventStorePath):null,t==="codex"?this.sessionScanCache=new L(ge,_e):t==="claude"?this.sessionScanCache=new L(me,ve):t==="agy"?this.sessionScanCache=new L(()=>[],()=>[]):this.sessionScanCache=new L(pe,be)}async start(){(this.config.adapterType??"acp")==="claude"&&(await D().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 ke({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.reportSessionSkills(t),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()}resolveSpawnEnv(){const e=le(this.config.agent.clientType,this.config.agent.provider),o=this.config.agent.env,t=Object.keys(e).length>0?{...e,...o}:o;return de(t)}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 I({command:this.config.agent.command,args:this.config.agent.args,env:this.resolveSpawnEnv(),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=Oe(`[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_${M()}`,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.resolveSpawnEnv(),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 j,stat as H}from"node:fs/promises";import{tmpdir as V}from"node:os";import{randomUUID as M}from"node:crypto";import{ConnectionManager as Y}from"../core/aibot/index.js";import{ClaudeAdapter as y}from"../adapter/claude/index.js";import{CodexAdapter as J}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 I}from"../adapter/cursor/index.js";import{CodeWhaleAdapter as U}from"../adapter/codewhale/index.js";import{OpenCodeAdapter as ee}from"../adapter/opencode/index.js";import{AgyAdapter as O}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{resolveSkillScanMode as se}from"../adapter/claude/skill-scanner.js";import{buildReportedSkills as oe}from"../default-skills/index.js";import{fetchAvailableModels as D,getCachedModels as Q,readSettingsEnv as re}from"../adapter/claude/model-list.js";import{parseAcpSessionUsage as ae}from"../adapter/acp/usage-parser.js";import{applyProxyEnv as de,getProxyManager as ce}from"../core/proxy/index.js";import{buildProviderEnv as le}from"../core/config/provider-env.js";import{parseCodexSessionUsage as ue}from"../adapter/codex/usage-parser.js";import{readCodexProviderSettings as he}from"../adapter/codex/codex-trust.js";import{scanCodexSessions as ge}from"../adapter/codex/session-scanner.js";import{scanClaudeSessions as me}from"../adapter/claude/session-scanner.js";import{scanAcpSessions as pe}from"../adapter/acp/session-scanner.js";import{parsePiSessionUsage as fe}from"../adapter/pi/usage-parser.js";import{SessionScanCache as L,resolveCodexLeafDirs as _e,resolveClaudeLeafDirs as ve,resolveAcpLeafDirs as be}from"./session-scan-cache.js";import{log as u,ConversationLog as Se,AgentApiPacketLog as we,BridgeEventLog as Ce}from"../core/log/index.js";import{RevokeHandler as Ae}from"./revoke-handler.js";import{AdapterPool as ke,PoolFullError as Re}from"./adapter-pool.js";import{parseSessionControlCommand as Ee,handleSessionControlCommand as q,handleSessionControlLocalAction as $e}from"./session-controller.js";import{handleAcpSetModel as W,handleAcpSetMode as N,resolveAcpInitialDefaults as ye}from"./acp-toolbar-persist.js";import{SessionBindingStore as xe}from"../core/persistence/session-binding-store.js";import{handleFileListAction as Te,handleCreateFolderAction as Le,serveLocalFile as Pe,realHomeDir as K}from"../core/files/index.js";import{getMachineName as He}from"../core/util/index.js";import{uploadReplyFileToAgentMedia as Me}from"../core/protocol/agent-api-media.js";import{AllowlistGate as Ie}from"../core/access/allowlist-gate.js";import{ActiveEventStore as De}from"../core/persistence/active-event-store.js";import{DEFAULT_CONNECTOR_RUNTIME_CONFIG as Qe,applyConnectorRuntimeConfigPatch as Be,extractConnectorRuntimeConfigPatch as Fe}from"./runtime-config.js";import{SendController as Ue}from"./send-controller.js";import{queryProviderQuota as z}from"../core/provider-quota/index.js";import{queryKiroQuota as B}from"../core/provider-quota/kiro.js";import{buildToolUseCard as E,buildToolResultCard as $,buildLocalGrixCardLink as Oe}from"./tool-card-utils.js";import{DeferredEventManager as qe}from"./deferred-events.js";import{buildAgentProbeResult as We,PROBE_CACHE_TTL_STATIC_MS as Ne,PROBE_CACHE_TTL_FULL_MS as Ke}from"./probe-helper.js";const ze=600*1e3,Ge=60*1e3,G=30*1e3,je=10*1e3,P=new Set(["claude","acp","agy","cursor","codex"]),Ve=new Set(["claude","codex","cursor","codewhale","opencode","pi","openhuman","agy","acp"]),F=3;class zt{config;name;aibotHandle;aibotConfig;pool;stopped=!1;revokeHandler=new Ae;sessionBindings=new Map;deferredMgr;sendCtrl=new Ue(Qe);bindingStore;globalConfigStore;upgradeTrigger=null;agentDeletedHandler=null;shareSetHandler=null;providerConfigHandler=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;lastReportedSkillsHash="";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<=Ge}async maybeQueryProviderQuota(){if(this.config.aibot.clientType==="kiro"){if(this.isRateLimitsCacheFresh(this.cachedProviderQuotaSampledAtMs)&&this.cachedProviderQuota)return this.cachedProviderQuota;try{const t=await B();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=re();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=he();!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 z(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(()=>{})},G),u.info(this.name,`[kiro-quota-timer] started (interval=${G}ms)`))}stopKiroQuotaTimer(){this.kiroQuotaTimer&&(clearInterval(this.kiroQuotaTimer),this.kiroQuotaTimer=null,u.info(this.name,"[kiro-quota-timer] stopped"))}async refreshAndPushKiroQuota(){try{const e=await B();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?Ke:Ne;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 We({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 Se(e.logDir):null,this.packetLog=e.logDir?new we(e.logDir):null,this.bindingStore=new xe(e.bindingsPath),this.bindingStore.load(),this.globalConfigStore=o??null,this.deferredMgr=new qe(this.name),this.allowlistGate=e.allowlistPath?new Ie(e.allowlistPath):null,this.activeEventStore=e.activeEventStorePath?new De(e.activeEventStorePath):null,t==="codex"?this.sessionScanCache=new L(ge,_e):t==="claude"?this.sessionScanCache=new L(me,ve):t==="agy"?this.sessionScanCache=new L(()=>[],()=>[]):this.sessionScanCache=new L(pe,be)}async start(){(this.config.adapterType??"acp")==="claude"&&(await D().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 ke({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.reportSessionSkills(t),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()}resolveSpawnEnv(){const e=le(this.config.agent.clientType,this.config.agent.provider),o=this.config.agent.env,t=Object.keys(e).length>0?{...e,...o}:o;return de(t)}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 I({command:this.config.agent.command,args:this.config.agent.args,env:this.resolveSpawnEnv(),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=Oe(`[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_${M()}`,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.resolveSpawnEnv(),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 J({command:this.config.agent.command,args:this.config.agent.args,env:this.resolveSpawnEnv(),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 U({command:this.config.agent.command,args:this.config.agent.args,env:this.resolveSpawnEnv(),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,9 +8,9 @@ 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.resolveSpawnEnv(),options:t},o,{port:t.port,hostname:t.hostname,model:t.model,agent:t.agent,permissionPolicy:t.permission_policy,enableSessionBinding:!0,aibotSessionId:e,bindingStore:this.bindingStore,...this.config.agent.provider?{provider:this.config.agent.provider}:{}})}createAgyAdapter(e){const o={sendStreamChunk:(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 O({command:this.config.agent.command,args:this.config.agent.args,env:this.resolveSpawnEnv(),options:this.config.adapterOptions??{}},o,t)}createAcpAdapter(e){const o=this.isAcpRawTransportEnabled(),t={sendStreamChunk:(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_${M()}`,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}=ye({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.resolveSpawnEnv()},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 Ce(this.config.logDir,`${this.name}-${e}`):null})}async connectAibot(){const e=new Y;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"?Pe(o):e==="file_upload"?this.uploadFileAndSendMedia(o):this.aibotHandle.agentInvoke(e,o,t)}async uploadFileAndSendMedia(e){const o=String(e.file_path??"").trim(),t=String(e.session_id??"").trim(),n=String(e.caption??"").trim(),i=String(e.reply_to_message_id??"").trim();if(!o)throw new Error("file_path is required");if(!t)throw new Error("session_id is required");const s=await Me({wsURL:this.config.aibot.url,apiKey:this.config.aibot.apiKey,sessionID:t,filePath:o}),r=await this.aibotHandle.sendMedia({session_id:t,msg_type:2,content:n||`[${s.attachment_type}]`,client_msg_id:`file_upload_${M()}`,...i?{quoted_message_id:i}:{},extra:s.extra});if(r.cmd!=="send_ack"){const d=r.payload??{},c=String(d.msg??r.cmd);throw new Error(`media message send failed: ${c}`)}const a=r.payload??{};return{ok:!0,file_name:s.file_name,attachment_type:s.attachment_type,access_url:s.access_url,message_id:a.msg_id!=null?String(a.msg_id):null}}sendReplyByRuntimeConfig(e,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_${M()}`,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}=ye({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.resolveSpawnEnv()},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 Ce(this.config.logDir,`${this.name}-${e}`):null})}async connectAibot(){const e=new Y;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.onAgentDeleted(t=>{u.warn(this.name,`agent deleted on platform (source=${t?.source??"unknown"}${t?.reason?` reason=${t.reason}`:""}), notifying manager to clean up`);try{this.agentDeletedHandler?.()}catch(n){u.error(this.name,`agentDeletedHandler 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"?Pe(o):e==="file_upload"?this.uploadFileAndSendMedia(o):this.aibotHandle.agentInvoke(e,o,t)}async uploadFileAndSendMedia(e){const o=String(e.file_path??"").trim(),t=String(e.session_id??"").trim(),n=String(e.caption??"").trim(),i=String(e.reply_to_message_id??"").trim();if(!o)throw new Error("file_path is required");if(!t)throw new Error("session_id is required");const s=await Me({wsURL:this.config.aibot.url,apiKey:this.config.aibot.apiKey,sessionID:t,filePath:o}),r=await this.aibotHandle.sendMedia({session_id:t,msg_type:2,content:n||`[${s.attachment_type}]`,client_msg_id:`file_upload_${M()}`,...i?{quoted_message_id:i}:{},extra:s.extra});if(r.cmd!=="send_ack"){const d=r.payload??{},c=String(d.msg??r.cmd);throw new Error(`media message send failed: ${c}`)}const a=r.payload??{};return{ok:!0,file_name:s.file_name,attachment_type:s.attachment_type,access_url:s.access_url,message_id:a.msg_id!=null?String(a.msg_id):null}}sendReplyByRuntimeConfig(e,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>F){u.error(this.name,`[recovery] adapter=${r} session=${t} event=${o} restart=${s}/${F} 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}/${F} 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=Fe(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=Ee(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(q(i,e,this.sessionControlCtx(e.session_id),{...this.sessionControlSenders(),sendEventAck:()=>{}}),i.verb===p.open&&(await this.deferredMgr.release(e.session_id,this.deferredCallbacks()),o==="agy")){const 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(Ve.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 Re)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),q(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=Q(),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 j(t)}catch{return t}}getClaudeWorkerStatus(e){const o=this.pool.getSlot(e);return o?o.state==="starting"?"starting":o.state==="stopped"?"stopped":o.adapter.getStatus().busy?"busy":"ready":"stopped"}refreshClaudeWorkerStatusCard(e,o){const t=this.getClaudeWorkerStatus(e);return this.claudeWorkerStatus.set(e,t),this.aibotHandle.sendUpdateBindingCard({session_id:e,worker_status:t,cwd:o,meta:this.buildClaudeToolbarMeta(e)}),t}reportSessionSkills(e){const o=se(this.config.adapterType,this.config.aibot.clientType),t=this.bindingStore.get(e)?.cwd||this.sessionBindings.get(e)||void 0,n=o==="kiro"?t:t||process.cwd();let i;try{i=oe({mode:o,projectDir:n})}catch{return}if(i.length===0)return;const s=JSON.stringify(i.map(r=>`${r.source}:${r.name}`));if(s!==this.lastReportedSkillsHash){this.lastReportedSkillsHash=s;try{this.aibotHandle.sendSkillsUpdate({skills:i})}catch{}}}async ensureSlotStarted(e,o=6e4){const t=this.pool.getOrCreateSlot(e);if(!t)throw new Error("Failed to allocate session slot");t.startPromise&&(u.info(this.name,`ensureSlotStarted: awaiting startPromise for session=${e}`),await Promise.race([t.startPromise,new Promise((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(`
|
|
15
15
|
`)}`,updated_at:Date.now()})}async handleListSessionsLocalAction(e){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,bindingUpdatedAt:d.updatedAt??0})}}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),l={agentSessionId:d.threadId,cwd:d.cwd||null,workerStatus:c?.workerStatus??(d.archived?"archived":"inactive"),createdAt:d.createdAt,updatedAt:d.updatedAt,archived:d.archived};c&&(l.aibotSessionId=c.aibotSessionId),d.title&&(l.title=d.title),s.push(l)}}else if(o==="claude"){const a=this.sessionScanCache.get();for(const d of a){r.add(d.sessionId);const c=i.get(d.sessionId),l={agentSessionId:d.sessionId,cwd:d.cwd||null,workerStatus:c?.workerStatus??"inactive",updatedAt:d.updatedAt};c&&(l.aibotSessionId=c.aibotSessionId),d.title&&(l.title=d.title),s.push(l)}}else if(o==="acp"){const a=this.sessionScanCache.get();for(const d of a){r.add(d.sessionId);const c=i.get(d.sessionId),l=c&&c.bindingUpdatedAt>0?c.bindingUpdatedAt:d.updatedAt,h={agentSessionId:d.sessionId,cwd:d.cwd||null,agentType:d.agentType,workerStatus:c?.workerStatus??"inactive",updatedAt:l};c&&(h.aibotSessionId=c.aibotSessionId),d.title&&(h.title=d.title),d.createdAt&&(h.createdAt=d.createdAt),s.push(h)}}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",m={aibotSessionId:a,cwd:d.cwd??null,workerStatus:h,updatedAt:d.updatedAt,title:c?`${c.slice(0,8)}\u2026`:`${a.slice(0,8)}\u2026`};c&&(m.agentSessionId=c),s.push(m)}s.sort((a,d)=>d.updatedAt-a.updatedAt),this.aibotHandle.sendLocalActionResult({action_id:e.action_id,status:"ok",result:{outcome:"sessions_listed",sessions:s,total:s.length}})}async handleSessionControlCommand(e,o){const t=o.session_id,n=(i,s,r)=>{this.aibotHandle.sendEventResult({event_id:o.event_id,status:i,...s?{msg:s}:{},...r?{code:r}:{},updated_at:Date.now()})};this.aibotHandle.sendEventAck({event_id:o.event_id,session_id:t,received_at:Date.now()});try{switch(e.verb){case p.open:{await D().catch(()=>{});const i=e.args.trim();if(!i){n("failed","Usage: /grix open <working-directory>",g.cwdRequired);return}let s="";try{s=await this.resolveCwdForBinding(i)}catch(a){n("failed",a instanceof Error?a.message:String(a),g.invalidCwd);return}const r=this.bindingStore.get(t);if(r?.cwd){const a=await this.resolveCwdForBinding(r.cwd);if(a!==s){n("failed","session binding cannot be changed to another working directory",g.rebindForbidden);return}this.bindingStore.ensureModeId(t,C.fullAuto),this.sessionBindings.set(t,a),await this.ensureSlotStarted(t),await this.deferredMgr.release(t,this.deferredCallbacks()),this.refreshClaudeWorkerStatusCard(t,a),n("responded",`Working directory already bound: ${a}`);return}this.bindingStore.set(t,s,{modeId:C.fullAuto}),this.sessionBindings.set(t,s),await this.ensureSlotStarted(t),await this.deferredMgr.release(t,this.deferredCallbacks()),this.refreshClaudeWorkerStatusCard(t,s),n("responded",`Session bound to ${s}`);return}case p.where:{const i=this.bindingStore.get(t);if(!i?.cwd){n("failed","session binding was not found",g.bindingMissing);return}n("responded",`Working directory: ${i.cwd}`);return}case p.status:{const i=this.bindingStore.get(t);if(!i?.cwd){n("failed","session binding was not found",g.bindingMissing);return}const s=this.normalizeClaudeModeId(i.modeId),r=this.getClaudeWorkerStatus(t);n("responded",`Status: worker=${r} mode=${s} cwd=${i.cwd}`);return}case p.stop:{const i=this.bindingStore.get(t);if(!i?.cwd){n("failed","session binding was not found",g.bindingMissing);return}await this.pool.removeSlot(t),this.claudeWorkerStatus.set(t,"stopped"),this.aibotHandle.sendUpdateBindingCard({session_id:t,worker_status:"stopped",cwd:i.cwd}),this.aibotHandle.sendUpdateBindingCard({session_id:t,worker_status:"stopped",cwd:i.cwd,meta:this.buildClaudeToolbarMeta(t)}),n("responded",`Session worker stopped for ${i.cwd}`);return}case p.restart:{const i=this.bindingStore.get(t);if(!i?.cwd){n("failed","session binding was not found",g.bindingMissing);return}await this.pool.removeSlot(t),await this.ensureSlotStarted(t),this.refreshClaudeWorkerStatusCard(t,i.cwd),n("responded",`Session worker restarted for ${i.cwd}`);return}case p.setMode:{const i=e.args.trim();if(!i){n("failed","Usage: /grix set_mode <mode-id>",x.modeInvalid);return}const s=this.normalizeClaudeModeId(i);if(s!==i.toLowerCase()){n("failed","set mode_id is invalid",x.modeInvalid);return}const r=this.bindingStore.get(t);if(!r?.cwd){n("failed","session binding was not found",g.bindingMissing);return}if(this.normalizeClaudeModeId(r.modeId)===s){n("responded",`Mode unchanged: ${s}`);return}if(this.getClaudeWorkerStatus(t)==="busy"){n("failed","\u5F53\u524D\u6709\u6D88\u606F\u6B63\u5728\u8FD0\u884C\uFF0C\u8BF7\u5148 /grix stop \u6216\u7B49\u5F85\u5176\u5B8C\u6210\u540E\u518D\u5207\u6362\u6A21\u5F0F",g.workerBusy);return}this.bindingStore.setModeId(t,s),await this.pool.removeSlot(t),await this.ensureSlotStarted(t),this.refreshClaudeWorkerStatusCard(t,r.cwd),n("responded",`Mode set to ${s}`);return}case p.setModel:{const i=e.args.trim();if(!i){n("failed","Usage: /grix set_model <model-id>");return}const s=this.bindingStore.get(t);if(!s?.cwd){n("failed","session binding was not found",g.bindingMissing);return}if((s.modelId??"")===i){n("responded",`Model unchanged: ${i}`);return}if(this.getClaudeWorkerStatus(t)==="busy"){n("failed","\u5F53\u524D\u6709\u6D88\u606F\u6B63\u5728\u8FD0\u884C\uFF0C\u8BF7\u5148 /grix stop \u6216\u7B49\u5F85\u5176\u5B8C\u6210\u540E\u518D\u5207\u6362\u6A21\u578B",g.workerBusy);return}this.bindingStore.setModelId(t,i),this.globalConfigStore?.set(this.name,{modelId:i}),await this.pool.removeSlot(t),await this.ensureSlotStarted(t),this.refreshClaudeWorkerStatusCard(t,s.cwd),n("responded",`Model set to ${i}`);return}case p.listOptions:{const i=Q(),s=this.bindingStore.get(t),r=i.map(d=>d.id).join(", "),a=`${C.fullAuto}, ${C.approval}`;n("responded",`Modes (current: ${this.normalizeClaudeModeId(s?.modeId)}): ${a}
|
|
16
|
-
Models (current: ${s?.modelId??"default"}): ${r}`);return}case p.exec:{const[i,...s]=e.args.trim().split(/\s+/);if(!i){n("failed","Usage: /grix exec <command> [args]",g.verbInvalid);return}const a=this.pool.getSlot(t)?.adapter;if(!a?.execCommand){n("failed","Agent does not support command execution",g.verbInvalid);return}const d=a.getSupportedCommands?.()??[];if(!d.some(c=>c.name===i)){n("failed",`Unknown command: ${i}. Supported: ${d.map(c=>c.name).join(", ")}`,g.verbInvalid);return}try{const c=await a.execCommand(i,s.join(" "),t);n(c.status==="ok"?"responded":"failed",c.message??`${i} ${c.status}`,c.status==="ok"?void 0:g.runtimeError)}catch(c){n("failed",`exec error: ${c instanceof Error?c.message:c}`,g.runtimeError)}return}default:n("failed",`Unsupported command for Claude: /grix ${e.verb}`,g.verbInvalid)}}catch(i){n("failed",i instanceof Error?i.message:String(i),g.runtimeError)}}async handleSessionControlLocalAction(e){const o=String(e.action_type??"").trim();if(o!==S.sessionControl&&o!==S.setMode&&o!=="set_mode"&&o!==S.setModel&&o!=="set_model")return!1;const t=e.params??{},n=String(t.session_id??"").trim(),i=o===S.setMode?p.setMode:o===S.setModel?p.setModel:String(t.verb??"").trim().toLowerCase();if(u.info(this.name,`handleSessionControlLocalAction verb=${i} action_id=${e.action_id} session_id=${n}`),!n)return this.aibotHandle.sendLocalActionResult({action_id:e.action_id,status:"failed",error_code:x.localActionRouteMissing,error_msg:"local action session_id is required"}),!0;const s=a=>{this.aibotHandle.sendLocalActionResult({action_id:e.action_id,status:"ok",result:a})},r=(a,d)=>{u.warn(this.name,`session_control local_action failed action_id=${e.action_id} session_id=${n} verb=${i} code=${a} msg=${d}`),this.aibotHandle.sendLocalActionResult({action_id:e.action_id,status:"failed",error_code:a,error_msg:d})};try{switch(i){case p.open:{await D().catch(()=>{});const a=String(t.cwd??"").trim(),d=String(t.agent_session_id??"").trim();if(!a)return r(g.cwdRequired,"session control cwd is required"),!0;u.info(this.name,`handleSessionControlLocalAction open cwd=${a} session_id=${n}`);const c=await this.resolveCwdForBinding(a);this.ensureImportedAgentSession(d,c);const l=this.bindingStore.get(n);if(l?.cwd){const h=await this.resolveCwdForBinding(l.cwd);return h!==c?(r(g.rebindForbidden,"session binding cannot be changed to another working directory"),!0):(this.bindingStore.ensureModeId(n,C.fullAuto),this.setResolvedAgentSessionId(n,d),this.sessionBindings.set(n,h),await this.ensureSlotStarted(n),await this.replayDeferredEventsForSession(n),this.refreshClaudeWorkerStatusCard(n,h),s({outcome:"opened",binding:{...this.buildOpenedBindingResult(n,h),mode_id:this.normalizeClaudeModeId(this.bindingStore.get(n)?.modeId)}}),!0)}return this.bindingStore.set(n,c,{modeId:C.fullAuto}),this.setResolvedAgentSessionId(n,d),this.sessionBindings.set(n,c),await this.ensureSlotStarted(n),await this.replayDeferredEventsForSession(n),this.refreshClaudeWorkerStatusCard(n,c),s({outcome:"opened",binding:{...this.buildOpenedBindingResult(n,c),mode_id:this.normalizeClaudeModeId(this.bindingStore.get(n)?.modeId)}}),!0}case p.status:case p.where:{const a=this.bindingStore.get(n);return a?.cwd?(s({outcome:i,binding:{cwd:a.cwd,mode_id:this.normalizeClaudeModeId(a.modeId),worker_status:this.getClaudeWorkerStatus(n)}}),!0):(r(g.bindingMissing,"session binding was not found"),!0)}case p.stop:{const a=this.bindingStore.get(n);return a?.cwd?(await this.pool.removeSlot(n),s({outcome:"stopped",binding:{cwd:a.cwd,mode_id:this.normalizeClaudeModeId(a.modeId),worker_status:"stopped"}}),!0):(r(g.bindingMissing,"session binding was not found"),!0)}case p.restart:{const a=this.bindingStore.get(n);return a?.cwd?(await this.pool.removeSlot(n),await this.ensureSlotStarted(n),this.refreshClaudeWorkerStatusCard(n,a.cwd),s({outcome:"restarted",binding:{cwd:a.cwd,mode_id:this.normalizeClaudeModeId(a.modeId)}}),!0):(r(g.bindingMissing,"session binding was not found"),!0)}case p.setMode:{const a=String(t.mode_id??t.modeId??"").trim();if(!a)return r(x.modeInvalid,"set mode_id is invalid"),!0;const d=this.normalizeClaudeModeId(a);if(d!==a.toLowerCase())return r(x.modeInvalid,"set mode_id is invalid"),!0;const c=this.bindingStore.get(n);if(!c?.cwd)return r(g.bindingMissing,"session binding was not found"),!0;if(this.normalizeClaudeModeId(c.modeId)!==d){if(this.getClaudeWorkerStatus(n)==="busy")return r(g.workerBusy,"\u5F53\u524D\u6709\u6D88\u606F\u6B63\u5728\u8FD0\u884C\uFF0C\u8BF7\u5148\u505C\u6B62\u6216\u7B49\u5F85\u5176\u5B8C\u6210\u540E\u518D\u5207\u6362\u6A21\u5F0F"),!0;this.bindingStore.setModeId(n,d),await this.pool.removeSlot(n),await this.ensureSlotStarted(n),this.refreshClaudeWorkerStatusCard(n,c.cwd)}return s({outcome:"mode_set",mode_id:d,binding:{cwd:c.cwd,mode_id:d}}),!0}case p.setModel:{const a=String(t.model_id??t.modelId??"").trim();if(!a)return r("set_model_invalid","model_id is required"),!0;const d=this.bindingStore.get(n);if(!d?.cwd)return r(g.bindingMissing,"session binding was not found"),!0;if((d.modelId??"")!==a){if(this.getClaudeWorkerStatus(n)==="busy")return r(g.workerBusy,"\u5F53\u524D\u6709\u6D88\u606F\u6B63\u5728\u8FD0\u884C\uFF0C\u8BF7\u5148\u505C\u6B62\u6216\u7B49\u5F85\u5176\u5B8C\u6210\u540E\u518D\u5207\u6362\u6A21\u578B"),!0;this.bindingStore.setModelId(n,a),this.globalConfigStore?.set(this.name,{modelId:a}),await this.pool.removeSlot(n),await this.ensureSlotStarted(n),this.refreshClaudeWorkerStatusCard(n,d.cwd)}return s({outcome:"model_set",model_id:a,binding:{cwd:d.cwd,model_id:a}}),!0}case p.listOptions:{const a=Q(),d=this.pool.getSlot(n);return s({modes:[C.fullAuto,C.approval],currentModeId:this.normalizeClaudeModeId(this.bindingStore.get(n)?.modeId),models:a.map(c=>({modelId:c.id,name:c.displayName})),currentModelId:this.bindingStore.get(n)?.modelId??"",available_models:a.map(c=>({id:c.id,displayName:c.displayName})),agent_commands:d?.adapter?.getSupportedCommands?.()??[]}),!0}case p.exec:{const[a,...d]=String(t.args??"").trim().split(/\s+/);if(!a)return r(g.verbInvalid,"Usage: exec <command> [args]"),!0;await this.ensureSlotStarted(n);const l=this.pool.getSlot(n)?.adapter;if(!l?.execCommand)return r(g.verbInvalid,"Agent does not support command execution"),!0;const h=l.getSupportedCommands?.()??[];if(!h.some(m=>m.name===a))return r(g.verbInvalid,`Unknown command: ${a}. Supported: ${h.map(m=>m.name).join(", ")}`),!0;try{const m=await l.execCommand(a,d.join(" "),n);m.status==="ok"?s({outcome:"exec",command:a,message:m.message,data:m.data}):r(g.runtimeError,m.message??`${a} failed`)}catch(m){r(g.runtimeError,`exec error: ${m instanceof Error?m.message:m}`)}return!0}default:return r(g.verbInvalid,`session control verb ${i} is not supported`),!0}}catch(a){const d=a instanceof Error&&a.cwdErrorCode?a.cwdErrorCode:a instanceof Error&&a.sessionControlErrorCode?a.sessionControlErrorCode:g.runtimeError;return u.error(this.name,`handleSessionControlLocalAction error verb=${i} session_id=${n}: ${a instanceof Error?a.message:a}`),r(d,a instanceof Error?a.message:String(a)),!0}}async handleEventCancel(e){const{event_id:o,session_id:t}=e;if(u.info(this.name,`handleEventCancel start event_id=${o} session_id=${t}`),this.pool.cancelEvent(o,t)){if(!(this.pool.getSlot(t)?.adapter?.getActiveEventIds().includes(o)??!1)){this.sendEventResultWithCleanup(o,"canceled","canceled"),this.aibotHandle.sendEventCancelResult({event_id:o,accepted:!0,final_state:"canceled"});return}await this.waitForEventDone(o,t,15e3),this.aibotHandle.sendEventCancelResult({event_id:o,accepted:!0,final_state:"canceled"}),this.pushQueueSnapshotForSession(t);return}this.aibotHandle.sendEventCancelResult({event_id:o,accepted:!1,reason:"event not found or not cancelable"})}waitForEventDone(e,o,t){return new Promise(n=>{const i=this.pool.getSlot(o);if(!i?.adapter){n();return}const s=setTimeout(()=>{i.adapter.removeListener("eventDone",r),n()},t),r=a=>{a===e&&(clearTimeout(s),i.adapter.removeListener("eventDone",r),n())};i.adapter.on("eventDone",r)})}handleAibotStop(e){if(u.info(this.name,`[stop-trace] handleAibotStop begin session=${e.session_id} event=${e.event_id} stop_id=${e.stop_id||"-"} adapterType=${this.config.adapterType??"acp"}`),this.aibotHandle.sendEventStopAck({stop_id:e.stop_id,event_id:e.event_id,accepted:!0,updated_at:Date.now()}),this.pool.removeQueuedEvent(e.session_id,e.event_id)){u.info(this.name,`[stop-trace] handleAibotStop removed queued(not-running) event -> stopResult(stopped) session=${e.session_id} event=${e.event_id} stop_id=${e.stop_id||"-"}`),this.pushQueueSnapshotForSession(e.session_id),this.aibotHandle.sendEventStopResult({stop_id:e.stop_id,event_id:e.event_id,status:"stopped",updated_at:Date.now()});return}const o=this.pool.getSlot(e.session_id),t=o?.adapter?.getStatus().busy??!1,n=(this.config.adapterType??"acp")==="acp",i=o?.adapter?.getActiveEventIds,s=typeof i=="function"?i.call(o.adapter):[],r=s.length>0?s.includes(e.event_id):t,a=(n||(this.config.adapterType??"acp")==="codex"||(this.config.adapterType??"acp")==="claude")&&r;if(u.info(this.name,`[stop-trace] handleAibotStop decision session=${e.session_id} event=${e.event_id} slotExists=${!!o?.adapter} busy=${t} activeIds=[${s.join(",")}] stoppingActiveEvent=${r} killOnStop=${a}`),r&&this.sendCtrl.markEventStopped(e.event_id),o?.adapter&&t){const d=a?this.pool.drainQueuedForSession(e.session_id):[];let c=!1,l=null;const h=v=>{c||(c=!0,l&&(clearTimeout(l),l=null),o.adapter.removeListener("eventDone",m),u.info(this.name,`[stop-trace] handleAibotStop ${v} -> stopResult(stopped) session=${e.session_id} event=${e.event_id} stop_id=${e.stop_id||"-"} killOnStop=${a}`),this.aibotHandle.sendEventStopResult({stop_id:e.stop_id,event_id:e.event_id,status:"stopped",updated_at:Date.now()}),a&&this.killAndResumeStopSlot(e.session_id,d))},m=v=>{v===e.event_id&&h("eventDone")};o.adapter.on("eventDone",m),l=setTimeout(()=>h("timeout"),je),this.pool.deliverStopEvent(e.event_id,e.session_id)}else u.info(this.name,`[stop-trace] handleAibotStop slot-not-busy -> immediate stopResult(stopped) session=${e.session_id} event=${e.event_id} slotExists=${!!o?.adapter}`),this.pool.deliverStopEvent(e.event_id,e.session_id),this.aibotHandle.sendEventStopResult({stop_id:e.stop_id,event_id:e.event_id,status:"stopped",updated_at:Date.now()});(this.config.adapterType??"acp")==="pi"&&this.aibotHandle.sendText({event_id:e.event_id,session_id:e.session_id,content:"/stop",msg_type:0})}async killAndResumeStopSlot(e,o){if(!this.stopped){u.info(this.name,`[stop-trace] killAndResumeStopSlot begin session=${e} siblings=${o.length} -> removeSlot (kill process group)`);try{await this.pool.removeSlot(e),u.info(this.name,`[stop-trace] killAndResumeStopSlot removeSlot done session=${e} (process killed) -> redeliver ${o.length} sibling(s)`)}catch(t){u.warn(this.name,`[acp-stop] removeSlot failed session=${e}: ${t instanceof Error?t.message:String(t)}`)}if(!this.stopped)for(const t of o){if(this.stopped)break;try{await this.pool.deliverInboundEvent(t)}catch(n){u.error(this.name,`[acp-stop] sibling redeliver failed event=${t.event_id} session=${e}: ${n instanceof Error?n.message:String(n)}`),this.sendEventResultWithCleanup(t.event_id,"failed",n instanceof Error?n.message:String(n))}}}}handleAibotRevoke(e){if(!e.event_id||!this.revokeHandler.checkAndTrack(e.event_id))return;const o=e.event_id,t=e.session_id;if(t&&this.pool.cancelEvent(o,t)){(this.pool.getSlot(t)?.adapter?.getActiveEventIds().includes(o)??!1)||this.sendEventResultWithCleanup(o,"canceled","revoked");return}if(this.deferredMgr.removeEvent(o)){this.aibotHandle.sendEventResult({event_id:o,status:"canceled",msg:"revoked",updated_at:Date.now()});return}this.pool.deliverStopEvent(o,t||void 0)}async handleConfigureGatewayProvider(e){const o=e.params??{},t=String(o.api_key??"").trim(),n=String(o.anthropic_base_url??"").trim(),i=String(o.openai_base_url??"").trim(),s=typeof o.model=="string"&&o.model.trim()||void 0;if(!t){this.aibotHandle.sendLocalActionResult({action_id:e.action_id,status:"failed",error_code:"MISSING_API_KEY",error_msg:"api_key is required"});return}const r=this.config.adapterType??"acp",a=r==="claude"||r==="codex";try{if(a){const d=r==="claude"?n:i;if(!d){this.aibotHandle.sendLocalActionResult({action_id:e.action_id,status:"failed",error_code:"MISSING_TARGET_BASE_URL",error_msg:`${r==="claude"?"anthropic_base_url":"openai_base_url"} is required`});return}const c=ce();if(!c){this.aibotHandle.sendLocalActionResult({action_id:e.action_id,status:"failed",error_code:"PROXY_UNAVAILABLE",error_msg:"MITM proxy manager not initialized"});return}const l="grix-gateway";c.setRoute({routeKey:l,targetBaseUrl:d,headers:{Authorization:`Bearer ${t}`},...s?{model:s}:{}}),c.setDefaultRouteKey(l),await c.enable(),await c.persistConfig()}else{if(!this.providerConfigHandler){this.aibotHandle.sendLocalActionResult({action_id:e.action_id,status:"unsupported",error_code:"UNSUPPORTED_CLIENT_TYPE",error_msg:`client type "${r}" does not support Grix relay provider config`});return}await this.providerConfigHandler({base_url:i||void 0,api_key:t,model:s})}this.aibotHandle.sendLocalActionResult({action_id:e.action_id,status:"ok",result:{accepted:!0}})}catch(d){u.error(this.name,`handleConfigureGatewayProvider failed: ${d}`),this.aibotHandle.sendLocalActionResult({action_id:e.action_id,status:"failed",error_code:"CONFIGURE_FAILED",error_msg:d instanceof Error?d.message:String(d)})}}async handleAibotLocalAction(e){const o=e.action_type??"",t=String((e.params??{}).session_id??""),n=String((e.params??{}).verb??"").trim().toLowerCase();u.debug(this.name,`local_action received action_type=${o} verb=${n||"-"} action_id=${e.action_id} session_id=${t}`);const i=(this.config.adapterType??"acp")==="claude";if(o===S.sessionControl&&n===p.exec&&await this.handleSessionControlLocalAction(e))return;if(o===S.sessionControl&&n===p.listSessions){await this.handleListSessionsLocalAction(e);return}if(i&&(o===S.interactionReply||o==="exec_approve"||o==="exec_reject")&&(await this.pool.deliverLocalAction(e)).handled||i&&await this.handleSessionControlLocalAction(e))return;if(o===S.sessionControl){const c=this.config.adapterType??"acp",l=c==="codex",h=c==="pi",m=String((e.params??{}).verb??"").trim().toLowerCase();if(l&&m===p.open){await this.handleCodexSessionControlLocalActionOpen(e);return}if(c==="cursor"&&m===p.open){await this.handleCursorSessionControlLocalActionOpen(e);return}if(l&&m==="restart"){const b=this.bindingStore.get(t)?.cwd??"";await this.pool.removeSlot(t).catch(()=>{}),this.aibotHandle.sendLocalActionResult({action_id:e.action_id,status:"ok",result:{outcome:"restarted",binding:{aibotSessionId:t,cwd:b,workerStatus:"ready"}}});return}if(h&&m===p.open){try{const f=e.params??{},b=String(f.cwd??"").trim();if(!b){this.aibotHandle.sendLocalActionResult({action_id:e.action_id,status:"failed",error_code:g.cwdRequired,error_msg:"session cwd is required"});return}const w=await this.resolveCwdForBinding(b),k=String(f.agent_session_id??"").trim();this.ensureImportedAgentSession(k,w),this.bindingStore.set(t,w),this.setResolvedAgentSessionId(t,k),this.sessionBindings.set(t,w),await this.deferredMgr.release(t,this.deferredCallbacks()),this.aibotHandle.sendUpdateBindingCard({session_id:t,worker_status:"ready",cwd:w}),this.aibotHandle.sendLocalActionResult({action_id:e.action_id,status:"ok",result:{outcome:"opened",binding:this.buildOpenedBindingResult(t,w)}})}catch(f){this.aibotHandle.sendLocalActionResult({action_id:e.action_id,status:"failed",error_code:f?.sessionControlErrorCode??f?.cwdErrorCode??g.invalidCwd,error_msg:f instanceof Error?f.message:String(f)})}return}if(h&&m===p.restart){await this.handlePiSessionControlRestartLocalAction(e);return}if((c==="openhuman"||c==="opencode")&&m===p.open){try{const f=e.params??{},b=String(f.cwd??"").trim();if(!b){this.aibotHandle.sendLocalActionResult({action_id:e.action_id,status:"failed",error_code:g.cwdRequired,error_msg:"session cwd is required"});return}const w=await this.resolveCwdForBinding(b),k=String(f.agent_session_id??"").trim();this.ensureImportedAgentSession(k,w),this.bindingStore.set(t,w),this.setResolvedAgentSessionId(t,k),this.sessionBindings.set(t,w),await this.deferredMgr.release(t,this.deferredCallbacks()),this.aibotHandle.sendLocalActionResult({action_id:e.action_id,status:"ok",result:{outcome:"opened",binding:this.buildOpenedBindingResult(t,w)}})}catch(f){this.aibotHandle.sendLocalActionResult({action_id:e.action_id,status:"failed",error_code:f?.sessionControlErrorCode??f?.cwdErrorCode??g.invalidCwd,error_msg:f instanceof Error?f.message:String(f)})}return}if(c==="codewhale"&&m===p.open){await this.handleCodeWhaleSessionControlLocalActionOpen(e);return}if(c==="acp"&&m===p.stop){const b=this.bindingStore.get(t)?.cwd??"";await this.pool.removeSlot(t).catch(()=>{}),this.aibotHandle.sendLocalActionResult({action_id:e.action_id,status:"ok",result:{outcome:"stopped",binding:{aibotSessionId:t,cwd:b,workerStatus:"stopped"}}});return}try{if(m===p.open){const f=e.params??{},b=String(f.cwd??"").trim();if(!b){this.aibotHandle.sendLocalActionResult({action_id:e.action_id,status:"failed",error_code:g.cwdRequired,error_msg:"session cwd is required"});return}const w=String(f.agent_session_id??"").trim();if(w){const k=await this.resolveCwdForBinding(b);this.ensureImportedAgentSession(w,k)}}await this.handleSessionControlLocalActionForPool(e)}catch(f){this.aibotHandle.sendLocalActionResult({action_id:e.action_id,status:"failed",error_code:f?.sessionControlErrorCode??f?.cwdErrorCode??g.runtimeError,error_msg:f instanceof Error?f.message:String(f)});return}if(m===p.open){const f=e.params??{},b=await this.resolveCwdForBinding(String(f.cwd??"").trim());this.setResolvedAgentSessionId(t,String(f.agent_session_id??"").trim()),c==="agy"&&(this.bindingStore.set(t,b),this.sessionBindings.set(t,b)),await this.deferredMgr.release(t,this.deferredCallbacks()),this.aibotHandle.sendLocalActionResult({action_id:e.action_id,status:"ok",result:{outcome:"opened",binding:this.buildOpenedBindingResult(t,b)}}),(this.config.adapterType??"acp")==="agy"&&this.aibotHandle.sendUpdateBindingCard({session_id:t,worker_status:"ready",cwd:b,meta:this.buildAgyToolbarMeta(t)})}else $e(e,this.sessionControlCtx(t),this.sessionControlSenders());return}if(o==="file_list"){const c=Date.now(),l=t?this.bindingStore.get(t)?.cwd:void 0,h=e.params??{},m=String(h.parent_id??"").trim(),v=Array.isArray(h.allowed_extensions)?h.allowed_extensions.filter(b=>typeof b=="string").map(b=>b.trim()).filter(b=>b.length>0):[];u.info("file-list-diag",`plugin << recv action_id=${e.action_id} session_id=${t} parent_id=${m||"<root>"} show_hidden=${!!h.show_hidden} ext_count=${v.length} bound_cwd=${l??"<none>"}`);const _=await Te({parent_id:m||null,session_id:t,show_hidden:!!h.show_hidden,allowed_extensions:v},{resolveCwd:()=>l??this.config.agent.cwd??process.cwd(),fallbackDir:K()}),R=Date.now()-c,f=_.result?.files?.length??0;u.info("file-list-diag",`plugin -> reply action_id=${e.action_id} status=${_.status} elapsed=${R}ms count=${f} current_path=${_.result?.current_path??""} error_code=${_.error_code??""} error_msg=${_.error_msg??""}`),this.aibotHandle.sendLocalActionResult({action_id:e.action_id,status:_.status,..._.result?{result:{..._.result,machine_name:He()}}:{},..._.error_code?{error_code:_.error_code}:{},..._.error_msg?{error_msg:_.error_msg}:{}});return}if(o==="create_folder"){const c=t?this.bindingStore.get(t)?.cwd:void 0,l=String((e.params??{}).parent_id??"").trim(),h=String((e.params??{}).name??"").trim(),m=await Le({parent_id:l||null,name:h,session_id:t},{resolveCwd:()=>c??this.config.agent.cwd??process.cwd(),fallbackDir:K()});this.aibotHandle.sendLocalActionResult({action_id:e.action_id,status:m.status,...m.result?{result:m.result}:{},...m.error_code?{error_code:m.error_code}:{},...m.error_msg?{error_msg:m.error_msg}:{}});return}if(o===S.setModel&&(this.config.adapterType??"acp")==="agy"){await this.handleAgySetModel(e,t);return}if(o===S.getSessionUsage){await this.handleGetSessionUsage(e,t);return}if(o===S.getRateLimits){await this.handleGetRateLimits(e,t);return}const s=(this.config.adapterType??"acp")==="acp";if((i||s)&&o===S.threadCompact){await this.handleThreadCompact(e,t);return}if(o==="connector_rollback"){await this.handleConnectorRollback(e);return}if(o==="connector_upgrade_push"){this.aibotHandle.sendLocalActionResult({action_id:e.action_id,status:"ok",result:{accepted:!0}}),this.upgradeTrigger?.();return}if(o===S.getAgentGlobalConfig){const c=this.globalConfigStore?.get(this.name);this.aibotHandle.sendLocalActionResult({action_id:e.action_id,status:"ok",result:{agentName:this.name,...c?{config:c}:{config:null}}});return}if(o==="configure_gateway_provider"){await this.handleConfigureGatewayProvider(e);return}const r=this.config.adapterType??"acp",a=(r==="codex"||r==="cursor"||r==="pi"||r==="openhuman"||r==="opencode"||r==="acp")&&!!t&&!!this.bindingStore.get(t)?.cwd,d=await this.pool.deliverLocalAction(e,{autoCreateSlot:a});if(d.handled){if(d.kind==="set_mode"){const c=String((e.params??{}).mode_id??"");c&&(r==="cursor"||r==="claude"?this.bindingStore.setModeId(t,c):P.has(r)||this.globalConfigStore?.set(this.name,{acpInitialMode:c}))}else if(d.kind==="set_model"){const c=String((e.params??{}).model_id??"");c&&(r==="cursor"?(this.bindingStore.setModelId(t,c),this.globalConfigStore?.set(this.name,{modelId:c})):P.has(r)||this.globalConfigStore?.set(this.name,{modelId:c}))}else if(d.kind==="set_reasoning_effort"){const c=String((e.params??{}).reasoning_effort??(e.params??{}).reasoning_eff??(e.params??{}).effort??"");c&&this.globalConfigStore?.set(this.name,{codexReasoningEffort:c})}else if(d.kind==="set_sandbox_mode"){const c=String((e.params??{}).sandbox_mode??(e.params??{}).sandboxMode??"");if(c){const l=c==="default"?void 0:c;this.globalConfigStore?.set(this.name,{codexSandboxMode:l})}}return}if((r==="codex"||r==="cursor"||r==="pi"||r==="openhuman"||r==="opencode")&&t&&!this.bindingStore.get(t)?.cwd){this.aibotHandle.sendLocalActionResult({action_id:e.action_id,status:"failed",error_code:g.bindingMissing,error_msg:"Session binding missing. Open a workspace first."});return}if(r==="acp"&&(o==="set_mode"||o==="set_model")){const c=this.sessionControlSenders(),l=this.pool.getSlot(t)?.adapter,h={bindingStore:this.bindingStore,acpAdapter:l instanceof A?l:null,globalConfigStore:this.globalConfigStore,agentName:this.name,log:u};if(o==="set_mode"){const m=String((e.params??{}).mode_id??""),v=await N(h,t,m);if(v.status==="failed")c.sendLocalActionResult(e.action_id,"failed",void 0,v.errorCode,v.errorMsg);else{const _=l instanceof A?l.buildToolbarContext(v.result?.outcome==="mode_set"?"mode_set":"mode_set_failed"):null,R=_?{..._,...v.result}:v.result;c.sendLocalActionResult(e.action_id,"ok",R)}}else{const m=String((e.params??{}).model_id??""),v=await W(h,t,m);if(v.status==="failed")c.sendLocalActionResult(e.action_id,"failed",void 0,v.errorCode,v.errorMsg);else{const _=l instanceof A?l.buildToolbarContext(v.result?.outcome==="model_set"?"model_set":"model_set_failed"):null,R=_?{..._,...v.result}:v.result;c.sendLocalActionResult(e.action_id,"ok",R)}}return}this.aibotHandle.sendLocalActionResult({action_id:e.action_id,status:"failed",error_code:"unsupported_local_action",error_msg:`action type ${o} is not supported`})}async handleGetSessionUsage(e,o){if(!o){u.warn(this.name,`[usage] get_session_usage rejected: no session_id in params, action_id=${e.action_id}`),this.aibotHandle.sendLocalActionResult({action_id:e.action_id,status:"failed",error_code:"session_id_required",error_msg:"session_id is required for get_session_usage"});return}const t=this.config.adapterType??"acp",n=this.bindingStore.get(o),i=n?.cwd??this.config.agent.cwd??process.cwd();u.info(this.name,`[usage] get_session_usage action_id=${e.action_id} session_id=${o} adapterType=${t} hasBinding=${!!n} cwd=${i}`);let s=null,r,a;switch(t){case"claude":{if(r=n?.claudeSessionId,!r){u.warn(this.name,`[usage] no claude binding for session_id=${o}, action_id=${e.action_id}`),this.aibotHandle.sendLocalActionResult({action_id:e.action_id,status:"failed",error_code:"no_binding",error_msg:"No Claude session binding found"});return}u.info(this.name,`[usage] parsing claude usage: claudeSessionId=${r} cwd=${i}`),s=await ne(r,i),a="claude";break}case"agy":{this.aibotHandle.sendLocalActionResult({action_id:e.action_id,status:"ok",result:{sessionId:o,adapterType:"agy",available:!1,reason:"agy print-mode adapter does not track token usage"}});return}case"acp":default:{if(r=n?.acpSessionId,!r){this.aibotHandle.sendLocalActionResult({action_id:e.action_id,status:"failed",error_code:"no_binding",error_msg:"No ACP session binding found"});return}s=await ae(r,i,this.config.aibot.clientType),a=t,(!a||a==="acp")&&(a="acp");break}case"codex":{if(r=n?.codexThreadId,!r){this.aibotHandle.sendLocalActionResult({action_id:e.action_id,status:"failed",error_code:"no_binding",error_msg:"No Codex thread binding found"});return}s=await ue(r),a="codex";break}case"pi":{if(r=n?.piSessionPath,!r){this.aibotHandle.sendLocalActionResult({action_id:e.action_id,status:"failed",error_code:"no_binding",error_msg:"No Pi session path binding found"});return}s=await fe(r),a="pi";break}case"cursor":{const c=this.pool.getSlot(o)?.adapter;if(!(c instanceof I)){this.aibotHandle.sendLocalActionResult({action_id:e.action_id,status:"failed",error_code:"no_binding",error_msg:"No Cursor session found"});return}const l=c.getUsageSnapshot(o);if(!l){this.aibotHandle.sendLocalActionResult({action_id:e.action_id,status:"failed",error_code:"usage_not_found",error_msg:"No usage data found for this session"});return}this.aibotHandle.sendLocalActionResult({action_id:e.action_id,status:"ok",result:{sessionId:o,adapterType:"cursor",models:[{modelId:this.bindingStore.get(o)?.modelId??"auto",turns:l.turns,input:l.total.input,output:l.total.output,cacheRead:l.total.cacheRead,cacheWrite:l.total.cacheWrite}],total:{input:l.total.input,output:l.total.output,cacheRead:l.total.cacheRead,cacheWrite:l.total.cacheWrite},turns:l.turns,sampledAt:l.sampledAt}});return}case"codewhale":{const c=this.pool.getSlot(o)?.adapter;if(!(c instanceof U)){this.aibotHandle.sendLocalActionResult({action_id:e.action_id,status:"failed",error_code:"no_binding",error_msg:"No CodeWhale session found"});return}const l=c.getUsageSnapshot();if(!l){this.aibotHandle.sendLocalActionResult({action_id:e.action_id,status:"failed",error_code:"usage_not_found",error_msg:"No usage data found for this session"});return}this.aibotHandle.sendLocalActionResult({action_id:e.action_id,status:"ok",result:{sessionId:o,adapterType:"codewhale",models:[{modelId:this.bindingStore.get(o)?.modelId??"codewhale",turns:l.turns,input:l.total.input,output:l.total.output}],total:{input:l.total.input,output:l.total.output},turns:l.turns,sampledAt:l.sampledAt}});return}}if(!s){u.info(this.name,`[usage] no usage data found: session_id=${o} adapterSessionId=${r} adapterType=${a}`),this.aibotHandle.sendLocalActionResult({action_id:e.action_id,status:"failed",error_code:"usage_not_found",error_msg:"No usage data found for this session"});return}u.info(this.name,`[usage] result ok: session_id=${o} adapterSessionId=${r} turns=${s.turns} models=${s.models.length}`),this.aibotHandle.sendLocalActionResult({action_id:e.action_id,status:"ok",result:{sessionId:r,adapterType:a,models:s.models,total:s.total,turns:s.turns,sampledAt:new Date().toISOString()}})}async handleThreadCompact(e,o){if(!o){this.aibotHandle.sendLocalActionResult({action_id:e.action_id,status:"failed",error_code:"session_id_required",error_msg:"session_id is required for thread_compact"});return}if(!this.bindingStore.get(o)?.cwd){this.aibotHandle.sendLocalActionResult({action_id:e.action_id,status:"failed",error_code:g.bindingMissing,error_msg:"session binding was not found"});return}try{await this.ensureSlotStarted(o);const i=this.pool.getSlot(o)?.adapter;if(!i?.execCommand){this.aibotHandle.sendLocalActionResult({action_id:e.action_id,status:"failed",error_code:g.runtimeError,error_msg:"Agent does not support command execution"});return}u.info(this.name,`thread_compact session_id=${o} action_id=${e.action_id}`);const s=await i.execCommand("compact","",o);s.status==="ok"?this.aibotHandle.sendLocalActionResult({action_id:e.action_id,status:"ok",result:{outcome:"compacted",message:s.message,data:s.data}}):this.aibotHandle.sendLocalActionResult({action_id:e.action_id,status:"failed",error_code:g.runtimeError,error_msg:s.message??"compact failed"})}catch(n){u.warn(this.name,`thread_compact error session_id=${o}: ${n instanceof Error?n.message:n}`),this.aibotHandle.sendLocalActionResult({action_id:e.action_id,status:"failed",error_code:g.runtimeError,error_msg:n instanceof Error?n.message:String(n)})}}async handleGetRateLimits(e,o){const t=this.config.adapterType??"acp";if(this.config.aibot.clientType==="kiro"){const n=this.isRateLimitsCacheFresh(this.cachedProviderQuotaSampledAtMs),i=this.cachedAcpContextWindow;if(n&&this.cachedProviderQuota){this.aibotHandle.sendLocalActionResult({action_id:e.action_id,status:"ok",result:{adapterType:"acp",available:!0,cached:!0,sampledAt:this.cachedProviderQuotaSampledAtMs,rateLimits:null,contextWindow:i,tokenUsage:null,providerQuota:this.cachedProviderQuota}});return}try{const s=await B();this.cachedProviderQuota=s,this.cachedProviderQuotaSampledAtMs=Date.now(),u.info(this.name,`[rate-limits] kiro quota queried: success=${s.success}`+(s.balance?` balance=${s.balance.remaining} ${s.balance.unit}`:"")+(s.error?` error=${s.error}`:"")),this.aibotHandle.sendLocalActionResult({action_id:e.action_id,status:"ok",result:{adapterType:"acp",available:!0,cached:!1,sampledAt:this.cachedProviderQuotaSampledAtMs,rateLimits:null,contextWindow:i,tokenUsage:null,providerQuota:s}})}catch(s){u.warn(this.name,`[rate-limits] kiro quota query failed: ${s instanceof Error?s.message:String(s)}`),this.aibotHandle.sendLocalActionResult({action_id:e.action_id,status:"ok",result:{adapterType:"acp",available:!1,cached:!1,sampledAt:null,rateLimits:null,contextWindow:i,tokenUsage:null}})}return}switch(t){case"codex":{const n=this.maybeQueryProviderQuota(),i=this.getFreshCodexGlobalRateLimitCache();if(i.hasData&&!this.isRateLimitsCacheFresh(this.cachedRateLimitsSampledAtMs)&&!this.isRateLimitsCacheFresh(this.cachedCodexUsageSampledAtMs)){const l=this.pool.getAllSlots().find(h=>h.state==="ready"&&h.adapter);if(l&&(u.info(this.name,`[rate-limits] codex cache stale, refreshing from slot: session=${l.sessionId}`),(await l.adapter.handleLocalAction?.(e))?.handled))return}if(i.hasData){i.rateLimits?u.info(this.name,`[rate-limits] codex cached: primary=${i.rateLimits.primary.usedPercent.toFixed(1)}% resetsAt=${i.rateLimits.primary.resetsAt} secondary=${i.rateLimits.secondary.usedPercent.toFixed(1)}% resetsAt=${i.rateLimits.secondary.resetsAt}`):u.info(this.name,`[rate-limits] codex cached context/token only: hasContext=${!!i.contextWindow} hasToken=${!!i.tokenUsage}`);const l=await n;this.aibotHandle.sendLocalActionResult({action_id:e.action_id,status:"ok",result:{adapterType:"codex",available:i.hasData||!!l,cached:!0,sampledAt:i.sampledAt,rateLimits:i.rateLimits,contextWindow:i.contextWindow,tokenUsage:i.tokenUsage,providerQuota:l}});return}const r=this.pool.getAllSlots().find(l=>l.state==="ready"&&l.adapter);if(r&&(u.info(this.name,`[rate-limits] codex reuse existing slot: session=${r.sessionId}`),(await r.adapter.handleLocalAction?.(e))?.handled))return;const a=this.resolveRateLimitWakeSessionId(o,t);if(a){const l=await this.wakeRateLimitSlot(a,t);if(l?.adapter&&(await l.adapter.handleLocalAction?.(e))?.handled)return}const d=await n,c=!!d;u.info(this.name,`[rate-limits] codex no native data, providerQuota=${d?d.provider:"none"} available=${c}`),this.aibotHandle.sendLocalActionResult({action_id:e.action_id,status:"ok",result:{adapterType:"codex",available:c,cached:!1,sampledAt:null,rateLimits:null,contextWindow:null,tokenUsage:null,providerQuota:d}});return}case"claude":{const n=this.maybeQueryProviderQuota(),i=this.getFreshClaudeRateLimitState();if(i){const c=i.rateLimits,l=await n;u.info(this.name,`[rate-limits] claude global cached: sampledAt=${i.sampledAt} hasRateLimits=${!!c}`+(l?` providerQuota=${l.provider}`:"")),this.aibotHandle.sendLocalActionResult({action_id:e.action_id,status:"ok",result:{adapterType:"claude",available:!0,cached:!0,sampledAt:i.sampledAt,rateLimits:i.rateLimits??null,contextWindow:i.contextWindow,tokenUsage:null,providerQuota:l}});return}let s=this.pool.getAllSlots().find(c=>c.state==="ready"&&c.adapter instanceof y)??null;if(!s){const c=this.resolveRateLimitWakeSessionId(o,t);c&&(s=await this.wakeRateLimitSlot(c,t))}u.info(this.name,`[rate-limits] handleGetRateLimits: session_id=${o} adapterType=claude hasSlot=${!!s} hasAdapter=${!!s?.adapter}`);const r=s?.adapter,a=r instanceof y?r.getSessionState():null,d=await n;if(a){(a.rateLimits?.fiveHour||a.rateLimits?.sevenDay||a.contextWindow?.usedPercentage!=null)&&(this.cachedClaudeRateLimitState=a);const c=this.getFreshClaudeRateLimitState(),l=c?.rateLimits&&!a.rateLimits?{...a,rateLimits:c.rateLimits}:a,h=l.rateLimits;u.info(this.name,`[rate-limits] claude global state: sampledAt=${l.sampledAt} hasRateLimits=${!!h}`+(h?` fiveHour=${h.fiveHour?.usedPercentage??"n/a"}% resetsAt=${h.fiveHour?.resetsAt??"n/a"} sevenDay=${h.sevenDay?.usedPercentage??"n/a"}% resetsAt=${h.sevenDay?.resetsAt??"n/a"}`:"")+(c?.rateLimits&&!a.rateLimits?" source=live+cached-fallback":" source=live")+(d?` providerQuota=${d.provider}`:"")),this.aibotHandle.sendLocalActionResult({action_id:e.action_id,status:"ok",result:{adapterType:"claude",available:!0,cached:!1,sampledAt:l.sampledAt,rateLimits:l.rateLimits??null,contextWindow:l.contextWindow,tokenUsage:null,providerQuota:d}})}else u.info(this.name,`[rate-limits] claude no global state: hasAdapter=${!!r} adapterType=${r?.constructor?.name??"n/a"}`+(d?` providerQuota=${d.provider}`:"")),this.aibotHandle.sendLocalActionResult({action_id:e.action_id,status:"ok",result:{adapterType:"claude",available:!!d,cached:!1,sampledAt:null,rateLimits:null,contextWindow:null,tokenUsage:null,providerQuota:d}});return}case"cursor":{const i=(o?this.pool.getSlot(o):null)?.adapter,s=i instanceof I?i.getRateLimitsSnapshot():{adapterType:"cursor",available:!1,cached:!1,sampledAt:null,rateLimits:null,contextWindow:null,tokenUsage:null};this.aibotHandle.sendLocalActionResult({action_id:e.action_id,status:"ok",result:s});return}default:{const n=this.config.providerBaseUrl,i=this.config.providerApiKey;if(!n||!i){u.info(this.name,`[rate-limits] no provider config for adapterType=${t}`),this.aibotHandle.sendLocalActionResult({action_id:e.action_id,status:"ok",result:{adapterType:t,available:!1,cached:!1,sampledAt:null,rateLimits:null,contextWindow:null,tokenUsage:null}});return}if(this.isRateLimitsCacheFresh(this.cachedProviderQuotaSampledAtMs)&&this.cachedProviderQuota){u.info(this.name,`[rate-limits] provider quota cached: provider=${this.cachedProviderQuota.provider} success=${this.cachedProviderQuota.success}`),this.aibotHandle.sendLocalActionResult({action_id:e.action_id,status:"ok",result:{adapterType:t,available:!0,cached:!0,sampledAt:this.cachedProviderQuotaSampledAtMs,rateLimits:null,contextWindow:null,tokenUsage:null,providerQuota:this.cachedProviderQuota}});return}try{const r=await z(n,i);this.cachedProviderQuota=r,this.cachedProviderQuotaSampledAtMs=Date.now(),u.info(this.name,`[rate-limits] provider quota queried: provider=${r.provider} success=${r.success}`+(r.tiers.length>0?` tiers=${r.tiers.map(a=>`${a.name}=${a.usedPercent}%`).join(",")}`:"")+(r.balance?` balance=${r.balance.remaining} ${r.balance.unit}`:"")+(r.error?` error=${r.error}`:"")),this.aibotHandle.sendLocalActionResult({action_id:e.action_id,status:"ok",result:{adapterType:t,available:!0,cached:!1,sampledAt:this.cachedProviderQuotaSampledAtMs,rateLimits:null,contextWindow:null,tokenUsage:null,providerQuota:r}})}catch(r){u.warn(this.name,`[rate-limits] provider quota query failed: ${r instanceof Error?r.message:String(r)}`),this.aibotHandle.sendLocalActionResult({action_id:e.action_id,status:"ok",result:{adapterType:t,available:!1,cached:!1,sampledAt:null,rateLimits:null,contextWindow:null,tokenUsage:null}})}return}}}resolveRateLimitWakeSessionId(e,o){const t=String(e??"").trim(),n=this.bindingStore.getMostRecentlyUpdatedSessionId({requireCwd:!0});return n?t&&t===n?t:o==="codex"||o==="claude"?(t&&t!==n&&u.info(this.name,`[rate-limits] ${o} remap wake session: requested=${t} use_recent=${n}`),n):t&&t!==n?(u.info(this.name,`[rate-limits] skip wake slot: session=${t} adapterType=${o} reason=not_recent_session recent=${n}`),null):t||null:(u.info(this.name,`[rate-limits] skip wake slot: adapterType=${o} reason=no_recent_binding`),null)}async wakeRateLimitSlot(e,o){const t=String(e??"").trim();if(!t)return null;if(!this.bindingStore.get(t)?.cwd)return u.info(this.name,`[rate-limits] skip wake slot: session=${t} adapterType=${o} reason=binding_cwd_missing`),null;try{const i=this.pool.getOrCreateSlot(t);if(!i)return u.info(this.name,`[rate-limits] skip wake slot: session=${t} adapterType=${o} reason=slot_unavailable`),null;if(i.startPromise){const s=o==="claude"?6e4:2e4;await Promise.race([i.startPromise,new Promise((r,a)=>setTimeout(()=>a(new Error(`wake rate-limits slot timeout (${s}ms)`)),s))])}return u.info(this.name,`[rate-limits] wake slot success: session=${t} adapterType=${o}`),this.pool.getSlot(t)??i}catch(i){return u.warn(this.name,`[rate-limits] wake slot failed: session=${t} adapterType=${o} err=${i instanceof Error?i.message:String(i)}`),null}}sessionControlCtx(e){const o=this.pool.getSlot(e),t=o?.adapter instanceof A?o.adapter:null,n=(this.config.adapterType??"acp")==="acp",i={bindingStore:this.bindingStore,acpAdapter:t,globalConfigStore:this.globalConfigStore,agentName:this.name,log:u};return{getCwd:()=>this.bindingStore.get(e)?.cwd??this.config.agent.cwd??process.cwd(),getSessionBindings:()=>{if(t)return t.getSessionBindings();const s=this.sessionBindings;if(e&&!s.has(e)){const r=this.bindingStore.get(e);r?.cwd&&s.set(e,r.cwd)}return s},getStatus:()=>this.getStatus(),isAcpAlive:!!t?.isAlive(),getAcpSessionOptions:()=>t?.acpSessionOptions??null,setMode:s=>t?t.setMode(s):Promise.resolve(!1),setModel:s=>t?t.setModel(s):Promise.resolve(!1),acpSetMode:n?(s,r)=>N(i,s,r):void 0,acpSetModel:n?(s,r)=>W(i,s,r):void 0,getPendingApproval:s=>{const r=t?.pendingApprovalEntries.get(s);return r?{requestId:r}:void 0},deletePendingApproval:s=>t?.pendingApprovalEntries.delete(s)??!1,respondPermission:(s,r)=>(t&&t.respondToPermission(s,r),Promise.resolve()),onSessionBound:(s,r)=>{this.bindingStore.set(s,r)},onSessionUnbound:s=>{const a=this.bindingStore.get(s)?.cwd??"";this.bindingStore.delete(s),this.sessionBindings.delete(s),this.claudeWorkerStatus.delete(s),this.deferredMgr.clearSession(s),this.pool.drainQueuedForSession(s),a&&this.aibotHandle.sendUpdateBindingCard({session_id:s,worker_status:"stopped",cwd:a})},cancelActiveRun:()=>(this.config.adapterType??"acp")==="agy"&&o?.adapter instanceof O?(o.adapter.cancelCurrentRun(),Promise.resolve()):o?.adapter?.cancel("")??Promise.resolve(),onModeSet:s=>{const r=this.config.adapterType??"acp";P.has(r)||this.globalConfigStore?.set(this.name,{acpInitialMode:s})},onModelSet:s=>{const r=this.config.adapterType??"acp";P.has(r)||this.globalConfigStore?.set(this.name,{modelId:s})}}}sessionControlSenders(){return{sendEventAck:(e,o)=>this.aibotHandle.sendEventAck({event_id:e,session_id:o,received_at:Date.now()}),sendEventResult:(e,o,t)=>this.aibotHandle.sendEventResult({event_id:e,status:o,...t?.msg?{msg:t.msg}:{},...t?.code?{code:t.code}:{},updated_at:Date.now()}),sendLocalActionResult:(e,o,t,n,i)=>this.aibotHandle.sendLocalActionResult({action_id:e,status:o,...t?{result:t}:{},...n?{error_code:n}:{},...i?{error_msg:i}:{}})}}resolveBindingChannelKey(e){switch(e){case"claude":return"grix-claude";case"codex":return"codex";case"cursor":return"cursor";case"pi":return"pi";case"openhuman":return"openhuman";case"codewhale":return"codewhale";case"opencode":return"opencode";case"agy":return"acp";case"acp":return this.config.aibot.clientType==="qwen"?"qwen":"acp";default:return e}}finalizeThinking(e,o){this.sendCtrl.finalizeThinking(e,o)}logInboundConversation(e){const o=String(e.session_id??"").trim();o&&this.conversationLog?.logInbound(o,{event_id:e.event_id,msg_id:e.msg_id,sender_id:e.sender_id,msg_type:e.msg_type,content:e.content??""})}buildInboundEvent(e,o){const t=Be(this.sendCtrl.getGlobalRuntimeConfig(),o);return{event_id:e.event_id,session_id:e.session_id,thread_id:e.thread_id,sender_id:e.sender_id,msg_id:e.msg_id,msg_type:e.msg_type,content:e.content??"",quoted_message_id:e.quoted_message_id,context_messages_json:e.context_messages?JSON.stringify(e.context_messages):void 0,extra_json:e.extra?JSON.stringify(e.extra):void 0,connector_runtime_config:{response_delivery:t.responseDelivery,tool_events:t.toolEvents,thinking_events:t.thinkingEvents},session_type:e.session_type,created_at:e.created_at}}isStaleEvent(e){const o=Number(e.created_at);return!Number.isFinite(o)||o<=0?!1:Date.now()-o>ze}async handleConnectorRollback(e){const o=String((e.params??{}).target_version??"").trim(),t=String((e.params??{}).reason??"server_initiated");if(!o){this.aibotHandle.sendLocalActionResult({action_id:e.action_id,status:"failed",error_code:"MISSING_TARGET_VERSION",error_msg:"target_version is required for connector_rollback"});return}u.info(this.name,`connector_rollback: target=${o} reason=${t}`);try{const{npmInstall:n,writePending:i,removePending:s,upgradeLog:r}=await import("../core/upgrade/npm-upgrader.js"),{resolveClientVersion:a}=await import("../core/util/client-version.js"),d=a();r(`server rollback: ${d} -> ${o} reason=${t}`),i(d,o),await n("grix-connector",o),this.aibotHandle.sendLocalActionResult({action_id:e.action_id,status:"ok",result:{rolled_back_to:o}}),process.kill(process.pid,"SIGTERM")}catch(n){try{const{removePending:s}=await import("../core/upgrade/npm-upgrader.js");s()}catch{}const i=n instanceof Error?n.message:String(n);u.error(this.name,`connector_rollback failed: ${i}`),this.aibotHandle.sendLocalActionResult({action_id:e.action_id,status:"failed",error_code:"ROLLBACK_FAILED",error_msg:i})}}setUpgradeTrigger(e){this.upgradeTrigger=e}setShareSetHandler(e){this.shareSetHandler=e}setProviderConfigHandler(e){this.providerConfigHandler=e}isSharedInstance(){return!!this.config.aibot.sharedOwnerId}}export{zt as AgentInstance};
|
|
16
|
+
Models (current: ${s?.modelId??"default"}): ${r}`);return}case p.exec:{const[i,...s]=e.args.trim().split(/\s+/);if(!i){n("failed","Usage: /grix exec <command> [args]",g.verbInvalid);return}const a=this.pool.getSlot(t)?.adapter;if(!a?.execCommand){n("failed","Agent does not support command execution",g.verbInvalid);return}const d=a.getSupportedCommands?.()??[];if(!d.some(c=>c.name===i)){n("failed",`Unknown command: ${i}. Supported: ${d.map(c=>c.name).join(", ")}`,g.verbInvalid);return}try{const c=await a.execCommand(i,s.join(" "),t);n(c.status==="ok"?"responded":"failed",c.message??`${i} ${c.status}`,c.status==="ok"?void 0:g.runtimeError)}catch(c){n("failed",`exec error: ${c instanceof Error?c.message:c}`,g.runtimeError)}return}default:n("failed",`Unsupported command for Claude: /grix ${e.verb}`,g.verbInvalid)}}catch(i){n("failed",i instanceof Error?i.message:String(i),g.runtimeError)}}async handleSessionControlLocalAction(e){const o=String(e.action_type??"").trim();if(o!==S.sessionControl&&o!==S.setMode&&o!=="set_mode"&&o!==S.setModel&&o!=="set_model")return!1;const t=e.params??{},n=String(t.session_id??"").trim(),i=o===S.setMode?p.setMode:o===S.setModel?p.setModel:String(t.verb??"").trim().toLowerCase();if(u.info(this.name,`handleSessionControlLocalAction verb=${i} action_id=${e.action_id} session_id=${n}`),!n)return this.aibotHandle.sendLocalActionResult({action_id:e.action_id,status:"failed",error_code:x.localActionRouteMissing,error_msg:"local action session_id is required"}),!0;const s=a=>{this.aibotHandle.sendLocalActionResult({action_id:e.action_id,status:"ok",result:a})},r=(a,d)=>{u.warn(this.name,`session_control local_action failed action_id=${e.action_id} session_id=${n} verb=${i} code=${a} msg=${d}`),this.aibotHandle.sendLocalActionResult({action_id:e.action_id,status:"failed",error_code:a,error_msg:d})};try{switch(i){case p.open:{await D().catch(()=>{});const a=String(t.cwd??"").trim(),d=String(t.agent_session_id??"").trim();if(!a)return r(g.cwdRequired,"session control cwd is required"),!0;u.info(this.name,`handleSessionControlLocalAction open cwd=${a} session_id=${n}`);const c=await this.resolveCwdForBinding(a);this.ensureImportedAgentSession(d,c);const l=this.bindingStore.get(n);if(l?.cwd){const h=await this.resolveCwdForBinding(l.cwd);return h!==c?(r(g.rebindForbidden,"session binding cannot be changed to another working directory"),!0):(this.bindingStore.ensureModeId(n,C.fullAuto),this.setResolvedAgentSessionId(n,d),this.sessionBindings.set(n,h),await this.ensureSlotStarted(n),await this.replayDeferredEventsForSession(n),this.refreshClaudeWorkerStatusCard(n,h),s({outcome:"opened",binding:{...this.buildOpenedBindingResult(n,h),mode_id:this.normalizeClaudeModeId(this.bindingStore.get(n)?.modeId)}}),!0)}return this.bindingStore.set(n,c,{modeId:C.fullAuto}),this.setResolvedAgentSessionId(n,d),this.sessionBindings.set(n,c),await this.ensureSlotStarted(n),await this.replayDeferredEventsForSession(n),this.refreshClaudeWorkerStatusCard(n,c),s({outcome:"opened",binding:{...this.buildOpenedBindingResult(n,c),mode_id:this.normalizeClaudeModeId(this.bindingStore.get(n)?.modeId)}}),!0}case p.status:case p.where:{const a=this.bindingStore.get(n);return a?.cwd?(s({outcome:i,binding:{cwd:a.cwd,mode_id:this.normalizeClaudeModeId(a.modeId),worker_status:this.getClaudeWorkerStatus(n)}}),!0):(r(g.bindingMissing,"session binding was not found"),!0)}case p.stop:{const a=this.bindingStore.get(n);return a?.cwd?(await this.pool.removeSlot(n),s({outcome:"stopped",binding:{cwd:a.cwd,mode_id:this.normalizeClaudeModeId(a.modeId),worker_status:"stopped"}}),!0):(r(g.bindingMissing,"session binding was not found"),!0)}case p.restart:{const a=this.bindingStore.get(n);return a?.cwd?(await this.pool.removeSlot(n),await this.ensureSlotStarted(n),this.refreshClaudeWorkerStatusCard(n,a.cwd),s({outcome:"restarted",binding:{cwd:a.cwd,mode_id:this.normalizeClaudeModeId(a.modeId)}}),!0):(r(g.bindingMissing,"session binding was not found"),!0)}case p.setMode:{const a=String(t.mode_id??t.modeId??"").trim();if(!a)return r(x.modeInvalid,"set mode_id is invalid"),!0;const d=this.normalizeClaudeModeId(a);if(d!==a.toLowerCase())return r(x.modeInvalid,"set mode_id is invalid"),!0;const c=this.bindingStore.get(n);if(!c?.cwd)return r(g.bindingMissing,"session binding was not found"),!0;if(this.normalizeClaudeModeId(c.modeId)!==d){if(this.getClaudeWorkerStatus(n)==="busy")return r(g.workerBusy,"\u5F53\u524D\u6709\u6D88\u606F\u6B63\u5728\u8FD0\u884C\uFF0C\u8BF7\u5148\u505C\u6B62\u6216\u7B49\u5F85\u5176\u5B8C\u6210\u540E\u518D\u5207\u6362\u6A21\u5F0F"),!0;this.bindingStore.setModeId(n,d),await this.pool.removeSlot(n),await this.ensureSlotStarted(n),this.refreshClaudeWorkerStatusCard(n,c.cwd)}return s({outcome:"mode_set",mode_id:d,binding:{cwd:c.cwd,mode_id:d}}),!0}case p.setModel:{const a=String(t.model_id??t.modelId??"").trim();if(!a)return r("set_model_invalid","model_id is required"),!0;const d=this.bindingStore.get(n);if(!d?.cwd)return r(g.bindingMissing,"session binding was not found"),!0;if((d.modelId??"")!==a){if(this.getClaudeWorkerStatus(n)==="busy")return r(g.workerBusy,"\u5F53\u524D\u6709\u6D88\u606F\u6B63\u5728\u8FD0\u884C\uFF0C\u8BF7\u5148\u505C\u6B62\u6216\u7B49\u5F85\u5176\u5B8C\u6210\u540E\u518D\u5207\u6362\u6A21\u578B"),!0;this.bindingStore.setModelId(n,a),this.globalConfigStore?.set(this.name,{modelId:a}),await this.pool.removeSlot(n),await this.ensureSlotStarted(n),this.refreshClaudeWorkerStatusCard(n,d.cwd)}return s({outcome:"model_set",model_id:a,binding:{cwd:d.cwd,model_id:a}}),!0}case p.listOptions:{const a=Q(),d=this.pool.getSlot(n);return s({modes:[C.fullAuto,C.approval],currentModeId:this.normalizeClaudeModeId(this.bindingStore.get(n)?.modeId),models:a.map(c=>({modelId:c.id,name:c.displayName})),currentModelId:this.bindingStore.get(n)?.modelId??"",available_models:a.map(c=>({id:c.id,displayName:c.displayName})),agent_commands:d?.adapter?.getSupportedCommands?.()??[]}),!0}case p.exec:{const[a,...d]=String(t.args??"").trim().split(/\s+/);if(!a)return r(g.verbInvalid,"Usage: exec <command> [args]"),!0;await this.ensureSlotStarted(n);const l=this.pool.getSlot(n)?.adapter;if(!l?.execCommand)return r(g.verbInvalid,"Agent does not support command execution"),!0;const h=l.getSupportedCommands?.()??[];if(!h.some(m=>m.name===a))return r(g.verbInvalid,`Unknown command: ${a}. Supported: ${h.map(m=>m.name).join(", ")}`),!0;try{const m=await l.execCommand(a,d.join(" "),n);m.status==="ok"?s({outcome:"exec",command:a,message:m.message,data:m.data}):r(g.runtimeError,m.message??`${a} failed`)}catch(m){r(g.runtimeError,`exec error: ${m instanceof Error?m.message:m}`)}return!0}default:return r(g.verbInvalid,`session control verb ${i} is not supported`),!0}}catch(a){const d=a instanceof Error&&a.cwdErrorCode?a.cwdErrorCode:a instanceof Error&&a.sessionControlErrorCode?a.sessionControlErrorCode:g.runtimeError;return u.error(this.name,`handleSessionControlLocalAction error verb=${i} session_id=${n}: ${a instanceof Error?a.message:a}`),r(d,a instanceof Error?a.message:String(a)),!0}}async handleEventCancel(e){const{event_id:o,session_id:t}=e;if(u.info(this.name,`handleEventCancel start event_id=${o} session_id=${t}`),this.pool.cancelEvent(o,t)){if(!(this.pool.getSlot(t)?.adapter?.getActiveEventIds().includes(o)??!1)){this.sendEventResultWithCleanup(o,"canceled","canceled"),this.aibotHandle.sendEventCancelResult({event_id:o,accepted:!0,final_state:"canceled"});return}await this.waitForEventDone(o,t,15e3),this.aibotHandle.sendEventCancelResult({event_id:o,accepted:!0,final_state:"canceled"}),this.pushQueueSnapshotForSession(t);return}this.aibotHandle.sendEventCancelResult({event_id:o,accepted:!1,reason:"event not found or not cancelable"})}waitForEventDone(e,o,t){return new Promise(n=>{const i=this.pool.getSlot(o);if(!i?.adapter){n();return}const s=setTimeout(()=>{i.adapter.removeListener("eventDone",r),n()},t),r=a=>{a===e&&(clearTimeout(s),i.adapter.removeListener("eventDone",r),n())};i.adapter.on("eventDone",r)})}handleAibotStop(e){if(u.info(this.name,`[stop-trace] handleAibotStop begin session=${e.session_id} event=${e.event_id} stop_id=${e.stop_id||"-"} adapterType=${this.config.adapterType??"acp"}`),this.aibotHandle.sendEventStopAck({stop_id:e.stop_id,event_id:e.event_id,accepted:!0,updated_at:Date.now()}),this.pool.removeQueuedEvent(e.session_id,e.event_id)){u.info(this.name,`[stop-trace] handleAibotStop removed queued(not-running) event -> stopResult(stopped) session=${e.session_id} event=${e.event_id} stop_id=${e.stop_id||"-"}`),this.pushQueueSnapshotForSession(e.session_id),this.aibotHandle.sendEventStopResult({stop_id:e.stop_id,event_id:e.event_id,status:"stopped",updated_at:Date.now()});return}const o=this.pool.getSlot(e.session_id),t=o?.adapter?.getStatus().busy??!1,n=(this.config.adapterType??"acp")==="acp",i=o?.adapter?.getActiveEventIds,s=typeof i=="function"?i.call(o.adapter):[],r=s.length>0?s.includes(e.event_id):t,a=(n||(this.config.adapterType??"acp")==="codex"||(this.config.adapterType??"acp")==="claude")&&r;if(u.info(this.name,`[stop-trace] handleAibotStop decision session=${e.session_id} event=${e.event_id} slotExists=${!!o?.adapter} busy=${t} activeIds=[${s.join(",")}] stoppingActiveEvent=${r} killOnStop=${a}`),r&&this.sendCtrl.markEventStopped(e.event_id),o?.adapter&&t){const d=a?this.pool.drainQueuedForSession(e.session_id):[];let c=!1,l=null;const h=v=>{c||(c=!0,l&&(clearTimeout(l),l=null),o.adapter.removeListener("eventDone",m),u.info(this.name,`[stop-trace] handleAibotStop ${v} -> stopResult(stopped) session=${e.session_id} event=${e.event_id} stop_id=${e.stop_id||"-"} killOnStop=${a}`),this.aibotHandle.sendEventStopResult({stop_id:e.stop_id,event_id:e.event_id,status:"stopped",updated_at:Date.now()}),a&&this.killAndResumeStopSlot(e.session_id,d))},m=v=>{v===e.event_id&&h("eventDone")};o.adapter.on("eventDone",m),l=setTimeout(()=>h("timeout"),je),this.pool.deliverStopEvent(e.event_id,e.session_id)}else u.info(this.name,`[stop-trace] handleAibotStop slot-not-busy -> immediate stopResult(stopped) session=${e.session_id} event=${e.event_id} slotExists=${!!o?.adapter}`),this.pool.deliverStopEvent(e.event_id,e.session_id),this.aibotHandle.sendEventStopResult({stop_id:e.stop_id,event_id:e.event_id,status:"stopped",updated_at:Date.now()});(this.config.adapterType??"acp")==="pi"&&this.aibotHandle.sendText({event_id:e.event_id,session_id:e.session_id,content:"/stop",msg_type:0})}async killAndResumeStopSlot(e,o){if(!this.stopped){u.info(this.name,`[stop-trace] killAndResumeStopSlot begin session=${e} siblings=${o.length} -> removeSlot (kill process group)`);try{await this.pool.removeSlot(e),u.info(this.name,`[stop-trace] killAndResumeStopSlot removeSlot done session=${e} (process killed) -> redeliver ${o.length} sibling(s)`)}catch(t){u.warn(this.name,`[acp-stop] removeSlot failed session=${e}: ${t instanceof Error?t.message:String(t)}`)}if(!this.stopped)for(const t of o){if(this.stopped)break;try{await this.pool.deliverInboundEvent(t)}catch(n){u.error(this.name,`[acp-stop] sibling redeliver failed event=${t.event_id} session=${e}: ${n instanceof Error?n.message:String(n)}`),this.sendEventResultWithCleanup(t.event_id,"failed",n instanceof Error?n.message:String(n))}}}}handleAibotRevoke(e){if(!e.event_id||!this.revokeHandler.checkAndTrack(e.event_id))return;const o=e.event_id,t=e.session_id;if(t&&this.pool.cancelEvent(o,t)){(this.pool.getSlot(t)?.adapter?.getActiveEventIds().includes(o)??!1)||this.sendEventResultWithCleanup(o,"canceled","revoked");return}if(this.deferredMgr.removeEvent(o)){this.aibotHandle.sendEventResult({event_id:o,status:"canceled",msg:"revoked",updated_at:Date.now()});return}this.pool.deliverStopEvent(o,t||void 0)}async handleConfigureGatewayProvider(e){const o=e.params??{},t=String(o.api_key??"").trim(),n=String(o.anthropic_base_url??"").trim(),i=String(o.openai_base_url??"").trim(),s=typeof o.model=="string"&&o.model.trim()||void 0;if(!t){this.aibotHandle.sendLocalActionResult({action_id:e.action_id,status:"failed",error_code:"MISSING_API_KEY",error_msg:"api_key is required"});return}const r=this.config.adapterType??"acp",a=r==="claude"||r==="codex";try{if(a){const d=r==="claude"?n:i;if(!d){this.aibotHandle.sendLocalActionResult({action_id:e.action_id,status:"failed",error_code:"MISSING_TARGET_BASE_URL",error_msg:`${r==="claude"?"anthropic_base_url":"openai_base_url"} is required`});return}const c=ce();if(!c){this.aibotHandle.sendLocalActionResult({action_id:e.action_id,status:"failed",error_code:"PROXY_UNAVAILABLE",error_msg:"MITM proxy manager not initialized"});return}const l="grix-gateway";c.setRoute({routeKey:l,targetBaseUrl:d,headers:{Authorization:`Bearer ${t}`},...s?{model:s}:{}}),c.setDefaultRouteKey(l),await c.enable(),await c.persistConfig()}else{if(!this.providerConfigHandler){this.aibotHandle.sendLocalActionResult({action_id:e.action_id,status:"unsupported",error_code:"UNSUPPORTED_CLIENT_TYPE",error_msg:`client type "${r}" does not support Grix relay provider config`});return}await this.providerConfigHandler({base_url:i||void 0,api_key:t,model:s})}this.aibotHandle.sendLocalActionResult({action_id:e.action_id,status:"ok",result:{accepted:!0}})}catch(d){u.error(this.name,`handleConfigureGatewayProvider failed: ${d}`),this.aibotHandle.sendLocalActionResult({action_id:e.action_id,status:"failed",error_code:"CONFIGURE_FAILED",error_msg:d instanceof Error?d.message:String(d)})}}async handleAibotLocalAction(e){const o=e.action_type??"",t=String((e.params??{}).session_id??""),n=String((e.params??{}).verb??"").trim().toLowerCase();u.debug(this.name,`local_action received action_type=${o} verb=${n||"-"} action_id=${e.action_id} session_id=${t}`);const i=(this.config.adapterType??"acp")==="claude";if(o===S.sessionControl&&n===p.exec&&await this.handleSessionControlLocalAction(e))return;if(o===S.sessionControl&&n===p.listSessions){await this.handleListSessionsLocalAction(e);return}if(i&&(o===S.interactionReply||o==="exec_approve"||o==="exec_reject")&&(await this.pool.deliverLocalAction(e)).handled||i&&await this.handleSessionControlLocalAction(e))return;if(o===S.sessionControl){const c=this.config.adapterType??"acp",l=c==="codex",h=c==="pi",m=String((e.params??{}).verb??"").trim().toLowerCase();if(l&&m===p.open){await this.handleCodexSessionControlLocalActionOpen(e);return}if(c==="cursor"&&m===p.open){await this.handleCursorSessionControlLocalActionOpen(e);return}if(l&&m==="restart"){const b=this.bindingStore.get(t)?.cwd??"";await this.pool.removeSlot(t).catch(()=>{}),this.aibotHandle.sendLocalActionResult({action_id:e.action_id,status:"ok",result:{outcome:"restarted",binding:{aibotSessionId:t,cwd:b,workerStatus:"ready"}}});return}if(h&&m===p.open){try{const f=e.params??{},b=String(f.cwd??"").trim();if(!b){this.aibotHandle.sendLocalActionResult({action_id:e.action_id,status:"failed",error_code:g.cwdRequired,error_msg:"session cwd is required"});return}const w=await this.resolveCwdForBinding(b),k=String(f.agent_session_id??"").trim();this.ensureImportedAgentSession(k,w),this.bindingStore.set(t,w),this.setResolvedAgentSessionId(t,k),this.sessionBindings.set(t,w),await this.deferredMgr.release(t,this.deferredCallbacks()),this.aibotHandle.sendUpdateBindingCard({session_id:t,worker_status:"ready",cwd:w}),this.aibotHandle.sendLocalActionResult({action_id:e.action_id,status:"ok",result:{outcome:"opened",binding:this.buildOpenedBindingResult(t,w)}})}catch(f){this.aibotHandle.sendLocalActionResult({action_id:e.action_id,status:"failed",error_code:f?.sessionControlErrorCode??f?.cwdErrorCode??g.invalidCwd,error_msg:f instanceof Error?f.message:String(f)})}return}if(h&&m===p.restart){await this.handlePiSessionControlRestartLocalAction(e);return}if((c==="openhuman"||c==="opencode")&&m===p.open){try{const f=e.params??{},b=String(f.cwd??"").trim();if(!b){this.aibotHandle.sendLocalActionResult({action_id:e.action_id,status:"failed",error_code:g.cwdRequired,error_msg:"session cwd is required"});return}const w=await this.resolveCwdForBinding(b),k=String(f.agent_session_id??"").trim();this.ensureImportedAgentSession(k,w),this.bindingStore.set(t,w),this.setResolvedAgentSessionId(t,k),this.sessionBindings.set(t,w),await this.deferredMgr.release(t,this.deferredCallbacks()),this.aibotHandle.sendLocalActionResult({action_id:e.action_id,status:"ok",result:{outcome:"opened",binding:this.buildOpenedBindingResult(t,w)}})}catch(f){this.aibotHandle.sendLocalActionResult({action_id:e.action_id,status:"failed",error_code:f?.sessionControlErrorCode??f?.cwdErrorCode??g.invalidCwd,error_msg:f instanceof Error?f.message:String(f)})}return}if(c==="codewhale"&&m===p.open){await this.handleCodeWhaleSessionControlLocalActionOpen(e);return}if(c==="acp"&&m===p.stop){const b=this.bindingStore.get(t)?.cwd??"";await this.pool.removeSlot(t).catch(()=>{}),this.aibotHandle.sendLocalActionResult({action_id:e.action_id,status:"ok",result:{outcome:"stopped",binding:{aibotSessionId:t,cwd:b,workerStatus:"stopped"}}});return}try{if(m===p.open){const f=e.params??{},b=String(f.cwd??"").trim();if(!b){this.aibotHandle.sendLocalActionResult({action_id:e.action_id,status:"failed",error_code:g.cwdRequired,error_msg:"session cwd is required"});return}const w=String(f.agent_session_id??"").trim();if(w){const k=await this.resolveCwdForBinding(b);this.ensureImportedAgentSession(w,k)}}await this.handleSessionControlLocalActionForPool(e)}catch(f){this.aibotHandle.sendLocalActionResult({action_id:e.action_id,status:"failed",error_code:f?.sessionControlErrorCode??f?.cwdErrorCode??g.runtimeError,error_msg:f instanceof Error?f.message:String(f)});return}if(m===p.open){const f=e.params??{},b=await this.resolveCwdForBinding(String(f.cwd??"").trim());this.setResolvedAgentSessionId(t,String(f.agent_session_id??"").trim()),c==="agy"&&(this.bindingStore.set(t,b),this.sessionBindings.set(t,b)),await this.deferredMgr.release(t,this.deferredCallbacks()),this.aibotHandle.sendLocalActionResult({action_id:e.action_id,status:"ok",result:{outcome:"opened",binding:this.buildOpenedBindingResult(t,b)}}),(this.config.adapterType??"acp")==="agy"&&this.aibotHandle.sendUpdateBindingCard({session_id:t,worker_status:"ready",cwd:b,meta:this.buildAgyToolbarMeta(t)})}else $e(e,this.sessionControlCtx(t),this.sessionControlSenders());return}if(o==="file_list"){const c=Date.now(),l=t?this.bindingStore.get(t)?.cwd:void 0,h=e.params??{},m=String(h.parent_id??"").trim(),v=Array.isArray(h.allowed_extensions)?h.allowed_extensions.filter(b=>typeof b=="string").map(b=>b.trim()).filter(b=>b.length>0):[];u.info("file-list-diag",`plugin << recv action_id=${e.action_id} session_id=${t} parent_id=${m||"<root>"} show_hidden=${!!h.show_hidden} ext_count=${v.length} bound_cwd=${l??"<none>"}`);const _=await Te({parent_id:m||null,session_id:t,show_hidden:!!h.show_hidden,allowed_extensions:v},{resolveCwd:()=>l??this.config.agent.cwd??process.cwd(),fallbackDir:K()}),R=Date.now()-c,f=_.result?.files?.length??0;u.info("file-list-diag",`plugin -> reply action_id=${e.action_id} status=${_.status} elapsed=${R}ms count=${f} current_path=${_.result?.current_path??""} error_code=${_.error_code??""} error_msg=${_.error_msg??""}`),this.aibotHandle.sendLocalActionResult({action_id:e.action_id,status:_.status,..._.result?{result:{..._.result,machine_name:He()}}:{},..._.error_code?{error_code:_.error_code}:{},..._.error_msg?{error_msg:_.error_msg}:{}});return}if(o==="create_folder"){const c=t?this.bindingStore.get(t)?.cwd:void 0,l=String((e.params??{}).parent_id??"").trim(),h=String((e.params??{}).name??"").trim(),m=await Le({parent_id:l||null,name:h,session_id:t},{resolveCwd:()=>c??this.config.agent.cwd??process.cwd(),fallbackDir:K()});this.aibotHandle.sendLocalActionResult({action_id:e.action_id,status:m.status,...m.result?{result:m.result}:{},...m.error_code?{error_code:m.error_code}:{},...m.error_msg?{error_msg:m.error_msg}:{}});return}if(o===S.setModel&&(this.config.adapterType??"acp")==="agy"){await this.handleAgySetModel(e,t);return}if(o===S.getSessionUsage){await this.handleGetSessionUsage(e,t);return}if(o===S.getRateLimits){await this.handleGetRateLimits(e,t);return}const s=(this.config.adapterType??"acp")==="acp";if((i||s)&&o===S.threadCompact){await this.handleThreadCompact(e,t);return}if(o==="connector_rollback"){await this.handleConnectorRollback(e);return}if(o==="connector_upgrade_push"){this.aibotHandle.sendLocalActionResult({action_id:e.action_id,status:"ok",result:{accepted:!0}}),this.upgradeTrigger?.();return}if(o===S.getAgentGlobalConfig){const c=this.globalConfigStore?.get(this.name);this.aibotHandle.sendLocalActionResult({action_id:e.action_id,status:"ok",result:{agentName:this.name,...c?{config:c}:{config:null}}});return}if(o==="configure_gateway_provider"){await this.handleConfigureGatewayProvider(e);return}const r=this.config.adapterType??"acp",a=(r==="codex"||r==="cursor"||r==="pi"||r==="openhuman"||r==="opencode"||r==="acp")&&!!t&&!!this.bindingStore.get(t)?.cwd,d=await this.pool.deliverLocalAction(e,{autoCreateSlot:a});if(d.handled){if(d.kind==="set_mode"){const c=String((e.params??{}).mode_id??"");c&&(r==="cursor"||r==="claude"?this.bindingStore.setModeId(t,c):P.has(r)||this.globalConfigStore?.set(this.name,{acpInitialMode:c}))}else if(d.kind==="set_model"){const c=String((e.params??{}).model_id??"");c&&(r==="cursor"?(this.bindingStore.setModelId(t,c),this.globalConfigStore?.set(this.name,{modelId:c})):P.has(r)||this.globalConfigStore?.set(this.name,{modelId:c}))}else if(d.kind==="set_reasoning_effort"){const c=String((e.params??{}).reasoning_effort??(e.params??{}).reasoning_eff??(e.params??{}).effort??"");c&&this.globalConfigStore?.set(this.name,{codexReasoningEffort:c})}else if(d.kind==="set_sandbox_mode"){const c=String((e.params??{}).sandbox_mode??(e.params??{}).sandboxMode??"");if(c){const l=c==="default"?void 0:c;this.globalConfigStore?.set(this.name,{codexSandboxMode:l})}}return}if((r==="codex"||r==="cursor"||r==="pi"||r==="openhuman"||r==="opencode")&&t&&!this.bindingStore.get(t)?.cwd){this.aibotHandle.sendLocalActionResult({action_id:e.action_id,status:"failed",error_code:g.bindingMissing,error_msg:"Session binding missing. Open a workspace first."});return}if(r==="acp"&&(o==="set_mode"||o==="set_model")){const c=this.sessionControlSenders(),l=this.pool.getSlot(t)?.adapter,h={bindingStore:this.bindingStore,acpAdapter:l instanceof A?l:null,globalConfigStore:this.globalConfigStore,agentName:this.name,log:u};if(o==="set_mode"){const m=String((e.params??{}).mode_id??""),v=await N(h,t,m);if(v.status==="failed")c.sendLocalActionResult(e.action_id,"failed",void 0,v.errorCode,v.errorMsg);else{const _=l instanceof A?l.buildToolbarContext(v.result?.outcome==="mode_set"?"mode_set":"mode_set_failed"):null,R=_?{..._,...v.result}:v.result;c.sendLocalActionResult(e.action_id,"ok",R)}}else{const m=String((e.params??{}).model_id??""),v=await W(h,t,m);if(v.status==="failed")c.sendLocalActionResult(e.action_id,"failed",void 0,v.errorCode,v.errorMsg);else{const _=l instanceof A?l.buildToolbarContext(v.result?.outcome==="model_set"?"model_set":"model_set_failed"):null,R=_?{..._,...v.result}:v.result;c.sendLocalActionResult(e.action_id,"ok",R)}}return}this.aibotHandle.sendLocalActionResult({action_id:e.action_id,status:"failed",error_code:"unsupported_local_action",error_msg:`action type ${o} is not supported`})}async handleGetSessionUsage(e,o){if(!o){u.warn(this.name,`[usage] get_session_usage rejected: no session_id in params, action_id=${e.action_id}`),this.aibotHandle.sendLocalActionResult({action_id:e.action_id,status:"failed",error_code:"session_id_required",error_msg:"session_id is required for get_session_usage"});return}const t=this.config.adapterType??"acp",n=this.bindingStore.get(o),i=n?.cwd??this.config.agent.cwd??process.cwd();u.info(this.name,`[usage] get_session_usage action_id=${e.action_id} session_id=${o} adapterType=${t} hasBinding=${!!n} cwd=${i}`);let s=null,r,a;switch(t){case"claude":{if(r=n?.claudeSessionId,!r){u.warn(this.name,`[usage] no claude binding for session_id=${o}, action_id=${e.action_id}`),this.aibotHandle.sendLocalActionResult({action_id:e.action_id,status:"failed",error_code:"no_binding",error_msg:"No Claude session binding found"});return}u.info(this.name,`[usage] parsing claude usage: claudeSessionId=${r} cwd=${i}`),s=await ne(r,i),a="claude";break}case"agy":{this.aibotHandle.sendLocalActionResult({action_id:e.action_id,status:"ok",result:{sessionId:o,adapterType:"agy",available:!1,reason:"agy print-mode adapter does not track token usage"}});return}case"acp":default:{if(r=n?.acpSessionId,!r){this.aibotHandle.sendLocalActionResult({action_id:e.action_id,status:"failed",error_code:"no_binding",error_msg:"No ACP session binding found"});return}s=await ae(r,i,this.config.aibot.clientType),a=t,(!a||a==="acp")&&(a="acp");break}case"codex":{if(r=n?.codexThreadId,!r){this.aibotHandle.sendLocalActionResult({action_id:e.action_id,status:"failed",error_code:"no_binding",error_msg:"No Codex thread binding found"});return}s=await ue(r),a="codex";break}case"pi":{if(r=n?.piSessionPath,!r){this.aibotHandle.sendLocalActionResult({action_id:e.action_id,status:"failed",error_code:"no_binding",error_msg:"No Pi session path binding found"});return}s=await fe(r),a="pi";break}case"cursor":{const c=this.pool.getSlot(o)?.adapter;if(!(c instanceof I)){this.aibotHandle.sendLocalActionResult({action_id:e.action_id,status:"failed",error_code:"no_binding",error_msg:"No Cursor session found"});return}const l=c.getUsageSnapshot(o);if(!l){this.aibotHandle.sendLocalActionResult({action_id:e.action_id,status:"failed",error_code:"usage_not_found",error_msg:"No usage data found for this session"});return}this.aibotHandle.sendLocalActionResult({action_id:e.action_id,status:"ok",result:{sessionId:o,adapterType:"cursor",models:[{modelId:this.bindingStore.get(o)?.modelId??"auto",turns:l.turns,input:l.total.input,output:l.total.output,cacheRead:l.total.cacheRead,cacheWrite:l.total.cacheWrite}],total:{input:l.total.input,output:l.total.output,cacheRead:l.total.cacheRead,cacheWrite:l.total.cacheWrite},turns:l.turns,sampledAt:l.sampledAt}});return}case"codewhale":{const c=this.pool.getSlot(o)?.adapter;if(!(c instanceof U)){this.aibotHandle.sendLocalActionResult({action_id:e.action_id,status:"failed",error_code:"no_binding",error_msg:"No CodeWhale session found"});return}const l=c.getUsageSnapshot();if(!l){this.aibotHandle.sendLocalActionResult({action_id:e.action_id,status:"failed",error_code:"usage_not_found",error_msg:"No usage data found for this session"});return}this.aibotHandle.sendLocalActionResult({action_id:e.action_id,status:"ok",result:{sessionId:o,adapterType:"codewhale",models:[{modelId:this.bindingStore.get(o)?.modelId??"codewhale",turns:l.turns,input:l.total.input,output:l.total.output}],total:{input:l.total.input,output:l.total.output},turns:l.turns,sampledAt:l.sampledAt}});return}}if(!s){u.info(this.name,`[usage] no usage data found: session_id=${o} adapterSessionId=${r} adapterType=${a}`),this.aibotHandle.sendLocalActionResult({action_id:e.action_id,status:"failed",error_code:"usage_not_found",error_msg:"No usage data found for this session"});return}u.info(this.name,`[usage] result ok: session_id=${o} adapterSessionId=${r} turns=${s.turns} models=${s.models.length}`),this.aibotHandle.sendLocalActionResult({action_id:e.action_id,status:"ok",result:{sessionId:r,adapterType:a,models:s.models,total:s.total,turns:s.turns,sampledAt:new Date().toISOString()}})}async handleThreadCompact(e,o){if(!o){this.aibotHandle.sendLocalActionResult({action_id:e.action_id,status:"failed",error_code:"session_id_required",error_msg:"session_id is required for thread_compact"});return}if(!this.bindingStore.get(o)?.cwd){this.aibotHandle.sendLocalActionResult({action_id:e.action_id,status:"failed",error_code:g.bindingMissing,error_msg:"session binding was not found"});return}try{await this.ensureSlotStarted(o);const i=this.pool.getSlot(o)?.adapter;if(!i?.execCommand){this.aibotHandle.sendLocalActionResult({action_id:e.action_id,status:"failed",error_code:g.runtimeError,error_msg:"Agent does not support command execution"});return}u.info(this.name,`thread_compact session_id=${o} action_id=${e.action_id}`);const s=await i.execCommand("compact","",o);s.status==="ok"?this.aibotHandle.sendLocalActionResult({action_id:e.action_id,status:"ok",result:{outcome:"compacted",message:s.message,data:s.data}}):this.aibotHandle.sendLocalActionResult({action_id:e.action_id,status:"failed",error_code:g.runtimeError,error_msg:s.message??"compact failed"})}catch(n){u.warn(this.name,`thread_compact error session_id=${o}: ${n instanceof Error?n.message:n}`),this.aibotHandle.sendLocalActionResult({action_id:e.action_id,status:"failed",error_code:g.runtimeError,error_msg:n instanceof Error?n.message:String(n)})}}async handleGetRateLimits(e,o){const t=this.config.adapterType??"acp";if(this.config.aibot.clientType==="kiro"){const n=this.isRateLimitsCacheFresh(this.cachedProviderQuotaSampledAtMs),i=this.cachedAcpContextWindow;if(n&&this.cachedProviderQuota){this.aibotHandle.sendLocalActionResult({action_id:e.action_id,status:"ok",result:{adapterType:"acp",available:!0,cached:!0,sampledAt:this.cachedProviderQuotaSampledAtMs,rateLimits:null,contextWindow:i,tokenUsage:null,providerQuota:this.cachedProviderQuota}});return}try{const s=await B();this.cachedProviderQuota=s,this.cachedProviderQuotaSampledAtMs=Date.now(),u.info(this.name,`[rate-limits] kiro quota queried: success=${s.success}`+(s.balance?` balance=${s.balance.remaining} ${s.balance.unit}`:"")+(s.error?` error=${s.error}`:"")),this.aibotHandle.sendLocalActionResult({action_id:e.action_id,status:"ok",result:{adapterType:"acp",available:!0,cached:!1,sampledAt:this.cachedProviderQuotaSampledAtMs,rateLimits:null,contextWindow:i,tokenUsage:null,providerQuota:s}})}catch(s){u.warn(this.name,`[rate-limits] kiro quota query failed: ${s instanceof Error?s.message:String(s)}`),this.aibotHandle.sendLocalActionResult({action_id:e.action_id,status:"ok",result:{adapterType:"acp",available:!1,cached:!1,sampledAt:null,rateLimits:null,contextWindow:i,tokenUsage:null}})}return}switch(t){case"codex":{const n=this.maybeQueryProviderQuota(),i=this.getFreshCodexGlobalRateLimitCache();if(i.hasData&&!this.isRateLimitsCacheFresh(this.cachedRateLimitsSampledAtMs)&&!this.isRateLimitsCacheFresh(this.cachedCodexUsageSampledAtMs)){const l=this.pool.getAllSlots().find(h=>h.state==="ready"&&h.adapter);if(l&&(u.info(this.name,`[rate-limits] codex cache stale, refreshing from slot: session=${l.sessionId}`),(await l.adapter.handleLocalAction?.(e))?.handled))return}if(i.hasData){i.rateLimits?u.info(this.name,`[rate-limits] codex cached: primary=${i.rateLimits.primary.usedPercent.toFixed(1)}% resetsAt=${i.rateLimits.primary.resetsAt} secondary=${i.rateLimits.secondary.usedPercent.toFixed(1)}% resetsAt=${i.rateLimits.secondary.resetsAt}`):u.info(this.name,`[rate-limits] codex cached context/token only: hasContext=${!!i.contextWindow} hasToken=${!!i.tokenUsage}`);const l=await n;this.aibotHandle.sendLocalActionResult({action_id:e.action_id,status:"ok",result:{adapterType:"codex",available:i.hasData||!!l,cached:!0,sampledAt:i.sampledAt,rateLimits:i.rateLimits,contextWindow:i.contextWindow,tokenUsage:i.tokenUsage,providerQuota:l}});return}const r=this.pool.getAllSlots().find(l=>l.state==="ready"&&l.adapter);if(r&&(u.info(this.name,`[rate-limits] codex reuse existing slot: session=${r.sessionId}`),(await r.adapter.handleLocalAction?.(e))?.handled))return;const a=this.resolveRateLimitWakeSessionId(o,t);if(a){const l=await this.wakeRateLimitSlot(a,t);if(l?.adapter&&(await l.adapter.handleLocalAction?.(e))?.handled)return}const d=await n,c=!!d;u.info(this.name,`[rate-limits] codex no native data, providerQuota=${d?d.provider:"none"} available=${c}`),this.aibotHandle.sendLocalActionResult({action_id:e.action_id,status:"ok",result:{adapterType:"codex",available:c,cached:!1,sampledAt:null,rateLimits:null,contextWindow:null,tokenUsage:null,providerQuota:d}});return}case"claude":{const n=this.maybeQueryProviderQuota(),i=this.getFreshClaudeRateLimitState();if(i){const c=i.rateLimits,l=await n;u.info(this.name,`[rate-limits] claude global cached: sampledAt=${i.sampledAt} hasRateLimits=${!!c}`+(l?` providerQuota=${l.provider}`:"")),this.aibotHandle.sendLocalActionResult({action_id:e.action_id,status:"ok",result:{adapterType:"claude",available:!0,cached:!0,sampledAt:i.sampledAt,rateLimits:i.rateLimits??null,contextWindow:i.contextWindow,tokenUsage:null,providerQuota:l}});return}let s=this.pool.getAllSlots().find(c=>c.state==="ready"&&c.adapter instanceof y)??null;if(!s){const c=this.resolveRateLimitWakeSessionId(o,t);c&&(s=await this.wakeRateLimitSlot(c,t))}u.info(this.name,`[rate-limits] handleGetRateLimits: session_id=${o} adapterType=claude hasSlot=${!!s} hasAdapter=${!!s?.adapter}`);const r=s?.adapter,a=r instanceof y?r.getSessionState():null,d=await n;if(a){(a.rateLimits?.fiveHour||a.rateLimits?.sevenDay||a.contextWindow?.usedPercentage!=null)&&(this.cachedClaudeRateLimitState=a);const c=this.getFreshClaudeRateLimitState(),l=c?.rateLimits&&!a.rateLimits?{...a,rateLimits:c.rateLimits}:a,h=l.rateLimits;u.info(this.name,`[rate-limits] claude global state: sampledAt=${l.sampledAt} hasRateLimits=${!!h}`+(h?` fiveHour=${h.fiveHour?.usedPercentage??"n/a"}% resetsAt=${h.fiveHour?.resetsAt??"n/a"} sevenDay=${h.sevenDay?.usedPercentage??"n/a"}% resetsAt=${h.sevenDay?.resetsAt??"n/a"}`:"")+(c?.rateLimits&&!a.rateLimits?" source=live+cached-fallback":" source=live")+(d?` providerQuota=${d.provider}`:"")),this.aibotHandle.sendLocalActionResult({action_id:e.action_id,status:"ok",result:{adapterType:"claude",available:!0,cached:!1,sampledAt:l.sampledAt,rateLimits:l.rateLimits??null,contextWindow:l.contextWindow,tokenUsage:null,providerQuota:d}})}else u.info(this.name,`[rate-limits] claude no global state: hasAdapter=${!!r} adapterType=${r?.constructor?.name??"n/a"}`+(d?` providerQuota=${d.provider}`:"")),this.aibotHandle.sendLocalActionResult({action_id:e.action_id,status:"ok",result:{adapterType:"claude",available:!!d,cached:!1,sampledAt:null,rateLimits:null,contextWindow:null,tokenUsage:null,providerQuota:d}});return}case"cursor":{const i=(o?this.pool.getSlot(o):null)?.adapter,s=i instanceof I?i.getRateLimitsSnapshot():{adapterType:"cursor",available:!1,cached:!1,sampledAt:null,rateLimits:null,contextWindow:null,tokenUsage:null};this.aibotHandle.sendLocalActionResult({action_id:e.action_id,status:"ok",result:s});return}default:{const n=this.config.providerBaseUrl,i=this.config.providerApiKey;if(!n||!i){u.info(this.name,`[rate-limits] no provider config for adapterType=${t}`),this.aibotHandle.sendLocalActionResult({action_id:e.action_id,status:"ok",result:{adapterType:t,available:!1,cached:!1,sampledAt:null,rateLimits:null,contextWindow:null,tokenUsage:null}});return}if(this.isRateLimitsCacheFresh(this.cachedProviderQuotaSampledAtMs)&&this.cachedProviderQuota){u.info(this.name,`[rate-limits] provider quota cached: provider=${this.cachedProviderQuota.provider} success=${this.cachedProviderQuota.success}`),this.aibotHandle.sendLocalActionResult({action_id:e.action_id,status:"ok",result:{adapterType:t,available:!0,cached:!0,sampledAt:this.cachedProviderQuotaSampledAtMs,rateLimits:null,contextWindow:null,tokenUsage:null,providerQuota:this.cachedProviderQuota}});return}try{const r=await z(n,i);this.cachedProviderQuota=r,this.cachedProviderQuotaSampledAtMs=Date.now(),u.info(this.name,`[rate-limits] provider quota queried: provider=${r.provider} success=${r.success}`+(r.tiers.length>0?` tiers=${r.tiers.map(a=>`${a.name}=${a.usedPercent}%`).join(",")}`:"")+(r.balance?` balance=${r.balance.remaining} ${r.balance.unit}`:"")+(r.error?` error=${r.error}`:"")),this.aibotHandle.sendLocalActionResult({action_id:e.action_id,status:"ok",result:{adapterType:t,available:!0,cached:!1,sampledAt:this.cachedProviderQuotaSampledAtMs,rateLimits:null,contextWindow:null,tokenUsage:null,providerQuota:r}})}catch(r){u.warn(this.name,`[rate-limits] provider quota query failed: ${r instanceof Error?r.message:String(r)}`),this.aibotHandle.sendLocalActionResult({action_id:e.action_id,status:"ok",result:{adapterType:t,available:!1,cached:!1,sampledAt:null,rateLimits:null,contextWindow:null,tokenUsage:null}})}return}}}resolveRateLimitWakeSessionId(e,o){const t=String(e??"").trim(),n=this.bindingStore.getMostRecentlyUpdatedSessionId({requireCwd:!0});return n?t&&t===n?t:o==="codex"||o==="claude"?(t&&t!==n&&u.info(this.name,`[rate-limits] ${o} remap wake session: requested=${t} use_recent=${n}`),n):t&&t!==n?(u.info(this.name,`[rate-limits] skip wake slot: session=${t} adapterType=${o} reason=not_recent_session recent=${n}`),null):t||null:(u.info(this.name,`[rate-limits] skip wake slot: adapterType=${o} reason=no_recent_binding`),null)}async wakeRateLimitSlot(e,o){const t=String(e??"").trim();if(!t)return null;if(!this.bindingStore.get(t)?.cwd)return u.info(this.name,`[rate-limits] skip wake slot: session=${t} adapterType=${o} reason=binding_cwd_missing`),null;try{const i=this.pool.getOrCreateSlot(t);if(!i)return u.info(this.name,`[rate-limits] skip wake slot: session=${t} adapterType=${o} reason=slot_unavailable`),null;if(i.startPromise){const s=o==="claude"?6e4:2e4;await Promise.race([i.startPromise,new Promise((r,a)=>setTimeout(()=>a(new Error(`wake rate-limits slot timeout (${s}ms)`)),s))])}return u.info(this.name,`[rate-limits] wake slot success: session=${t} adapterType=${o}`),this.pool.getSlot(t)??i}catch(i){return u.warn(this.name,`[rate-limits] wake slot failed: session=${t} adapterType=${o} err=${i instanceof Error?i.message:String(i)}`),null}}sessionControlCtx(e){const o=this.pool.getSlot(e),t=o?.adapter instanceof A?o.adapter:null,n=(this.config.adapterType??"acp")==="acp",i={bindingStore:this.bindingStore,acpAdapter:t,globalConfigStore:this.globalConfigStore,agentName:this.name,log:u};return{getCwd:()=>this.bindingStore.get(e)?.cwd??this.config.agent.cwd??process.cwd(),getSessionBindings:()=>{if(t)return t.getSessionBindings();const s=this.sessionBindings;if(e&&!s.has(e)){const r=this.bindingStore.get(e);r?.cwd&&s.set(e,r.cwd)}return s},getStatus:()=>this.getStatus(),isAcpAlive:!!t?.isAlive(),getAcpSessionOptions:()=>t?.acpSessionOptions??null,setMode:s=>t?t.setMode(s):Promise.resolve(!1),setModel:s=>t?t.setModel(s):Promise.resolve(!1),acpSetMode:n?(s,r)=>N(i,s,r):void 0,acpSetModel:n?(s,r)=>W(i,s,r):void 0,getPendingApproval:s=>{const r=t?.pendingApprovalEntries.get(s);return r?{requestId:r}:void 0},deletePendingApproval:s=>t?.pendingApprovalEntries.delete(s)??!1,respondPermission:(s,r)=>(t&&t.respondToPermission(s,r),Promise.resolve()),onSessionBound:(s,r)=>{this.bindingStore.set(s,r)},onSessionUnbound:s=>{const a=this.bindingStore.get(s)?.cwd??"";this.bindingStore.delete(s),this.sessionBindings.delete(s),this.claudeWorkerStatus.delete(s),this.deferredMgr.clearSession(s),this.pool.drainQueuedForSession(s),a&&this.aibotHandle.sendUpdateBindingCard({session_id:s,worker_status:"stopped",cwd:a})},cancelActiveRun:()=>(this.config.adapterType??"acp")==="agy"&&o?.adapter instanceof O?(o.adapter.cancelCurrentRun(),Promise.resolve()):o?.adapter?.cancel("")??Promise.resolve(),onModeSet:s=>{const r=this.config.adapterType??"acp";P.has(r)||this.globalConfigStore?.set(this.name,{acpInitialMode:s})},onModelSet:s=>{const r=this.config.adapterType??"acp";P.has(r)||this.globalConfigStore?.set(this.name,{modelId:s})}}}sessionControlSenders(){return{sendEventAck:(e,o)=>this.aibotHandle.sendEventAck({event_id:e,session_id:o,received_at:Date.now()}),sendEventResult:(e,o,t)=>this.aibotHandle.sendEventResult({event_id:e,status:o,...t?.msg?{msg:t.msg}:{},...t?.code?{code:t.code}:{},updated_at:Date.now()}),sendLocalActionResult:(e,o,t,n,i)=>this.aibotHandle.sendLocalActionResult({action_id:e,status:o,...t?{result:t}:{},...n?{error_code:n}:{},...i?{error_msg:i}:{}})}}resolveBindingChannelKey(e){switch(e){case"claude":return"grix-claude";case"codex":return"codex";case"cursor":return"cursor";case"pi":return"pi";case"openhuman":return"openhuman";case"codewhale":return"codewhale";case"opencode":return"opencode";case"agy":return"acp";case"acp":return this.config.aibot.clientType==="qwen"?"qwen":"acp";default:return e}}finalizeThinking(e,o){this.sendCtrl.finalizeThinking(e,o)}logInboundConversation(e){const o=String(e.session_id??"").trim();o&&this.conversationLog?.logInbound(o,{event_id:e.event_id,msg_id:e.msg_id,sender_id:e.sender_id,msg_type:e.msg_type,content:e.content??""})}buildInboundEvent(e,o){const t=Be(this.sendCtrl.getGlobalRuntimeConfig(),o);return{event_id:e.event_id,session_id:e.session_id,thread_id:e.thread_id,sender_id:e.sender_id,msg_id:e.msg_id,msg_type:e.msg_type,content:e.content??"",quoted_message_id:e.quoted_message_id,context_messages_json:e.context_messages?JSON.stringify(e.context_messages):void 0,extra_json:e.extra?JSON.stringify(e.extra):void 0,connector_runtime_config:{response_delivery:t.responseDelivery,tool_events:t.toolEvents,thinking_events:t.thinkingEvents},session_type:e.session_type,created_at:e.created_at}}isStaleEvent(e){const o=Number(e.created_at);return!Number.isFinite(o)||o<=0?!1:Date.now()-o>ze}async handleConnectorRollback(e){const o=String((e.params??{}).target_version??"").trim(),t=String((e.params??{}).reason??"server_initiated");if(!o){this.aibotHandle.sendLocalActionResult({action_id:e.action_id,status:"failed",error_code:"MISSING_TARGET_VERSION",error_msg:"target_version is required for connector_rollback"});return}u.info(this.name,`connector_rollback: target=${o} reason=${t}`);try{const{npmInstall:n,writePending:i,removePending:s,upgradeLog:r}=await import("../core/upgrade/npm-upgrader.js"),{resolveClientVersion:a}=await import("../core/util/client-version.js"),d=a();r(`server rollback: ${d} -> ${o} reason=${t}`),i(d,o),await n("grix-connector",o),this.aibotHandle.sendLocalActionResult({action_id:e.action_id,status:"ok",result:{rolled_back_to:o}}),process.kill(process.pid,"SIGTERM")}catch(n){try{const{removePending:s}=await import("../core/upgrade/npm-upgrader.js");s()}catch{}const i=n instanceof Error?n.message:String(n);u.error(this.name,`connector_rollback failed: ${i}`),this.aibotHandle.sendLocalActionResult({action_id:e.action_id,status:"failed",error_code:"ROLLBACK_FAILED",error_msg:i})}}setUpgradeTrigger(e){this.upgradeTrigger=e}setAgentDeletedHandler(e){this.agentDeletedHandler=e}setShareSetHandler(e){this.shareSetHandler=e}setProviderConfigHandler(e){this.providerConfigHandler=e}isSharedInstance(){return!!this.config.aibot.sharedOwnerId}}export{zt as AgentInstance};
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{EventEmitter as
|
|
2
|
-
`}),this.sendPacket("client_stream_chunk",e)}sendMsg(e){this.sendPacket("send_msg",e)||o.warn("aibot",`send_msg NOT sent (ws not open) event=${e.event_id??""} session=${e.session_id??""} ws=${this.ws?`state=${this.ws.readyState}`:"null"}`)}editMsg(e){this.sendPacket("edit_msg",e)}sendEventResult(e){if(!this.ws||this.ws.readyState!==m.OPEN){this.sendPacket("event_result",e);return}this.sendEventResultReliable(e)}sendLocalActionResult(e){this.sendPacket("local_action_result",e)}sendEventStopAck(e){this.sendPacket("event_stop_ack",e)}sendEventStopResult(e){this.sendPacket("event_stop_result",e)}sendSessionActivitySet(e){this.sendPacket("session_activity_set",e)}sendCodexEvent(e){this.sendPacket("codex_event",e)}sendUpdateBindingCard(e){this.sendPacket("update_binding_card",e)}sendSkillsUpdate(e){this.sendPacket("agent_skills_update",e)}sendPing(){this.sendPacket("ping",{})}sendEventState(e){this.sendPacket("event_state",e)}sendEventCancelResult(e){this.sendPacket("event_cancel_result",e)}sendQueueClearResult(e){this.sendPacket("queue_clear_result",e)}sendQueueSnapshot(e){this.sendPacket("queue_snapshot",e)}agentInvoke(e,t,n=15e3){return new Promise((r,s)=>{const h=v(),c=Math.max(1e3,Math.min(n,6e4)),a=setTimeout(()=>{this.pendingInvokes.delete(h),s(new Error(`agent_invoke timeout: ${e}`))},c);this.pendingInvokes.set(h,{resolve:r,reject:s,timer:a}),this.sendPacket("agent_invoke",{invoke_id:h,action:e,params:t,timeout_ms:c})})}sendMcpFrame(e,t){this.sendPacket("mcp_frame",{session_id:e,frame:t})}request(e,t,n){return new Promise((r,s)=>{const h=++this.seq,c=setTimeout(()=>{this.pendingRequests.delete(h),s(new Error(`request timeout: ${e} (expected ${n.expected.join("/")})`))},n.timeoutMs);this.pendingRequests.set(h,{expected:n.expected,resolve:r,reject:s,timer:c}),this.sendPacket(e,t,h)||(this.pendingRequests.delete(h),clearTimeout(c),s(new Error(`send failed: ${e}`)))})}async sendStreamChunkRequest(e,t=2e4){return this.request("client_stream_chunk",e,{expected:["send_ack","send_nack","error"],timeoutMs:t})}async sendText(e,t=2e4){return this.request("send_msg",{msg_type:1,...e},{expected:["send_ack","send_nack","error"],timeoutMs:t})}async sendMedia(e,t=2e4){return this.request("send_msg",{...e,msg_type:2},{expected:["send_ack","send_nack","error"],timeoutMs:t})}async editMessage(e,t=2e4){return this.request("edit_msg",e,{expected:["send_ack","send_nack","error"],timeoutMs:t})}async deleteMessage(e,t,n=2e4){return this.request("delete_msg",{session_id:e,msg_id:t},{expected:["send_ack","send_nack","error"],timeoutMs:n})}async sendEventResultRequest(e,t=5e3){return this.request("event_result",e,{expected:["send_ack","send_nack","error"],timeoutMs:t})}disconnect(){o.info("aibot",`disconnect() agent=${this.config.clientType}:${this.config.agentId} wasConnected=${this.connected} reconnecting=${this.reconnecting} reconnectAttempts=${this.reconnectAttempts}`),this.connected=!1,this.everConnected=!1,this.reconnecting=!1,this.reconnectAttempts=0,this.stopHeartbeat(),this.rejectAllPendingInvokes("disconnect"),this.rejectAllPendingRequests("disconnect"),this.outboundBuffer.length=0,this.ws&&(this.ws.close(1e3,"client disconnect"),this.ws=null)}static MAX_CONSECUTIVE_AUTH_FAILURES=5;async attemptReconnect(){if(this.reconnecting)return;this.reconnecting=!0,o.info("aibot",`attemptReconnect start agent=${this.config.clientType}:${this.config.agentId} fromAttempts=${this.reconnectAttempts}`),this.emit("disconnected");let e=0;for(;this.reconnecting;){const t=Math.min(1e3*2**this.reconnectAttempts,3e4),n=Math.floor(t*.2*Math.random());if(this.reconnectAttempts++,await new Promise(r=>setTimeout(r,t+n)),!this.reconnecting)return;try{const r=await this.connect(),s=this.reconnectAttempts;this.reconnectAttempts=0,this.reconnecting=!1,o.info("aibot",`reconnect succeeded agent=${this.config.clientType}:${this.config.agentId} attempt=${s}`),this.emit("auth",r);return}catch(r){if(this.ws){try{this.ws.close()}catch{}this.ws=null}const s=r instanceof Error?r.message:String(r);if(o.warn("aibot",`reconnect failed agent=${this.config.clientType}:${this.config.agentId} attempt=${this.reconnectAttempts} err=${s}`),/Auth failed/i.test(s)){if(e++,e>=f.MAX_CONSECUTIVE_AUTH_FAILURES){this.reconnecting=!1,o.error("aibot",`reconnect giving up after ${e} consecutive auth failures agent=${this.config.clientType}:${this.config.agentId}`);return}}else e=0}}}sendPacket(e,t,n){if(this.ws&&this.ws.readyState===m.OPEN){const r=this.ws.bufferedAmount>f.BACKPRESSURE_THRESHOLD;if(!r||!f.DROPPABLE_COMMANDS.has(e)){if(r&&f.DROPPABLE_COMMANDS.has(e))return!1;const s=n??++this.seq;if(e==="client_stream_chunk"&&t&&typeof t=="object"){const c=t.event_id;if(c&&(this.seqEventMap.set(s,c),this.seqEventMap.size>200)){const a=this.seqEventMap.keys().next().value;a!==void 0&&this.seqEventMap.delete(a)}}const h={cmd:e,seq:s,payload:t};this.packetLog?.logOutboundPacket(e,s,t,"sent");try{const c=this.ws.readyState,a=this.ws.bufferedAmount;return this.ws.send(JSON.stringify(h),d=>{if(e==="event_result"){const i=t;d?o.warn("aibot",`event_result ws send callback failed event=${i.event_id??""} status=${i.status??""} seq=${s} readyState=${c} bufferedAmount=${a} err=${d.message}`):o.info("aibot",`event_result ws send callback ok event=${i.event_id??""} status=${i.status??""} seq=${s} readyState=${c} bufferedAmount=${a}`)}else if(e==="client_stream_chunk"){const i=t;d?o.warn("aibot",`stream_chunk ws send failed event=${i.event_id??""} session=${i.session_id??""} seq=${s} chunk_seq=${i.chunk_seq??""} is_finish=${i.is_finish??""} readyState=${c} bufferedAmount=${a} err=${d.message}`):o.info("aibot",`stream_chunk ws send ok event=${i.event_id??""} session=${i.session_id??""} seq=${s} chunk_seq=${i.chunk_seq??""} is_finish=${i.is_finish??""} client_msg_id=${i.client_msg_id??""} quoted_message_id=${i.quoted_message_id??""} readyState=${c} bufferedAmount=${a}`)}else if(e==="event_ack"){const i=t;d?o.warn("aibot",`event_ack ws send failed event=${i.event_id??""} seq=${s} readyState=${c} bufferedAmount=${a} err=${d.message}`):o.info("aibot",`event_ack ws send ok event=${i.event_id??""} seq=${s} readyState=${c} bufferedAmount=${a}`)}else if(e==="send_msg"){const i=t;d?o.warn("aibot",`send_msg ws send failed event=${i.event_id??""} session=${i.session_id??""} seq=${s} readyState=${c} bufferedAmount=${a} err=${d.message}`):o.info("aibot",`send_msg ws send ok event=${i.event_id??""} session=${i.session_id??""} seq=${s} readyState=${c} bufferedAmount=${a}`)}else if(d){const i=t;o.warn("aibot",`${e} ws send failed seq=${s} session=${i.session_id??""} event=${i.event_id??""} client_msg_id=${i.client_msg_id??""} readyState=${c} bufferedAmount=${a} err=${d.message}`)}}),!0}catch(c){return this.emitClientError(new Error(`sendPacket failed: ${c}`)),!1}}}if(f.DROPPABLE_COMMANDS.has(e))return this.packetLog?.logOutboundPacket(e,n??0,t,"dropped"),!1;if(n!==void 0)return this.packetLog?.logOutboundPacket(e,n,t,"dropped"),!1;if(this.outboundBuffer.length>=f.MAX_OUTBOUND_BUFFER_SIZE&&(this.outboundBuffer=this.outboundBuffer.filter(r=>f.BUFFER_OVERFLOW_RETAIN_COMMANDS.has(r.cmd)),this.outboundBuffer.length>=f.MAX_OUTBOUND_BUFFER_SIZE&&this.outboundBuffer.shift()),this.outboundBuffer.push({cmd:e,payload:t}),this.packetLog?.logOutboundPacket(e,n??0,t,"buffered"),e==="client_stream_chunk"){const r=t;o.info("aibot",`stream_chunk buffered (ws not open) event=${r.event_id??""} session=${r.session_id??""} chunk_seq=${r.chunk_seq??""} is_finish=${r.is_finish??""} ws=${this.ws?`state=${this.ws.readyState}`:"null"}`)}return!1}async sendEventResultReliable(e){const t=this.ackPolicy?.max_retries??3,n=this.ackPolicy?.push_ack_timeout_ms??5e3,r=750;for(let s=1;s<=t;s++){const h=this.ws?.readyState??-1,c=this.ws?.bufferedAmount??0;o.info("aibot",`event_result send attempt event=${e.event_id} status=${e.status} attempt=${s}/${t} readyState=${h} bufferedAmount=${c}`);try{const a=await this.sendEventResultRequest(e,n);if(a.cmd==="send_ack"){const i=a.payload;o.info("aibot",`event_result ack event=${e.event_id} status=${e.status} attempt=${s}/${t} ack_event=${i.event_id??""} ack_status=${i.status??""}`);return}const d=a.payload;if(o.warn("aibot",`event_result rejected event=${e.event_id} status=${e.status} attempt=${s}/${t} cmd=${a.cmd} code=${d.code??""} msg=${d.msg??""}${d.ref_cmd?` ref_cmd=${d.ref_cmd}`:""}${d.ref_id?` ref_id=${d.ref_id}`:""}`),d.code===4003){o.warn("aibot",`event_result stopping retries: 4003 ownership denied event=${e.event_id}`);return}return}catch(a){const d=a instanceof Error?a.message:String(a);if(o.warn("aibot",`event_result attempt failed event=${e.event_id} status=${e.status} attempt=${s}/${t} err=${d}`),s===t){this.emitClientError(new Error(`event_result ack failed after ${t} attempts: event=${e.event_id} status=${e.status}`));return}await new Promise(i=>setTimeout(i,r*s))}}}purgeBufferedStreamChunks(e){const t=this.outboundBuffer.length;this.outboundBuffer=this.outboundBuffer.filter(n=>n.cmd!=="client_stream_chunk"?!0:n.payload?.event_id!==e),this.outboundBuffer.length<t&&o.info("aibot",`purged ${t-this.outboundBuffer.length} buffered stream chunks for event=${e}`)}emitClientError(e){if(this.listenerCount("error")===0){o.warn("aibot",`Client error (no listeners): ${e.message}`);return}this.emit("error",e)}flushOutboundBuffer(){if(this.outboundBuffer.length===0||!this.ws||this.ws.readyState!==m.OPEN)return;const e=this.outboundBuffer;this.outboundBuffer=[];for(const{cmd:t,payload:n}of e){const r=++this.seq;if(t==="client_stream_chunk"&&n&&typeof n=="object"){const h=n.event_id;h&&this.seqEventMap.set(r,h)}const s={cmd:t,seq:r,payload:n};try{this.ws.send(JSON.stringify(s))}catch{break}}if(this.seqEventMap.size>200){const t=[...this.seqEventMap.entries()].sort((n,r)=>n[0]-r[0]);this.seqEventMap.clear();for(const[n,r]of t.slice(-100))this.seqEventMap.set(n,r)}}handleInvokeResult(e){const t=this.pendingInvokes.get(e.invoke_id);t&&(this.pendingInvokes.delete(e.invoke_id),clearTimeout(t.timer),e.code===0?t.resolve(e.data??null):t.reject(new Error(`agent_invoke error code=${e.code}: ${e.msg??""}`)))}rejectAllPendingInvokes(e){for(const[,t]of this.pendingInvokes)clearTimeout(t.timer),t.reject(new Error(`agent_invoke canceled: ${e}`));this.pendingInvokes.clear()}rejectAllPendingRequests(e){for(const[,t]of this.pendingRequests)clearTimeout(t.timer),t.reject(new Error(`request canceled: ${e}`));this.pendingRequests.clear()}cleanupSocket(){if(this.ws)try{this.ws.close()}catch{}}startHeartbeat(){this.stopHeartbeat(),this.heartbeatTimer=setInterval(()=>{this.connected&&this.request("ping",{ts:Date.now()},{expected:["pong"],timeoutMs:5e3}).catch(()=>{this.connected&&(this.cleanupSocket(),this.attemptReconnect())})},this.heartbeatSec*1e3)}stopHeartbeat(){this.heartbeatTimer&&(clearInterval(this.heartbeatTimer),this.heartbeatTimer=null)}}export{f as AibotClient};
|
|
1
|
+
import{EventEmitter as v}from"node:events";import{randomUUID as b}from"node:crypto";import p from"node:os";import m from"ws";import{log as c}from"../log/index.js";import{getMachineName as w}from"../util/index.js";import{detectTailnetIPv4 as E,ensureServerAndGetPort as q,getFileServerHttpsPort as S}from"../files/file-serve.js";import{AUTH_CODE_AGENT_DELETED as k,KICKED_REASON_AGENT_DELETED as A,AgentDeletedError as $}from"./errors.js";function y(g){return g.replace(/(?<=[\[:,\[]\s*)(\d{16,})(?=\s*[,}\]\n])/g,'"$1"')}function P(g){const e=[...g??["stream_chunk","local_action_v1","agent_invoke"]];return e.includes("agent_invoke")||e.push("agent_invoke"),e.includes("event_result_ack")||e.push("event_result_ack"),e}const R="aibot-agent-api-v1",T=1;class f extends v{static DROPPABLE_COMMANDS=new Set(["update_binding_card"]);static BUFFER_OVERFLOW_RETAIN_COMMANDS=new Set(["event_result","codex_event","client_stream_chunk"]);static MAX_OUTBOUND_BUFFER_SIZE=1e3;static BACKPRESSURE_THRESHOLD=64*1024;ws=null;seq=0;heartbeatTimer=null;heartbeatSec=30;connected=!1;reconnecting=!1;reconnectAttempts=0;everConnected=!1;agentDeleted=!1;config;packetLog;pendingInvokes=new Map;seqEventMap=new Map;pendingRequests=new Map;outboundBuffer=[];ackPolicy=null;constructor(e,t){super(),this.packetLog=t?.packetLog??null,this.config={url:e.url,agentId:e.agentId,apiKey:e.apiKey,clientType:e.clientType,clientVersion:e.clientVersion??"",adapterHint:e.adapterHint??"",capabilities:P(e.capabilities),localActions:e.localActions??["exec_approve","exec_reject"],skills:e.skills}}get isConnected(){return this.connected}async connect(){let e,t,n;const i=(async()=>{try{if(e=await E(),e!==void 0)try{t=await q(e);const s=S();s>0&&(n=s)}catch(s){c.warn("aibot",`file server pre-start failed: ${s}`)}}catch(s){c.warn("aibot",`tailnet detect failed: ${s}`)}})();return new Promise((s,h)=>{const o=new m(this.config.url);this.ws=o;const a=setTimeout(()=>{h(new Error("Auth timeout: no auth_ack received within 15s")),this.cleanupSocket()},15e3),d=++this.seq,r=setTimeout(()=>{this.pendingRequests.delete(d),h(new Error("Auth request timeout")),this.cleanupSocket()},15e3);this.pendingRequests.set(d,{expected:["auth_ack"],resolve:u=>{clearTimeout(a);const l=u.payload;l.code===0?(this.connected=!0,this.everConnected=!0,this.reconnectAttempts=0,l.heartbeat_sec&&(this.heartbeatSec=l.heartbeat_sec),l.ack_policy&&(this.ackPolicy=l.ack_policy,c.info("aibot",`ack_policy received: push_ack_timeout_ms=${l.ack_policy.push_ack_timeout_ms??"default"} max_retries=${l.ack_policy.max_retries??"default"} timeout_action=${l.ack_policy.timeout_action??"default"}`)),this.startHeartbeat(),this.flushOutboundBuffer(),this.emit("auth",l),s(l)):l.code===k?(this.agentDeleted=!0,h(new $(`Agent deleted: code=${l.code} msg=${l.msg}`))):h(new Error(`Auth failed: code=${l.code} msg=${l.msg}`))},reject:u=>{clearTimeout(a),h(u)},timer:r}),o.on("open",async()=>{await i;const u={agent_id:this.config.agentId,api_key:this.config.apiKey,client_type:this.config.clientType,protocol_version:R,contract_version:T,capabilities:this.config.capabilities??[],local_actions:this.config.localActions,skills:this.config.skills};this.config.sharedOwnerId&&(u.shared_owner_id=this.config.sharedOwnerId),this.config.clientVersion&&(u.client="grix-connector",u.client_version=this.config.clientVersion,u.host_type=this.config.clientType,u.host_version=this.config.clientVersion),this.config.adapterHint&&(u.adapter_hint=this.config.adapterHint),u.host_meta={hostname:w(),platform:p.platform(),arch:p.arch(),os_release:p.release(),...e!==void 0&&{tailnet_ip:e},...t!==void 0&&t>0&&{file_server_port:t},...n!==void 0&&n>0&&{file_server_https_port:n}},this.config.concurrency&&(u.concurrency=this.config.concurrency),this.sendPacket("auth",u,d)}),o.on("message",u=>{if(this.ws!==o)return;let l;try{l=JSON.parse(y(u.toString()))}catch{return}try{this.handlePacket(l)}catch(_){this.emitClientError(new Error(`handlePacket error: ${_}`))}}),o.on("close",(u,l)=>{if(this.ws!==o)return;this.connected=!1,this.stopHeartbeat(),this.rejectAllPendingRequests("websocket closed"),this.emit("close",u,l.toString());const _=u!==1e3&&this.everConnected&&!this.agentDeleted;c.info("aibot",`ws closed agent=${this.config.clientType}:${this.config.agentId} code=${u} reason=${l.toString()||"<none>"} everConnected=${this.everConnected} reconnecting=${this.reconnecting} agentDeleted=${this.agentDeleted} willReconnect=${_}`),_&&this.attemptReconnect()}),o.on("error",u=>{this.ws===o&&(this.emitClientError(u instanceof Error?u:new Error(String(u))),this.connected||h(u))})})}handlePacket(e){if(this.packetLog?.logInboundPacket(e.cmd,e.seq,e.payload),e.seq>0&&this.pendingRequests.has(e.seq)){const t=this.pendingRequests.get(e.seq);this.pendingRequests.delete(e.seq),clearTimeout(t.timer),t.expected.includes(e.cmd)?t.resolve(e):t.reject(new Error(`unexpected response: got ${e.cmd}, expected ${t.expected.join("/")}`));return}switch(e.cmd){case"auth_ack":break;case"ping":{this.sendPacket("pong",e.payload??{});break}case"event_msg":{this.emit("event",e.payload);break}case"local_action":{this.emit("localAction",e.payload);break}case"event_stop":{this.emit("stop",e.payload);break}case"event_revoke":{this.emit("revoke",e.payload);break}case"event_edit":{this.emit("edit",e.payload);break}case"event_cancel":{this.emit("eventCancel",e.payload);break}case"queue_clear":{this.emit("queueClear",e.payload);break}case"queue_snapshot_query":{this.emit("queueSnapshotQuery",e.payload);break}case"control_share_set":{this.emit("shareSet",e.payload);break}case"agent_profile_push":{this.emit("profilePush",e.payload);break}case"kicked":{const t=e.payload;if(this.emit("kicked",t),this.connected=!1,this.stopHeartbeat(),this.rejectAllPendingRequests("kicked"),this.outboundBuffer.length=0,t?.reason===A){if(this.agentDeleted=!0,this.reconnecting=!1,this.ws){try{this.ws.close(4001,"kicked")}catch{}this.ws=null}c.error("aibot",`kicked: agent deleted on platform agent=${this.config.clientType}:${this.config.agentId}, reconnect disabled`),this.emit("agentDeleted",{source:"kicked",reason:t.reason});break}if(this.reconnectAttempts=Math.max(this.reconnectAttempts,3),this.ws){try{this.ws.close(4001,"kicked")}catch{}this.ws=null}break}case"error":{const t=e.payload,n=[t.ref_cmd?`ref_cmd=${t.ref_cmd}`:"",t.ref_id?`ref_id=${t.ref_id}`:""].filter(Boolean).join(" ");this.emitClientError(new Error(`Server error: code=${t.code} msg=${t.msg}${n?` ${n}`:""}`));break}case"agent_invoke_result":{this.handleInvokeResult(e.payload);break}case"mcp_frame":{const t=e.payload;this.emit("mcpFrame",t.session_id??"",t.frame??null);break}case"send_ack":break;case"send_nack":{const t=e.payload;if(t.code===4003&&e.seq>0){const n=this.seqEventMap.get(e.seq);n&&(this.seqEventMap.delete(e.seq),this.purgeBufferedStreamChunks(n),c.warn("aibot",`stream chunk rejected (4003), purging buffered chunks for event=${n}`),this.emit("streamRejected",n,t.code))}break}case"local_action_ack":break;default:break}}sendEventAck(e){this.sendPacket("event_ack",e)||c.warn("aibot",`event_ack NOT sent (ws not open) event=${e.event_id} ws=${this.ws?`state=${this.ws.readyState}`:"null"}`)}sendStreamChunk(e){!e.delta_content&&!e.is_finish&&(c.warn("aibot",`stream_chunk delta_content empty, patched to newline event=${e.event_id??""} session=${e.session_id} chunk_seq=${e.chunk_seq} is_finish=${e.is_finish}`),e={...e,delta_content:`
|
|
2
|
+
`}),this.sendPacket("client_stream_chunk",e)}sendMsg(e){this.sendPacket("send_msg",e)||c.warn("aibot",`send_msg NOT sent (ws not open) event=${e.event_id??""} session=${e.session_id??""} ws=${this.ws?`state=${this.ws.readyState}`:"null"}`)}editMsg(e){this.sendPacket("edit_msg",e)}sendEventResult(e){if(!this.ws||this.ws.readyState!==m.OPEN){this.sendPacket("event_result",e);return}this.sendEventResultReliable(e)}sendLocalActionResult(e){this.sendPacket("local_action_result",e)}sendEventStopAck(e){this.sendPacket("event_stop_ack",e)}sendEventStopResult(e){this.sendPacket("event_stop_result",e)}sendSessionActivitySet(e){this.sendPacket("session_activity_set",e)}sendCodexEvent(e){this.sendPacket("codex_event",e)}sendUpdateBindingCard(e){this.sendPacket("update_binding_card",e)}sendSkillsUpdate(e){this.sendPacket("agent_skills_update",e)}sendPing(){this.sendPacket("ping",{})}sendEventState(e){this.sendPacket("event_state",e)}sendEventCancelResult(e){this.sendPacket("event_cancel_result",e)}sendQueueClearResult(e){this.sendPacket("queue_clear_result",e)}sendQueueSnapshot(e){this.sendPacket("queue_snapshot",e)}agentInvoke(e,t,n=15e3){return new Promise((i,s)=>{const h=b(),o=Math.max(1e3,Math.min(n,6e4)),a=setTimeout(()=>{this.pendingInvokes.delete(h),s(new Error(`agent_invoke timeout: ${e}`))},o);this.pendingInvokes.set(h,{resolve:i,reject:s,timer:a}),this.sendPacket("agent_invoke",{invoke_id:h,action:e,params:t,timeout_ms:o})})}sendMcpFrame(e,t){this.sendPacket("mcp_frame",{session_id:e,frame:t})}request(e,t,n){return new Promise((i,s)=>{const h=++this.seq,o=setTimeout(()=>{this.pendingRequests.delete(h),s(new Error(`request timeout: ${e} (expected ${n.expected.join("/")})`))},n.timeoutMs);this.pendingRequests.set(h,{expected:n.expected,resolve:i,reject:s,timer:o}),this.sendPacket(e,t,h)||(this.pendingRequests.delete(h),clearTimeout(o),s(new Error(`send failed: ${e}`)))})}async sendStreamChunkRequest(e,t=2e4){return this.request("client_stream_chunk",e,{expected:["send_ack","send_nack","error"],timeoutMs:t})}async sendText(e,t=2e4){return this.request("send_msg",{msg_type:1,...e},{expected:["send_ack","send_nack","error"],timeoutMs:t})}async sendMedia(e,t=2e4){return this.request("send_msg",{...e,msg_type:2},{expected:["send_ack","send_nack","error"],timeoutMs:t})}async editMessage(e,t=2e4){return this.request("edit_msg",e,{expected:["send_ack","send_nack","error"],timeoutMs:t})}async deleteMessage(e,t,n=2e4){return this.request("delete_msg",{session_id:e,msg_id:t},{expected:["send_ack","send_nack","error"],timeoutMs:n})}async sendEventResultRequest(e,t=5e3){return this.request("event_result",e,{expected:["send_ack","send_nack","error"],timeoutMs:t})}disconnect(){c.info("aibot",`disconnect() agent=${this.config.clientType}:${this.config.agentId} wasConnected=${this.connected} reconnecting=${this.reconnecting} reconnectAttempts=${this.reconnectAttempts}`),this.connected=!1,this.everConnected=!1,this.reconnecting=!1,this.reconnectAttempts=0,this.stopHeartbeat(),this.rejectAllPendingInvokes("disconnect"),this.rejectAllPendingRequests("disconnect"),this.outboundBuffer.length=0,this.ws&&(this.ws.close(1e3,"client disconnect"),this.ws=null)}static MAX_CONSECUTIVE_AUTH_FAILURES=5;async attemptReconnect(){if(this.reconnecting||this.agentDeleted)return;this.reconnecting=!0,c.info("aibot",`attemptReconnect start agent=${this.config.clientType}:${this.config.agentId} fromAttempts=${this.reconnectAttempts}`),this.emit("disconnected");let e=0;for(;this.reconnecting;){const t=Math.min(1e3*2**this.reconnectAttempts,3e4),n=Math.floor(t*.2*Math.random());if(this.reconnectAttempts++,await new Promise(i=>setTimeout(i,t+n)),!this.reconnecting)return;try{const i=await this.connect(),s=this.reconnectAttempts;this.reconnectAttempts=0,this.reconnecting=!1,c.info("aibot",`reconnect succeeded agent=${this.config.clientType}:${this.config.agentId} attempt=${s}`),this.emit("auth",i);return}catch(i){if(this.ws){try{this.ws.close()}catch{}this.ws=null}const s=i instanceof Error?i.message:String(i);if(c.warn("aibot",`reconnect failed agent=${this.config.clientType}:${this.config.agentId} attempt=${this.reconnectAttempts} err=${s}`),i instanceof $){this.agentDeleted=!0,this.reconnecting=!1,c.error("aibot",`reconnect aborted: agent deleted on platform agent=${this.config.clientType}:${this.config.agentId}`),this.emit("agentDeleted",{source:"auth_ack",code:k});return}if(/Auth failed/i.test(s)){if(e++,e>=f.MAX_CONSECUTIVE_AUTH_FAILURES){this.reconnecting=!1,c.error("aibot",`reconnect giving up after ${e} consecutive auth failures agent=${this.config.clientType}:${this.config.agentId}`);return}}else e=0}}}sendPacket(e,t,n){if(this.ws&&this.ws.readyState===m.OPEN){const i=this.ws.bufferedAmount>f.BACKPRESSURE_THRESHOLD;if(!i||!f.DROPPABLE_COMMANDS.has(e)){if(i&&f.DROPPABLE_COMMANDS.has(e))return!1;const s=n??++this.seq;if(e==="client_stream_chunk"&&t&&typeof t=="object"){const o=t.event_id;if(o&&(this.seqEventMap.set(s,o),this.seqEventMap.size>200)){const a=this.seqEventMap.keys().next().value;a!==void 0&&this.seqEventMap.delete(a)}}const h={cmd:e,seq:s,payload:t};this.packetLog?.logOutboundPacket(e,s,t,"sent");try{const o=this.ws.readyState,a=this.ws.bufferedAmount;return this.ws.send(JSON.stringify(h),d=>{if(e==="event_result"){const r=t;d?c.warn("aibot",`event_result ws send callback failed event=${r.event_id??""} status=${r.status??""} seq=${s} readyState=${o} bufferedAmount=${a} err=${d.message}`):c.info("aibot",`event_result ws send callback ok event=${r.event_id??""} status=${r.status??""} seq=${s} readyState=${o} bufferedAmount=${a}`)}else if(e==="client_stream_chunk"){const r=t;d?c.warn("aibot",`stream_chunk ws send failed event=${r.event_id??""} session=${r.session_id??""} seq=${s} chunk_seq=${r.chunk_seq??""} is_finish=${r.is_finish??""} readyState=${o} bufferedAmount=${a} err=${d.message}`):c.info("aibot",`stream_chunk ws send ok event=${r.event_id??""} session=${r.session_id??""} seq=${s} chunk_seq=${r.chunk_seq??""} is_finish=${r.is_finish??""} client_msg_id=${r.client_msg_id??""} quoted_message_id=${r.quoted_message_id??""} readyState=${o} bufferedAmount=${a}`)}else if(e==="event_ack"){const r=t;d?c.warn("aibot",`event_ack ws send failed event=${r.event_id??""} seq=${s} readyState=${o} bufferedAmount=${a} err=${d.message}`):c.info("aibot",`event_ack ws send ok event=${r.event_id??""} seq=${s} readyState=${o} bufferedAmount=${a}`)}else if(e==="send_msg"){const r=t;d?c.warn("aibot",`send_msg ws send failed event=${r.event_id??""} session=${r.session_id??""} seq=${s} readyState=${o} bufferedAmount=${a} err=${d.message}`):c.info("aibot",`send_msg ws send ok event=${r.event_id??""} session=${r.session_id??""} seq=${s} readyState=${o} bufferedAmount=${a}`)}else if(d){const r=t;c.warn("aibot",`${e} ws send failed seq=${s} session=${r.session_id??""} event=${r.event_id??""} client_msg_id=${r.client_msg_id??""} readyState=${o} bufferedAmount=${a} err=${d.message}`)}}),!0}catch(o){return this.emitClientError(new Error(`sendPacket failed: ${o}`)),!1}}}if(f.DROPPABLE_COMMANDS.has(e))return this.packetLog?.logOutboundPacket(e,n??0,t,"dropped"),!1;if(n!==void 0)return this.packetLog?.logOutboundPacket(e,n,t,"dropped"),!1;if(this.outboundBuffer.length>=f.MAX_OUTBOUND_BUFFER_SIZE&&(this.outboundBuffer=this.outboundBuffer.filter(i=>f.BUFFER_OVERFLOW_RETAIN_COMMANDS.has(i.cmd)),this.outboundBuffer.length>=f.MAX_OUTBOUND_BUFFER_SIZE&&this.outboundBuffer.shift()),this.outboundBuffer.push({cmd:e,payload:t}),this.packetLog?.logOutboundPacket(e,n??0,t,"buffered"),e==="client_stream_chunk"){const i=t;c.info("aibot",`stream_chunk buffered (ws not open) event=${i.event_id??""} session=${i.session_id??""} chunk_seq=${i.chunk_seq??""} is_finish=${i.is_finish??""} ws=${this.ws?`state=${this.ws.readyState}`:"null"}`)}return!1}async sendEventResultReliable(e){const t=this.ackPolicy?.max_retries??3,n=this.ackPolicy?.push_ack_timeout_ms??5e3,i=750;for(let s=1;s<=t;s++){const h=this.ws?.readyState??-1,o=this.ws?.bufferedAmount??0;c.info("aibot",`event_result send attempt event=${e.event_id} status=${e.status} attempt=${s}/${t} readyState=${h} bufferedAmount=${o}`);try{const a=await this.sendEventResultRequest(e,n);if(a.cmd==="send_ack"){const r=a.payload;c.info("aibot",`event_result ack event=${e.event_id} status=${e.status} attempt=${s}/${t} ack_event=${r.event_id??""} ack_status=${r.status??""}`);return}const d=a.payload;if(c.warn("aibot",`event_result rejected event=${e.event_id} status=${e.status} attempt=${s}/${t} cmd=${a.cmd} code=${d.code??""} msg=${d.msg??""}${d.ref_cmd?` ref_cmd=${d.ref_cmd}`:""}${d.ref_id?` ref_id=${d.ref_id}`:""}`),d.code===4003){c.warn("aibot",`event_result stopping retries: 4003 ownership denied event=${e.event_id}`);return}return}catch(a){const d=a instanceof Error?a.message:String(a);if(c.warn("aibot",`event_result attempt failed event=${e.event_id} status=${e.status} attempt=${s}/${t} err=${d}`),s===t){this.emitClientError(new Error(`event_result ack failed after ${t} attempts: event=${e.event_id} status=${e.status}`));return}await new Promise(r=>setTimeout(r,i*s))}}}purgeBufferedStreamChunks(e){const t=this.outboundBuffer.length;this.outboundBuffer=this.outboundBuffer.filter(n=>n.cmd!=="client_stream_chunk"?!0:n.payload?.event_id!==e),this.outboundBuffer.length<t&&c.info("aibot",`purged ${t-this.outboundBuffer.length} buffered stream chunks for event=${e}`)}emitClientError(e){if(this.listenerCount("error")===0){c.warn("aibot",`Client error (no listeners): ${e.message}`);return}this.emit("error",e)}flushOutboundBuffer(){if(this.outboundBuffer.length===0||!this.ws||this.ws.readyState!==m.OPEN)return;const e=this.outboundBuffer;this.outboundBuffer=[];for(const{cmd:t,payload:n}of e){const i=++this.seq;if(t==="client_stream_chunk"&&n&&typeof n=="object"){const h=n.event_id;h&&this.seqEventMap.set(i,h)}const s={cmd:t,seq:i,payload:n};try{this.ws.send(JSON.stringify(s))}catch{break}}if(this.seqEventMap.size>200){const t=[...this.seqEventMap.entries()].sort((n,i)=>n[0]-i[0]);this.seqEventMap.clear();for(const[n,i]of t.slice(-100))this.seqEventMap.set(n,i)}}handleInvokeResult(e){const t=this.pendingInvokes.get(e.invoke_id);t&&(this.pendingInvokes.delete(e.invoke_id),clearTimeout(t.timer),e.code===0?t.resolve(e.data??null):t.reject(new Error(`agent_invoke error code=${e.code}: ${e.msg??""}`)))}rejectAllPendingInvokes(e){for(const[,t]of this.pendingInvokes)clearTimeout(t.timer),t.reject(new Error(`agent_invoke canceled: ${e}`));this.pendingInvokes.clear()}rejectAllPendingRequests(e){for(const[,t]of this.pendingRequests)clearTimeout(t.timer),t.reject(new Error(`request canceled: ${e}`));this.pendingRequests.clear()}cleanupSocket(){if(this.ws)try{this.ws.close()}catch{}}startHeartbeat(){this.stopHeartbeat(),this.heartbeatTimer=setInterval(()=>{this.connected&&this.request("ping",{ts:Date.now()},{expected:["pong"],timeoutMs:5e3}).catch(()=>{this.connected&&(this.cleanupSocket(),this.attemptReconnect())})},this.heartbeatSec*1e3)}stopHeartbeat(){this.heartbeatTimer&&(clearInterval(this.heartbeatTimer),this.heartbeatTimer=null)}}export{f as AibotClient};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
class r{client;_status="ready";connectedAt;listeners=[];authAck=null;constructor(e){this.client=e,this.connectedAt=Date.now(),this.addInternalListener("close",()=>{this._status!=="closing"&&(this._status="reconnecting")}),this.addInternalListener("auth",t=>{this._status="ready",t&&(this.authAck=t)}),this.addInternalListener("disconnected",()=>{this._status!=="closing"&&this._status!=="closed"&&(this._status="reconnecting")})}get status(){return this._status}getStatusSnapshot(){return{status:this._status,connectedAt:this.connectedAt,reconnectAttempts:0}}sendEventAck(e){this.client.sendEventAck(e)}sendStreamChunk(e){this.client.sendStreamChunk(e)}sendMsg(e){this.client.sendMsg(e)}editMsg(e){this.client.editMsg(e)}sendEventResult(e){this.client.sendEventResult(e)}sendLocalActionResult(e){this.client.sendLocalActionResult(e)}sendEventStopAck(e){this.client.sendEventStopAck(e)}sendEventStopResult(e){this.client.sendEventStopResult(e)}sendSessionActivitySet(e){this.client.sendSessionActivitySet(e)}sendCodexEvent(e){this.client.sendCodexEvent(e)}sendUpdateBindingCard(e){this.client.sendUpdateBindingCard(e)}sendSkillsUpdate(e){this.client.sendSkillsUpdate(e)}sendPing(){this.client.sendPing()}sendEventState(e){this.client.sendEventState(e)}sendEventCancelResult(e){this.client.sendEventCancelResult(e)}sendQueueClearResult(e){this.client.sendQueueClearResult(e)}sendQueueSnapshot(e){this.client.sendQueueSnapshot(e)}agentInvoke(e,t,s){return this.client.agentInvoke(e,t,s)}sendStreamChunkRequest(e,t){return this.client.sendStreamChunkRequest(e,t)}sendText(e,t){return this.client.sendText(e,t)}sendMedia(e,t){return this.client.sendMedia(e,t)}editMessage(e,t){return this.client.editMessage(e,t)}deleteMessage(e,t,s){return this.client.deleteMessage(e,t,s)}sendEventResultRequest(e,t){return this.client.sendEventResultRequest(e,t)}sendCodexEventReliable(e,t){return this.client.request("codex_event",e,{expected:["send_ack","send_nack","error"],timeoutMs:t??2e4})}onEvent(e){return this.subscribe("event",e)}onLocalAction(e){return this.subscribe("localAction",e)}onStop(e){return this.subscribe("stop",e)}onRevoke(e){return this.subscribe("revoke",e)}onEdit(e){return this.subscribe("edit",e)}onEventCancel(e){return this.subscribe("eventCancel",e)}onQueueClear(e){return this.subscribe("queueClear",e)}onQueueSnapshotQuery(e){return this.subscribe("queueSnapshotQuery",e)}onKicked(e){return this.subscribe("kicked",e)}onShareSet(e){return this.subscribe("shareSet",e)}onProfilePush(e){return this.subscribe("profilePush",e)}onError(e){return this.subscribe("error",e)}onClose(e){return this.subscribe("close",e)}onDisconnected(e){return this.subscribe("disconnected",e)}onStreamRejected(e){return this.subscribe("streamRejected",e)}onReconnected(e){return this.subscribe("auth",e)}disconnect(){this._status="closing",this.removeAllListeners(),this.client.disconnect(),this._status="closed"}sendMcpFrame(e,t){this.client.sendMcpFrame(e,t)}onMcpFrame(e){return this.subscribe("mcpFrame",e)}subscribe(e,t){return this.client.on(e,t),this.listeners.push({event:e,handler:t}),()=>{this.client.removeListener(e,t);const s=this.listeners.findIndex(n=>n.event===e&&n.handler===t);s>=0&&this.listeners.splice(s,1)}}addInternalListener(e,t){this.client.on(e,t),this.listeners.push({event:e,handler:t})}removeAllListeners(){for(const{event:e,handler:t}of this.listeners)this.client.removeListener(e,t);this.listeners.length=0}}export{r as AibotConnectionHandleImpl};
|
|
1
|
+
class r{client;_status="ready";connectedAt;listeners=[];authAck=null;constructor(e){this.client=e,this.connectedAt=Date.now(),this.addInternalListener("close",()=>{this._status!=="closing"&&(this._status="reconnecting")}),this.addInternalListener("auth",t=>{this._status="ready",t&&(this.authAck=t)}),this.addInternalListener("disconnected",()=>{this._status!=="closing"&&this._status!=="closed"&&(this._status="reconnecting")})}get status(){return this._status}getStatusSnapshot(){return{status:this._status,connectedAt:this.connectedAt,reconnectAttempts:0}}sendEventAck(e){this.client.sendEventAck(e)}sendStreamChunk(e){this.client.sendStreamChunk(e)}sendMsg(e){this.client.sendMsg(e)}editMsg(e){this.client.editMsg(e)}sendEventResult(e){this.client.sendEventResult(e)}sendLocalActionResult(e){this.client.sendLocalActionResult(e)}sendEventStopAck(e){this.client.sendEventStopAck(e)}sendEventStopResult(e){this.client.sendEventStopResult(e)}sendSessionActivitySet(e){this.client.sendSessionActivitySet(e)}sendCodexEvent(e){this.client.sendCodexEvent(e)}sendUpdateBindingCard(e){this.client.sendUpdateBindingCard(e)}sendSkillsUpdate(e){this.client.sendSkillsUpdate(e)}sendPing(){this.client.sendPing()}sendEventState(e){this.client.sendEventState(e)}sendEventCancelResult(e){this.client.sendEventCancelResult(e)}sendQueueClearResult(e){this.client.sendQueueClearResult(e)}sendQueueSnapshot(e){this.client.sendQueueSnapshot(e)}agentInvoke(e,t,s){return this.client.agentInvoke(e,t,s)}sendStreamChunkRequest(e,t){return this.client.sendStreamChunkRequest(e,t)}sendText(e,t){return this.client.sendText(e,t)}sendMedia(e,t){return this.client.sendMedia(e,t)}editMessage(e,t){return this.client.editMessage(e,t)}deleteMessage(e,t,s){return this.client.deleteMessage(e,t,s)}sendEventResultRequest(e,t){return this.client.sendEventResultRequest(e,t)}sendCodexEventReliable(e,t){return this.client.request("codex_event",e,{expected:["send_ack","send_nack","error"],timeoutMs:t??2e4})}onEvent(e){return this.subscribe("event",e)}onLocalAction(e){return this.subscribe("localAction",e)}onStop(e){return this.subscribe("stop",e)}onRevoke(e){return this.subscribe("revoke",e)}onEdit(e){return this.subscribe("edit",e)}onEventCancel(e){return this.subscribe("eventCancel",e)}onQueueClear(e){return this.subscribe("queueClear",e)}onQueueSnapshotQuery(e){return this.subscribe("queueSnapshotQuery",e)}onKicked(e){return this.subscribe("kicked",e)}onAgentDeleted(e){return this.subscribe("agentDeleted",e)}onShareSet(e){return this.subscribe("shareSet",e)}onProfilePush(e){return this.subscribe("profilePush",e)}onError(e){return this.subscribe("error",e)}onClose(e){return this.subscribe("close",e)}onDisconnected(e){return this.subscribe("disconnected",e)}onStreamRejected(e){return this.subscribe("streamRejected",e)}onReconnected(e){return this.subscribe("auth",e)}disconnect(){this._status="closing",this.removeAllListeners(),this.client.disconnect(),this._status="closed"}sendMcpFrame(e,t){this.client.sendMcpFrame(e,t)}onMcpFrame(e){return this.subscribe("mcpFrame",e)}subscribe(e,t){return this.client.on(e,t),this.listeners.push({event:e,handler:t}),()=>{this.client.removeListener(e,t);const s=this.listeners.findIndex(n=>n.event===e&&n.handler===t);s>=0&&this.listeners.splice(s,1)}}addInternalListener(e,t){this.client.on(e,t),this.listeners.push({event:e,handler:t})}removeAllListeners(){for(const{event:e,handler:t}of this.listeners)this.client.removeListener(e,t);this.listeners.length=0}}export{r as AibotConnectionHandleImpl};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{AibotClient as
|
|
1
|
+
import{AibotClient as E}from"./client.js";import{AibotConnectionHandleImpl as _}from"./connection-handle.js";import{isAgentDeletedError as w}from"./errors.js";import{log as r}from"../log/index.js";class u{static MAX_CONSECUTIVE_AUTH_FAILURES=5;_status="closed";get status(){return this._status}async connect(o,f={}){const{maxRetries:l,aborted:m,label:g,packetLog:p}=f,i=g??o.agentId;this._status="connecting";let s=0,a=0;const h=o.apiKey.length>8?o.apiKey.slice(0,4)+"****"+o.apiKey.slice(-4):"****";for(;;){if(m?.())throw this._status="closed",new Error("connection aborted");const c=new E(o,{packetLog:p});try{const t=await c.connect();this._status="ready",r.info("connection-manager",`Connected: agent=${o.agentId} key=${h} adapter=${t.adapter_id??""}`);const e=new _(c);return e.authAck=t,e.onError(n=>{r.error(i,`Aibot connection error: ${n instanceof Error?n.message:n}`)}),e.onClose((n,d)=>{r.info(i,`Aibot connection closed: code=${n} reason=${d}`)}),e.onDisconnected(()=>{r.info(i,"Aibot disconnected, attempting reconnect...")}),e}catch(t){if(s++,c.disconnect(),w(t))throw this._status="closed",r.error("connection-manager",`Connect aborted: agent deleted on platform agent=${o.agentId} key=${h}`),t;if(t instanceof Error&&/Auth failed/i.test(t.message)){if(a++,a>=u.MAX_CONSECUTIVE_AUTH_FAILURES)throw this._status="closed",new Error(`connection failed: ${a} consecutive auth failures: ${t.message}`)}else a=0;if(l!==void 0&&s>l)throw this._status="closed",new Error(`connection failed after ${s} attempts: ${t instanceof Error?t.message:t}`);if(m?.())throw this._status="closed",new Error("connection aborted");const e=Math.min(1e3*2**Math.min(s-1,5),3e4),n=Math.floor(e*.2*Math.random());r.error("connection-manager",`Connect failed (attempt ${s}), retrying in ${e+n}ms: ${t instanceof Error?t.message:t}`),await new Promise(d=>setTimeout(d,e+n))}}}}export{u as ConnectionManager};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
const n=10008,o="agent_deleted";class r extends Error{constructor(t){super(t),this.name="AgentDeletedError"}}function E(e){return e instanceof r||e instanceof Error&&e.name==="AgentDeletedError"}export{n as AUTH_CODE_AGENT_DELETED,r as AgentDeletedError,o as KICKED_REASON_AGENT_DELETED,E as isAgentDeletedError};
|
package/dist/core/aibot/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{AibotClient as
|
|
1
|
+
import{AibotClient as r}from"./client.js";import{ConnectionManager as E}from"./connection-manager.js";import{AgentDeletedError as A,isAgentDeletedError as D,AUTH_CODE_AGENT_DELETED as i,KICKED_REASON_AGENT_DELETED as _}from"./errors.js";import{AibotConnectionHandleImpl as m}from"./connection-handle.js";export{i as AUTH_CODE_AGENT_DELETED,A as AgentDeletedError,r as AibotClient,m as AibotConnectionHandleImpl,E as ConnectionManager,_ as KICKED_REASON_AGENT_DELETED,D as isAgentDeletedError};
|
|
@@ -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
|
|
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};
|
package/dist/log.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import{createWriteStream as g,mkdirSync as l,existsSync as f}from"node:fs";import{join as
|
|
2
|
-
`)},error(o,r,...
|
|
1
|
+
import{createWriteStream as g,mkdirSync as l,existsSync as f}from"node:fs";import{join as i}from"node:path";import{homedir as m}from"node:os";const n=i(m(),".grix"),s={base:n,config:i(n,"config"),log:i(n,"log"),data:i(n,"data")};function S(){for(const o of Object.values(s))f(o)||l(o,{recursive:!0})}let a=null;function $(){const o=new Date().toISOString().slice(0,10),r=i(s.log,`grix-acp-${o}.log`);a=g(r,{flags:"a"})}function c(){return new Date().toISOString().slice(11,19)}const u={info(o,r,...t){const e=`${c()} [${o}] ${r}${t.length?" "+t.map(String).join(" "):""}`;console.log(e),a?.write(e+`
|
|
2
|
+
`)},error(o,r,...t){const e=`${c()} [${o}] ERROR ${r}${t.length?" "+t.map(String).join(" "):""}`;console.error(e),a?.write(e+`
|
|
3
3
|
`)}};export{s as GRIX_PATHS,S as ensureGrixDirs,$ as initLogger,u as log};
|
package/dist/manager.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{readFileSync as L,readdirSync as Q,writeFileSync as B}from"node:fs";import{join as S}from"node:path";import{AgentInstance as T}from"./bridge/bridge.js";import{buildSharedInstanceConfig as K,diffSharedOwners as z,sharedInstanceKey as G,SharedOwnersCache as W}from"./manager-share-config.js";import{GRIX_PATHS as b,log as p}from"./core/log/index.js";import{resolveClientVersion as J}from"./core/util/client-version.js";import{UpgradeChecker as V}from"./core/upgrade/upgrade-checker.js";import{AgentGlobalConfigStore as X}from"./core/persistence/agent-global-config-store.js";import{resolveSkillScanMode as Y}from"./adapter/claude/skill-scanner.js";import{buildReportedSkills as Z,logDefaultSkillsCheck as ee,cleanupProjectedSkills as j}from"./default-skills/index.js";import{resolveCopilotCommand as E}from"./core/runtime/copilot-resolve.js";import{getCliVersion as te,resolveCliPath as ne}from"./core/util/cli-probe.js";import{AgentInstaller as ae}from"./core/installer/installer.js";import{reportInstallFailure as re}from"./core/observability/sentry.js";const se=8e3;function oe(){const n=E();return[{clientType:"openclaw",command:"openclaw"},{clientType:"claude",command:"claude"},{clientType:"codex",command:"codex"},{clientType:"gemini",command:"gemini"},{clientType:"qwen",command:"qwen"},{clientType:"hermes",command:"hermes"},{clientType:"reasonix",command:"reasonix"},{clientType:"codewhale",command:"codewhale"},{clientType:"opencode",command:"opencode"},{clientType:"cursor",command:"agent"},{clientType:"pi",command:"pi"},{clientType:"openhuman",command:"openhuman-core"},{clientType:"kiro",command:"kiro-cli"},{clientType:"copilot",command:n.command},{clientType:"agy",command:"agy"}]}async function ie(){return Promise.all(oe().map(async n=>{const e=await ne(n.command);if(!e)return{client_type:n.clientType,command:n.command,installed:!1,path:null,version:null};const t=await te(e,n.versionArgs??["--version"]);return{client_type:n.clientType,command:n.command,installed:!0,path:e,version:t.version,error:t.error}}))}function ce(n){switch(n){case"claude":return{adapterType:"claude",command:"claude"};case"codex":return{adapterType:"codex",command:"codex",options:{sandboxMode:"danger-full-access"}};case"gemini":return{adapterType:"acp",command:"gemini",autoInjectArgs:{acp:!0},enableSessionBinding:!0};case"qwen":return{adapterType:"acp",command:"qwen",adapterHint:"qwen/base",autoInjectArgs:{acp:!0},enableSessionBinding:!0};case"pi":return{adapterType:"pi",command:"pi"};case"cursor":return{adapterType:"cursor",command:"agent"};case"reasonix":return{adapterType:"acp",command:"reasonix",args:["acp"],enableSessionBinding:!0};case"codewhale":return{adapterType:"codewhale",command:"codewhale",enableSessionBinding:!0};case"openhuman":return{adapterType:"openhuman",command:"openhuman-core",enableSessionBinding:!0};case"kiro":return{adapterType:"acp",command:"kiro-cli",args:["acp"],enableSessionBinding:!0};case"opencode":return{adapterType:"opencode",command:"opencode",args:["serve"],enableSessionBinding:!0};case"copilot":{const e=E();return{adapterType:"acp",command:e.command,args:[...e.prefixArgs,"--acp"],enableSessionBinding:!0}}case"agy":return{adapterType:"agy",command:"agy",enableSessionBinding:!0};case"hermes":throw new Error('client_type "hermes" is not handled by grix-connector. Hermes runs as a separate project \u2014 see https://github.com/askie/grix-hermes-python');default:throw new Error(`Unsupported client_type: ${n}`)}}function O(n,e){return String(n??"").trim().toLowerCase().replace(/[^a-z0-9._-]+/g,"-").replace(/-+/g,"-").replace(/^-|-$/g,"")||String(e??"").trim().toLowerCase().replace(/[^a-z0-9._-]+/g,"-").replace(/-+/g,"-").replace(/^-|-$/g,"")||"default"}function le(n,e){return S(b.data,`session-bindings-${O(n,e)}.json`)}function de(n,e){return S(b.data,`active-events-${O(n,e)}.json`)}function pe(...n){const e=[],t=new Set;for(const s of n)for(const a of s??[]){const r=String(a??"").trim(),o=r.toLowerCase();!r||t.has(o)||(t.add(o),e.push(r))}return e.length>0?e:void 0}function ue(n,e){const t={claude:{maxConcurrent:1,maxQueued:5,queueTimeoutMs:0},codex:{maxConcurrent:1,maxQueued:5,queueTimeoutMs:0},cursor:{maxConcurrent:1,maxQueued:3,queueTimeoutMs:0},acp:{maxConcurrent:1,maxQueued:3,queueTimeoutMs:0},pi:{maxConcurrent:1,maxQueued:5,queueTimeoutMs:0},codewhale:{maxConcurrent:1,maxQueued:3,queueTimeoutMs:0},openhuman:{maxConcurrent:1,maxQueued:3,queueTimeoutMs:0},opencode:{maxConcurrent:1,maxQueued:3,queueTimeoutMs:0},agy:{maxConcurrent:1,maxQueued:3,queueTimeoutMs:0}},s=t[n]??t.acp;return{maxConcurrent:e?.max_concurrent??s.maxConcurrent??1,maxQueued:e?.max_queued??s.maxQueued??3,queueTimeoutMs:e?.queue_timeout_ms??s.queueTimeoutMs??0,cancelableQueued:!0,cancelableRunning:!0}}function $(n){const e=J(),t=String(n.client_type??"").trim().toLowerCase(),s=ce(t),a=String(n.ws_url??"").trim(),r="get_session_usage",o="get_rate_limits",i="get_agent_global_config";if(!n.name?.trim())throw new Error("agent name is required");if(!a)throw new Error(`agent ${n.name}: ws_url is required`);if(!n.agent_id?.trim())throw new Error(`agent ${n.name}: agent_id is required`);if(!n.api_key?.trim())throw new Error(`agent ${n.name}: api_key is required`);const c=s.adapterType,l=c==="acp",g=t==="qwen",h={...s.options??{}},w=c==="codex"?{capabilities:["local_action_v1","agent_invoke"],localActions:["session_control","get_context","set_model","set_mode","set_reasoning_effort","set_sandbox_mode","exec_approve","exec_reject","file_list","create_folder","turn_interrupt","permission_approve","permission_reject","thread_compact",r,o]}:null,d=c==="claude"?{localActions:["session_control","set_mode","set_model","claude_interaction_reply","exec_approve","exec_reject","file_list","create_folder","thread_compact",r,o]}:null,m=g?{capabilities:["stream_chunk","local_action_v1"],localActions:["exec_approve","exec_reject","permission_approve","permission_reject","session_control","set_model","set_mode","file_list","create_folder",r],adapterHint:"qwen/base"}:null,f=c==="pi"?{adapterHint:"pi/base",capabilities:["local_action_v1"],localActions:["session_control","set_model","get_context","file_list","create_folder",r]}:null,u=c==="openhuman"?{adapterHint:"openhuman/base",capabilities:["local_action_v1"],localActions:["session_control","set_model","file_list","create_folder",r]}:null,_=c==="cursor"?{adapterHint:"cursor/base",capabilities:["stream_chunk","local_action_v1"],localActions:["session_control","set_model","set_mode","get_context","file_list","create_folder",r,o]}:null,y=c==="codewhale"?{capabilities:["stream_chunk","local_action_v1"],localActions:["session_control","set_model","file_list","create_folder",r]}:null,v=c==="opencode"?{adapterHint:"opencode/base",capabilities:["stream_chunk","local_action_v1"],localActions:["exec_approve","exec_reject","permission_approve","permission_reject","session_control","set_model","set_mode","file_list","create_folder",r]}:null,C=c==="agy"?{adapterHint:"agy/base",capabilities:["stream_chunk","local_action_v1"],localActions:["session_control","set_model","file_list","create_folder",r]}:null,P=l&&!g?{localActions:["exec_approve","exec_reject","permission_approve","permission_reject","session_control","set_model","set_mode","file_list","create_folder",r]}:null,q=t==="kiro"?{localActions:["exec_approve","exec_reject","permission_approve","permission_reject","session_control","set_model","set_mode","file_list","create_folder","thread_compact",r,o]}:null,N=c==="codex"||c==="claude"||t==="gemini"?["session_control","set_model","set_mode"]:void 0,F=[r,o];l&&h.raw_transport===void 0&&(h.raw_transport=t==="gemini");const H=`${t}/base`,I=Y(c,t),U=I==="kiro"?void 0:process.cwd(),k=Z({mode:I,projectDir:U}),M=k.filter(x=>x.source==="connector").map(x=>x.name);M.length>0&&p.info("manager",`[${n.name}] injecting connector skills: [${M.join(", ")}]`);let R=k.length>0?k:void 0;return{name:n.name,adapterType:c,aibot:{url:a,agentId:n.agent_id,apiKey:n.api_key,clientType:t,clientVersion:e,adapterHint:s.adapterHint??m?.adapterHint??f?.adapterHint??u?.adapterHint??_?.adapterHint??v?.adapterHint??C?.adapterHint??H,capabilities:w?.capabilities??y?.capabilities??f?.capabilities??u?.capabilities??_?.capabilities??v?.capabilities??C?.capabilities??m?.capabilities??["stream_chunk","local_action_v1","connector_upgrade"],localActions:pe(w?.localActions??y?.localActions??d?.localActions??f?.localActions??u?.localActions??_?.localActions??v?.localActions??C?.localActions??m?.localActions??q?.localActions??P?.localActions??["exec_approve","exec_reject"],N,F,["connector_rollback","connector_upgrade_push",i,"configure_gateway_provider"]),skills:R},agent:{command:s.command,args:[...s.args??[],...n.args??[]],env:n.env,clientType:t,...n.provider?{provider:{...n.provider.base_url?.trim()?{baseUrl:n.provider.base_url.trim()}:{},...n.provider.api_key?.trim()?{apiKey:n.provider.api_key.trim()}:{},...n.provider.model?.trim()?{model:n.provider.model.trim()}:{}}}:{}},adapterOptions:h,acpAuthMethod:h.auth_method,acpInitialMode:h.initial_mode,acpMcpTools:h.acp_mcp_tools,promptTimeoutMs:n.prompt_timeout_ms,bindingsPath:le(n.name,n.agent_id),activeEventStorePath:de(n.name,n.agent_id),...s.enableSessionBinding||l?{enableSessionBinding:!0}:{},...s.autoInjectArgs?{autoInjectArgs:s.autoInjectArgs}:{},poolMaxSize:n.pool?.maxSize,poolIdleTimeoutMs:n.pool?.idleTimeoutMs,eventQueue:ue(c,n.event_queue),logDir:b.log,providerBaseUrl:n.provider_base_url?.trim()||void 0,providerApiKey:n.provider_api_key?.trim()||void 0}}function me(){const n=process.env.GRIX_AGENT_STARTUP_WAIT_MS,e=Number(n);return Number.isFinite(e)&&e>=500?Math.floor(e):se}function D(n){const e=[],t=[],s=Q(n).filter(a=>a.endsWith(".json")).sort();for(const a of s)try{const r=JSON.parse(L(S(n,a),"utf-8"));if(Array.isArray(r.agents)){if(r.agents.length===0){t.push({file:a,error:"agents array is empty"});continue}for(const o of r.agents)e.push({entry:o,file:a})}else t.push({file:a,error:"unrecognized config format"})}catch(r){t.push({file:a,error:r instanceof Error?r.message:String(r)})}return{entries:e,fileErrors:t}}function A(n){if(n===null||typeof n!="object")return JSON.stringify(n)??"null";if(Array.isArray(n))return`[${n.map(A).join(",")}]`;const e=n;return`{${Object.keys(e).sort().map(s=>`${JSON.stringify(s)}:${A(e[s])}`).join(",")}}`}function ge(n,e){return A(n)===A(e)}class je{instances=[];configMap=new Map;rawConfigMap=new Map;reloadChain=Promise.resolve();sharedInstances=new Map;shareSyncChains=new Map;stopping=!1;upgradeChecker=null;globalConfigStore;configDir=b.config;sharedOwnersCache=new W(b.data);installer=new ae;async start(e){const t=e??b.config;this.configDir=t,p.info("manager",`Loading configs from ${t}`),ee(),j(),this.globalConfigStore=new X(S(b.data,"agent-global-configs.json")),this.globalConfigStore.load();const{entries:s,fileErrors:a}=D(t);for(const c of a)p.error("manager",`Failed to load ${c.file}: ${c.error}`);if(s.length===0&&a.length===0)throw new Error(`No config files found in ${t}`);const r=[];let o=a.length;for(const{entry:c,file:l}of s)try{const g=$(c);r.push({config:g,entry:c,file:l}),p.info("manager",`Loaded ${g.name} (${g.adapterType??"acp"}) from ${l}`)}catch(g){const h=typeof c?.name=="string"?c.name:"<unknown>";p.error("manager",`Invalid agent config in ${l} (name=${h}): ${g}`),o++}let i=0;if(r.length>0){const c=me();p.info("manager",`Starting ${r.length} agent(s), startup wait=${c}ms`);const l=()=>this.upgradeChecker?.triggerCheck(),g=d=>{this.instances=this.instances.filter(m=>m!==d)},h=r.map(({config:d,entry:m,file:f})=>{const u=new T(d,this.globalConfigStore);return u.setUpgradeTrigger(l),u.setShareSetHandler(_=>this.onShareSet(d,_)),this.instances.push(u),this.configMap.set(d.name,d),this.rawConfigMap.set(d.name,{entry:m,file:f}),{config:d,instance:u,startPromise:u.start()}}),w=await Promise.all(h.map(async d=>{const m=await new Promise(f=>{let u=!1;const _=setTimeout(()=>{u||(u=!0,f({kind:"timeout"}))},c);d.startPromise.then(()=>{u||(u=!0,clearTimeout(_),f({kind:"started"}))}).catch(y=>{u||(u=!0,clearTimeout(_),f({kind:"failed",error:y}))})});return{task:d,outcome:m}}));for(const{task:d,outcome:m}of w){if(m.kind==="started"){this.restoreCachedSharedInstances(d.config);continue}if(m.kind==="failed"){g(d.instance),p.error("manager",`Failed to start ${d.config.name}: ${m.error}`);continue}i++,p.warn("manager",`Startup pending for ${d.config.name}, continue retrying in background`),d.startPromise.then(()=>{p.info("manager",`Delayed start succeeded: ${d.config.name}`),this.restoreCachedSharedInstances(d.config)}).catch(f=>{g(d.instance),p.error("manager",`Delayed start failed: ${d.config.name}: ${f}`)})}if(this.instances.length>0){const d=Math.max(0,this.instances.length-i);p.info("manager",`${d}/${r.length} agent(s) running now`)}i>0&&p.warn("manager",`${i} agent(s) still connecting in background`)}if(this.instances.length===0&&r.length>0)throw new Error("All agent configurations failed to start");r.length>0&&(this.upgradeChecker=new V(r.map(({config:c})=>({apiKey:c.aibot.apiKey,wsUrl:c.aibot.url})),()=>this.instances.some(c=>c.getStatus().busy)),await this.upgradeChecker.start())}async stop(){p.info("manager","Stopping all agents..."),this.stopping=!0,this.upgradeChecker?.stop(),await Promise.allSettled([...this.shareSyncChains.values()]),this.shareSyncChains.clear();const e=[...this.sharedInstances.values()];this.sharedInstances.clear(),await Promise.allSettled([...this.instances.map(t=>t.stop()),...e.map(t=>t.stop())]),await this.globalConfigStore?.flush(),this.instances=[],j(),p.info("manager","All stopped")}onShareSet(e,t){this.sharedOwnersCache.save(e.name,t);const a=(this.shareSyncChains.get(e.name)??Promise.resolve()).catch(()=>{}).then(()=>this.syncSharedInstances(e,t));this.shareSyncChains.set(e.name,a)}async syncSharedInstances(e,t){if(this.stopping)return;const{toAdd:s,toRemove:a}=z(e.name,this.sharedInstances.keys(),t);for(const r of s){if(this.stopping)break;const o=G(e.name,r);try{const i=K(e,r),c=new T(i,this.globalConfigStore);this.sharedInstances.set(o,c),await c.start(),p.info("manager",`shared instance started: ${o}`)}catch(i){this.sharedInstances.delete(o);const c=i instanceof Error?i.message:String(i);/Auth failed/i.test(c)?(this.sharedOwnersCache.remove(e.name,r),p.warn("manager",`shared instance auth rejected, removed from cache: ${o}`)):p.error("manager",`start shared instance failed ${o}: ${i}`)}}for(const{key:r}of a){const o=this.sharedInstances.get(r);o&&(this.sharedInstances.delete(r),o.stop().catch(i=>p.error("manager",`stop shared instance failed ${r}: ${i}`)),p.info("manager",`shared instance stopped: ${r}`))}}restoreCachedSharedInstances(e){const t=this.sharedOwnersCache.load(e.name);t.length!==0&&(p.info("manager",`restoring ${t.length} cached shared instance(s) for ${e.name}`),this.onShareSet(e,t))}getAgentsStatus(){return this.instances.map(e=>e.getStatus())}async addAgent(e){await this.addAgentInternal(e,"agents.json"),this.persistAgentsConfig(),p.info("manager",`Added agent: ${e.name}`)}async addAgentInternal(e,t){const s=$(e);if(this.instances.some(a=>a.name===s.name))throw new Error(`Agent "${s.name}" already exists`);await this.startInstanceFromConfig(s,e,t)}async startInstanceFromConfig(e,t,s){const a=new T(e,this.globalConfigStore);a.setUpgradeTrigger(()=>this.upgradeChecker?.triggerCheck()),a.setShareSetHandler(r=>this.onShareSet(e,r)),a.setProviderConfigHandler(r=>this.setAgentProvider(e.name,r)),await a.start(),this.instances.push(a),this.configMap.set(e.name,e),this.rawConfigMap.set(e.name,{entry:t,file:s})}async removeAgent(e){if(!this.instances.some(t=>t.name===e))throw Object.assign(new Error(`Agent "${e}" not found`),{code:"NOT_FOUND"});await this.removeAgentInternal(e),this.persistAgentsConfig(),p.info("manager",`Removed agent: ${e}`)}async removeAgentInternal(e,t){const s=this.instances.findIndex(i=>i.name===e),a=s===-1?void 0:this.instances[s];s!==-1&&this.instances.splice(s,1),this.configMap.delete(e),this.rawConfigMap.delete(e);const r=this.shareSyncChains.get(e);this.shareSyncChains.delete(e),r&&await r.catch(()=>{});const o=`${e}#shared:`;for(const[i,c]of[...this.sharedInstances.entries()])i.startsWith(o)&&(this.sharedInstances.delete(i),c.stop().catch(l=>p.error("manager",`stop shared instance failed ${i}: ${l}`)));t?.keepShareCache||this.sharedOwnersCache.delete(e),a&&await a.stop()}persistAgentsConfig(){const e=S(this.configDir,"agents.json");try{const t=[];for(const[,a]of this.configMap){const r=this.rawConfigMap.get(a.name)?.entry.provider;t.push({name:a.name,ws_url:a.aibot.url,agent_id:a.aibot.agentId,api_key:a.aibot.apiKey,client_type:a.aibot.clientType,...r?{provider:r}:{}})}B(e,JSON.stringify({agents:t},null,4)+`
|
|
2
|
-
`,"utf-8")}catch(t){
|
|
1
|
+
import{readFileSync as L,readdirSync as Q,writeFileSync as K}from"node:fs";import{join as b}from"node:path";import{AgentInstance as $}from"./bridge/bridge.js";import{buildSharedInstanceConfig as z,diffSharedOwners as G,sharedInstanceKey as W,SharedOwnersCache as J}from"./manager-share-config.js";import{isAgentDeletedError as j}from"./core/aibot/errors.js";import{GRIX_PATHS as A,log as d}from"./core/log/index.js";import{resolveClientVersion as V}from"./core/util/client-version.js";import{UpgradeChecker as X}from"./core/upgrade/upgrade-checker.js";import{AgentGlobalConfigStore as Y}from"./core/persistence/agent-global-config-store.js";import{resolveSkillScanMode as Z}from"./adapter/claude/skill-scanner.js";import{buildReportedSkills as ee,logDefaultSkillsCheck as te,cleanupProjectedSkills as D}from"./default-skills/index.js";import{resolveCopilotCommand as E}from"./core/runtime/copilot-resolve.js";import{getCliVersion as ne,resolveCliPath as ae}from"./core/util/cli-probe.js";import{AgentInstaller as re}from"./core/installer/installer.js";import{reportInstallFailure as se}from"./core/observability/sentry.js";const oe=8e3;function ie(){const n=E();return[{clientType:"openclaw",command:"openclaw"},{clientType:"claude",command:"claude"},{clientType:"codex",command:"codex"},{clientType:"gemini",command:"gemini"},{clientType:"qwen",command:"qwen"},{clientType:"hermes",command:"hermes"},{clientType:"reasonix",command:"reasonix"},{clientType:"codewhale",command:"codewhale"},{clientType:"opencode",command:"opencode"},{clientType:"cursor",command:"agent"},{clientType:"pi",command:"pi"},{clientType:"openhuman",command:"openhuman-core"},{clientType:"kiro",command:"kiro-cli"},{clientType:"copilot",command:n.command},{clientType:"agy",command:"agy"}]}async function ce(){return Promise.all(ie().map(async n=>{const e=await ae(n.command);if(!e)return{client_type:n.clientType,command:n.command,installed:!1,path:null,version:null};const t=await ne(e,n.versionArgs??["--version"]);return{client_type:n.clientType,command:n.command,installed:!0,path:e,version:t.version,error:t.error}}))}function le(n){switch(n){case"claude":return{adapterType:"claude",command:"claude"};case"codex":return{adapterType:"codex",command:"codex",options:{sandboxMode:"danger-full-access"}};case"gemini":return{adapterType:"acp",command:"gemini",autoInjectArgs:{acp:!0},enableSessionBinding:!0};case"qwen":return{adapterType:"acp",command:"qwen",adapterHint:"qwen/base",autoInjectArgs:{acp:!0},enableSessionBinding:!0};case"pi":return{adapterType:"pi",command:"pi"};case"cursor":return{adapterType:"cursor",command:"agent"};case"reasonix":return{adapterType:"acp",command:"reasonix",args:["acp"],enableSessionBinding:!0};case"codewhale":return{adapterType:"codewhale",command:"codewhale",enableSessionBinding:!0};case"openhuman":return{adapterType:"openhuman",command:"openhuman-core",enableSessionBinding:!0};case"kiro":return{adapterType:"acp",command:"kiro-cli",args:["acp"],enableSessionBinding:!0};case"opencode":return{adapterType:"opencode",command:"opencode",args:["serve"],enableSessionBinding:!0};case"copilot":{const e=E();return{adapterType:"acp",command:e.command,args:[...e.prefixArgs,"--acp"],enableSessionBinding:!0}}case"agy":return{adapterType:"agy",command:"agy",enableSessionBinding:!0};case"hermes":throw new Error('client_type "hermes" is not handled by grix-connector. Hermes runs as a separate project \u2014 see https://github.com/askie/grix-hermes-python');default:throw new Error(`Unsupported client_type: ${n}`)}}function O(n,e){return String(n??"").trim().toLowerCase().replace(/[^a-z0-9._-]+/g,"-").replace(/-+/g,"-").replace(/^-|-$/g,"")||String(e??"").trim().toLowerCase().replace(/[^a-z0-9._-]+/g,"-").replace(/-+/g,"-").replace(/^-|-$/g,"")||"default"}function de(n,e){return b(A.data,`session-bindings-${O(n,e)}.json`)}function pe(n,e){return b(A.data,`active-events-${O(n,e)}.json`)}function ue(...n){const e=[],t=new Set;for(const s of n)for(const a of s??[]){const r=String(a??"").trim(),o=r.toLowerCase();!r||t.has(o)||(t.add(o),e.push(r))}return e.length>0?e:void 0}function me(n,e){const t={claude:{maxConcurrent:1,maxQueued:5,queueTimeoutMs:0},codex:{maxConcurrent:1,maxQueued:5,queueTimeoutMs:0},cursor:{maxConcurrent:1,maxQueued:3,queueTimeoutMs:0},acp:{maxConcurrent:1,maxQueued:3,queueTimeoutMs:0},pi:{maxConcurrent:1,maxQueued:5,queueTimeoutMs:0},codewhale:{maxConcurrent:1,maxQueued:3,queueTimeoutMs:0},openhuman:{maxConcurrent:1,maxQueued:3,queueTimeoutMs:0},opencode:{maxConcurrent:1,maxQueued:3,queueTimeoutMs:0},agy:{maxConcurrent:1,maxQueued:3,queueTimeoutMs:0}},s=t[n]??t.acp;return{maxConcurrent:e?.max_concurrent??s.maxConcurrent??1,maxQueued:e?.max_queued??s.maxQueued??3,queueTimeoutMs:e?.queue_timeout_ms??s.queueTimeoutMs??0,cancelableQueued:!0,cancelableRunning:!0}}function T(n){const e=V(),t=String(n.client_type??"").trim().toLowerCase(),s=le(t),a=String(n.ws_url??"").trim(),r="get_session_usage",o="get_rate_limits",i="get_agent_global_config";if(!n.name?.trim())throw new Error("agent name is required");if(!a)throw new Error(`agent ${n.name}: ws_url is required`);if(!n.agent_id?.trim())throw new Error(`agent ${n.name}: agent_id is required`);if(!n.api_key?.trim())throw new Error(`agent ${n.name}: api_key is required`);const c=s.adapterType,p=c==="acp",g=t==="qwen",f={...s.options??{}},w=c==="codex"?{capabilities:["local_action_v1","agent_invoke"],localActions:["session_control","get_context","set_model","set_mode","set_reasoning_effort","set_sandbox_mode","exec_approve","exec_reject","file_list","create_folder","turn_interrupt","permission_approve","permission_reject","thread_compact",r,o]}:null,l=c==="claude"?{localActions:["session_control","set_mode","set_model","claude_interaction_reply","exec_approve","exec_reject","file_list","create_folder","thread_compact",r,o]}:null,m=g?{capabilities:["stream_chunk","local_action_v1"],localActions:["exec_approve","exec_reject","permission_approve","permission_reject","session_control","set_model","set_mode","file_list","create_folder",r],adapterHint:"qwen/base"}:null,h=c==="pi"?{adapterHint:"pi/base",capabilities:["local_action_v1"],localActions:["session_control","set_model","get_context","file_list","create_folder",r]}:null,u=c==="openhuman"?{adapterHint:"openhuman/base",capabilities:["local_action_v1"],localActions:["session_control","set_model","file_list","create_folder",r]}:null,_=c==="cursor"?{adapterHint:"cursor/base",capabilities:["stream_chunk","local_action_v1"],localActions:["session_control","set_model","set_mode","get_context","file_list","create_folder",r,o]}:null,y=c==="codewhale"?{capabilities:["stream_chunk","local_action_v1"],localActions:["session_control","set_model","file_list","create_folder",r]}:null,v=c==="opencode"?{adapterHint:"opencode/base",capabilities:["stream_chunk","local_action_v1"],localActions:["exec_approve","exec_reject","permission_approve","permission_reject","session_control","set_model","set_mode","file_list","create_folder",r]}:null,C=c==="agy"?{adapterHint:"agy/base",capabilities:["stream_chunk","local_action_v1"],localActions:["session_control","set_model","file_list","create_folder",r]}:null,q=p&&!g?{localActions:["exec_approve","exec_reject","permission_approve","permission_reject","session_control","set_model","set_mode","file_list","create_folder",r]}:null,H=t==="kiro"?{localActions:["exec_approve","exec_reject","permission_approve","permission_reject","session_control","set_model","set_mode","file_list","create_folder","thread_compact",r,o]}:null,N=c==="codex"||c==="claude"||t==="gemini"?["session_control","set_model","set_mode"]:void 0,F=[r,o];p&&f.raw_transport===void 0&&(f.raw_transport=t==="gemini");const U=`${t}/base`,I=Z(c,t),B=I==="kiro"?void 0:process.cwd(),k=ee({mode:I,projectDir:B}),M=k.filter(x=>x.source==="connector").map(x=>x.name);M.length>0&&d.info("manager",`[${n.name}] injecting connector skills: [${M.join(", ")}]`);let R=k.length>0?k:void 0;return{name:n.name,adapterType:c,aibot:{url:a,agentId:n.agent_id,apiKey:n.api_key,clientType:t,clientVersion:e,adapterHint:s.adapterHint??m?.adapterHint??h?.adapterHint??u?.adapterHint??_?.adapterHint??v?.adapterHint??C?.adapterHint??U,capabilities:w?.capabilities??y?.capabilities??h?.capabilities??u?.capabilities??_?.capabilities??v?.capabilities??C?.capabilities??m?.capabilities??["stream_chunk","local_action_v1","connector_upgrade"],localActions:ue(w?.localActions??y?.localActions??l?.localActions??h?.localActions??u?.localActions??_?.localActions??v?.localActions??C?.localActions??m?.localActions??H?.localActions??q?.localActions??["exec_approve","exec_reject"],N,F,["connector_rollback","connector_upgrade_push",i,"configure_gateway_provider"]),skills:R},agent:{command:s.command,args:[...s.args??[],...n.args??[]],env:n.env,clientType:t,...n.provider?{provider:{...n.provider.base_url?.trim()?{baseUrl:n.provider.base_url.trim()}:{},...n.provider.api_key?.trim()?{apiKey:n.provider.api_key.trim()}:{},...n.provider.model?.trim()?{model:n.provider.model.trim()}:{}}}:{}},adapterOptions:f,acpAuthMethod:f.auth_method,acpInitialMode:f.initial_mode,acpMcpTools:f.acp_mcp_tools,promptTimeoutMs:n.prompt_timeout_ms,bindingsPath:de(n.name,n.agent_id),activeEventStorePath:pe(n.name,n.agent_id),...s.enableSessionBinding||p?{enableSessionBinding:!0}:{},...s.autoInjectArgs?{autoInjectArgs:s.autoInjectArgs}:{},poolMaxSize:n.pool?.maxSize,poolIdleTimeoutMs:n.pool?.idleTimeoutMs,eventQueue:me(c,n.event_queue),logDir:A.log,providerBaseUrl:n.provider_base_url?.trim()||void 0,providerApiKey:n.provider_api_key?.trim()||void 0}}function ge(){const n=process.env.GRIX_AGENT_STARTUP_WAIT_MS,e=Number(n);return Number.isFinite(e)&&e>=500?Math.floor(e):oe}function P(n){const e=[],t=[],s=Q(n).filter(a=>a.endsWith(".json")).sort();for(const a of s)try{const r=JSON.parse(L(b(n,a),"utf-8"));if(Array.isArray(r.agents)){if(r.agents.length===0){t.push({file:a,error:"agents array is empty"});continue}for(const o of r.agents)e.push({entry:o,file:a})}else t.push({file:a,error:"unrecognized config format"})}catch(r){t.push({file:a,error:r instanceof Error?r.message:String(r)})}return{entries:e,fileErrors:t}}function S(n){if(n===null||typeof n!="object")return JSON.stringify(n)??"null";if(Array.isArray(n))return`[${n.map(S).join(",")}]`;const e=n;return`{${Object.keys(e).sort().map(s=>`${JSON.stringify(s)}:${S(e[s])}`).join(",")}}`}function he(n,e){return S(n)===S(e)}class Ee{instances=[];configMap=new Map;rawConfigMap=new Map;reloadChain=Promise.resolve();sharedInstances=new Map;shareSyncChains=new Map;stopping=!1;deletedAgentCleanups=new Set;upgradeChecker=null;globalConfigStore;configDir=A.config;sharedOwnersCache=new J(A.data);installer=new re;async start(e){const t=e??A.config;this.configDir=t,d.info("manager",`Loading configs from ${t}`),te(),D(),this.globalConfigStore=new Y(b(A.data,"agent-global-configs.json")),this.globalConfigStore.load();const{entries:s,fileErrors:a}=P(t);for(const c of a)d.error("manager",`Failed to load ${c.file}: ${c.error}`);if(s.length===0&&a.length===0)throw new Error(`No config files found in ${t}`);const r=[];let o=a.length;for(const{entry:c,file:p}of s)try{const g=T(c);r.push({config:g,entry:c,file:p}),d.info("manager",`Loaded ${g.name} (${g.adapterType??"acp"}) from ${p}`)}catch(g){const f=typeof c?.name=="string"?c.name:"<unknown>";d.error("manager",`Invalid agent config in ${p} (name=${f}): ${g}`),o++}let i=0;if(r.length>0){const c=ge();d.info("manager",`Starting ${r.length} agent(s), startup wait=${c}ms`);const p=()=>this.upgradeChecker?.triggerCheck(),g=l=>{this.instances=this.instances.filter(m=>m!==l)},f=r.map(({config:l,entry:m,file:h})=>{const u=new $(l,this.globalConfigStore);return u.setUpgradeTrigger(p),u.setShareSetHandler(_=>this.onShareSet(l,_)),u.setAgentDeletedHandler(()=>this.onAgentDeletedByPlatform(l.name)),this.instances.push(u),this.configMap.set(l.name,l),this.rawConfigMap.set(l.name,{entry:m,file:h}),{config:l,instance:u,startPromise:u.start()}}),w=await Promise.all(f.map(async l=>{const m=await new Promise(h=>{let u=!1;const _=setTimeout(()=>{u||(u=!0,h({kind:"timeout"}))},c);l.startPromise.then(()=>{u||(u=!0,clearTimeout(_),h({kind:"started"}))}).catch(y=>{u||(u=!0,clearTimeout(_),h({kind:"failed",error:y}))})});return{task:l,outcome:m}}));for(const{task:l,outcome:m}of w){if(m.kind==="started"){this.restoreCachedSharedInstances(l.config);continue}if(m.kind==="failed"){if(j(m.error)){this.onAgentDeletedByPlatform(l.config.name);continue}g(l.instance),d.error("manager",`Failed to start ${l.config.name}: ${m.error}`);continue}i++,d.warn("manager",`Startup pending for ${l.config.name}, continue retrying in background`),l.startPromise.then(()=>{d.info("manager",`Delayed start succeeded: ${l.config.name}`),this.restoreCachedSharedInstances(l.config)}).catch(h=>{if(j(h)){this.onAgentDeletedByPlatform(l.config.name);return}g(l.instance),d.error("manager",`Delayed start failed: ${l.config.name}: ${h}`)})}if(this.instances.length>0){const l=Math.max(0,this.instances.length-i);d.info("manager",`${l}/${r.length} agent(s) running now`)}i>0&&d.warn("manager",`${i} agent(s) still connecting in background`)}if(this.instances.length===0&&r.length>0)throw new Error("All agent configurations failed to start");r.length>0&&(this.upgradeChecker=new X(r.map(({config:c})=>({apiKey:c.aibot.apiKey,wsUrl:c.aibot.url})),()=>this.instances.some(c=>c.getStatus().busy)),await this.upgradeChecker.start())}async stop(){d.info("manager","Stopping all agents..."),this.stopping=!0,this.upgradeChecker?.stop(),await Promise.allSettled([...this.shareSyncChains.values()]),this.shareSyncChains.clear();const e=[...this.sharedInstances.values()];this.sharedInstances.clear(),await Promise.allSettled([...this.instances.map(t=>t.stop()),...e.map(t=>t.stop())]),await this.globalConfigStore?.flush(),this.instances=[],D(),d.info("manager","All stopped")}onShareSet(e,t){this.sharedOwnersCache.save(e.name,t);const a=(this.shareSyncChains.get(e.name)??Promise.resolve()).catch(()=>{}).then(()=>this.syncSharedInstances(e,t));this.shareSyncChains.set(e.name,a)}async syncSharedInstances(e,t){if(this.stopping)return;const{toAdd:s,toRemove:a}=G(e.name,this.sharedInstances.keys(),t);for(const r of s){if(this.stopping)break;const o=W(e.name,r);try{const i=z(e,r),c=new $(i,this.globalConfigStore);this.sharedInstances.set(o,c),await c.start(),d.info("manager",`shared instance started: ${o}`)}catch(i){this.sharedInstances.delete(o);const c=i instanceof Error?i.message:String(i);/Auth failed/i.test(c)?(this.sharedOwnersCache.remove(e.name,r),d.warn("manager",`shared instance auth rejected, removed from cache: ${o}`)):d.error("manager",`start shared instance failed ${o}: ${i}`)}}for(const{key:r}of a){const o=this.sharedInstances.get(r);o&&(this.sharedInstances.delete(r),o.stop().catch(i=>d.error("manager",`stop shared instance failed ${r}: ${i}`)),d.info("manager",`shared instance stopped: ${r}`))}}restoreCachedSharedInstances(e){const t=this.sharedOwnersCache.load(e.name);t.length!==0&&(d.info("manager",`restoring ${t.length} cached shared instance(s) for ${e.name}`),this.onShareSet(e,t))}getAgentsStatus(){return this.instances.map(e=>e.getStatus())}async addAgent(e){await this.addAgentInternal(e,"agents.json"),this.persistAgentsConfig(),d.info("manager",`Added agent: ${e.name}`)}async addAgentInternal(e,t){const s=T(e);if(this.instances.some(a=>a.name===s.name))throw new Error(`Agent "${s.name}" already exists`);await this.startInstanceFromConfig(s,e,t)}async startInstanceFromConfig(e,t,s){const a=new $(e,this.globalConfigStore);a.setUpgradeTrigger(()=>this.upgradeChecker?.triggerCheck()),a.setShareSetHandler(r=>this.onShareSet(e,r)),a.setAgentDeletedHandler(()=>this.onAgentDeletedByPlatform(e.name)),a.setProviderConfigHandler(r=>this.setAgentProvider(e.name,r)),await a.start(),this.instances.push(a),this.configMap.set(e.name,e),this.rawConfigMap.set(e.name,{entry:t,file:s})}async removeAgent(e){if(!this.instances.some(t=>t.name===e))throw Object.assign(new Error(`Agent "${e}" not found`),{code:"NOT_FOUND"});await this.removeAgentInternal(e),this.persistAgentsConfig(),d.info("manager",`Removed agent: ${e}`)}async removeAgentInternal(e,t){const s=this.instances.findIndex(i=>i.name===e),a=s===-1?void 0:this.instances[s];s!==-1&&this.instances.splice(s,1),this.configMap.delete(e),this.rawConfigMap.delete(e);const r=this.shareSyncChains.get(e);this.shareSyncChains.delete(e),r&&await r.catch(()=>{});const o=`${e}#shared:`;for(const[i,c]of[...this.sharedInstances.entries()])i.startsWith(o)&&(this.sharedInstances.delete(i),c.stop().catch(p=>d.error("manager",`stop shared instance failed ${i}: ${p}`)));t?.keepShareCache||this.sharedOwnersCache.delete(e),a&&await a.stop()}onAgentDeletedByPlatform(e){this.stopping||this.deletedAgentCleanups.has(e)||(this.deletedAgentCleanups.add(e),(async()=>{try{if(!(this.rawConfigMap.has(e)||this.instances.some(s=>s.name===e)))return;d.warn("manager",`Agent "${e}" \u5DF2\u5728\u5E73\u53F0\u5220\u9664\uFF0C\u505C\u6B62\u5B9E\u4F8B\u5E76\u6E05\u7406\u672C\u5730\u914D\u7F6E`),await this.removeAgentInternal(e),this.persistAgentsConfig(),d.info("manager",`Agent "${e}" \u5DF2\u5728\u5E73\u53F0\u5220\u9664\uFF0C\u672C\u5730\u914D\u7F6E\u5DF2\u6E05\u7406\uFF08agents.json \u5DF2\u79FB\u9664\u8BE5\u6761\u76EE\uFF09`)}catch(t){d.error("manager",`cleanup for deleted agent "${e}" failed: ${t}`)}finally{this.deletedAgentCleanups.delete(e)}})())}persistAgentsConfig(){const e=b(this.configDir,"agents.json");try{const t=[];for(const[,a]of this.configMap){const r=this.rawConfigMap.get(a.name)?.entry.provider;t.push({name:a.name,ws_url:a.aibot.url,agent_id:a.aibot.agentId,api_key:a.aibot.apiKey,client_type:a.aibot.clientType,...r?{provider:r}:{}})}K(e,JSON.stringify({agents:t},null,4)+`
|
|
2
|
+
`,"utf-8")}catch(t){d.error("manager",`Failed to persist agents config: ${t}`)}}async restartAgent(e){const t=this.rawConfigMap.get(e);if(!t)throw Object.assign(new Error(`Agent "${e}" not found`),{code:"NOT_FOUND"});await this.replaceInstance(e,t.entry,t.file),d.info("manager",`Restarted agent: ${e}`)}async setAgentProvider(e,t){const s=this.rawConfigMap.get(e);if(!s)throw Object.assign(new Error(`Agent "${e}" not found`),{code:"NOT_FOUND"});const a={...s.entry,provider:t};await this.replaceInstance(e,a,s.file),this.persistAgentsConfig(),d.info("manager",`Updated provider config for agent: ${e}`)}async replaceInstance(e,t,s){const a=T(t);await this.removeAgentInternal(e,{keepShareCache:!0}),await this.startInstanceFromConfig(a,t,s),this.restoreCachedSharedInstances(a)}async reload(){const e=this.reloadChain.catch(()=>{}).then(()=>this.doReload());return this.reloadChain=e.catch(()=>{}),e}async doReload(){if(this.stopping)throw Object.assign(new Error("manager is stopping"),{code:"RELOAD_UNSAFE"});const{entries:e,fileErrors:t}=P(this.configDir);if(t.length>0){const o=t.map(i=>`${i.file}: ${i.error}`).join("; ");throw Object.assign(new Error(`reload aborted: ${t.length} config file(s) failed to parse [${o}]`),{code:"RELOAD_UNSAFE"})}if(e.length===0)throw Object.assign(new Error("reload aborted: no valid agent config found"),{code:"RELOAD_UNSAFE"});const s=new Map;for(const o of e){const i=String(o.entry?.name??"").trim();i&&(s.has(i)&&d.warn("manager",`reload: duplicate agent name "${i}", last one wins`),s.set(i,o))}const a={added:[],removed:[],restarted:[],unchanged:[],failed:[]},r=new Set(this.rawConfigMap.keys());for(const o of r)if(!s.has(o))try{await this.removeAgentInternal(o),a.removed.push(o)}catch(i){a.failed.push({name:o,error:i instanceof Error?i.message:String(i)})}for(const[o,i]of s){const c=this.rawConfigMap.get(o);if(c)if(he(c.entry,i.entry))this.rawConfigMap.set(o,i),a.unchanged.push(o);else try{await this.replaceInstance(o,i.entry,i.file),a.restarted.push(o)}catch(p){a.failed.push({name:o,error:p instanceof Error?p.message:String(p)})}else try{await this.addAgentInternal(i.entry,i.file),a.added.push(o)}catch(p){a.failed.push({name:o,error:p instanceof Error?p.message:String(p)})}}return d.info("manager",`reload done: +${a.added.length} -${a.removed.length} ~${a.restarted.length} =${a.unchanged.length} !${a.failed.length}`),a}async checkUpgrade(){return this.upgradeChecker?this.upgradeChecker.checkForUpdate():{available:!1}}triggerUpgrade(){this.upgradeChecker?.triggerCheck()}async probeAll(e={}){return fe(this.instances,e)}async probeOne(e,t={}){return _e(this.instances,e,t)}listInstallable(){return this.installer.listInstallable()}async installAgent(e){const t=await this.installer.install(e);return se(t),t}getInstallProgress(e){return this.installer.getProgress(e)??null}}async function fe(n,e){const t=e.concurrency??4,s=Date.now(),a=new Array(n.length);await new Promise(p=>{let g=0,f=0;const w=n.length;if(w===0){p();return}function l(u){const _=n[u];_.probe(e).then(y=>{a[u]=y,m()},y=>{a[u]={agent_name:_.name,client_type:"unknown",adapter_type:"acp",ok:!1,status:"error",probed_at:Date.now(),duration_ms:0,cached:!1,cli:{command:"",installed:!1,path:null,version:null,error:{code:"internal",message:y?.message??String(y)}},conversation:{attempted:!1,ok:!1,latency_ms:null},config:{model:null,base_url:null,source:{model:"unknown",base_url:"unknown"}},process:{started:!1,alive:!1,busy:!1}},m()})}function m(){f++,g<w?l(g++):f===w&&p()}const h=Math.min(t,w);for(let u=0;u<h;u++)l(g++)});const r=a.filter(p=>p.status==="healthy").length,o=a.filter(p=>p.status==="degraded").length,i=a.filter(p=>p.status==="unavailable").length,c=await ce();return{ok:r===a.length&&a.length>0,total:a.length,healthy:r,degraded:o,unavailable:i,installed_clients:c,agents:a,probed_at:s,duration_ms:Date.now()-s}}async function _e(n,e,t){const s=n.find(a=>a.name===e);if(!s)throw Object.assign(new Error(`Agent "${e}" not found`),{code:"NOT_FOUND"});return s.probe(t)}export{Ee as Manager,ce as probeInstalledClientCommands,fe as probeInstances,_e as probeOneInstance};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import*as
|
|
1
|
+
import*as n from"node:net";const i={bind:"127.0.0.1",port:0,endpoint:"/mcp",sessionTimeoutMs:18e5,invokeTimeoutMs:3e4};function s(u){const e={bind:u?.bind??i.bind,port:u?.port??i.port,endpoint:u?.endpoint??i.endpoint,sessionTimeoutMs:u?.sessionTimeoutMs??i.sessionTimeoutMs,invokeTimeoutMs:u?.invokeTimeoutMs??i.invokeTimeoutMs,allowedOrigins:u?.allowedOrigins,allowedHosts:u?.allowedHosts};return t(e.bind),e.port!==0&&o(e.port),r(e.sessionTimeoutMs),e}function t(u){if(!u||!n.isIPv4(u)&&!n.isIPv6(u))throw new Error(`\u914D\u7F6E\u6821\u9A8C\u5931\u8D25: bind \u5730\u5740 "${u}" \u4E0D\u662F\u5408\u6CD5\u7684 IPv4 \u6216 IPv6 \u5730\u5740`)}function o(u){if(!Number.isInteger(u)||u<1||u>65535)throw new Error(`\u914D\u7F6E\u6821\u9A8C\u5931\u8D25: port \u503C ${u} \u4E0D\u5728\u5408\u6CD5\u8303\u56F4 1-65535 \u5185\u6216\u4E0D\u662F\u6574\u6570`)}function r(u){if(!Number.isInteger(u)||u<1e3||u>864e5)throw new Error(`\u914D\u7F6E\u6821\u9A8C\u5931\u8D25: session_timeout_ms \u503C ${u} \u4E0D\u5728\u5408\u6CD5\u8303\u56F4 1000-86400000 \u5185`)}export{s as createDefaultGatewayConfig};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
const
|
|
1
|
+
const a=3e4;class c{connectionManager;onDisconnected;bindings=new Map;constructor(n,i){this.connectionManager=n,this.onDisconnected=i}async bind(n,i){if(this.bindings.has(n))throw new Error(`Session ${n} is already bound to a connection`);const e=await this.connectWithTimeout(i),t=[],s=e.onDisconnected(()=>{this.removeBinding(n),this.onDisconnected(n)});return t.push(s),this.bindings.set(n,{sessionId:n,handle:e,subscriptions:t}),e}getHandle(n){return this.bindings.get(n)?.handle}unbind(n){const i=this.bindings.get(n);if(i){this.bindings.delete(n);for(const e of i.subscriptions)e();i.handle.disconnect()}}unbindAll(){const n=[...this.bindings.keys()];for(const i of n)this.unbind(i)}connectWithTimeout(n){return new Promise((i,e)=>{let t=!1;const s=setTimeout(()=>{t||(t=!0,e(new Error("Connection bind timeout after 30000ms")))},3e4);this.connectionManager.connect({agentId:n.agentId,apiKey:n.apiKey,url:n.wsUrl,clientType:n.clientType,capabilities:["agent_invoke"],adapterHint:`${n.clientType}/base`},{maxRetries:0}).then(o=>{t?o.disconnect():(t=!0,clearTimeout(s),i(o))}).catch(o=>{t||(t=!0,clearTimeout(s),e(o))})})}removeBinding(n){const i=this.bindings.get(n);if(i){this.bindings.delete(n);for(const e of i.subscriptions)e()}}}export{c as ConnectionBindingImpl};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
function a(
|
|
1
|
+
function a(o){const e=new Set([`http://127.0.0.1:${o.serverPort}`,`http://localhost:${o.serverPort}`,...o.allowedOrigins]),t=new Set([`127.0.0.1:${o.serverPort}`,`localhost:${o.serverPort}`,...o.allowedHosts]);return{validateRequest(s){const r=i(s,e);if(!r.ok)return r;const n=l(s,t);return n.ok?{ok:!0}:n}}}function i(o,e){const t=o.headers.origin;return t?e.has(t)?{ok:!0}:{ok:!1,statusCode:403,message:`Origin not allowed: ${t}`}:{ok:!0}}function l(o,e){const t=o.headers.host;return t?e.has(t)?{ok:!0}:{ok:!1,statusCode:403,message:`Host not allowed: ${t}`}:{ok:!1,statusCode:403,message:"Missing Host header"}}export{a as createSecurityPolicy};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{toolCallToInvoke as i}from"../../core/mcp/tools.js";import{ToolRegistryImpl as l}from"./tool-registry.js";import{validateToolArgs as a}from"./tool-schemas.js";import{isEventTool as p,executeEventTool as d}from"./event-tool-executor.js";class y{registry;constructor(){this.registry=new l}async execute(
|
|
1
|
+
import{toolCallToInvoke as i}from"../../core/mcp/tools.js";import{ToolRegistryImpl as l}from"./tool-registry.js";import{validateToolArgs as a}from"./tool-schemas.js";import{isEventTool as p,executeEventTool as d}from"./event-tool-executor.js";class y{registry;constructor(){this.registry=new l}async execute(r,e,t,n){if(!this.registry.hasTool(e))return this.errorResult(`\u672A\u77E5\u5DE5\u5177: ${e}`);const s=a(e,t);if(!s.valid)return this.errorResult(`\u53C2\u6570\u6821\u9A8C\u5931\u8D25: ${s.error}`);if(r.status!=="ready")return this.errorResult(`\u8FDE\u63A5\u4E0D\u53EF\u7528: \u5F53\u524D\u72B6\u6001\u4E3A ${r.status}`);if(p(e))return this.executeEventTool(r,e,t);const o=i(e,t);try{const u=await r.agentInvoke(o.action,o.params,n);return this.normalizeResult(u)}catch(u){const c=u instanceof Error?u.message:String(u);return c.toLowerCase().includes("timeout")?this.errorResult(`\u8C03\u7528\u8D85\u65F6: ${c}`):this.errorResult(`\u8C03\u7528\u5931\u8D25: ${c}`)}}normalizeResult(r){if(r==null||typeof r!="object")return this.successResult(r??null);const e=r,t=typeof e.code=="number"?e.code:0;if(t===0){const s="data"in e?e.data:null;return this.successResult(s??null)}const n=typeof e.msg=="string"?e.msg:"\u672A\u77E5\u9519\u8BEF";return this.errorResult(`\u4E0A\u6E38\u9519\u8BEF [code=${t}]: ${n}`)}successResult(r){return{content:[{type:"text",text:JSON.stringify(r)}],isError:!1}}errorResult(r){return{content:[{type:"text",text:r}],isError:!0}}async executeEventTool(r,e,t){return e==="grix_access_control"?this.executeAccessControl(r,t):d(r,e,t)}async executeAccessControl(r,e){const t=String(e.action??""),n={pair_approve:"pair_approve",pair_deny:"pair_deny",allow_sender:"sender_allow",remove_sender:"sender_remove",set_policy:"policy_set"}[t];if(!n)return this.errorResult(`\u672A\u77E5 access_control action: ${t}`);const s={};e.code!=null&&(s.code=e.code),e.sender_id!=null&&(s.sender_id=e.sender_id),e.policy!=null&&(s.policy=e.policy);try{const o=await r.agentInvoke("claude_access_control",{verb:n,payload:s},3e4);return this.successResult(o)}catch(o){const u=o instanceof Error?o.message:String(o);return this.errorResult(`access_control \u8C03\u7528\u5931\u8D25: ${u}`)}}}export{y as ToolExecutorImpl};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{TOOLS as
|
|
1
|
+
import{TOOLS as o,EVENT_TOOLS as s}from"../../core/mcp/tools.js";const e=new Set(["grix_query","grix_group","grix_message_send","grix_message_unsend","grix_admin"]),r=new Set(["grix_reply","grix_complete","grix_event_ack","grix_composing","grix_access_control","grix_status"]);class a{tools;toolMap;constructor(){this.tools=[...o.filter(t=>e.has(t.name)),...s.filter(t=>r.has(t.name))],this.toolMap=new Map(this.tools.map(t=>[t.name,t]))}getTools(){return this.tools}getTool(t){return this.toolMap.get(t)}hasTool(t){return this.toolMap.has(t)}}export{a as ToolRegistryImpl};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
const o={required:["action"],properties:{action:{type:"string",enum:["contact_search","session_search","message_history","message_search"]},id:{type:"string"},keyword:{type:"string",maxLength:200},limit:{type:"integer",minimum:1,maximum:100},offset:{type:"integer",minimum:0},sessionId:{type:"string"},beforeId:{type:"string"}}},a={required:["action"],properties:{action:{type:"string",enum:["create","detail","leave","add_members","remove_members","update_member_role","update_all_members_muted","update_member_speaking","dissolve"]},sessionId:{type:"string"},name:{type:"string",maxLength:128},memberIds:{type:"array",items:{type:"string"},maxItems:100},memberTypes:{type:"array",items:{type:"integer",enum:[1,2]}},memberId:{type:"string"},role:{type:"integer",enum:[1,2]},memberType:{type:"integer"},allMembersMuted:{type:"boolean"},isSpeakMuted:{type:"boolean"},canSpeakWhenAllMuted:{type:"boolean"}}},p={required:["sessionId","content"],properties:{sessionId:{type:"string"},content:{type:"string",maxLength:1e4},msgType:{type:"integer"},quotedMessageId:{type:"string"},threadId:{type:"string"}}},m={required:["sessionId","msgId"],properties:{sessionId:{type:"string"},msgId:{type:"string"}}},g={required:["action"],properties:{action:{type:"string",enum:["create_agent","list_categories","create_category","update_category","assign_category","rotate_api_key"]},agentName:{type:"string"},introduction:{type:"string"},isMain:{type:"boolean"},agentId:{type:"string"},categoryId:{type:"string"},name:{type:"string"},parentId:{type:"string"},sortOrder:{type:"integer"}}},y={required:["session_id","text"],properties:{event_id:{type:"string"},session_id:{type:"string"},text:{type:"string",maxLength:5e4},quoted_message_id:{type:"string"},is_final:{type:"boolean"}}},d={required:["event_id","status"],properties:{event_id:{type:"string"},status:{type:"string",enum:["responded","canceled","failed"]},msg:{type:"string",maxLength:500}}},c={required:["event_id"],properties:{event_id:{type:"string"},session_id:{type:"string"}}},l={required:["session_id","active"],properties:{session_id:{type:"string"},active:{type:"boolean"},event_id:{type:"string"}}},_={required:["action"],properties:{action:{type:"string",enum:["pair_approve","pair_deny","allow_sender","remove_sender","set_policy"]},code:{type:"string"},sender_id:{type:"string"},policy:{type:"string",enum:["allowlist","open","disabled"]}}},f={required:[],properties:{}},C={grix_query:o,grix_group:a,grix_message_send:p,grix_message_unsend:m,grix_admin:g,grix_reply:y,grix_complete:d,grix_event_ack:c,grix_composing:l,grix_access_control:_,grix_status:f};function B(
|
|
1
|
+
const o={required:["action"],properties:{action:{type:"string",enum:["contact_search","session_search","message_history","message_search"]},id:{type:"string"},keyword:{type:"string",maxLength:200},limit:{type:"integer",minimum:1,maximum:100},offset:{type:"integer",minimum:0},sessionId:{type:"string"},beforeId:{type:"string"}}},a={required:["action"],properties:{action:{type:"string",enum:["create","detail","leave","add_members","remove_members","update_member_role","update_all_members_muted","update_member_speaking","dissolve"]},sessionId:{type:"string"},name:{type:"string",maxLength:128},memberIds:{type:"array",items:{type:"string"},maxItems:100},memberTypes:{type:"array",items:{type:"integer",enum:[1,2]}},memberId:{type:"string"},role:{type:"integer",enum:[1,2]},memberType:{type:"integer"},allMembersMuted:{type:"boolean"},isSpeakMuted:{type:"boolean"},canSpeakWhenAllMuted:{type:"boolean"}}},p={required:["sessionId","content"],properties:{sessionId:{type:"string"},content:{type:"string",maxLength:1e4},msgType:{type:"integer"},quotedMessageId:{type:"string"},threadId:{type:"string"}}},m={required:["sessionId","msgId"],properties:{sessionId:{type:"string"},msgId:{type:"string"}}},g={required:["action"],properties:{action:{type:"string",enum:["create_agent","list_categories","create_category","update_category","assign_category","rotate_api_key"]},agentName:{type:"string"},introduction:{type:"string"},isMain:{type:"boolean"},agentId:{type:"string"},categoryId:{type:"string"},name:{type:"string"},parentId:{type:"string"},sortOrder:{type:"integer"}}},y={required:["session_id","text"],properties:{event_id:{type:"string"},session_id:{type:"string"},text:{type:"string",maxLength:5e4},quoted_message_id:{type:"string"},is_final:{type:"boolean"}}},d={required:["event_id","status"],properties:{event_id:{type:"string"},status:{type:"string",enum:["responded","canceled","failed"]},msg:{type:"string",maxLength:500}}},c={required:["event_id"],properties:{event_id:{type:"string"},session_id:{type:"string"}}},l={required:["session_id","active"],properties:{session_id:{type:"string"},active:{type:"boolean"},event_id:{type:"string"}}},_={required:["action"],properties:{action:{type:"string",enum:["pair_approve","pair_deny","allow_sender","remove_sender","set_policy"]},code:{type:"string"},sender_id:{type:"string"},policy:{type:"string",enum:["allowlist","open","disabled"]}}},f={required:[],properties:{}},C={grix_query:o,grix_group:a,grix_message_send:p,grix_message_unsend:m,grix_admin:g,grix_reply:y,grix_complete:d,grix_event_ack:c,grix_composing:l,grix_access_control:_,grix_status:f};function B(r,t){const e=C[r];if(!e)return{valid:!1,error:`\u672A\u77E5\u5DE5\u5177: ${r}`};for(const i of e.required)if(t[i]===void 0||t[i]===null)return{valid:!1,error:`\u7F3A\u5C11\u5FC5\u586B\u53C2\u6570: ${i}`};for(const[i,u]of Object.entries(t)){if(u==null)continue;const n=e.properties[i];if(!n)continue;const s=$(i,u,n);if(s)return{valid:!1,error:s}}return{valid:!0}}function $(r,t,e){switch(e.type){case"string":if(typeof t!="string")return`\u53C2\u6570 ${r} \u7C7B\u578B\u9519\u8BEF: \u671F\u671B string\uFF0C\u5B9E\u9645 ${typeof t}`;if(e.maxLength!==void 0&&t.length>e.maxLength)return`\u53C2\u6570 ${r} \u8D85\u8FC7\u6700\u5927\u957F\u5EA6 ${e.maxLength}\uFF0C\u5B9E\u9645 ${t.length}`;if(e.enum&&!e.enum.includes(t))return`\u53C2\u6570 ${r} \u503C "${t}" \u4E0D\u5728\u5141\u8BB8\u8303\u56F4 [${e.enum.join(", ")}]`;break;case"integer":if(typeof t!="number"||!Number.isInteger(t))return`\u53C2\u6570 ${r} \u7C7B\u578B\u9519\u8BEF: \u671F\u671B integer\uFF0C\u5B9E\u9645 ${typeof t=="number"?"\u6D6E\u70B9\u6570":typeof t}`;if(e.minimum!==void 0&&t<e.minimum)return`\u53C2\u6570 ${r} \u503C ${t} \u5C0F\u4E8E\u6700\u5C0F\u503C ${e.minimum}`;if(e.maximum!==void 0&&t>e.maximum)return`\u53C2\u6570 ${r} \u503C ${t} \u5927\u4E8E\u6700\u5927\u503C ${e.maximum}`;if(e.enum&&!e.enum.includes(t))return`\u53C2\u6570 ${r} \u503C ${t} \u4E0D\u5728\u5141\u8BB8\u8303\u56F4 [${e.enum.join(", ")}]`;break;case"boolean":if(typeof t!="boolean")return`\u53C2\u6570 ${r} \u7C7B\u578B\u9519\u8BEF: \u671F\u671B boolean\uFF0C\u5B9E\u9645 ${typeof t}`;break;case"array":if(!Array.isArray(t))return`\u53C2\u6570 ${r} \u7C7B\u578B\u9519\u8BEF: \u671F\u671B array\uFF0C\u5B9E\u9645 ${typeof t}`;if(e.maxItems!==void 0&&t.length>e.maxItems)return`\u53C2\u6570 ${r} \u8D85\u8FC7\u6700\u5927\u5143\u7D20\u6570 ${e.maxItems}\uFF0C\u5B9E\u9645 ${t.length}`;if(e.items)for(let i=0;i<t.length;i++){const u=t[i];if(e.items.type==="string"&&typeof u!="string")return`\u53C2\u6570 ${r}[${i}] \u7C7B\u578B\u9519\u8BEF: \u671F\u671B string\uFF0C\u5B9E\u9645 ${typeof u}`;if(e.items.type==="integer"){if(typeof u!="number"||!Number.isInteger(u))return`\u53C2\u6570 ${r}[${i}] \u7C7B\u578B\u9519\u8BEF: \u671F\u671B integer\uFF0C\u5B9E\u9645 ${typeof u}`;if(e.items.enum&&!e.items.enum.includes(u))return`\u53C2\u6570 ${r}[${i}] \u503C ${u} \u4E0D\u5728\u5141\u8BB8\u8303\u56F4 [${e.items.enum.join(", ")}]`}}break}}export{B as validateToolArgs};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "grix-connector",
|
|
3
|
-
"version": "3.2.
|
|
3
|
+
"version": "3.2.8",
|
|
4
4
|
"description": "Connect local AI coding agents (Claude, Codex, Gemini, Qwen, DeepSeek, Cursor, OpenCode, Pi, OpenHuman, Reasonix) to the Grix scheduling platform. Also serves as an OpenClaw plugin for Grix channel transport.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|