grix-connector 2.2.3 → 2.2.4
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 +1 -1
- package/dist/core/aibot/client.js +2 -2
- package/dist/core/file-ops/list-files.js +1 -1
- package/dist/log.js +2 -2
- package/dist/manager-share-config.js +1 -1
- 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 n of t)e.push(n);const r=Buffer.concat(e).toString("utf8").trim();return r?JSON.parse(r):{}}function k(t){const e=(t.headers.authorization??"").trim();return e.toLowerCase().startsWith("bearer ")?e.slice(7).trim():""}class w{host="127.0.0.1";port=0;token;callbacks;server=null;address=null;constructor(e){this.token=d(),this.callbacks=e}getToken(){return this.token}getURL(){return this.address?`http://${this.address.address}:${this.address.port}`:""}async start(){this.server||(this.server=c.createServer(async(e,r)=>{try{await this.handleRequest(e,r)}catch(n){h(r,n instanceof Error?n.message:String(n))}}),await new Promise((e,r)=>{this.server.once("error",r),this.server.listen(this.port,this.host,()=>{this.server.off("error",r),e()})}),this.address=this.server.address(),o.info("claude-bridge",`Bridge server listening on ${this.getURL()}`))}async stop(){if(!this.server)return;const e=this.server;this.server=null,this.address=null,e.closeIdleConnections?.(),e.closeAllConnections?.(),await new Promise((r,n)=>{e.close(s=>s?n(s):r())})}async handleRequest(e,r){if(k(e)!==this.token){l(r);return}if(e.method!=="POST"){r.writeHead(405,{"content-type":"application/json"}),r.end(JSON.stringify({error:"method_not_allowed"}));return}const n=new URL(e.url,"http://localhost").pathname,s=await v(e),i=f.get(n);if(!i){u(r);return}const a=await i(this.callbacks,s);p(r,a??{ok:!0})}}const f=new Map([["/v1/worker/register",async(t,e)=>(o.info("claude-bridge",`Worker registered: ${e.worker_id} (pid=${e.pid})`),t.onRegisterWorker(e))],["/v1/worker/status",async(t,e)=>(o.info("claude-bridge",`Worker status: ${e.status}`),t.onStatusUpdate(e))],["/v1/worker/send-text",async(t,e)=>t.onSendText(e)],["/v1/worker/send-stream-chunk",async(t,e)=>t.onSendStreamChunk(e)],["/v1/worker/send-media",async(t,e)=>t.onSendMedia(e)],["/v1/worker/delete-message",async(t,e)=>t.onDeleteMessage(e)],["/v1/worker/ack-event",async(t,e)=>t.onAckEvent(e)],["/v1/worker/event-result",async(t,e)=>t.onSendEventResult(e)],["/v1/worker/event-stop-ack",async(t,e)=>t.onSendEventStopAck(e)],["/v1/worker/event-stop-result",async(t,e)=>t.onSendEventStopResult(e)],["/v1/worker/session-composing",async(t,e)=>t.onSetSessionComposing(e)],["/v1/worker/agent-invoke",async(t,e)=>t.onAgentInvoke(e)],["/v1/worker/local-action-result",async(t,e)=>t.onLocalActionResult(e)]]);export{w as ClaudeBridgeServer};
|
|
@@ -1 +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(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
|
+
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 +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(t,e){this.controlURL=t.replace(/\/+$/,""),this.token=e.trim(),l.info("claude-worker-client",`Configured with control URL: ${this.controlURL}`)}async post(t,e,s){if(!this.isConfigured())throw new Error("worker control not configured");const i=new AbortController,o=setTimeout(()=>i.abort(),s);try{const r=await fetch(`${this.controlURL}${t}`,{method:"POST",headers:{"content-type":"application/json",authorization:`Bearer ${this.token}`},body:JSON.stringify(e),signal:i.signal}),n=await r.text(),a=n.trim()?JSON.parse(n):{};if(!r.ok)throw new Error(a.error||`worker control failed ${r.status}`);return a}finally{clearTimeout(o)}}isRetryableError(t){const e=t instanceof Error?t.message:String(t);return/fetch failed|network|ECONNRESET|ETIMEDOUT|EAI_AGAIN|socket hang up|aborted/i.test(e)}async postWithRetry(t,e,s,i=1){let o;for(let r=0;r<=i;r++)try{return r>0&&l.info("claude-worker-client",`Retrying ${t} attempt=${r+1}`),await this.post(t,e,s)}catch(n){if(o=n,r>=i||!this.isRetryableError(n))break;await new Promise(a=>setTimeout(a,150))}throw o instanceof Error?o:new Error(String(o))}async deliverEvent(t){return this.postWithRetry("/v1/worker/deliver-event",{payload:t},1e4,1)}async deliverStop(t){return this.postWithRetry("/v1/worker/deliver-stop",{payload:t},1e4,1)}async deliverLocalAction(t){return this.postWithRetry("/v1/worker/deliver-local-action",{payload:t},1e4,1)}async ping(){try{return await this.postWithRetry("/v1/worker/ping",{},5e3,1),!0}catch{return!1}}}export{c as ClaudeWorkerClient};
|
|
@@ -1,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 l}from"./protocol-contract.js";function P(e){let t=null,r=0,n=!1,i=!1;const a=v(),s=e.gatewayUrl??"http://127.0.0.1:19580/mcp";return{async start(){await F(e.command,s,e.env);const c=E(e.grix),u=[...e.args??[],"--name",`grix-mcp-${e.name}`,"--session-id",a];e.fullAuto&&u.push("--dangerously-skip-permissions"),u.push("--dangerously-load-development-channels",`server:${l}`,"--append-system-prompt",c);const f=d(I(),`grix-mcp-claude-${e.name}`);await S(f,{recursive:!0});const{expectPath:$,pidPath:g}=await M(f,e.command,u),_={...process.env,...e.env??{}};t=x("/usr/bin/expect",[$],{cwd:e.cwd,env:_,stdio:["ignore","pipe","pipe"],detached:!0}),o.info("mcp-http-launcher",`\u542F\u52A8 Claude: name=${e.name} cwd=${e.cwd} pid=${t.pid}`),r=await k(g),n=!0,o.info("mcp-http-launcher",`Claude \u5B50\u8FDB\u7A0B PID: ${r}`),t.on("exit",(m,p)=>{o.info("mcp-http-launcher",`Claude \u9000\u51FA: code=${m} signal=${p}`),n=!1,t=null,r=0,i||(o.info("mcp-http-launcher","3 \u79D2\u540E\u81EA\u52A8\u91CD\u542F..."),setTimeout(()=>{i||this.start().catch(w=>{o.error("mcp-http-launcher",`\u91CD\u542F\u5931\u8D25: ${w}`)})},3e3))}),t.stdout?.on("data",m=>{const p=m.toString().trim();p&&o.info("mcp-http-launcher",`[stdout] ${p.slice(0,300)}`)}),t.stderr?.on("data",m=>{const p=m.toString().trim();p&&o.info("mcp-http-launcher",`[stderr] ${p.slice(0,300)}`)})},async stop(){if(i=!0,n=!1,r>0)try{process.kill(r,"SIGTERM")}catch{}if(t?.pid){try{process.kill(-t.pid,"SIGTERM")}catch{}await new Promise(c=>{const u=setTimeout(()=>{if(r>0)try{process.kill(r,"SIGKILL")}catch{}if(t?.pid)try{process.kill(-t.pid,"SIGKILL")}catch{}c()},5e3);t?.once("exit",()=>{clearTimeout(u),c()})})}t=null,r=0},getStatus(){return{name:e.name,alive:n,pid:r}}}}function E(e){return["You are connected to a chat via the grix MCP server.",`On startup, immediately call grix_authorize with: agentId="${e.agentId}", apiKey="${e.apiKey}", wsUrl="${e.wsUrl}", clientType="${e.clientType}".`,"When you receive a <channel> message, you MUST respond by calling the grix_reply tool (or the grix_complete tool if no response is needed).","Never write your reply as plain text \u2014 it will NOT reach the user. Only the grix_reply tool delivers your response to the chat.","The <channel> message contains event_id and session_id \u2014 pass them to grix_reply."].join(" ")}async function F(e,t,r){const n=d(T(),".claude.json");let i=null;try{const s=C(n,"utf8");i=JSON.parse(s)?.mcpServers?.[l]??null}catch{}if(i&&String(i.type??"").trim()==="http"&&String(i.url??"").trim()===t)return;o.info("mcp-http-launcher",`\u6CE8\u518C MCP Server: ${l} -> ${t}`);const a={...process.env,...r??{}};try{y(`${e} mcp remove -s user ${l}`,{encoding:"utf8",timeout:1e4,env:a,stdio:"pipe"})}catch{}y(`${e} mcp add --scope user --transport http ${l} ${t}`,{encoding:"utf8",timeout:1e4,env:a,stdio:"pipe"})}async function M(e,t,r){const{writeFile:n}=await import("node:fs/promises"),i=d(e,"claude.pid"),a=d(e,"claude.expect"),s=["log_user 1","set timeout -1","set startup_prompt_armed 1",`set claude_command [list {${h(t)}}${r.map(c=>` {${h(c)}}`).join("")}]`,"spawn -noecho {*}$claude_command",`set pid_file [open {${h(i)}} w]`,"puts $pid_file [exp_pid -i $spawn_id]","close $pid_file","expect {"," -re {(?i)(Quick.*safety.*check|trust.*folder)} {",' if {$startup_prompt_armed} { send -- "1\\r"; after 300 }; exp_continue'," }"," -re {(?i)I am using this for local development} {",' if {$startup_prompt_armed} { send -- "1\\r"; after 300 }; exp_continue'," }"," -re {(?i)(Enter.*confirm|Press.*Enter|Hit.*Enter)} {",' if {$startup_prompt_armed} { send -- "\\r"; after 300 }; exp_continue'," }"," -re {Listening for channel} {"," set startup_prompt_armed 0"," after 1000",' send -- "Call grix_authorize now as instructed in your system prompt.\\r"'," }"," -re {bypass permissions} {"," set startup_prompt_armed 0"," after 1000",' send -- "Call grix_authorize now as instructed in your system prompt.\\r"'," }"," eof {}","}","expect eof",""];return await n(i,"","utf8"),await n(a,s.join(`
|
|
2
|
+
`),"utf8"),{expectPath:a,pidPath:i}}function h(e){return e.replace(/[\\{}$\[\]"]/g,"\\$&")}async function k(e,t=1e4){const{readFile:r}=await import("node:fs/promises"),n=Math.ceil(t/100);for(let i=0;i<n;i++){try{const a=await r(e,"utf8"),s=parseInt(String(a).trim(),10);if(Number.isFinite(s)&&s>0)return s}catch{}await new Promise(a=>setTimeout(a,100))}return 0}export{P as createMcpHttpLauncher};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
class m{defaultTimeoutMs;onTimeout;timers=new Map;constructor(
|
|
1
|
+
class m{defaultTimeoutMs;onTimeout;timers=new Map;constructor(t){this.defaultTimeoutMs=t.defaultTimeoutMs??9e4,this.onTimeout=t.onTimeout}arm(t,e){this.cancel(t);const s=e?.timeoutMs??this.defaultTimeoutMs,i=Date.now()+s,o=setTimeout(()=>{this.timers.delete(t),this.onTimeout(t).catch(()=>{})},s);return this.timers.set(t,o),i}cancel(t){const e=this.timers.get(t);e&&(clearTimeout(e),this.timers.delete(t))}has(t){return this.timers.has(t)}close(){for(const t of this.timers.values())clearTimeout(t);this.timers.clear()}}export{m as ResultTimeoutManager};
|
package/dist/bridge/bridge.js
CHANGED
|
@@ -8,7 +8,7 @@ Error: ${s}`,0,!1)},sendUpdateBindingCard:(o,i,s)=>this.aibotHandle.sendUpdateBi
|
|
|
8
8
|
|
|
9
9
|
Error: ${s}`,0,!1)},sendUpdateBindingCard:(o,i,s)=>this.aibotHandle.sendUpdateBindingCard({session_id:o,worker_status:i,cwd:s}),agentInvoke:async(o,i)=>this.platformInvoke(o,i),sendLocalActionResult:(o,i,s,r,a)=>{this.aibotHandle.sendLocalActionResult({action_id:o,status:i,...s!==void 0?{result:s}:{},...r?{error_code:r}:{},...a?{error_msg:a}:{}})}},t=this.config.adapterOptions??{};return new ee({command:this.config.agent.command,args:this.config.agent.args,env:this.config.agent.env,options:t},n,{port:t.port,hostname:t.hostname,model:t.model,agent:t.agent,permissionPolicy:t.permission_policy,enableSessionBinding:!0,aibotSessionId:e,bindingStore:this.bindingStore})}createAgyAdapter(e){const n={sendStreamChunk:(o,i,s,r,a)=>{this.sendStreamChunkByRuntimeConfig(o,i,s,r,a)},sendEventResult:(o,i,s)=>{this.sendEventResultWithCleanup(o,i,s)},sendEventAck:(o,i)=>{this.aibotHandle.sendEventAck({event_id:o,session_id:i,received_at:Date.now()})},agentInvoke:async(o,i,s)=>this.platformInvoke(o,i,s),forceCompleteInternalEvent:(o,i)=>{this.pool.eventComplete(o,i),this.pushQueueSnapshotForSession(i)},persistConversationId:(o,i)=>{this.bindingStore.setAgyConversationId(o,i)}},t=o=>{const i=this.bindingStore.get(o);return{cwd:i?.cwd,modelId:i?.modelId??this.globalConfigStore?.get(this.name)?.modelId,conversationId:i?.agyConversationId}};return new U({command:this.config.agent.command,args:this.config.agent.args,env:this.config.agent.env,options:this.config.adapterOptions??{}},n,t)}createAcpAdapter(e){const n=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(n){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(n){this.sendAcpRawEventEnvelope(a.eventId,a.sessionId,{type:"permission_request",payload:{tool_call_id:a.toolCallId,tool_name:a.toolName,tool_title:a.toolTitle,options:a.options}});return}this.aibotHandle.sendMsg({event_id:a.eventId,session_id:a.sessionId,client_msg_id:`perm_${F()}`,msg_type:1,content:a.toolTitle?`Permission required: ${a.toolTitle}`:"Permission request",extra:{channel_data:{execApproval:{approvalId:a.toolCallId,approvalSlug:a.toolName},grix:{execApproval:{approval_command_id:a.toolCallId,command:a.toolTitle||a.toolName,host:"acp"}}},agent_api_origin:!0}})},sendAuthNotification:(a,d)=>{a&&this.aibotHandle.sendMsg({session_id:a,msg_type:1,content:d,extra:{biz_card:{version:1,type:"agent_error",payload:{error:{name:"AuthRequired",message:d}}}}})},sendUpdateBindingCard:(a,d,c,l)=>{const h={...l??{}};if(!h.rate_limits&&this.cachedProviderQuota?.success){this.isRateLimitsCacheFresh(this.cachedProviderQuotaSampledAtMs)||this.maybeQueryProviderQuota().catch(()=>{});const m=this.providerQuotaToRateLimits(this.cachedProviderQuota);m&&(h.rate_limits=m)}!h.provider_quota&&this.cachedProviderQuota?.success&&(h.provider_quota=this.cachedProviderQuota),this.aibotHandle.sendUpdateBindingCard({session_id:a,worker_status:d,cwd:c,...Object.keys(h).length>0?{meta:h}:{}})},onSkillsUpdate:a=>{try{this.aibotHandle.sendSkillsUpdate({skills:a})}catch(d){}},onContextWindowUpdated:a=>{this.cachedAcpContextWindow=a,this.cachedAcpContextWindowSampledAtMs=Date.now();const d="usedPercentage"in a?a.usedPercentage.toFixed(1):(a.used/a.size*100).toFixed(1);u.info(this.name,`[acp] context_window updated: ${d}%`);const c=this.bindingStore.get(e);if(c?.cwd){const l={context_window:"usedPercentage"in a?{usedPercentage:a.usedPercentage,remainingPercentage:100-a.usedPercentage}:a};if(this.config.aibot.clientType==="kiro"&&this.cachedProviderQuota?.success){this.isRateLimitsCacheFresh(this.cachedProviderQuotaSampledAtMs)||this.maybeQueryProviderQuota().catch(()=>{}),l.provider_quota=this.cachedProviderQuota;const h=this.providerQuotaToRateLimits(this.cachedProviderQuota);h&&(l.rate_limits=h)}this.aibotHandle.sendUpdateBindingCard({session_id:e,worker_status:"ready",cwd:c.cwd,meta:l})}},sendMcpFrame:a=>{this.aibotHandle.sendMcpFrame(e,a)}},o=e?this.bindingStore.get(e):void 0,i=this.globalConfigStore?.get(this.name),{initialModel:s,initialMode:r}=Ae({sessionBinding:o,globalDefaults:i,configInitialMode:this.config.acpInitialMode});return(s||r)&&u.info(this.name,`[toolbar] hydrate from binding: session=${e} model=${s??"<none>"} mode=${r??"<none>"}`),new A({command:this.config.agent.command,args:this.config.agent.args,env:this.config.agent.env},t,{acpAuthMethod:this.config.acpAuthMethod,acpInitialMode:r,acpInitialModel:s,acpMcpTools:this.config.acpMcpTools,rawTransport:n,eventResultsPath:this.config.eventResultsPath,approvalMode:this.config.approvalMode,bindingStore:this.config.enableSessionBinding?this.bindingStore:void 0,aibotSessionId:e,autoInjectArgs:this.config.autoInjectArgs,bridgeLog:this.config.logDir?new _e(this.config.logDir,`${this.name}-${e}`):null})}async connectAibot(){const e=new J;if(this.aibotHandle=await e.connect(this.aibotConfig,{aborted:()=>this.stopped,label:this.name,packetLog:this.packetLog}),this.aibotHandle.onEvent(n=>{this.handleAibotEvent(n).catch(t=>{u.error(this.name,`handleAibotEvent failed: ${t}`),this.aibotHandle.sendEventAck({event_id:n.event_id,session_id:n.session_id,received_at:Date.now()});const o=t instanceof Error?t.message:String(t);if(/CWD must be|Bound directory does not exist|Bound path is not a directory/i.test(o)&&n.session_id){this.bindingStore.delete(n.session_id),this.sessionBindings.delete(n.session_id);const s=this.pool.getSlot(n.session_id);s?.adapter instanceof A&&s.adapter.getSessionBindings().delete(n.session_id);const r=this.config.adapterType??"acp",a=this.resolveBindingChannelKey(r);this.aibotHandle.sendMsg({event_id:n.event_id,session_id:n.session_id,msg_type:1,content:o,extra:{channel_data:{[a]:{sessionBinding:{status:"missing",reason:"binding_stale",error_code:g.invalidCwd}}}},quoted_message_id:n.msg_id});return}this.aibotHandle.sendEventResult({event_id:n.event_id,status:"failed",msg:o,updated_at:Date.now()})})}),this.aibotHandle.onStop(n=>{try{this.handleAibotStop(n)}catch(t){u.error(this.name,`handleAibotStop failed: ${t}`)}}),this.aibotHandle.onShareSet(n=>{try{this.shareSetHandler?.(Array.isArray(n?.shared_to)?n.shared_to:[])}catch(t){u.error(this.name,`onShareSet failed: ${t}`)}}),this.aibotHandle.onRevoke(n=>{try{this.handleAibotRevoke(n)}catch(t){u.error(this.name,`handleAibotRevoke failed: ${t}`)}}),this.aibotHandle.onLocalAction(n=>{this.handleAibotLocalAction(n).catch(t=>{u.error(this.name,`handleAibotLocalAction failed: ${t}`)})}),this.aibotHandle.onEventCancel(n=>{u.info(this.name,`recv event_cancel event_id=${n.event_id} session_id=${n.session_id}`),this.handleEventCancel(n).catch(t=>{u.error(this.name,`handleEventCancel failed: ${t}`)})}),this.aibotHandle.onMcpFrame((n,t)=>{const o=this.pool.getSlot(n)?.adapter;o?.deliverMcpFrameToAgent?o.deliverMcpFrameToAgent(t):u.warn(this.name,`mcp_frame: no adapter for session=${n}`)}),this.aibotHandle.onQueueClear(n=>{const t=this.pool.clearQueue(n.session_id);this.aibotHandle.sendQueueClearResult({session_id:n.session_id,canceled_event_ids:t}),this.pushQueueSnapshotForSession(n.session_id)}),this.aibotHandle.onQueueSnapshotQuery(n=>{this.replyQueueSnapshotForSession(n.session_id)}),u.info(this.name,"Connected to aibot"),this.activeEventStore){const n=await this.activeEventStore.drain();if(n.length>0){u.warn(this.name,`Recovering ${n.length} stale event(s) from previous run`);for(const t of n)u.info(this.name,`Failing stale event on startup: ${t}`),this.aibotHandle.sendEventResult({event_id:t,status:"failed",msg:"process restarted, event lost",updated_at:Date.now()})}}this.pushQueueSnapshots(),this.aibotHandle.onReconnected(()=>{this.pushQueueSnapshots()}),this.aibotHandle.onStreamRejected((n,t)=>{this.sendCtrl.markEventRejected(n)})}pushQueueSnapshots(){if(!(!this.config.eventQueue||!this.pool))for(const e of this.pool.getAllSlots())this.pushQueueSnapshotForSession(e.sessionId)}buildQueueSnapshotPayload(e){const n=this.pool?.getQueueSnapshot(e)??null,t=n?[...n.running]:[],o=n?n.running_items.map(s=>({event_id:s.event_id,...s.content_preview?{content_preview:s.content_preview}:{},...s.title?{title:s.title}:{},...s.summary?{summary:s.summary}:{},actions:[{type:"stop"}]})):[],i=n?n.queued.map(s=>({event_id:s.event_id,position:s.position,...s.content_preview?{content_preview:s.content_preview}:{},...s.title?{title:s.title}:{},...s.summary?{summary:s.summary}:{},actions:[{type:"cancel"}]})):[];if(t.length===0&&this.selfDrivenSessions.has(e)&&this.pool?.getSlot(e)){const s=`selfdrive_${e}`,r="Background task in progress";t.push(s),o.push({event_id:s,content_preview:r,title:r,summary:r,actions:[]})}return{session_id:e,running:t,running_items:o,queued:i}}pushQueueSnapshotForSession(e){if(!this.config.eventQueue||!this.pool)return;const n=this.buildQueueSnapshotPayload(e);u.info(this.name,`[queue-debug] push snapshot session=${e} running=${n.running.length} queued=${n.queued.length} running_ids=[${n.running.join(",")}]`),this.aibotHandle.sendQueueSnapshot(n)}replyQueueSnapshotForSession(e){!this.config.eventQueue||!this.pool||this.aibotHandle.sendQueueSnapshot(this.buildQueueSnapshotPayload(e))}async platformInvoke(e,n,t){return e==="file_link"?$e(n):this.aibotHandle.agentInvoke(e,n,t)}sendReplyByRuntimeConfig(e,n,t,o,i){this.indexEventSession(e,n),this.sendCtrl.sendReply(e,n,t,o,i),t&&this.conversationLog?.logOutbound?.(n,e,"reply",t)}sendStreamChunkByRuntimeConfig(e,n,t,o,i,s,r){this.indexEventSession(e,n),this.sendCtrl.sendStreamChunk(e,n,t,o,i,s,r),(t||i)&&this.conversationLog?.logOutbound?.(n,e,i?"stream_chunk_finish":"stream_chunk",t)}sendRunErrorAsChunk(e,n,t){this.sendStreamChunkByRuntimeConfig(e,n,`
|
|
11
|
+
Error: ${c}`,1,!1)},sendPermissionCard:a=>{if(n){this.sendAcpRawEventEnvelope(a.eventId,a.sessionId,{type:"permission_request",payload:{tool_call_id:a.toolCallId,tool_name:a.toolName,tool_title:a.toolTitle,options:a.options}});return}this.aibotHandle.sendMsg({event_id:a.eventId,session_id:a.sessionId,client_msg_id:`perm_${F()}`,msg_type:1,content:a.toolTitle?`Permission required: ${a.toolTitle}`:"Permission request",extra:{channel_data:{execApproval:{approvalId:a.toolCallId,approvalSlug:a.toolName},grix:{execApproval:{approval_command_id:a.toolCallId,command:a.toolTitle||a.toolName,host:"acp"}}},agent_api_origin:!0}})},sendAuthNotification:(a,d)=>{a&&this.aibotHandle.sendMsg({session_id:a,msg_type:1,content:d,extra:{biz_card:{version:1,type:"agent_error",payload:{error:{name:"AuthRequired",message:d}}}}})},sendUpdateBindingCard:(a,d,c,l)=>{const h={...l??{}};if(!h.rate_limits&&this.cachedProviderQuota?.success){this.isRateLimitsCacheFresh(this.cachedProviderQuotaSampledAtMs)||this.maybeQueryProviderQuota().catch(()=>{});const m=this.providerQuotaToRateLimits(this.cachedProviderQuota);m&&(h.rate_limits=m)}!h.provider_quota&&this.cachedProviderQuota?.success&&(h.provider_quota=this.cachedProviderQuota),this.aibotHandle.sendUpdateBindingCard({session_id:a,worker_status:d,cwd:c,...Object.keys(h).length>0?{meta:h}:{}})},onSkillsUpdate:a=>{try{this.aibotHandle.sendSkillsUpdate({skills:a})}catch(d){}},onContextWindowUpdated:a=>{this.cachedAcpContextWindow=a,this.cachedAcpContextWindowSampledAtMs=Date.now();const d="usedPercentage"in a?a.usedPercentage.toFixed(1):(a.used/a.size*100).toFixed(1);u.info(this.name,`[acp] context_window updated: ${d}%`);const c=this.bindingStore.get(e);if(c?.cwd){const l={context_window:"usedPercentage"in a?{usedPercentage:a.usedPercentage,remainingPercentage:100-a.usedPercentage}:a};if(this.config.aibot.clientType==="kiro"&&this.cachedProviderQuota?.success){this.isRateLimitsCacheFresh(this.cachedProviderQuotaSampledAtMs)||this.maybeQueryProviderQuota().catch(()=>{}),l.provider_quota=this.cachedProviderQuota;const h=this.providerQuotaToRateLimits(this.cachedProviderQuota);h&&(l.rate_limits=h)}this.aibotHandle.sendUpdateBindingCard({session_id:e,worker_status:"ready",cwd:c.cwd,meta:l})}},sendMcpFrame:a=>{this.aibotHandle.sendMcpFrame(e,a)}},o=e?this.bindingStore.get(e):void 0,i=this.globalConfigStore?.get(this.name),{initialModel:s,initialMode:r}=Ae({sessionBinding:o,globalDefaults:i,configInitialMode:this.config.acpInitialMode});return(s||r)&&u.info(this.name,`[toolbar] hydrate from binding: session=${e} model=${s??"<none>"} mode=${r??"<none>"}`),new A({command:this.config.agent.command,args:this.config.agent.args,env:this.config.agent.env},t,{acpAuthMethod:this.config.acpAuthMethod,acpInitialMode:r,acpInitialModel:s,acpMcpTools:this.config.acpMcpTools,rawTransport:n,eventResultsPath:this.config.eventResultsPath,approvalMode:this.config.approvalMode,bindingStore:this.config.enableSessionBinding?this.bindingStore:void 0,aibotSessionId:e,autoInjectArgs:this.config.autoInjectArgs,bridgeLog:this.config.logDir?new _e(this.config.logDir,`${this.name}-${e}`):null})}async connectAibot(){const e=new J;if(this.aibotHandle=await e.connect(this.aibotConfig,{aborted:()=>this.stopped,label:this.name,packetLog:this.packetLog,maxRetries:this.config.connectMaxRetries}),this.aibotHandle.onEvent(n=>{this.handleAibotEvent(n).catch(t=>{u.error(this.name,`handleAibotEvent failed: ${t}`),this.aibotHandle.sendEventAck({event_id:n.event_id,session_id:n.session_id,received_at:Date.now()});const o=t instanceof Error?t.message:String(t);if(/CWD must be|Bound directory does not exist|Bound path is not a directory/i.test(o)&&n.session_id){this.bindingStore.delete(n.session_id),this.sessionBindings.delete(n.session_id);const s=this.pool.getSlot(n.session_id);s?.adapter instanceof A&&s.adapter.getSessionBindings().delete(n.session_id);const r=this.config.adapterType??"acp",a=this.resolveBindingChannelKey(r);this.aibotHandle.sendMsg({event_id:n.event_id,session_id:n.session_id,msg_type:1,content:o,extra:{channel_data:{[a]:{sessionBinding:{status:"missing",reason:"binding_stale",error_code:g.invalidCwd}}}},quoted_message_id:n.msg_id});return}this.aibotHandle.sendEventResult({event_id:n.event_id,status:"failed",msg:o,updated_at:Date.now()})})}),this.aibotHandle.onStop(n=>{try{this.handleAibotStop(n)}catch(t){u.error(this.name,`handleAibotStop failed: ${t}`)}}),this.aibotHandle.onShareSet(n=>{try{this.shareSetHandler?.(Array.isArray(n?.shared_to)?n.shared_to:[])}catch(t){u.error(this.name,`onShareSet failed: ${t}`)}}),this.aibotHandle.onRevoke(n=>{try{this.handleAibotRevoke(n)}catch(t){u.error(this.name,`handleAibotRevoke failed: ${t}`)}}),this.aibotHandle.onLocalAction(n=>{this.handleAibotLocalAction(n).catch(t=>{u.error(this.name,`handleAibotLocalAction failed: ${t}`)})}),this.aibotHandle.onEventCancel(n=>{u.info(this.name,`recv event_cancel event_id=${n.event_id} session_id=${n.session_id}`),this.handleEventCancel(n).catch(t=>{u.error(this.name,`handleEventCancel failed: ${t}`)})}),this.aibotHandle.onMcpFrame((n,t)=>{const o=this.pool.getSlot(n)?.adapter;o?.deliverMcpFrameToAgent?o.deliverMcpFrameToAgent(t):u.warn(this.name,`mcp_frame: no adapter for session=${n}`)}),this.aibotHandle.onQueueClear(n=>{const t=this.pool.clearQueue(n.session_id);this.aibotHandle.sendQueueClearResult({session_id:n.session_id,canceled_event_ids:t}),this.pushQueueSnapshotForSession(n.session_id)}),this.aibotHandle.onQueueSnapshotQuery(n=>{this.replyQueueSnapshotForSession(n.session_id)}),u.info(this.name,"Connected to aibot"),this.activeEventStore){const n=await this.activeEventStore.drain();if(n.length>0){u.warn(this.name,`Recovering ${n.length} stale event(s) from previous run`);for(const t of n)u.info(this.name,`Failing stale event on startup: ${t}`),this.aibotHandle.sendEventResult({event_id:t,status:"failed",msg:"process restarted, event lost",updated_at:Date.now()})}}this.pushQueueSnapshots(),this.aibotHandle.onReconnected(()=>{this.pushQueueSnapshots()}),this.aibotHandle.onStreamRejected((n,t)=>{this.sendCtrl.markEventRejected(n)})}pushQueueSnapshots(){if(!(!this.config.eventQueue||!this.pool))for(const e of this.pool.getAllSlots())this.pushQueueSnapshotForSession(e.sessionId)}buildQueueSnapshotPayload(e){const n=this.pool?.getQueueSnapshot(e)??null,t=n?[...n.running]:[],o=n?n.running_items.map(s=>({event_id:s.event_id,...s.content_preview?{content_preview:s.content_preview}:{},...s.title?{title:s.title}:{},...s.summary?{summary:s.summary}:{},actions:[{type:"stop"}]})):[],i=n?n.queued.map(s=>({event_id:s.event_id,position:s.position,...s.content_preview?{content_preview:s.content_preview}:{},...s.title?{title:s.title}:{},...s.summary?{summary:s.summary}:{},actions:[{type:"cancel"}]})):[];if(t.length===0&&this.selfDrivenSessions.has(e)&&this.pool?.getSlot(e)){const s=`selfdrive_${e}`,r="Background task in progress";t.push(s),o.push({event_id:s,content_preview:r,title:r,summary:r,actions:[]})}return{session_id:e,running:t,running_items:o,queued:i}}pushQueueSnapshotForSession(e){if(!this.config.eventQueue||!this.pool)return;const n=this.buildQueueSnapshotPayload(e);u.info(this.name,`[queue-debug] push snapshot session=${e} running=${n.running.length} queued=${n.queued.length} running_ids=[${n.running.join(",")}]`),this.aibotHandle.sendQueueSnapshot(n)}replyQueueSnapshotForSession(e){!this.config.eventQueue||!this.pool||this.aibotHandle.sendQueueSnapshot(this.buildQueueSnapshotPayload(e))}async platformInvoke(e,n,t){return e==="file_link"?$e(n):this.aibotHandle.agentInvoke(e,n,t)}sendReplyByRuntimeConfig(e,n,t,o,i){this.indexEventSession(e,n),this.sendCtrl.sendReply(e,n,t,o,i),t&&this.conversationLog?.logOutbound?.(n,e,"reply",t)}sendStreamChunkByRuntimeConfig(e,n,t,o,i,s,r){this.indexEventSession(e,n),this.sendCtrl.sendStreamChunk(e,n,t,o,i,s,r),(t||i)&&this.conversationLog?.logOutbound?.(n,e,i?"stream_chunk_finish":"stream_chunk",t)}sendRunErrorAsChunk(e,n,t){this.sendStreamChunkByRuntimeConfig(e,n,`
|
|
12
12
|
|
|
13
13
|
Error: ${t}`,1,!1)}sendEventResultWithCleanup(e,n,t,o){this.sendCtrl.sendEventResult(e,n,t,o);const i=this.eventSessionIndex.get(e);i&&(this.pool.eventComplete(e,i),this.pushQueueSnapshotForSession(i),this.conversationLog?.logResult?.(i,e,n,t),this.eventSessionIndex.delete(e)),this.inflightEvents.delete(e),this.restartCount.delete(e),n==="responded"&&(this.cachedProviderQuotaSampledAtMs=null,this.maybeQueryProviderQuota().catch(()=>{}))}async handleSessionInternalError(e){const{eventId:n,sessionId:t,errorMsg:o}=e;if(this.stopped)return;const i=this.inflightEvents.get(n);if(!i){u.warn(this.name,`[recovery] no inflight event for internalError event=${n} session=${t}; surface failure directly`),this.sendRunErrorAsChunk(n,t,o),this.sendEventResultWithCleanup(n,"failed",o,"agent_stop_failure");return}const s=(this.restartCount.get(n)??0)+1;this.restartCount.set(n,s);const r=this.config.adapterType??"acp";if(s>B){u.error(this.name,`[recovery] adapter=${r} session=${t} event=${n} restart=${s}/${B} outcome=give-up err=${o}`),this.sendRunErrorAsChunk(n,t,o),this.sendEventResultWithCleanup(n,"failed",o,"agent_stop_failure");return}u.info(this.name,`[recovery] adapter=${r} session=${t} event=${n} restart=${s}/${B} outcome=restarting err=${o}`);const a=this.pool.drainQueuedForSession(t);a.length>0&&u.info(this.name,`[recovery] session=${t} preserved ${a.length} queued sibling event(s) across restart`);try{await this.pool.removeSlot(t)}catch(l){u.warn(this.name,`[recovery] removeSlot failed session=${t}: ${l instanceof Error?l.message:String(l)}`)}if(this.stopped)return;const d=this.resolveRecoveryPrompt(r,i),c={...i,content:d};try{await this.pool.deliverInboundEvent(c)}catch(l){u.error(this.name,`[recovery] redeliver failed event=${n} session=${t}: ${l instanceof Error?l.message:String(l)}`),this.sendEventResultWithCleanup(n,"failed",l instanceof Error?l.message:String(l));return}for(const l of a){if(this.stopped)break;try{await this.pool.deliverInboundEvent(l)}catch(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,n){return e==="acp"?"continue":n.content}sendThinkingByRuntimeConfig(e,n,t){this.sendCtrl.sendThinking(e,n,t)}bufferStreamChunk(e,n,t,o,i){this.sendCtrl.bufferOnly(e,n,t,o,i)}flushBufferedStreamText(e){}resolveEventRuntimeConfig(e){return this.sendCtrl.resolveEventRuntimeConfig(e)}captureEventRuntimeConfig(e){this.sendCtrl.captureEventRuntimeConfig(e),this.indexEventSession(e.event_id,e.session_id),e.event_id&&this.inflightEvents.set(e.event_id,e)}indexEventSession(e,n){!e||!n||this.eventSessionIndex.set(e,n)}shouldDropToolDisplayEvent(e){return this.sendCtrl.shouldDropToolDisplayEvent(e)}shouldDropThinkingDisplayEvent(e){return this.sendCtrl.shouldDropThinkingDisplayEvent(e)}shouldDropCodexDisplayEvent(e,n){return this.sendCtrl.shouldDropCodexDisplayEvent(e,n)}logCodexEventToConversation(e){if(!this.conversationLog||e.codex_method!=="item/agentMessage/delta")return;const t=e.codex_payload?.params?.delta;if(!t)return;const o=this.eventSessionIndex.get(e.event_id)??e.session_id;this.conversationLog.append(o,{ts:Date.now(),dir:"outbound",event_id:e.event_id,kind:"codex_delta",text_len:t.length,content:t})}isAcpRawTransportEnabled(){return(this.config.adapterOptions??{}).raw_transport===!0}shouldDropAcpRawDisplayEvent(e,n){return this.sendCtrl.shouldDropAcpRawDisplayEvent(e,n)}sendAcpRawEventEnvelope(e,n,t){this.shouldDropAcpRawDisplayEvent(e,t.type)||this.aibotHandle.sendMsg({event_id:e,session_id:n,msg_type:1,content:this.buildAcpRawEventFallbackText(t),extra:{channel_data:{acp:{raw_event:t}},agent_api_origin:!0}})}buildAcpRawEventFallbackText(e){const n=String(e.type??"").trim();if(!n)return"[acp] event";switch(n){case"permission_request":return`Permission required: ${String(e.payload?.tool_title??e.payload?.tool_name??"permission request")}`;case"tool_use":return`[tool] ${String(e.payload?.tool_name??"tool")}`;case"tool_result":return"[tool result]";case"thinking":return"[thinking]";case"error":return`[error] ${String(e.payload?.message??"agent error")}`;case"result":return"[result]";default:return`[acp] ${n}`}}sendToolExecutionCard(e,n,t,o){this.sendCtrl.sendToolExecutionCard(e,n,t,o)}async handleAibotEvent(e){if(this.stopped){this.aibotHandle.sendEventAck({event_id:e.event_id,session_id:e.session_id,received_at:Date.now()}),this.aibotHandle.sendEventResult({event_id:e.event_id,status:"failed",msg:"agent shutting down",updated_at:Date.now()});return}this.logInboundConversation(e);const n=this.config.adapterType??"acp";if(this.allowlistGate&&e.sender_id&&!await this.allowlistGate.checkAccess(e.sender_id)){this.aibotHandle.sendEventAck({event_id:e.event_id,session_id:e.session_id,received_at:Date.now()}),this.aibotHandle.sendEventResult({event_id:e.event_id,status:"responded",code:"access_denied",msg:`sender ${e.sender_id} is not authorized`,updated_at:Date.now()});return}const t=Me(e.extra);if(t.error){this.aibotHandle.sendEventAck({event_id:e.event_id,session_id:e.session_id,received_at:Date.now()}),this.aibotHandle.sendEventResult({event_id:e.event_id,status:"failed",code:"connector_config_invalid",msg:t.error,updated_at:Date.now()});return}const o=t.patch,i=we(e);if(i){if(i.verb===p.exec){await this.handleSessionControlCommand(i,e);return}if(i.verb===p.listSessions){await this.handleListSessionsTextCommand(e);return}if(n==="claude"){await this.handleSessionControlCommand(i,e);return}if(n==="codex"&&i.verb===p.open){await this.handleCodexSessionControlOpen(i,e);return}if(n==="pi"&&i.verb===p.open){await this.handlePiSessionControlOpen(i,e);return}if(n==="pi"&&i.verb===p.restart){await this.handlePiSessionControlRestart(e);return}if((n==="openhuman"||n==="opencode")&&i.verb===p.open){await this.handleOpenHumanSessionControlOpen(i,e);return}if(n==="codewhale"&&i.verb===p.open){await this.handleCodeWhaleSessionControlOpen(i,e);return}if(this.aibotHandle.sendEventAck({event_id:e.event_id,session_id:e.session_id,received_at:Date.now()}),i.verb===p.open){const a=i.args.trim();if(!a){this.aibotHandle.sendEventResult({event_id:e.event_id,status:"failed",code:g.cwdRequired,msg:"cwd is required",updated_at:Date.now()});return}try{const d=x.resolve(a);if(!(await M(d)).isDirectory()){this.aibotHandle.sendEventResult({event_id:e.event_id,status:"failed",code:g.invalidCwd,msg:`Path is not a directory: ${d}`,updated_at:Date.now()});return}}catch(d){const c=String(d?.code??""),l=c==="ENOENT"?`Directory does not exist: ${x.resolve(a)}`:c==="EACCES"||c==="EPERM"?"Directory is not accessible":`Invalid path: ${a}`;this.aibotHandle.sendEventResult({event_id:e.event_id,status:"failed",code:g.invalidCwd,msg:l,updated_at:Date.now()});return}}if(i.verb===p.open){const a=this.bindingStore.get(e.session_id);if(a?.cwd)try{await M(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),n==="acp"&&i.verb===p.stop){const d=this.bindingStore.get(e.session_id)?.cwd??"";await this.pool.removeSlot(e.session_id).catch(()=>{}),this.sessionBindings.delete(e.session_id),this.aibotHandle.sendEventResult({event_id:e.event_id,status:"responded",msg:`Session worker stopped for ${d}`,updated_at:Date.now()});return}if(O(i,e,this.sessionControlCtx(e.session_id),{...this.sessionControlSenders(),sendEventAck:()=>{}}),i.verb===p.open&&(await this.deferredMgr.release(e.session_id,this.deferredCallbacks()),n==="agy")){const a=this.bindingStore.get(e.session_id)?.cwd??"";a&&this.aibotHandle.sendUpdateBindingCard({session_id:e.session_id,worker_status:"ready",cwd:a,meta:this.buildAgyToolbarMeta(e.session_id)})}return}if(e.mirror_mode==="record_only"){this.aibotHandle.sendEventAck({event_id:e.event_id,session_id:e.session_id,received_at:Date.now()}),this.aibotHandle.sendEventResult({event_id:e.event_id,status:"responded",updated_at:Date.now()});return}if(this.isStaleEvent(e)){this.aibotHandle.sendEventAck({event_id:e.event_id,session_id:e.session_id,received_at:Date.now()}),this.aibotHandle.sendEventResult({event_id:e.event_id,status:"failed",code:"event_stale",msg:"event is stale and will not be processed",updated_at:Date.now()});return}if(We.has(n)&&!this.bindingStore.get(e.session_id)?.cwd){const d=n;u.info(this.name,`[${d}] binding missing session_id=${e.session_id} event_id=${e.event_id}`),this.deferredMgr.defer(d,String(e.session_id??"").trim(),this.buildInboundEvent(e,o));const c=this.resolveBindingChannelKey(n);this.aibotHandle.sendEventAck({event_id:e.event_id,session_id:e.session_id,received_at:Date.now()}),this.aibotHandle.sendMsg({event_id:e.event_id,session_id:e.session_id,msg_type:1,content:"Session binding missing.",extra:{channel_data:{[c]:{sessionBinding:{status:"missing",reason:"binding_missing",error_code:g.bindingMissing}}}},quoted_message_id:e.msg_id});return}if((this.config.adapterType??"acp")==="acp"){const d=String(e.content??"").trim().match(/^\/(\S+)\s*(.*)/);if(d){const[,c,l]=d,m=this.pool.getSlot(e.session_id)?.adapter;if(m?.execCommand&&(m.getSupportedCommands?.()??[]).some(_=>_.name===c||_.name===`/${c}`)){this.aibotHandle.sendEventAck({event_id:e.event_id,session_id:e.session_id,received_at:Date.now()});try{const _=await m.execCommand(c,l.trim(),e.session_id);_.status==="options"&&_.data&&this.handleExecCommandOptions(e.session_id,c,_.data),this.aibotHandle.sendEventResult({event_id:e.event_id,status:_.status==="failed"?"failed":"responded",msg:_.message,updated_at:Date.now()})}catch(_){this.aibotHandle.sendEventResult({event_id:e.event_id,status:"failed",msg:_ instanceof Error?_.message:String(_),updated_at:Date.now()})}return}}}const r=this.buildInboundEvent(e,o);try{this.captureEventRuntimeConfig(r),await this.pool.deliverInboundEvent(r)}catch(a){if(this.aibotHandle.sendEventAck({event_id:e.event_id,session_id:e.session_id,received_at:Date.now()}),this.inflightEvents.delete(e.event_id),this.restartCount.delete(e.event_id),this.eventSessionIndex.delete(e.event_id),a instanceof Se)this.aibotHandle.sendEventResult({event_id:e.event_id,status:"failed",msg:a.message,updated_at:Date.now()});else throw a}}async handleCodexSessionControlOpen(e,n){const t=n.session_id,o=e.args.trim(),i=()=>this.aibotHandle.sendEventAck({event_id:n.event_id,session_id:t,received_at:Date.now()}),s=(r,a)=>this.aibotHandle.sendEventResult({event_id:n.event_id,status:r,...a,updated_at:Date.now()});if(!o){i(),s("failed",{msg:"Usage: /grix open <working-directory>",code:g.cwdRequired});return}try{const r=await this.resolveCwdForBinding(o),a=this.bindingStore.get(t);if(a?.cwd){let d=!1;try{if(await this.resolveCwdForBinding(a.cwd)===r){i(),s("responded",{msg:`Session already bound to ${a.cwd}`});return}}catch{d=!0,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,n){e.verb===p.open&&await this.bindSessionForPool(n.session_id,e.args.trim())}async replayDeferredEventsForSession(e){await this.deferredMgr.release(e,this.deferredCallbacks())}async handleSessionControlLocalActionForPool(e){if(String(e.action_type??"")!==S.sessionControl)return;const n=e.params??{};if(String(n.verb??"").trim().toLowerCase()!==p.open)return;const o=String(n.session_id??"").trim(),i=String(n.cwd??"").trim();!o||!i||await this.bindSessionForPool(o,i)}async handleCodexSessionControlLocalActionOpen(e){const n=e.params??{},t=String(n.session_id??"").trim(),o=String(n.cwd??"").trim(),i=String(n.agent_session_id??"").trim(),s=(r,a,d,c)=>{this.aibotHandle.sendLocalActionResult({action_id:e.action_id,status:r,...a!==void 0?{result:a}:{},...d?{error_code:d}:{},...c?{error_msg:c}:{}})};if(!t||!o){s("failed",void 0,g.cwdRequired,"session cwd is required");return}try{const r=await this.resolveCwdForBinding(o);this.ensureImportedAgentSession(i,r);const a=this.bindingStore.get(t);if(a?.cwd){let d=!1;try{const c=await this.resolveCwdForBinding(a.cwd);if(c===r){this.setResolvedAgentSessionId(t,i),s("ok",{outcome:"opened",binding:this.buildOpenedBindingResult(t,c)});return}}catch{d=!0,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 n=e.params??{},t=String(n.session_id??"").trim(),o=String(n.cwd??"").trim(),i=String(n.agent_session_id??"").trim(),s=(r,a,d,c)=>{this.aibotHandle.sendLocalActionResult({action_id:e.action_id,status:r,...a!==void 0?{result:a}:{},...d?{error_code:d}:{},...c?{error_msg:c}:{}})};if(!t||!o){s("failed",void 0,g.cwdRequired,"session cwd is required");return}try{const r=await this.resolveCwdForBinding(o);this.ensureImportedAgentSession(i,r);const a=this.bindingStore.get(t);if(a?.cwd){let d=!1;try{const c=await this.resolveCwdForBinding(a.cwd);if(c===r){this.setResolvedAgentSessionId(t,i),s("ok",{outcome:"opened",binding:this.buildOpenedBindingResult(t,c)});return}}catch{d=!0,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,n){const t=n.session_id,o=e.args.trim(),i=()=>this.aibotHandle.sendEventAck({event_id:n.event_id,session_id:t,received_at:Date.now()}),s=(r,a)=>this.aibotHandle.sendEventResult({event_id:n.event_id,status:r,...a,updated_at:Date.now()});if(!o){i(),s("failed",{msg:"Usage: /grix open <working-directory>",code:g.cwdRequired});return}try{const r=await this.resolveCwdForBinding(o),a=this.bindingStore.get(t);if(a?.cwd){let d=!1;try{if(await this.resolveCwdForBinding(a.cwd)===r){i(),s("responded",{msg:`Session already bound to ${a.cwd}`}),this.aibotHandle.sendMsg({event_id:n.event_id,session_id:t,msg_type:1,content:`\u2705 Session already bound to \`${a.cwd}\``,quoted_message_id:n.msg_id});return}}catch{d=!0,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:n.event_id,session_id:t,msg_type:1,content:`\u2705 Working directory bound: \`${r}\``,quoted_message_id:n.msg_id})}catch(r){i(),s("failed",{code:g.invalidCwd,msg:r instanceof Error?r.message:String(r)})}}async handlePiSessionControlRestart(e){const n=e.session_id,t=()=>this.aibotHandle.sendEventAck({event_id:e.event_id,session_id:n,received_at:Date.now()}),o=(r,a)=>this.aibotHandle.sendEventResult({event_id:e.event_id,status:r,...a,updated_at:Date.now()}),s=this.bindingStore.get(n)?.cwd??"";if(!s){t(),o("failed",{msg:"session binding was not found",code:g.bindingMissing});return}t(),await this.pool.removeSlot(n).catch(()=>{}),this.aibotHandle.sendUpdateBindingCard({session_id:n,worker_status:"ready",cwd:s}),o("responded",{msg:`Session worker restarted for ${s}`})}async handlePiSessionControlRestartLocalAction(e){const n=e.params??{},t=String(n.session_id??"").trim(),i=(t?this.bindingStore.get(t):void 0)?.cwd??"",s=(r,a,d,c)=>{this.aibotHandle.sendLocalActionResult({action_id:e.action_id,status:r,...a!==void 0?{result:a}:{},...d?{error_code:d}:{},...c?{error_msg:c}:{}})};if(!t){s("failed",void 0,"session_id_required","session_id is required for restart");return}if(!i){s("failed",void 0,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,n){const t=String(n??"").trim();if(!e||!t)return;const o=this.pool.getOrCreateSlot(e);if(!o||(o.startPromise&&await o.startPromise,!o.adapter))return;const i=o.adapter instanceof A?o.adapter:null;if(!i?.hasSessionBinding)return;const s=await this.resolveCwdForBinding(t);i.announceDeferredComposing(e),await i.bindSession(e,s),this.sessionBindings.set(e,s),this.sessionScanCache.invalidate(),i.replayDeferredEvents(e)}deferredCallbacks(){return{captureEventRuntimeConfig:e=>this.captureEventRuntimeConfig(e),deliverInboundEvent:e=>this.pool.deliverInboundEvent(e),sendEventResult:(e,n,t)=>this.sendEventResultWithCleanup(e,n,t),sendSessionComposing:(e,n,t)=>{const o={};n&&(o.ttl_ms=t?.ttlMs??3e4,t?.activity&&(o.activity=t.activity)),this.aibotHandle.sendSessionActivitySet({session_id:e,kind:"composing",active:n,...o})}}}async handleOpenHumanSessionControlOpen(e,n){const t=()=>this.aibotHandle.sendEventAck({event_id:n.event_id,session_id:n.session_id,received_at:Date.now()});try{await this.resolveCwdForBinding(e.args.trim()),await this.handleSessionControlForPool(e,n),O(e,n,this.sessionControlCtx(n.session_id),this.sessionControlSenders()),await this.deferredMgr.release(n.session_id,this.deferredCallbacks())}catch(o){t(),this.aibotHandle.sendEventResult({event_id:n.event_id,status:"failed",code:o?.cwdErrorCode,msg:o instanceof Error?o.message:String(o),updated_at:Date.now()})}}async handleCodeWhaleSessionControlOpen(e,n){const t=n.session_id,o=e.args.trim(),i=()=>this.aibotHandle.sendEventAck({event_id:n.event_id,session_id:t,received_at:Date.now()}),s=(r,a)=>this.aibotHandle.sendEventResult({event_id:n.event_id,status:r,...a,updated_at:Date.now()});if(!o){i(),s("failed",{msg:"Usage: /grix open <working-directory>",code:g.cwdRequired});return}try{const r=await this.resolveCwdForBinding(o),a=this.bindingStore.get(t);if(a?.cwd){if(await this.resolveCwdForBinding(a.cwd)!==r){i(),s("failed",{msg:`Session already bound to ${a.cwd}. Rebinding is not allowed.`,code: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 n=e.params??{},t=String(n.session_id??"").trim(),o=String(n.cwd??"").trim(),i=String(n.agent_session_id??"").trim(),s=(r,a,d,c)=>{this.aibotHandle.sendLocalActionResult({action_id:e.action_id,status:r,...a!==void 0?{result:a}:{},...d?{error_code:d}:{},...c?{error_msg:c}:{}})};if(!t||!o){s("failed",void 0,g.cwdRequired,"session cwd is required");return}try{const r=await this.resolveCwdForBinding(o);this.ensureImportedAgentSession(i,r);const a=this.bindingStore.get(t);if(a?.cwd){const d=await this.resolveCwdForBinding(a.cwd);if(d!==r){s("failed",void 0,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,n,t){const o=Array.isArray(t?.options)?t.options:[];if(o.length===0)return;const s=this.bindingStore.get(e)?.cwd??"",r={};if(n==="model"){r.available_models=o.map(d=>({id:d.id,displayName:d.label}));const a=o.find(d=>d.current);a&&(r.model_id=a.id)}else if(n==="mode"){r.available_modes=o.map(d=>({id:d.id,displayName:d.label}));const a=o.find(d=>d.current);a&&(r.mode_id=a.id)}else r[`${n}_options`]=o;this.aibotHandle.sendUpdateBindingCard({session_id:e,worker_status:"ready",cwd:s,meta:r})}buildAgyToolbarMeta(e){const n=this.bindingStore.get(e);if(!n)return;const t=te(this.config.agent.command),o=t.length>0?t[0].id:"";let i=n.modelId||this.globalConfigStore?.get(this.name)?.modelId||o;t.length>0&&!t.some(r=>r.id===i)&&(i=o);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,n){const t=e.params??{},o=String(t.model_id??t.modelId??"").trim();if(!n){this.aibotHandle.sendLocalActionResult({action_id:e.action_id,status:"failed",error_code:"session_id_required",error_msg:"session_id is required for set_model"});return}if(!o){this.aibotHandle.sendLocalActionResult({action_id:e.action_id,status:"failed",error_code:"set_model_invalid",error_msg:"model_id is required"});return}const i=this.bindingStore.get(n);if(!i?.cwd){this.aibotHandle.sendLocalActionResult({action_id:e.action_id,status:"failed",error_code:g.bindingMissing,error_msg:"session binding was not found"});return}i.modelId!==o&&(this.bindingStore.setModelId(n,o),this.globalConfigStore?.set(this.name,{modelId:o})),this.aibotHandle.sendUpdateBindingCard({session_id:n,worker_status:"ready",cwd:i.cwd,meta:this.buildAgyToolbarMeta(n)}),this.aibotHandle.sendLocalActionResult({action_id:e.action_id,status:"ok",result:{outcome:"model_set",model_id:o,binding:{cwd:i.cwd,model_id:o}}})}buildClaudeToolbarMeta(e){const n=this.bindingStore.get(e);if(!n)return;const t=D(),o=t.length>0?t[0].id:"";let i=n.modelId||this.globalConfigStore?.get(this.name)?.modelId||o;t.length>0&&!t.some(l=>l.id===i)&&(i=o);const s=this.normalizeClaudeModeId(n.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 T?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 n=e.tiers.find(i=>i.name==="five_hour"),t=e.tiers.find(i=>i.name==="weekly_limit"),o=i=>i||null;if(n||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 n&&(i.primary={usedPercent:n.usedPercent,windowMinutes:300,resetsAt:o(n.resetsAt)},s=n.usedPercent,r=300),t&&(i.secondary={usedPercent:t.usedPercent,windowMinutes:10080,resetsAt:o(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 n=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"),o=e.tiers.find(i=>i.name==="weekly_limit");if(t||o)return{...t?{fiveHour:{usedPercentage:t.usedPercent,resetsAt:n(t.resetsAt)}}:{},...o?{sevenDay:{usedPercentage:o.usedPercent,resetsAt:n(o.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?n(i.resetsAt):0},planName:e.planName,sampledAt:this.cachedProviderQuotaSampledAtMs??Date.now()}}return null}async resolveCwdForBinding(e){const n=String(e??"").trim();if(process.platform!=="win32"&&(/^[a-zA-Z]:[\\/]/.test(n)||/^\\\\/.test(n))){const i=new Error(`Specified path is not valid on this host: ${n}`);throw i.cwdErrorCode=g.invalidCwd,i}const t=x.resolve(n);let o;try{o=await M(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(!o.isDirectory()){const i=new Error("Specified path is not a directory.");throw i.cwdErrorCode=g.invalidCwd,i}try{return await G(t)}catch{return t}}getClaudeWorkerStatus(e){const n=this.pool.getSlot(e);return n?n.state==="starting"?"starting":n.state==="stopped"?"stopped":n.adapter.getStatus().busy?"busy":"ready":"stopped"}refreshClaudeWorkerStatusCard(e,n){const t=this.getClaudeWorkerStatus(e);return this.claudeWorkerStatus.set(e,t),this.aibotHandle.sendUpdateBindingCard({session_id:e,worker_status:t,cwd:n,meta:this.buildClaudeToolbarMeta(e)}),t}async ensureSlotStarted(e,n=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((o,i)=>setTimeout(()=>i(new Error(`ensureSlotStarted timeout (${n}ms) session=${e}`)),n))]),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,n){const t=String(e??"").trim(),o=String(n??"").trim();if(!t||!o)return;switch(this.config.adapterType??"acp"){case"claude":this.bindingStore.setClaudeSessionId(t,o);break;case"codex":this.bindingStore.setCodexThreadId(t,o);break;case"pi":this.bindingStore.setPiSessionPath(t,o);break;case"codewhale":this.bindingStore.setCodeWhaleThreadId(t,o);break;case"agy":this.bindingStore.setAgyConversationId(t,o);break;default:this.bindingStore.setAcpSessionId(t,o);break}this.sessionScanCache.invalidate()}normalizePathForCompare(e){const n=String(e??"").trim();if(!n)return"";const t=x.resolve(n);return process.platform==="win32"?t.toLowerCase():t}ensureImportedAgentSession(e,n){const t=String(e??"").trim();if(!t)return;const o=this.normalizePathForCompare(n);let i="";const s=this.config.adapterType??"acp";if(s==="codex"?i=this.sessionScanCache.get().find(d=>d.threadId===t)?.cwd??"":s==="claude"?i=this.sessionScanCache.get().find(d=>d.sessionId===t)?.cwd??"":s==="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&&o&&r!==o){const a=new Error(`agent session cwd mismatch: expected ${n}, got ${i}`);throw a.sessionControlErrorCode=g.invalidAgentSession,a}}buildOpenedBindingResult(e,n,t="ready"){const o=this.bindingStore.get(e),i=o?String(this.resolveAgentSessionId(o)??"").trim():"",s={aibotSessionId:e,providerKey:this.providerKeyForAdapter(),cwd:n,workerStatus:t};return i&&(s.bindingId=i,s.agentSessionId=i),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 n=this.config.adapterType??"acp",t=new Map;for(const a of this.pool.getAllSlots())t.set(a.sessionId,a);const o=Array.from(this.bindingStore.entries()),i=new Map;for(const[a,d]of o){const c=this.resolveAgentSessionId(d);if(c){const l=t.get(a),h=l?l.adapter.getStatus().busy?"busy":"ready":"inactive";i.set(c,{aibotSessionId:a,workerStatus:h})}}const s=[],r=new Set;if(n==="codex"){const a=this.sessionScanCache.get();for(const d of a){r.add(d.threadId);const c=i.get(d.threadId);d.title&&s.push(` Title: ${d.title}`),s.push(` Agent: ${d.threadId}`),c&&s.push(` AIBot: ${c.aibotSessionId}`),s.push(` CWD: ${d.cwd||"-"}`),s.push(` State: ${c?.workerStatus??(d.archived?"archived":"inactive")}`),s.push(` Created: ${new Date(d.createdAt).toISOString()}`),s.push(` Updated: ${new Date(d.updatedAt).toISOString()}`),s.push("---")}}else if(n==="claude"){const a=this.sessionScanCache.get();for(const d of a){r.add(d.sessionId);const c=i.get(d.sessionId);d.title&&s.push(` Title: ${d.title}`),s.push(` Agent: ${d.sessionId}`),c&&s.push(` AIBot: ${c.aibotSessionId}`),s.push(` CWD: ${d.cwd||"-"}`),s.push(` State: ${c?.workerStatus??"inactive"}`),s.push(` Updated: ${new Date(d.updatedAt).toISOString()}`),s.push("---")}}else if(n==="acp"){const a=this.sessionScanCache.get();for(const d of a){r.add(d.sessionId);const c=i.get(d.sessionId);d.title&&s.push(` Title: ${d.title}`),s.push(` Agent: ${d.sessionId}`),c&&s.push(` AIBot: ${c.aibotSessionId}`),s.push(` CWD: ${d.cwd||"-"}`),s.push(` State: ${c?.workerStatus??"inactive"}`),s.push(` Updated: ${new Date(d.updatedAt).toISOString()}`),s.push("---")}}for(const[a,d]of o){const c=this.resolveAgentSessionId(d);if(c&&r.has(c))continue;const l=t.get(a),h=l?l.adapter.getStatus().busy?"busy":"ready":"closed";s.push(` Title: ${c?c.slice(0,8)+"\u2026":a.slice(0,8)+"\u2026"}`),s.push(` AIBot: ${a}`),c&&s.push(` Agent: ${c}`),s.push(` CWD: ${d.cwd??"-"}`),s.push(` State: ${h}`),s.push(` Updated: ${new Date(d.updatedAt).toISOString()}`),s.push("---")}if(s.length===0){this.aibotHandle.sendEventResult({event_id:e.event_id,status:"responded",msg:"No sessions found.",updated_at:Date.now()});return}this.aibotHandle.sendEventResult({event_id:e.event_id,status:"responded",msg:`Sessions (${s.filter(a=>a==="---").length}):
|
|
14
14
|
${s.join(`
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{EventEmitter as k}from"node:events";import{randomUUID as v}from"node:crypto";import p from"node:os";import m from"ws";import{log as
|
|
2
|
-
`}),this.sendPacket("client_stream_chunk",e)}sendMsg(e){this.sendPacket("send_msg",e)||r.warn("aibot",`send_msg NOT sent (ws not open) event=${e.event_id??""} session=${e.session_id??""} ws=${this.ws?`state=${this.ws.readyState}`:"null"}`)}editMsg(e){this.sendPacket("edit_msg",e)}sendEventResult(e){if(!this.ws||this.ws.readyState!==m.OPEN){this.sendPacket("event_result",e);return}this.sendEventResultReliable(e)}sendLocalActionResult(e){this.sendPacket("local_action_result",e)}sendEventStopAck(e){this.sendPacket("event_stop_ack",e)}sendEventStopResult(e){this.sendPacket("event_stop_result",e)}sendSessionActivitySet(e){this.sendPacket("session_activity_set",e)}sendCodexEvent(e){this.sendPacket("codex_event",e)}sendUpdateBindingCard(e){this.sendPacket("update_binding_card",e)}sendSkillsUpdate(e){this.sendPacket("agent_skills_update",e)}sendPing(){this.sendPacket("ping",{})}sendEventState(e){this.sendPacket("event_state",e)}sendEventCancelResult(e){this.sendPacket("event_cancel_result",e)}sendQueueClearResult(e){this.sendPacket("queue_clear_result",e)}sendQueueSnapshot(e){this.sendPacket("queue_snapshot",e)}agentInvoke(e,t,s=15e3){return new Promise((o,n)=>{const h=v(),c=Math.max(1e3,Math.min(s,6e4)),a=setTimeout(()=>{this.pendingInvokes.delete(h),n(new Error(`agent_invoke timeout: ${e}`))},c);this.pendingInvokes.set(h,{resolve:o,reject:n,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,s){return new Promise((o,n)=>{const h=++this.seq,c=setTimeout(()=>{this.pendingRequests.delete(h),n(new Error(`request timeout: ${e} (expected ${s.expected.join("/")})`))},s.timeoutMs);this.pendingRequests.set(h,{expected:s.expected,resolve:o,reject:n,timer:c}),this.sendPacket(e,t,h)||(this.pendingRequests.delete(h),clearTimeout(c),n(new Error(`send failed: ${e}`)))})}async sendStreamChunkRequest(e,t=2e4){return this.request("client_stream_chunk",e,{expected:["send_ack","send_nack","error"],timeoutMs:t})}async sendText(e,t=2e4){return this.request("send_msg",{msg_type:1,...e},{expected:["send_ack","send_nack","error"],timeoutMs:t})}async sendMedia(e,t=2e4){return this.request("send_msg",{...e,msg_type:2},{expected:["send_ack","send_nack","error"],timeoutMs:t})}async editMessage(e,t=2e4){return this.request("edit_msg",e,{expected:["send_ack","send_nack","error"],timeoutMs:t})}async deleteMessage(e,t,s=2e4){return this.request("delete_msg",{session_id:e,msg_id:t},{expected:["send_ack","send_nack","error"],timeoutMs:s})}async sendEventResultRequest(e,t=5e3){return this.request("event_result",e,{expected:["send_ack","send_nack","error"],timeoutMs:t})}disconnect(){r.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)}async attemptReconnect(){if(!this.reconnecting)for(this.reconnecting=!0,r.info("aibot",`attemptReconnect start agent=${this.config.clientType}:${this.config.agentId} fromAttempts=${this.reconnectAttempts}`),this.emit("disconnected");this.reconnecting;){const e=Math.min(1e3*2**this.reconnectAttempts,3e4),t=Math.floor(e*.2*Math.random());if(this.reconnectAttempts++,await new Promise(s=>setTimeout(s,e+t)),!this.reconnecting)return;try{const s=await this.connect(),o=this.reconnectAttempts;this.reconnectAttempts=0,this.reconnecting=!1,r.info("aibot",`reconnect succeeded agent=${this.config.clientType}:${this.config.agentId} attempt=${o}`),this.emit("auth",s);return}catch(s){r.warn("aibot",`reconnect failed agent=${this.config.clientType}:${this.config.agentId} attempt=${this.reconnectAttempts} err=${s instanceof Error?s.message:s}`)}}}sendPacket(e,t,s){if(this.ws&&this.ws.readyState===m.OPEN){const o=this.ws.bufferedAmount>f.BACKPRESSURE_THRESHOLD;if(!o||!f.DROPPABLE_COMMANDS.has(e)){if(o&&f.DROPPABLE_COMMANDS.has(e))return!1;const n=s??++this.seq;if(e==="client_stream_chunk"&&t&&typeof t=="object"){const c=t.event_id;if(c&&(this.seqEventMap.set(n,c),this.seqEventMap.size>200)){const a=this.seqEventMap.keys().next().value;a!==void 0&&this.seqEventMap.delete(a)}}const h={cmd:e,seq:n,payload:t};this.packetLog?.logOutboundPacket(e,n,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?r.warn("aibot",`event_result ws send callback failed event=${i.event_id??""} status=${i.status??""} seq=${n} readyState=${c} bufferedAmount=${a} err=${d.message}`):r.info("aibot",`event_result ws send callback ok event=${i.event_id??""} status=${i.status??""} seq=${n} readyState=${c} bufferedAmount=${a}`)}else if(e==="client_stream_chunk"){const i=t;d?r.warn("aibot",`stream_chunk ws send failed event=${i.event_id??""} session=${i.session_id??""} seq=${n} chunk_seq=${i.chunk_seq??""} is_finish=${i.is_finish??""} readyState=${c} bufferedAmount=${a} err=${d.message}`):r.info("aibot",`stream_chunk ws send ok event=${i.event_id??""} session=${i.session_id??""} seq=${n} chunk_seq=${i.chunk_seq??""} is_finish=${i.is_finish??""} readyState=${c} bufferedAmount=${a}`)}else if(e==="event_ack"){const i=t;d?r.warn("aibot",`event_ack ws send failed event=${i.event_id??""} seq=${n} readyState=${c} bufferedAmount=${a} err=${d.message}`):r.info("aibot",`event_ack ws send ok event=${i.event_id??""} seq=${n} readyState=${c} bufferedAmount=${a}`)}else if(e==="send_msg"){const i=t;d?r.warn("aibot",`send_msg ws send failed event=${i.event_id??""} session=${i.session_id??""} seq=${n} readyState=${c} bufferedAmount=${a} err=${d.message}`):r.info("aibot",`send_msg ws send ok event=${i.event_id??""} session=${i.session_id??""} seq=${n} readyState=${c} bufferedAmount=${a}`)}else if(d){const i=t;r.warn("aibot",`${e} ws send failed seq=${n} 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,s??0,t,"dropped"),!1;if(s!==void 0)return this.packetLog?.logOutboundPacket(e,s,t,"dropped"),!1;if(this.outboundBuffer.length>=f.MAX_OUTBOUND_BUFFER_SIZE&&(this.outboundBuffer=this.outboundBuffer.filter(o=>f.BUFFER_OVERFLOW_RETAIN_COMMANDS.has(o.cmd)),this.outboundBuffer.length>=f.MAX_OUTBOUND_BUFFER_SIZE&&this.outboundBuffer.shift()),this.outboundBuffer.push({cmd:e,payload:t}),this.packetLog?.logOutboundPacket(e,s??0,t,"buffered"),e==="client_stream_chunk"){const o=t;r.info("aibot",`stream_chunk buffered (ws not open) event=${o.event_id??""} session=${o.session_id??""} chunk_seq=${o.chunk_seq??""} is_finish=${o.is_finish??""} ws=${this.ws?`state=${this.ws.readyState}`:"null"}`)}return!1}async sendEventResultReliable(e){const t=this.ackPolicy?.max_retries??3,s=this.ackPolicy?.push_ack_timeout_ms??5e3,o=750;for(let n=1;n<=t;n++){const h=this.ws?.readyState??-1,c=this.ws?.bufferedAmount??0;r.info("aibot",`event_result send attempt event=${e.event_id} status=${e.status} attempt=${n}/${t} readyState=${h} bufferedAmount=${c}`);try{const a=await this.sendEventResultRequest(e,s);if(a.cmd==="send_ack"){const i=a.payload;r.info("aibot",`event_result ack event=${e.event_id} status=${e.status} attempt=${n}/${t} ack_event=${i.event_id??""} ack_status=${i.status??""}`);return}const d=a.payload;if(r.warn("aibot",`event_result rejected event=${e.event_id} status=${e.status} attempt=${n}/${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){r.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(r.warn("aibot",`event_result attempt failed event=${e.event_id} status=${e.status} attempt=${n}/${t} err=${d}`),n===t){this.emitClientError(new Error(`event_result ack failed after ${t} attempts: event=${e.event_id} status=${e.status}`));return}await new Promise(i=>setTimeout(i,o*n))}}}purgeBufferedStreamChunks(e){const t=this.outboundBuffer.length;this.outboundBuffer=this.outboundBuffer.filter(s=>s.cmd!=="client_stream_chunk"?!0:s.payload?.event_id!==e),this.outboundBuffer.length<t&&r.info("aibot",`purged ${t-this.outboundBuffer.length} buffered stream chunks for event=${e}`)}emitClientError(e){if(this.listenerCount("error")===0){r.warn("aibot",`Client error (no listeners): ${e.message}`);return}this.emit("error",e)}flushOutboundBuffer(){if(this.outboundBuffer.length===0||!this.ws||this.ws.readyState!==m.OPEN)return;const e=this.outboundBuffer;this.outboundBuffer=[];for(const{cmd:t,payload:s}of e){const o=++this.seq;if(t==="client_stream_chunk"&&s&&typeof s=="object"){const h=s.event_id;h&&this.seqEventMap.set(o,h)}const n={cmd:t,seq:o,payload:s};try{this.ws.send(JSON.stringify(n))}catch{break}}if(this.seqEventMap.size>200){const t=[...this.seqEventMap.entries()].sort((s,o)=>s[0]-o[0]);this.seqEventMap.clear();for(const[s,o]of t.slice(-100))this.seqEventMap.set(s,o)}}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{}this.ws=null}}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 k}from"node:events";import{randomUUID as v}from"node:crypto";import p from"node:os";import m from"ws";import{log as o}from"../log/index.js";import{getMachineName as $}from"../util/index.js";import{detectTailnetIPv4 as b,ensureServerAndGetPort as w,getFileServerHttpsPort as E}from"../files/file-serve.js";function q(g){return g.replace(/(?<=[\[:,\[]\s*)(\d{16,})(?=\s*[,}\]\n])/g,'"$1"')}function S(g){const e=[...g??["stream_chunk","local_action_v1","agent_invoke"]];return e.includes("agent_invoke")||e.push("agent_invoke"),e.includes("event_result_ack")||e.push("event_result_ack"),e}const P="aibot-agent-api-v1",y=1;class f extends k{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;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:S(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 b(),e!==void 0)try{t=await w(e);const s=E();s>0&&(n=s)}catch(s){o.warn("aibot",`file server pre-start failed: ${s}`)}}catch(s){o.warn("aibot",`tailnet detect failed: ${s}`)}})();return new Promise((s,h)=>{const c=new m(this.config.url);this.ws=c;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,o.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)):h(new Error(`Auth failed: code=${l.code} msg=${l.msg}`))},reject:u=>{clearTimeout(a),h(u)},timer:r}),c.on("open",async()=>{await i;const u={agent_id:this.config.agentId,api_key:this.config.apiKey,client_type:this.config.clientType,protocol_version:P,contract_version:y,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:$(),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)}),c.on("message",u=>{if(this.ws!==c)return;let l;try{l=JSON.parse(q(u.toString()))}catch{return}try{this.handlePacket(l)}catch(_){this.emitClientError(new Error(`handlePacket error: ${_}`))}}),c.on("close",(u,l)=>{if(this.ws!==c)return;this.connected=!1,this.stopHeartbeat(),this.rejectAllPendingRequests("websocket closed"),this.emit("close",u,l.toString());const _=u!==1e3&&this.everConnected;o.info("aibot",`ws closed agent=${this.config.clientType}:${this.config.agentId} code=${u} reason=${l.toString()||"<none>"} everConnected=${this.everConnected} reconnecting=${this.reconnecting} willReconnect=${_}`),_&&this.attemptReconnect()}),c.on("error",u=>{this.ws===c&&(this.emitClientError(u instanceof Error?u:new Error(String(u))),this.connected||h(u))})})}handlePacket(e){if(this.packetLog?.logInboundPacket(e.cmd,e.seq,e.payload),e.seq>0&&this.pendingRequests.has(e.seq)){const t=this.pendingRequests.get(e.seq);this.pendingRequests.delete(e.seq),clearTimeout(t.timer),t.expected.includes(e.cmd)?t.resolve(e):t.reject(new Error(`unexpected response: got ${e.cmd}, expected ${t.expected.join("/")}`));return}switch(e.cmd){case"auth_ack":break;case"ping":{this.sendPacket("pong",e.payload??{});break}case"event_msg":{this.emit("event",e.payload);break}case"local_action":{this.emit("localAction",e.payload);break}case"event_stop":{this.emit("stop",e.payload);break}case"event_revoke":{this.emit("revoke",e.payload);break}case"event_edit":{this.emit("edit",e.payload);break}case"event_cancel":{this.emit("eventCancel",e.payload);break}case"queue_clear":{this.emit("queueClear",e.payload);break}case"queue_snapshot_query":{this.emit("queueSnapshotQuery",e.payload);break}case"control_share_set":{this.emit("shareSet",e.payload);break}case"kicked":{if(this.emit("kicked",e.payload),this.connected=!1,this.stopHeartbeat(),this.rejectAllPendingRequests("kicked"),this.outboundBuffer.length=0,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),o.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)||o.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&&(o.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)||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((i,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:i,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((i,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:i,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(i=>setTimeout(i,t+n)),!this.reconnecting)return;try{const i=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",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(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 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 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 r=t;d?o.warn("aibot",`event_result ws send callback failed event=${r.event_id??""} status=${r.status??""} seq=${s} readyState=${c} bufferedAmount=${a} err=${d.message}`):o.info("aibot",`event_result ws send callback ok event=${r.event_id??""} status=${r.status??""} seq=${s} readyState=${c} bufferedAmount=${a}`)}else if(e==="client_stream_chunk"){const r=t;d?o.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=${c} bufferedAmount=${a} err=${d.message}`):o.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??""} readyState=${c} bufferedAmount=${a}`)}else if(e==="event_ack"){const r=t;d?o.warn("aibot",`event_ack ws send failed event=${r.event_id??""} seq=${s} readyState=${c} bufferedAmount=${a} err=${d.message}`):o.info("aibot",`event_ack ws send ok event=${r.event_id??""} seq=${s} readyState=${c} bufferedAmount=${a}`)}else if(e==="send_msg"){const r=t;d?o.warn("aibot",`send_msg ws send failed event=${r.event_id??""} session=${r.session_id??""} seq=${s} readyState=${c} bufferedAmount=${a} err=${d.message}`):o.info("aibot",`send_msg ws send ok event=${r.event_id??""} session=${r.session_id??""} seq=${s} readyState=${c} bufferedAmount=${a}`)}else if(d){const r=t;o.warn("aibot",`${e} ws send failed seq=${s} session=${r.session_id??""} event=${r.event_id??""} client_msg_id=${r.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(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;o.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,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 r=a.payload;o.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(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(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&&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 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
|
-
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 t of c){if(!p&&t.name.startsWith("."))continue;const i=l(a,t.name),e={id:i,name:t.name,is_directory:t.isDirectory()};try{if(t.isDirectory()){const o=await m(i);e.modified_at=o.mtime.toISOString()}else{const o=await m(i);e.size=o.size,e.modified_at=o.mtime.toISOString(),e.mime_type=n(t.name)}}catch{}s.push(e)}return s.sort((t,i)=>t.is_directory!==i.is_directory?t.is_directory?-1:1:t.name.localeCompare(i.name)),s}export{f as listFiles,n as resolveMimeType};
|
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 t}from"node:path";import{homedir as m}from"node:os";const i=t(m(),".grix"),s={base:i,config:t(i,"config"),log:t(i,"log"),data:t(i,"data")};function S(){for(const o of Object.values(s))f(o)||l(o,{recursive:!0})}let a=null;function $(){const o=new Date().toISOString().slice(0,10),r=t(s.log,`grix-acp-${o}.log`);a=g(r,{flags:"a"})}function c(){return new Date().toISOString().slice(11,19)}const u={info(o,r,...n){const e=`${c()} [${o}] ${r}${n.length?" "+n.map(String).join(" "):""}`;console.log(e),a?.write(e+`
|
|
2
|
+
`)},error(o,r,...n){const e=`${c()} [${o}] ERROR ${r}${n.length?" "+n.map(String).join(" "):""}`;console.error(e),a?.write(e+`
|
|
3
3
|
`)}};export{s as GRIX_PATHS,S as ensureGrixDirs,$ as initLogger,u as log};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
function l(t
|
|
1
|
+
import{readFileSync as f,writeFileSync as d,unlinkSync as u}from"node:fs";import{join as S}from"node:path";function l(e,t){return`${e}#shared:${t}`}function o(e,t){if(!e)return e;const r=e.lastIndexOf("."),i=Math.max(e.lastIndexOf("/"),e.lastIndexOf("\\"));return r>i?`${e.slice(0,r)}.shared.${t}${e.slice(r)}`:`${e}.shared.${t}`}function y(e,t){return{...e,name:l(e.name,t),aibot:{...e.aibot,sharedOwnerId:t},eventResultsPath:o(e.eventResultsPath,t),bindingsPath:o(e.bindingsPath,t),activeEventStorePath:o(e.activeEventStorePath,t),logDir:e.logDir?`${e.logDir}/shared-${t}`:void 0,allowlistPath:void 0,connectMaxRetries:e.connectMaxRetries??3}}function P(e,t,r){const i=new Set(r.map(n=>String(n).trim()).filter(n=>n.length>0)),s=[...t],a=`${e}#shared:`,c=[...i].filter(n=>!s.includes(l(e,n))),h=s.filter(n=>n.startsWith(a)).map(n=>({key:n,sharedOwnerId:n.slice(a.length)})).filter(n=>!i.has(n.sharedOwnerId));return{toAdd:c,toRemove:h}}class ${dataDir;constructor(t){this.dataDir=t}filePath(t){const r=t.replace(/[^a-zA-Z0-9_-]/g,"_");return S(this.dataDir,`shared-owners-${r}.json`)}load(t){try{const r=f(this.filePath(t),"utf-8"),i=JSON.parse(r);if(Array.isArray(i))return i.filter(s=>typeof s=="string"&&s.length>0)}catch{}return[]}save(t,r){const i=[...new Set(r.filter(s=>s.length>0))];if(i.length===0){this.delete(t);return}try{d(this.filePath(t),JSON.stringify(i),"utf-8")}catch{}}remove(t,r){const i=this.load(t).filter(s=>s!==r);i.length>0?this.save(t,i):this.delete(t)}delete(t){try{u(this.filePath(t))}catch{}}}export{$ as SharedOwnersCache,y as buildSharedInstanceConfig,P as diffSharedOwners,l as sharedInstanceKey,o as suffixSharedPath};
|
package/dist/manager.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{readFileSync as Q,readdirSync as F,writeFileSync as B}from"node:fs";import{join as x}from"node:path";import{AgentInstance as A}from"./bridge/bridge.js";import{buildSharedInstanceConfig as L,diffSharedOwners as R,sharedInstanceKey as z}from"./manager-share-config.js";import{GRIX_PATHS as S,log as d}from"./core/log/index.js";import{resolveClientVersion as G}from"./core/util/client-version.js";import{UpgradeChecker as K}from"./core/upgrade/upgrade-checker.js";import{AgentGlobalConfigStore as W}from"./core/persistence/agent-global-config-store.js";import{scanSkills as V,dedupeSkills as J}from"./adapter/claude/skill-scanner.js";import{scanDefaultSkills as X,logDefaultSkillsCheck as Y,cleanupProjectedSkills as D}from"./default-skills/index.js";import{resolveCopilotCommand as q}from"./core/runtime/copilot-resolve.js";import{getCliVersion as Z,resolveCliPath as ee}from"./core/util/cli-probe.js";import{AgentInstaller as te}from"./core/installer/installer.js";import{reportInstallFailure as ne}from"./core/observability/sentry.js";const ae=8e3;function oe(){const n=q();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 se(){return Promise.all(oe().map(async n=>{const e=await ee(n.command);if(!e)return{client_type:n.clientType,command:n.command,installed:!1,path:null,version:null};const t=await Z(e,n.versionArgs??["--version"]);return{client_type:n.clientType,command:n.command,installed:!0,path:e,version:t.version,error:t.error}}))}function re(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=q();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 ie(n){const e=String(n??"").trim().toLowerCase().replace(/[^a-z0-9._-]+/g,"-").replace(/-+/g,"-").replace(/^-|-$/g,"")||"default";return x(S.data,`session-bindings-${e}.json`)}function ce(n){const e=String(n??"").trim().toLowerCase().replace(/[^a-z0-9._-]+/g,"-").replace(/-+/g,"-").replace(/^-|-$/g,"")||"default";return x(S.data,`active-events-${e}.json`)}function le(...n){const e=[],t=new Set;for(const o of n)for(const a of o??[]){const s=String(a??"").trim(),r=s.toLowerCase();!s||t.has(r)||(t.add(r),e.push(s))}return e.length>0?e:void 0}function de(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}},o=t[n]??t.acp;return{maxConcurrent:e?.max_concurrent??o.maxConcurrent??1,maxQueued:e?.max_queued??o.maxQueued??3,queueTimeoutMs:e?.queue_timeout_ms??o.queueTimeoutMs??0,cancelableQueued:!0,cancelableRunning:!0}}function P(n){const e=G(),t=String(n.client_type??"").trim().toLowerCase(),o=re(t),a=String(n.ws_url??"").trim(),s="get_session_usage",r="get_rate_limits",c="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 l=o.adapterType,u=l==="acp",f=t==="qwen",m={...o.options??{}},i=l==="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",s,r]}:null,p=l==="claude"?{localActions:["session_control","set_mode","set_model","claude_interaction_reply","exec_approve","exec_reject","file_list","create_folder","thread_compact",s,r]}:null,g=f?{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",s],adapterHint:"qwen/base"}:null,_=l==="pi"?{adapterHint:"pi/base",capabilities:["local_action_v1"],localActions:["session_control","set_model","get_context","file_list","create_folder",s]}:null,h=l==="openhuman"?{adapterHint:"openhuman/base",capabilities:["local_action_v1"],localActions:["session_control","set_model","file_list","create_folder",s]}:null,w=l==="cursor"?{adapterHint:"cursor/base",capabilities:["stream_chunk","local_action_v1"],localActions:["session_control","set_model","set_mode","get_context","file_list","create_folder",s,r]}:null,y=l==="codewhale"?{capabilities:["stream_chunk","local_action_v1"],localActions:["session_control","set_model","file_list","create_folder",s]}:null,k=l==="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",s]}:null,T=l==="agy"?{adapterHint:"agy/base",capabilities:["stream_chunk","local_action_v1"],localActions:["session_control","set_model","file_list","create_folder",s]}:null,E=u&&!f?{localActions:["exec_approve","exec_reject","permission_approve","permission_reject","session_control","set_model","set_mode","file_list","create_folder",s]}: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",s,r]}:null,O=l==="codex"||l==="claude"||t==="gemini"?["session_control","set_model","set_mode"]:void 0,N=[s,r];u&&m.raw_transport===void 0&&(m.raw_transport=t==="gemini");const U=`${t}/base`,$=l==="claude"?"claude":l==="codex"?"codex":l==="pi"?"pi":t==="kiro"?"kiro":"gemini";let b;try{const M=$==="kiro"?void 0:process.cwd();b=V({mode:$,projectDir:M})??void 0,b&&b.length===0&&(b=void 0)}catch{}const I=X();if(I.length>0){const v=J([...b??[],...I]),j=v.filter(C=>C.source==="connector").map(C=>C.name);j.length>0&&d.info("manager",`[${n.name}] injecting connector skills: [${j.join(", ")}]`),b=v.length>0?v:void 0}return{name:n.name,adapterType:l,aibot:{url:a,agentId:n.agent_id,apiKey:n.api_key,clientType:t,clientVersion:e,adapterHint:o.adapterHint??g?.adapterHint??_?.adapterHint??h?.adapterHint??w?.adapterHint??k?.adapterHint??T?.adapterHint??U,capabilities:i?.capabilities??y?.capabilities??_?.capabilities??h?.capabilities??w?.capabilities??k?.capabilities??T?.capabilities??g?.capabilities??["stream_chunk","local_action_v1","connector_upgrade"],localActions:le(i?.localActions??y?.localActions??p?.localActions??_?.localActions??h?.localActions??w?.localActions??k?.localActions??T?.localActions??g?.localActions??H?.localActions??E?.localActions??["exec_approve","exec_reject"],O,N,["connector_rollback","connector_upgrade_push",c]),skills:b},agent:{command:o.command,args:[...o.args??[],...n.args??[]],env:n.env},adapterOptions:m,acpAuthMethod:m.auth_method,acpInitialMode:m.initial_mode,acpMcpTools:m.acp_mcp_tools,promptTimeoutMs:n.prompt_timeout_ms,bindingsPath:ie(n.name),activeEventStorePath:ce(n.name),...o.enableSessionBinding||u?{enableSessionBinding:!0}:{},...o.autoInjectArgs?{autoInjectArgs:o.autoInjectArgs}:{},poolMaxSize:n.pool?.maxSize,poolIdleTimeoutMs:n.pool?.idleTimeoutMs,eventQueue:de(l,n.event_queue),logDir:S.log,providerBaseUrl:n.provider_base_url?.trim()||void 0,providerApiKey:n.provider_api_key?.trim()||void 0}}function ue(){const n=process.env.GRIX_AGENT_STARTUP_WAIT_MS,e=Number(n);return Number.isFinite(e)&&e>=500?Math.floor(e):ae}class $e{instances=[];configMap=new Map;sharedInstances=new Map;shareSyncChains=new Map;stopping=!1;upgradeChecker=null;globalConfigStore;configDir=S.config;installer=new te;async start(e){const t=e??S.config;this.configDir=t,d.info("manager",`Loading configs from ${t}`),Y(),D(),this.globalConfigStore=new W(x(S.data,"agent-global-configs.json")),this.globalConfigStore.load();const o=F(t).filter(c=>c.endsWith(".json")).sort();if(o.length===0)throw new Error(`No config files found in ${t}`);const a=[];let s=0;for(const c of o)try{const l=Q(x(t,c),"utf-8"),u=JSON.parse(l);if(Array.isArray(u.agents)){if(u.agents.length===0){d.error("manager",`No agents array found in ${c}`),s++;continue}for(const f of u.agents)try{const m=P(f);a.push({config:m,file:c}),d.info("manager",`Loaded ${m.name} (${m.adapterType??"acp"}) from ${c}`)}catch(m){const i=typeof f?.name=="string"?f.name:"<unknown>";d.error("manager",`Invalid agent config in ${c} (name=${i}): ${m}`),s++}}else d.error("manager",`Unrecognized config format in ${c}`)}catch(l){d.error("manager",`Failed to load ${c}: ${l}`)}let r=0;if(a.length>0){const c=ue();d.info("manager",`Starting ${a.length} agent(s), startup wait=${c}ms`);const l=()=>this.upgradeChecker?.triggerCheck(),u=i=>{this.instances=this.instances.filter(p=>p!==i)},f=a.map(({config:i})=>{const p=new A(i,this.globalConfigStore);return p.setUpgradeTrigger(l),p.setShareSetHandler(g=>this.onShareSet(i,g)),this.instances.push(p),this.configMap.set(i.name,i),{config:i,instance:p,startPromise:p.start()}}),m=await Promise.all(f.map(async i=>{const p=await new Promise(g=>{let _=!1;const h=setTimeout(()=>{_||(_=!0,g({kind:"timeout"}))},c);i.startPromise.then(()=>{_||(_=!0,clearTimeout(h),g({kind:"started"}))}).catch(w=>{_||(_=!0,clearTimeout(h),g({kind:"failed",error:w}))})});return{task:i,outcome:p}}));for(const{task:i,outcome:p}of m)if(p.kind!=="started"){if(p.kind==="failed"){u(i.instance),d.error("manager",`Failed to start ${i.config.name}: ${p.error}`);continue}r++,d.warn("manager",`Startup pending for ${i.config.name}, continue retrying in background`),i.startPromise.then(()=>{d.info("manager",`Delayed start succeeded: ${i.config.name}`)}).catch(g=>{u(i.instance),d.error("manager",`Delayed start failed: ${i.config.name}: ${g}`)})}if(this.instances.length>0){const i=Math.max(0,this.instances.length-r);d.info("manager",`${i}/${a.length} agent(s) running now`)}r>0&&d.warn("manager",`${r} agent(s) still connecting in background`)}if(this.instances.length===0&&a.length>0)throw new Error("All agent configurations failed to start");a.length>0&&(this.upgradeChecker=new K(a.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){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:o,toRemove:a}=R(e.name,this.sharedInstances.keys(),t);for(const s of o){if(this.stopping)break;const r=z(e.name,s);try{const c=L(e,s),l=new A(c,this.globalConfigStore);this.sharedInstances.set(r,l),await l.start(),d.info("manager",`shared instance started: ${r}`)}catch(c){this.sharedInstances.delete(r),d.error("manager",`start shared instance failed ${r}: ${c}`)}}for(const{key:s}of a){const r=this.sharedInstances.get(s);r&&(this.sharedInstances.delete(s),r.stop().catch(c=>d.error("manager",`stop shared instance failed ${s}: ${c}`)),d.info("manager",`shared instance stopped: ${s}`))}}getAgentsStatus(){return this.instances.map(e=>e.getStatus())}async addAgent(e){const t=P(e);if(this.instances.some(a=>a.name===t.name))throw new Error(`Agent "${t.name}" already exists`);const o=new A(t,this.globalConfigStore);o.setUpgradeTrigger(()=>this.upgradeChecker?.triggerCheck()),o.setShareSetHandler(a=>this.onShareSet(t,a)),await o.start(),this.instances.push(o),this.configMap.set(t.name,t),this.persistAgentsConfig(),d.info("manager",`Added agent: ${t.name}`)}async removeAgent(e){const t=this.instances.findIndex(r=>r.name===e);if(t===-1)throw Object.assign(new Error(`Agent "${e}" not found`),{code:"NOT_FOUND"});const o=this.instances[t];this.instances.splice(t,1),this.configMap.delete(e);const a=this.shareSyncChains.get(e);this.shareSyncChains.delete(e),a&&await a.catch(()=>{});const s=`${e}#shared:`;for(const[r,c]of[...this.sharedInstances.entries()])r.startsWith(s)&&(this.sharedInstances.delete(r),c.stop().catch(l=>d.error("manager",`stop shared instance failed ${r}: ${l}`)));await o.stop(),this.persistAgentsConfig(),d.info("manager",`Removed agent: ${e}`)}persistAgentsConfig(){const e=x(this.configDir,"agents.json");try{const t=[];for(const[,a]of this.configMap)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});B(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.instances.findIndex(r=>r.name===e);if(t===-1)throw Object.assign(new Error(`Agent "${e}" not found`),{code:"NOT_FOUND"});const
|
|
1
|
+
import{readFileSync as Q,readdirSync as F,writeFileSync as B}from"node:fs";import{join as x}from"node:path";import{AgentInstance as A}from"./bridge/bridge.js";import{buildSharedInstanceConfig as L,diffSharedOwners as R,sharedInstanceKey as z,SharedOwnersCache as G}from"./manager-share-config.js";import{GRIX_PATHS as S,log as d}from"./core/log/index.js";import{resolveClientVersion as K}from"./core/util/client-version.js";import{UpgradeChecker as W}from"./core/upgrade/upgrade-checker.js";import{AgentGlobalConfigStore as V}from"./core/persistence/agent-global-config-store.js";import{scanSkills as J,dedupeSkills as X}from"./adapter/claude/skill-scanner.js";import{scanDefaultSkills as Y,logDefaultSkillsCheck as Z,cleanupProjectedSkills as D}from"./default-skills/index.js";import{resolveCopilotCommand as q}from"./core/runtime/copilot-resolve.js";import{getCliVersion as ee,resolveCliPath as te}from"./core/util/cli-probe.js";import{AgentInstaller as ne}from"./core/installer/installer.js";import{reportInstallFailure as ae}from"./core/observability/sentry.js";const se=8e3;function oe(){const n=q();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 re(){return Promise.all(oe().map(async n=>{const e=await te(n.command);if(!e)return{client_type:n.clientType,command:n.command,installed:!1,path:null,version:null};const t=await ee(e,n.versionArgs??["--version"]);return{client_type:n.clientType,command:n.command,installed:!0,path:e,version:t.version,error:t.error}}))}function ie(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=q();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 ce(n){const e=String(n??"").trim().toLowerCase().replace(/[^a-z0-9._-]+/g,"-").replace(/-+/g,"-").replace(/^-|-$/g,"")||"default";return x(S.data,`session-bindings-${e}.json`)}function le(n){const e=String(n??"").trim().toLowerCase().replace(/[^a-z0-9._-]+/g,"-").replace(/-+/g,"-").replace(/^-|-$/g,"")||"default";return x(S.data,`active-events-${e}.json`)}function de(...n){const e=[],t=new Set;for(const s of n)for(const a of s??[]){const o=String(a??"").trim(),r=o.toLowerCase();!o||t.has(r)||(t.add(r),e.push(o))}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 O(n){const e=K(),t=String(n.client_type??"").trim().toLowerCase(),s=ie(t),a=String(n.ws_url??"").trim(),o="get_session_usage",r="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 l=s.adapterType,u=l==="acp",f=t==="qwen",m={...s.options??{}},c=l==="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",o,r]}:null,p=l==="claude"?{localActions:["session_control","set_mode","set_model","claude_interaction_reply","exec_approve","exec_reject","file_list","create_folder","thread_compact",o,r]}:null,g=f?{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",o],adapterHint:"qwen/base"}:null,_=l==="pi"?{adapterHint:"pi/base",capabilities:["local_action_v1"],localActions:["session_control","set_model","get_context","file_list","create_folder",o]}:null,h=l==="openhuman"?{adapterHint:"openhuman/base",capabilities:["local_action_v1"],localActions:["session_control","set_model","file_list","create_folder",o]}:null,w=l==="cursor"?{adapterHint:"cursor/base",capabilities:["stream_chunk","local_action_v1"],localActions:["session_control","set_model","set_mode","get_context","file_list","create_folder",o,r]}:null,y=l==="codewhale"?{capabilities:["stream_chunk","local_action_v1"],localActions:["session_control","set_model","file_list","create_folder",o]}:null,k=l==="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",o]}:null,T=l==="agy"?{adapterHint:"agy/base",capabilities:["stream_chunk","local_action_v1"],localActions:["session_control","set_model","file_list","create_folder",o]}:null,P=u&&!f?{localActions:["exec_approve","exec_reject","permission_approve","permission_reject","session_control","set_model","set_mode","file_list","create_folder",o]}:null,E=t==="kiro"?{localActions:["exec_approve","exec_reject","permission_approve","permission_reject","session_control","set_model","set_mode","file_list","create_folder","thread_compact",o,r]}:null,H=l==="codex"||l==="claude"||t==="gemini"?["session_control","set_model","set_mode"]:void 0,N=[o,r];u&&m.raw_transport===void 0&&(m.raw_transport=t==="gemini");const U=`${t}/base`,$=l==="claude"?"claude":l==="codex"?"codex":l==="pi"?"pi":t==="kiro"?"kiro":"gemini";let b;try{const M=$==="kiro"?void 0:process.cwd();b=J({mode:$,projectDir:M})??void 0,b&&b.length===0&&(b=void 0)}catch{}const I=Y();if(I.length>0){const C=X([...b??[],...I]),j=C.filter(v=>v.source==="connector").map(v=>v.name);j.length>0&&d.info("manager",`[${n.name}] injecting connector skills: [${j.join(", ")}]`),b=C.length>0?C:void 0}return{name:n.name,adapterType:l,aibot:{url:a,agentId:n.agent_id,apiKey:n.api_key,clientType:t,clientVersion:e,adapterHint:s.adapterHint??g?.adapterHint??_?.adapterHint??h?.adapterHint??w?.adapterHint??k?.adapterHint??T?.adapterHint??U,capabilities:c?.capabilities??y?.capabilities??_?.capabilities??h?.capabilities??w?.capabilities??k?.capabilities??T?.capabilities??g?.capabilities??["stream_chunk","local_action_v1","connector_upgrade"],localActions:de(c?.localActions??y?.localActions??p?.localActions??_?.localActions??h?.localActions??w?.localActions??k?.localActions??T?.localActions??g?.localActions??E?.localActions??P?.localActions??["exec_approve","exec_reject"],H,N,["connector_rollback","connector_upgrade_push",i]),skills:b},agent:{command:s.command,args:[...s.args??[],...n.args??[]],env:n.env},adapterOptions:m,acpAuthMethod:m.auth_method,acpInitialMode:m.initial_mode,acpMcpTools:m.acp_mcp_tools,promptTimeoutMs:n.prompt_timeout_ms,bindingsPath:ce(n.name),activeEventStorePath:le(n.name),...s.enableSessionBinding||u?{enableSessionBinding:!0}:{},...s.autoInjectArgs?{autoInjectArgs:s.autoInjectArgs}:{},poolMaxSize:n.pool?.maxSize,poolIdleTimeoutMs:n.pool?.idleTimeoutMs,eventQueue:ue(l,n.event_queue),logDir:S.log,providerBaseUrl:n.provider_base_url?.trim()||void 0,providerApiKey:n.provider_api_key?.trim()||void 0}}function pe(){const n=process.env.GRIX_AGENT_STARTUP_WAIT_MS,e=Number(n);return Number.isFinite(e)&&e>=500?Math.floor(e):se}class Ie{instances=[];configMap=new Map;sharedInstances=new Map;shareSyncChains=new Map;stopping=!1;upgradeChecker=null;globalConfigStore;configDir=S.config;sharedOwnersCache=new G(S.data);installer=new ne;async start(e){const t=e??S.config;this.configDir=t,d.info("manager",`Loading configs from ${t}`),Z(),D(),this.globalConfigStore=new V(x(S.data,"agent-global-configs.json")),this.globalConfigStore.load();const s=F(t).filter(i=>i.endsWith(".json")).sort();if(s.length===0)throw new Error(`No config files found in ${t}`);const a=[];let o=0;for(const i of s)try{const l=Q(x(t,i),"utf-8"),u=JSON.parse(l);if(Array.isArray(u.agents)){if(u.agents.length===0){d.error("manager",`No agents array found in ${i}`),o++;continue}for(const f of u.agents)try{const m=O(f);a.push({config:m,file:i}),d.info("manager",`Loaded ${m.name} (${m.adapterType??"acp"}) from ${i}`)}catch(m){const c=typeof f?.name=="string"?f.name:"<unknown>";d.error("manager",`Invalid agent config in ${i} (name=${c}): ${m}`),o++}}else d.error("manager",`Unrecognized config format in ${i}`)}catch(l){d.error("manager",`Failed to load ${i}: ${l}`)}let r=0;if(a.length>0){const i=pe();d.info("manager",`Starting ${a.length} agent(s), startup wait=${i}ms`);const l=()=>this.upgradeChecker?.triggerCheck(),u=c=>{this.instances=this.instances.filter(p=>p!==c)},f=a.map(({config:c})=>{const p=new A(c,this.globalConfigStore);return p.setUpgradeTrigger(l),p.setShareSetHandler(g=>this.onShareSet(c,g)),this.instances.push(p),this.configMap.set(c.name,c),{config:c,instance:p,startPromise:p.start()}}),m=await Promise.all(f.map(async c=>{const p=await new Promise(g=>{let _=!1;const h=setTimeout(()=>{_||(_=!0,g({kind:"timeout"}))},i);c.startPromise.then(()=>{_||(_=!0,clearTimeout(h),g({kind:"started"}))}).catch(w=>{_||(_=!0,clearTimeout(h),g({kind:"failed",error:w}))})});return{task:c,outcome:p}}));for(const{task:c,outcome:p}of m){if(p.kind==="started"){this.restoreCachedSharedInstances(c.config);continue}if(p.kind==="failed"){u(c.instance),d.error("manager",`Failed to start ${c.config.name}: ${p.error}`);continue}r++,d.warn("manager",`Startup pending for ${c.config.name}, continue retrying in background`),c.startPromise.then(()=>{d.info("manager",`Delayed start succeeded: ${c.config.name}`),this.restoreCachedSharedInstances(c.config)}).catch(g=>{u(c.instance),d.error("manager",`Delayed start failed: ${c.config.name}: ${g}`)})}if(this.instances.length>0){const c=Math.max(0,this.instances.length-r);d.info("manager",`${c}/${a.length} agent(s) running now`)}r>0&&d.warn("manager",`${r} agent(s) still connecting in background`)}if(this.instances.length===0&&a.length>0)throw new Error("All agent configurations failed to start");a.length>0&&(this.upgradeChecker=new W(a.map(({config:i})=>({apiKey:i.aibot.apiKey,wsUrl:i.aibot.url})),()=>this.instances.some(i=>i.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}=R(e.name,this.sharedInstances.keys(),t);for(const o of s){if(this.stopping)break;const r=z(e.name,o);try{const i=L(e,o),l=new A(i,this.globalConfigStore);this.sharedInstances.set(r,l),await l.start(),d.info("manager",`shared instance started: ${r}`)}catch(i){this.sharedInstances.delete(r);const l=i instanceof Error?i.message:String(i);/Auth failed/i.test(l)?(this.sharedOwnersCache.remove(e.name,o),d.warn("manager",`shared instance auth rejected, removed from cache: ${r}`)):d.error("manager",`start shared instance failed ${r}: ${i}`)}}for(const{key:o}of a){const r=this.sharedInstances.get(o);r&&(this.sharedInstances.delete(o),r.stop().catch(i=>d.error("manager",`stop shared instance failed ${o}: ${i}`)),d.info("manager",`shared instance stopped: ${o}`))}}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){const t=O(e);if(this.instances.some(a=>a.name===t.name))throw new Error(`Agent "${t.name}" already exists`);const s=new A(t,this.globalConfigStore);s.setUpgradeTrigger(()=>this.upgradeChecker?.triggerCheck()),s.setShareSetHandler(a=>this.onShareSet(t,a)),await s.start(),this.instances.push(s),this.configMap.set(t.name,t),this.persistAgentsConfig(),d.info("manager",`Added agent: ${t.name}`)}async removeAgent(e){const t=this.instances.findIndex(r=>r.name===e);if(t===-1)throw Object.assign(new Error(`Agent "${e}" not found`),{code:"NOT_FOUND"});const s=this.instances[t];this.instances.splice(t,1),this.configMap.delete(e);const a=this.shareSyncChains.get(e);this.shareSyncChains.delete(e),a&&await a.catch(()=>{});const o=`${e}#shared:`;for(const[r,i]of[...this.sharedInstances.entries()])r.startsWith(o)&&(this.sharedInstances.delete(r),i.stop().catch(l=>d.error("manager",`stop shared instance failed ${r}: ${l}`)));this.sharedOwnersCache.delete(e),await s.stop(),this.persistAgentsConfig(),d.info("manager",`Removed agent: ${e}`)}persistAgentsConfig(){const e=x(this.configDir,"agents.json");try{const t=[];for(const[,a]of this.configMap)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});B(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.instances.findIndex(r=>r.name===e);if(t===-1)throw Object.assign(new Error(`Agent "${e}" not found`),{code:"NOT_FOUND"});const s=this.configMap.get(e);if(!s)throw Object.assign(new Error(`Config for "${e}" not found`),{code:"NOT_FOUND"});await this.instances[t].stop();const o=new A(s,this.globalConfigStore);o.setUpgradeTrigger(()=>this.upgradeChecker?.triggerCheck()),await o.start(),this.instances[t]=o,d.info("manager",`Restarted agent: ${e}`)}async checkUpgrade(){return this.upgradeChecker?this.upgradeChecker.checkForUpdate():{available:!1}}triggerUpgrade(){this.upgradeChecker?.triggerCheck()}async probeAll(e={}){return me(this.instances,e)}async probeOne(e,t={}){return ge(this.instances,e,t)}listInstallable(){return this.installer.listInstallable()}async installAgent(e){const t=await this.installer.install(e);return ae(t),t}getInstallProgress(e){return this.installer.getProgress(e)??null}}async function me(n,e){const t=e.concurrency??4,s=Date.now(),a=new Array(n.length);await new Promise(u=>{let f=0,m=0;const c=n.length;if(c===0){u();return}function p(h){const w=n[h];w.probe(e).then(y=>{a[h]=y,g()},y=>{a[h]={agent_name:w.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}},g()})}function g(){m++,f<c?p(f++):m===c&&u()}const _=Math.min(t,c);for(let h=0;h<_;h++)p(f++)});const o=a.filter(u=>u.status==="healthy").length,r=a.filter(u=>u.status==="degraded").length,i=a.filter(u=>u.status==="unavailable").length,l=await re();return{ok:o===a.length&&a.length>0,total:a.length,healthy:o,degraded:r,unavailable:i,installed_clients:l,agents:a,probed_at:s,duration_ms:Date.now()-s}}async function ge(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{Ie as Manager,re as probeInstalledClientCommands,me as probeInstances,ge as probeOneInstance};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import*as
|
|
1
|
+
import*as i from"node:net";const e={bind:"127.0.0.1",port:0,endpoint:"/mcp",sessionTimeoutMs:18e5,invokeTimeoutMs:3e4};function n(o){const u={bind:o?.bind??e.bind,port:o?.port??e.port,endpoint:o?.endpoint??e.endpoint,sessionTimeoutMs:o?.sessionTimeoutMs??e.sessionTimeoutMs,invokeTimeoutMs:o?.invokeTimeoutMs??e.invokeTimeoutMs,allowedOrigins:o?.allowedOrigins,allowedHosts:o?.allowedHosts};return s(u.bind),u.port!==0&&t(u.port),r(u.sessionTimeoutMs),u}function s(o){if(!o||!i.isIPv4(o)&&!i.isIPv6(o))throw new Error(`\u914D\u7F6E\u6821\u9A8C\u5931\u8D25: bind \u5730\u5740 "${o}" \u4E0D\u662F\u5408\u6CD5\u7684 IPv4 \u6216 IPv6 \u5730\u5740`)}function t(o){if(!Number.isInteger(o)||o<1||o>65535)throw new Error(`\u914D\u7F6E\u6821\u9A8C\u5931\u8D25: port \u503C ${o} \u4E0D\u5728\u5408\u6CD5\u8303\u56F4 1-65535 \u5185\u6216\u4E0D\u662F\u6574\u6570`)}function r(o){if(!Number.isInteger(o)||o<1e3||o>864e5)throw new Error(`\u914D\u7F6E\u6821\u9A8C\u5931\u8D25: session_timeout_ms \u503C ${o} \u4E0D\u5728\u5408\u6CD5\u8303\u56F4 1000-86400000 \u5185`)}export{n as createDefaultGatewayConfig};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
const
|
|
1
|
+
const r=3e4;class c{connectionManager;onDisconnected;bindings=new Map;constructor(n,i){this.connectionManager=n,this.onDisconnected=i}async bind(n,i){if(this.bindings.has(n))throw new Error(`Session ${n} is already bound to a connection`);const e=await this.connectWithTimeout(i),t=[],s=e.onDisconnected(()=>{this.removeBinding(n),this.onDisconnected(n)});return t.push(s),this.bindings.set(n,{sessionId:n,handle:e,subscriptions:t}),e}getHandle(n){return this.bindings.get(n)?.handle}unbind(n){const i=this.bindings.get(n);if(i){this.bindings.delete(n);for(const e of i.subscriptions)e();i.handle.disconnect()}}unbindAll(){const n=[...this.bindings.keys()];for(const i of n)this.unbind(i)}connectWithTimeout(n){return new Promise((i,e)=>{let t=!1;const s=setTimeout(()=>{t||(t=!0,e(new Error("Connection bind timeout after 30000ms")))},3e4);this.connectionManager.connect({agentId:n.agentId,apiKey:n.apiKey,url:n.wsUrl,clientType:n.clientType,capabilities:["agent_invoke"],adapterHint:`${n.clientType}/base`},{maxRetries:0}).then(o=>{t?o.disconnect():(t=!0,clearTimeout(s),i(o))}).catch(o=>{t||(t=!0,clearTimeout(s),e(o))})})}removeBinding(n){const i=this.bindings.get(n);if(i){this.bindings.delete(n);for(const e of i.subscriptions)e()}}}export{c as ConnectionBindingImpl};
|
|
@@ -1 +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(t,e,r,n){if(!this.registry.hasTool(e))return this.errorResult(`\u672A\u77E5\u5DE5\u5177: ${e}`);const s=a(e,r);if(!s.valid)return this.errorResult(`\u53C2\u6570\u6821\u9A8C\u5931\u8D25: ${s.error}`);if(t.status!=="ready")return this.errorResult(`\u8FDE\u63A5\u4E0D\u53EF\u7528: \u5F53\u524D\u72B6\u6001\u4E3A ${t.status}`);if(p(e))return this.executeEventTool(t,e,r);const o=i(e,r);try{const u=await t.agentInvoke(o.action,o.params,n);return this.normalizeResult(u)}catch(u){const c=u instanceof Error?u.message:String(u);return c.toLowerCase().includes("timeout")?this.errorResult(`\u8C03\u7528\u8D85\u65F6: ${c}`):this.errorResult(`\u8C03\u7528\u5931\u8D25: ${c}`)}}normalizeResult(t){if(t==null||typeof t!="object")return this.successResult(t??null);const e=t,r=typeof e.code=="number"?e.code:0;if(r===0){const s="data"in e?e.data:null;return this.successResult(s??null)}const n=typeof e.msg=="string"?e.msg:"\u672A\u77E5\u9519\u8BEF";return this.errorResult(`\u4E0A\u6E38\u9519\u8BEF [code=${r}]: ${n}`)}successResult(t){return{content:[{type:"text",text:JSON.stringify(t)}],isError:!1}}errorResult(t){return{content:[{type:"text",text:t}],isError:!0}}async executeEventTool(t,e,r){return e==="grix_access_control"?this.executeAccessControl(t,r):d(t,e,r)}async executeAccessControl(t,e){const r=String(e.action??""),n={pair_approve:"pair_approve",pair_deny:"pair_deny",allow_sender:"sender_allow",remove_sender:"sender_remove",set_policy:"policy_set"}[r];if(!n)return this.errorResult(`\u672A\u77E5 access_control action: ${r}`);const s={};e.code!=null&&(s.code=e.code),e.sender_id!=null&&(s.sender_id=e.sender_id),e.policy!=null&&(s.policy=e.policy);try{const o=await t.agentInvoke("claude_access_control",{verb:n,payload:s},3e4);return this.successResult(o)}catch(o){const u=o instanceof Error?o.message:String(o);return this.errorResult(`access_control \u8C03\u7528\u5931\u8D25: ${u}`)}}}export{y as ToolExecutorImpl};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{TOOLS as
|
|
1
|
+
import{TOOLS as s,EVENT_TOOLS as t}from"../../core/mcp/tools.js";const e=new Set(["grix_query","grix_group","grix_message_send","grix_message_unsend","grix_admin"]),r=new Set(["grix_reply","grix_complete","grix_event_ack","grix_composing","grix_access_control","grix_status"]);class a{tools;toolMap;constructor(){this.tools=[...s.filter(o=>e.has(o.name)),...t.filter(o=>r.has(o.name))],this.toolMap=new Map(this.tools.map(o=>[o.name,o]))}getTools(){return this.tools}getTool(o){return this.toolMap.get(o)}hasTool(o){return this.toolMap.has(o)}}export{a as ToolRegistryImpl};
|
|
@@ -1 +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(u,t){const e=C[u];if(!e)return{valid:!1,error:`\u672A\u77E5\u5DE5\u5177: ${u}`};for(const i of e.required)if(t[i]===void 0||t[i]===null)return{valid:!1,error:`\u7F3A\u5C11\u5FC5\u586B\u53C2\u6570: ${i}`};for(const[i,r]of Object.entries(t)){if(r==null)continue;const n=e.properties[i];if(!n)continue;const s=$(i,r,n);if(s)return{valid:!1,error:s}}return{valid:!0}}function $(u,t,e){switch(e.type){case"string":if(typeof t!="string")return`\u53C2\u6570 ${u} \u7C7B\u578B\u9519\u8BEF: \u671F\u671B string\uFF0C\u5B9E\u9645 ${typeof t}`;if(e.maxLength!==void 0&&t.length>e.maxLength)return`\u53C2\u6570 ${u} \u8D85\u8FC7\u6700\u5927\u957F\u5EA6 ${e.maxLength}\uFF0C\u5B9E\u9645 ${t.length}`;if(e.enum&&!e.enum.includes(t))return`\u53C2\u6570 ${u} \u503C "${t}" \u4E0D\u5728\u5141\u8BB8\u8303\u56F4 [${e.enum.join(", ")}]`;break;case"integer":if(typeof t!="number"||!Number.isInteger(t))return`\u53C2\u6570 ${u} \u7C7B\u578B\u9519\u8BEF: \u671F\u671B integer\uFF0C\u5B9E\u9645 ${typeof t=="number"?"\u6D6E\u70B9\u6570":typeof t}`;if(e.minimum!==void 0&&t<e.minimum)return`\u53C2\u6570 ${u} \u503C ${t} \u5C0F\u4E8E\u6700\u5C0F\u503C ${e.minimum}`;if(e.maximum!==void 0&&t>e.maximum)return`\u53C2\u6570 ${u} \u503C ${t} \u5927\u4E8E\u6700\u5927\u503C ${e.maximum}`;if(e.enum&&!e.enum.includes(t))return`\u53C2\u6570 ${u} \u503C ${t} \u4E0D\u5728\u5141\u8BB8\u8303\u56F4 [${e.enum.join(", ")}]`;break;case"boolean":if(typeof t!="boolean")return`\u53C2\u6570 ${u} \u7C7B\u578B\u9519\u8BEF: \u671F\u671B boolean\uFF0C\u5B9E\u9645 ${typeof t}`;break;case"array":if(!Array.isArray(t))return`\u53C2\u6570 ${u} \u7C7B\u578B\u9519\u8BEF: \u671F\u671B array\uFF0C\u5B9E\u9645 ${typeof t}`;if(e.maxItems!==void 0&&t.length>e.maxItems)return`\u53C2\u6570 ${u} \u8D85\u8FC7\u6700\u5927\u5143\u7D20\u6570 ${e.maxItems}\uFF0C\u5B9E\u9645 ${t.length}`;if(e.items)for(let i=0;i<t.length;i++){const r=t[i];if(e.items.type==="string"&&typeof r!="string")return`\u53C2\u6570 ${u}[${i}] \u7C7B\u578B\u9519\u8BEF: \u671F\u671B string\uFF0C\u5B9E\u9645 ${typeof r}`;if(e.items.type==="integer"){if(typeof r!="number"||!Number.isInteger(r))return`\u53C2\u6570 ${u}[${i}] \u7C7B\u578B\u9519\u8BEF: \u671F\u671B integer\uFF0C\u5B9E\u9645 ${typeof r}`;if(e.items.enum&&!e.items.enum.includes(r))return`\u53C2\u6570 ${u}[${i}] \u503C ${r} \u4E0D\u5728\u5141\u8BB8\u8303\u56F4 [${e.items.enum.join(", ")}]`}}break}}export{B as validateToolArgs};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "grix-connector",
|
|
3
|
-
"version": "2.2.
|
|
3
|
+
"version": "2.2.4",
|
|
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",
|