grix-connector 3.2.9 → 3.3.0
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-adapter.js +15 -15
- 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/adapter/claude/usage-parser.js +7 -7
- package/dist/bridge/adapter-pool.js +1 -1
- package/dist/bridge/bridge.js +1 -1
- package/dist/core/file-ops/list-files.js +1 -1
- package/dist/core/upgrade/upgrade-checker.js +1 -1
- package/dist/log.js +2 -2
- package/dist/manager.js +1 -1
- 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{randomUUID as x}from"node:crypto";import{CallToolRequestSchema as S,ListToolsRequestSchema as w}from"@modelcontextprotocol/sdk/types.js";import{log as f}from"../../core/log/index.js";import{toolCallToInvoke as k}from"../../core/mcp/tools.js";const E=new Set(["contact_search","session_search","message_history","message_search","group_create","group_detail_read","group_leave_self","group_member_add","group_member_remove","group_member_role_update","group_all_members_muted_update","group_member_speaking_update","group_dissolve","send_msg","delete_msg","agent_api_create","agent_category_list","agent_category_create","agent_category_update","agent_category_assign","agent_api_key_rotate"]),y=3e4,I=[{name:"reply",description:"Send a visible message back to the chat for this grix-claude event.",inputSchema:{type:"object",properties:{text:{type:"string",description:"The visible reply text to send."},chat_id:{type:"string",description:"The target chat/session id from the <channel> tag."},event_id:{type:"string",description:"The Aibot event_id from the <channel> tag."},reply_to:{type:"string",description:"Optional message_id to quote instead of the inbound trigger message."},files:{type:"array",items:{type:"string"},description:"Optional absolute local file paths. Each file is uploaded through Agent API OSS presign before sending."},final:{type:"boolean",description:"Whether this is the final reply for the event. Defaults to false \u2014 the event stays open while Claude continues working, and auto-completes after inactivity. Set true only when this is definitively the last message for the event."}},required:["chat_id","event_id"]}},{name:"complete",description:"Finish an event without sending a visible reply so the backend does not time out.",inputSchema:{type:"object",properties:{event_id:{type:"string",description:"The Aibot event_id from the <channel> tag."},status:{type:"string",enum:["responded","canceled","failed"]},code:{type:"string"},msg:{type:"string"}},required:["event_id","status"]}},{name:"delete_message",description:"Delete a previously sent message in the same grix-claude chat.",inputSchema:{type:"object",properties:{chat_id:{type:"string"},message_id:{type:"string"}},required:["chat_id","message_id"]}},{name:"status",description:"Show grix-claude runtime status, upstream access state, bridge health, and startup hints.",inputSchema:{type:"object",properties:{}}},{name:"send",description:"Send a message to a chat session proactively, without requiring an inbound event. Use for notifications or scheduled reports.",inputSchema:{type:"object",properties:{chat_id:{type:"string",description:"The target chat/session id."},text:{type:"string",description:"The message text to send."}},required:["chat_id","text"]}},{name:"access_pair",description:"Forward a sender pairing approval code to upstream access control.",inputSchema:{type:"object",properties:{code:{type:"string"}},required:["code"]}},{name:"access_deny",description:"Forward a sender pairing denial code to upstream access control.",inputSchema:{type:"object",properties:{code:{type:"string"}},required:["code"]}},{name:"allow_sender",description:"Ask upstream access control to allow a sender_id.",inputSchema:{type:"object",properties:{sender_id:{type:"string"}},required:["sender_id"]}},{name:"remove_sender",description:"Ask upstream access control to remove a sender_id.",inputSchema:{type:"object",properties:{sender_id:{type:"string"}},required:["sender_id"]}},{name:"access_policy",description:"Ask upstream access control to update the sender access policy.",inputSchema:{type:"object",properties:{policy:{type:"string",enum:["allowlist","open","disabled"]}},required:["policy"]}},{name:"grix_query",description:"Search contacts, sessions, message history, or messages by keyword in the Grix/AIBot platform.",inputSchema:{type:"object",properties:{action:{type:"string",enum:["contact_search","session_search","message_history","message_search"]},keyword:{type:"string"},id:{type:"string"},sessionId:{type:"string"},limit:{type:"number"},offset:{type:"number"},beforeId:{type:"string"}},required:["action"]}},{name:"grix_group",description:"Manage groups in the Grix/AIBot platform: create, get details, leave, dissolve, manage members and permissions.",inputSchema:{type:"object",properties:{action:{type:"string",enum:["create","detail","leave","add_members","remove_members","update_member_role","update_all_members_muted","update_member_speaking","dissolve"]},sessionId:{type:"string"},name:{type:"string"},memberIds:{type:"array",items:{type:"string"}},memberTypes:{type:"array",items:{type:"integer",enum:[1,2]}},memberId:{type:"string"},role:{type:"integer",enum:[1,2]},memberType:{type:"integer",description:"Member type (for update_member_role / update_member_speaking)."},allMembersMuted:{type:"boolean"},isSpeakMuted:{type:"boolean"},canSpeakWhenAllMuted:{type:"boolean",description:"Allow speaking when all muted (for update_member_speaking)."}},required:["action"]}},{name:"grix_message_send",description:"Send a message to a session in the Grix/AIBot platform.",inputSchema:{type:"object",properties:{sessionId:{type:"string"},content:{type:"string"},msgType:{type:"number"},quotedMessageId:{type:"string"},threadId:{type:"string"}},required:["sessionId","content"]}},{name:"grix_message_unsend",description:"Recall/unsend a message in the Grix/AIBot platform.",inputSchema:{type:"object",properties:{sessionId:{type:"string"},msgId:{type:"string"}},required:["sessionId","msgId"]}},{name:"grix_admin",description:"Agent and category management in the Grix/AIBot platform: create agents, manage categories, rotate API keys.",inputSchema:{type:"object",properties:{action:{type:"string",enum:["create_agent","list_categories","create_category","update_category","assign_category","rotate_api_key"]},agentId:{type:"string"},agentName:{type:"string"},introduction:{type:"string"},isMain:{type:"boolean"},categoryId:{type:"string"},name:{type:"string"},parentId:{type:"string"},sortOrder:{type:"number"}},required:["action"]}}];function q(n,e){n.setRequestHandler(w,async()=>({tools:I})),n.setRequestHandler(S,async i=>{const{name:r,arguments:t}=i.params,s=t??{};try{switch(r){case"reply":return await A(s,e);case"complete":return await $(s,e);case"delete_message":return await T(s,e);case"status":return j(e);case"send":return await M(s,e);case"access_pair":case"access_deny":case"allow_sender":case"remove_sender":case"access_policy":return await R(r,s,e);case"grix_query":case"grix_group":case"grix_message_send":case"grix_message_unsend":case"grix_admin":return await O(r,s,e);default:return{content:[{type:"text",text:`Unknown tool: ${r}`}],isError:!0}}}catch(a){return f.error("claude-tools",`Tool ${r} error: ${a}`),{content:[{type:"text",text:`Error: ${a instanceof Error?a.message:String(a)}`}],isError:!0}}})}async function A(n,e){const i=e.getActiveEvent();if(!i)return{content:[{type:"text",text:"No active event to reply to"}],isError:!0};const r=String(n.text??""),t=String(n.chat_id??""),s=String(n.event_id??i.eventId),a=n.reply_to,d=n.files,_=n.final===!0;if(!t||!s)return{content:[{type:"text",text:"reply requires chat_id and event_id"}],isError:!0};if(!r.trim()&&(!d||d.length===0))return{content:[{type:"text",text:"reply requires at least one of text or files"}],isError:!0};const{text:g,quotedMessageId:v}=e.resolveQuotedMessageId(a,r),b=[];let m=0;const h=`reply_${s}_${Date.now()}`;try{if(g){const p=e.splitText(g);for(let o=0;o<p.length;o++){if(!e.isEventActive(s))return c("ignored: event no longer active");m++,e.bridge.sendStreamChunk(s,t,p[o],++i.chunkSeq,!1,h)}}if(d&&d.length>0)for(const p of d){if(!e.isEventActive(s))return c("ignored: event no longer active");m++;const o=await e.uploadFile(p,t),l=`${x()}_${m}`;e.bridge.sendMedia(s,t,o.access_url,o.file_name,v,l,o.extra),f.info("claude-tools",`File sent: ${o.file_name}`)}e.bridge.sendStreamChunk(s,t,"",++i.chunkSeq,!0,h)}catch(p){if(g&&b.length===0)try{const o=`fallback_${s}_${Date.now()}`,l=e.splitText(g);for(let u=0;u<l.length;u++)e.bridge.sendStreamChunk("",t,l[u],u+1,!1,o);return e.bridge.sendStreamChunk("",t,"",l.length+1,!0,o),e.markReplySent(s),_&&e.finalizeEvent(s,"responded"),c("sent via fallback")}catch{}if(!e.isEventActive(s))return c("ignored: event no longer active");throw e.bridge.sendEventResult(s,"failed",String(p),"send_msg_failed"),p}return e.markReplySent(s),_?e.finalizeEvent(s,"responded"):(i.responded=!0,e.clearActiveEvent("completed")),c("Reply sent")}async function $(n,e){const i=e.getActiveEvent(),r=String(n.event_id??""),t=n.status??"",s=n.code,a=n.msg;if(!r||!t)return{content:[{type:"text",text:"complete requires event_id and status"}],isError:!0};const d=["responded","canceled","failed"];return d.includes(t)?e.isEventActive(r)?(e.bridge.sendEventResult(r,t,a,s),e.clearActiveEvent(t),c("Event completed")):c("ignored: event no longer active"):{content:[{type:"text",text:`status must be one of: ${d.join(", ")}`}],isError:!0}}async function T(n,e){const i=String(n.chat_id??""),r=String(n.message_id??"");if(!i||!r)return{content:[{type:"text",text:"chat_id and message_id are required"}],isError:!0};try{return await e.bridge.agentInvoke("grix_message_unsend",{sessionId:i,msgId:r},y),c(`deleted (${r})`)}catch(t){return{content:[{type:"text",text:`Delete failed: ${t}`}],isError:!0}}}function j(n){const e=n.getStatusInfo();return{content:[{type:"text",text:JSON.stringify({alive:e.alive,active_event:e.activeEvent,pending_approvals:e.pendingPermissions,pending_questions:e.pendingElicitations})}]}}async function M(n,e){const i=String(n.chat_id??""),r=String(n.text??"");if(!i||!r)return{content:[{type:"text",text:"chat_id and text are required"}],isError:!0};try{const t=e.splitText(r),s=`send_${i}_${Date.now()}`;for(let a=0;a<t.length;a++)e.bridge.sendStreamChunk("",i,t[a],a+1,!1,s);return e.bridge.sendStreamChunk("",i,"",t.length+1,!0,s),c("sent")}catch(t){return{content:[{type:"text",text:`Send failed: ${t}`}],isError:!0}}}const C={access_pair:{verb:"pair_approve",payloadKey:"code"},access_deny:{verb:"pair_deny",payloadKey:"code"},allow_sender:{verb:"sender_allow",payloadKey:"sender_id"},remove_sender:{verb:"sender_remove",payloadKey:"sender_id"},access_policy:{verb:"policy_set",payloadKey:"policy"}};async function R(n,e,i){try{const r=C[n];if(!r)throw new Error(`Unknown access control tool: ${n}`);const t={};e.code!=null&&(t.code=e.code),e.sender_id!=null&&(t.sender_id=e.sender_id),e.policy!=null&&(t.policy=e.policy);const s=await i.bridge.agentInvoke("claude_access_control",{verb:r.verb,payload:t},y);return{content:[{type:"text",text:typeof s=="string"?s:JSON.stringify(s)}]}}catch(r){return{content:[{type:"text",text:`${n} failed: ${r}`}],isError:!0}}}async function O(n,e,i){try{const r=k(n,e);if(!E.has(r.action))throw new Error(`Action not allowed: ${r.action}`);const t=await i.bridge.agentInvoke(r.action,r.params,y);if(t&&Number(t.code??0)!==0)throw new Error(String(t.msg??"invoke failed"));return{content:[{type:"text",text:t?.data!=null?typeof t.data=="string"?t.data:JSON.stringify(t.data):JSON.stringify(t)}]}}catch(r){return{content:[{type:"text",text:`${n} failed: ${r}`}],isError:!0}}}function c(n){return{content:[{type:"text",text:n}]}}export{q as registerClaudeTools};
|
|
1
|
+
import{randomUUID as x}from"node:crypto";import{CallToolRequestSchema as S,ListToolsRequestSchema as w}from"@modelcontextprotocol/sdk/types.js";import{log as f}from"../../core/log/index.js";import{toolCallToInvoke as k}from"../../core/mcp/tools.js";const E=new Set(["contact_search","session_search","message_history","message_search","group_create","group_detail_read","group_leave_self","group_member_add","group_member_remove","group_member_role_update","group_all_members_muted_update","group_member_speaking_update","group_dissolve","send_msg","delete_msg","agent_api_create","agent_category_list","agent_category_create","agent_category_update","agent_category_assign","agent_api_key_rotate"]),y=3e4,I=[{name:"reply",description:"Send a visible message back to the chat for this grix-claude event.",inputSchema:{type:"object",properties:{text:{type:"string",description:"The visible reply text to send."},chat_id:{type:"string",description:"The target chat/session id from the <channel> tag."},event_id:{type:"string",description:"The Aibot event_id from the <channel> tag."},reply_to:{type:"string",description:"Optional message_id to quote instead of the inbound trigger message."},files:{type:"array",items:{type:"string"},description:"Optional absolute local file paths. Each file is uploaded through Agent API OSS presign before sending."},final:{type:"boolean",description:"Whether this is the final reply for the event. Defaults to false \u2014 the event stays open while Claude continues working, and auto-completes after inactivity. Set true only when this is definitively the last message for the event."}},required:["chat_id","event_id"]}},{name:"complete",description:"Finish an event without sending a visible reply so the backend does not time out.",inputSchema:{type:"object",properties:{event_id:{type:"string",description:"The Aibot event_id from the <channel> tag."},status:{type:"string",enum:["responded","canceled","failed"]},code:{type:"string"},msg:{type:"string"}},required:["event_id","status"]}},{name:"delete_message",description:"Delete a previously sent message in the same grix-claude chat.",inputSchema:{type:"object",properties:{chat_id:{type:"string"},message_id:{type:"string"}},required:["chat_id","message_id"]}},{name:"status",description:"Show grix-claude runtime status, upstream access state, bridge health, and startup hints.",inputSchema:{type:"object",properties:{}}},{name:"send",description:"Send a message to a chat session proactively, without requiring an inbound event. Use for notifications or scheduled reports.",inputSchema:{type:"object",properties:{chat_id:{type:"string",description:"The target chat/session id."},text:{type:"string",description:"The message text to send."}},required:["chat_id","text"]}},{name:"access_pair",description:"Forward a sender pairing approval code to upstream access control.",inputSchema:{type:"object",properties:{code:{type:"string"}},required:["code"]}},{name:"access_deny",description:"Forward a sender pairing denial code to upstream access control.",inputSchema:{type:"object",properties:{code:{type:"string"}},required:["code"]}},{name:"allow_sender",description:"Ask upstream access control to allow a sender_id.",inputSchema:{type:"object",properties:{sender_id:{type:"string"}},required:["sender_id"]}},{name:"remove_sender",description:"Ask upstream access control to remove a sender_id.",inputSchema:{type:"object",properties:{sender_id:{type:"string"}},required:["sender_id"]}},{name:"access_policy",description:"Ask upstream access control to update the sender access policy.",inputSchema:{type:"object",properties:{policy:{type:"string",enum:["allowlist","open","disabled"]}},required:["policy"]}},{name:"grix_query",description:"Search contacts, sessions, message history, or messages by keyword in the Grix/AIBot platform.",inputSchema:{type:"object",properties:{action:{type:"string",enum:["contact_search","session_search","message_history","message_search"]},keyword:{type:"string"},id:{type:"string"},sessionId:{type:"string"},limit:{type:"number"},offset:{type:"number"},beforeId:{type:"string"}},required:["action"]}},{name:"grix_group",description:"Manage groups in the Grix/AIBot platform: create, get details, leave, dissolve, manage members and permissions.",inputSchema:{type:"object",properties:{action:{type:"string",enum:["create","detail","leave","add_members","remove_members","update_member_role","update_all_members_muted","update_member_speaking","dissolve"]},sessionId:{type:"string"},name:{type:"string"},memberIds:{type:"array",items:{type:"string"}},memberTypes:{type:"array",items:{type:"integer",enum:[1,2]}},memberId:{type:"string"},role:{type:"integer",enum:[1,2]},memberType:{type:"integer",description:"Member type (for update_member_role / update_member_speaking)."},allMembersMuted:{type:"boolean"},isSpeakMuted:{type:"boolean"},canSpeakWhenAllMuted:{type:"boolean",description:"Allow speaking when all muted (for update_member_speaking)."}},required:["action"]}},{name:"grix_message_send",description:"Send a message to a session in the Grix/AIBot platform.",inputSchema:{type:"object",properties:{sessionId:{type:"string"},content:{type:"string"},msgType:{type:"number"},quotedMessageId:{type:"string"},threadId:{type:"string"}},required:["sessionId","content"]}},{name:"grix_message_unsend",description:"Recall/unsend a message in the Grix/AIBot platform.",inputSchema:{type:"object",properties:{sessionId:{type:"string"},msgId:{type:"string"}},required:["sessionId","msgId"]}},{name:"grix_admin",description:"Agent and category management in the Grix/AIBot platform: create agents, manage categories, rotate API keys.",inputSchema:{type:"object",properties:{action:{type:"string",enum:["create_agent","list_categories","create_category","update_category","assign_category","rotate_api_key"]},agentId:{type:"string"},agentName:{type:"string"},introduction:{type:"string"},isMain:{type:"boolean"},categoryId:{type:"string"},name:{type:"string"},parentId:{type:"string"},sortOrder:{type:"number"}},required:["action"]}}];function q(r,e){r.setRequestHandler(w,async()=>({tools:I})),r.setRequestHandler(S,async i=>{const{name:n,arguments:t}=i.params,s=t??{};try{switch(n){case"reply":return await A(s,e);case"complete":return await $(s,e);case"delete_message":return await T(s,e);case"status":return j(e);case"send":return await M(s,e);case"access_pair":case"access_deny":case"allow_sender":case"remove_sender":case"access_policy":return await R(n,s,e);case"grix_query":case"grix_group":case"grix_message_send":case"grix_message_unsend":case"grix_admin":return await O(n,s,e);default:return{content:[{type:"text",text:`Unknown tool: ${n}`}],isError:!0}}}catch(a){return f.error("claude-tools",`Tool ${n} error: ${a}`),{content:[{type:"text",text:`Error: ${a instanceof Error?a.message:String(a)}`}],isError:!0}}})}async function A(r,e){const i=e.getActiveEvent();if(!i)return{content:[{type:"text",text:"No active event to reply to"}],isError:!0};const n=String(r.text??""),t=String(r.chat_id??""),s=String(r.event_id??i.eventId),a=r.reply_to,d=r.files,_=r.final===!0;if(!t||!s)return{content:[{type:"text",text:"reply requires chat_id and event_id"}],isError:!0};if(!n.trim()&&(!d||d.length===0))return{content:[{type:"text",text:"reply requires at least one of text or files"}],isError:!0};const{text:g,quotedMessageId:v}=e.resolveQuotedMessageId(a,n),b=[];let m=0;const h=`reply_${s}_${Date.now()}`;try{if(g){const p=e.splitText(g);for(let o=0;o<p.length;o++){if(!e.isEventActive(s))return c("ignored: event no longer active");m++,e.bridge.sendStreamChunk(s,t,p[o],++i.chunkSeq,!1,h)}}if(d&&d.length>0)for(const p of d){if(!e.isEventActive(s))return c("ignored: event no longer active");m++;const o=await e.uploadFile(p,t),l=`${x()}_${m}`;e.bridge.sendMedia(s,t,o.access_url,o.file_name,v,l,o.extra),f.info("claude-tools",`File sent: ${o.file_name}`)}e.bridge.sendStreamChunk(s,t,"",++i.chunkSeq,!0,h)}catch(p){if(g&&b.length===0)try{const o=`fallback_${s}_${Date.now()}`,l=e.splitText(g);for(let u=0;u<l.length;u++)e.bridge.sendStreamChunk("",t,l[u],u+1,!1,o);return e.bridge.sendStreamChunk("",t,"",l.length+1,!0,o),e.markReplySent(s),_&&e.finalizeEvent(s,"responded"),c("sent via fallback")}catch{}if(!e.isEventActive(s))return c("ignored: event no longer active");throw e.bridge.sendEventResult(s,"failed",String(p),"send_msg_failed"),p}return e.markReplySent(s),_?e.finalizeEvent(s,"responded"):(i.responded=!0,e.clearActiveEvent("completed")),c("Reply sent")}async function $(r,e){const i=e.getActiveEvent(),n=String(r.event_id??""),t=r.status??"",s=r.code,a=r.msg;if(!n||!t)return{content:[{type:"text",text:"complete requires event_id and status"}],isError:!0};const d=["responded","canceled","failed"];return d.includes(t)?e.isEventActive(n)?(e.bridge.sendEventResult(n,t,a,s),e.clearActiveEvent(t),c("Event completed")):c("ignored: event no longer active"):{content:[{type:"text",text:`status must be one of: ${d.join(", ")}`}],isError:!0}}async function T(r,e){const i=String(r.chat_id??""),n=String(r.message_id??"");if(!i||!n)return{content:[{type:"text",text:"chat_id and message_id are required"}],isError:!0};try{return await e.bridge.agentInvoke("grix_message_unsend",{sessionId:i,msgId:n},y),c(`deleted (${n})`)}catch(t){return{content:[{type:"text",text:`Delete failed: ${t}`}],isError:!0}}}function j(r){const e=r.getStatusInfo();return{content:[{type:"text",text:JSON.stringify({alive:e.alive,active_event:e.activeEvent,pending_approvals:e.pendingPermissions,pending_questions:e.pendingElicitations})}]}}async function M(r,e){const i=String(r.chat_id??""),n=String(r.text??"");if(!i||!n)return{content:[{type:"text",text:"chat_id and text are required"}],isError:!0};try{const t=e.splitText(n),s=`send_${i}_${Date.now()}`;for(let a=0;a<t.length;a++)e.bridge.sendStreamChunk("",i,t[a],a+1,!1,s);return e.bridge.sendStreamChunk("",i,"",t.length+1,!0,s),c("sent")}catch(t){return{content:[{type:"text",text:`Send failed: ${t}`}],isError:!0}}}const C={access_pair:{verb:"pair_approve",payloadKey:"code"},access_deny:{verb:"pair_deny",payloadKey:"code"},allow_sender:{verb:"sender_allow",payloadKey:"sender_id"},remove_sender:{verb:"sender_remove",payloadKey:"sender_id"},access_policy:{verb:"policy_set",payloadKey:"policy"}};async function R(r,e,i){try{const n=C[r];if(!n)throw new Error(`Unknown access control tool: ${r}`);const t={};e.code!=null&&(t.code=e.code),e.sender_id!=null&&(t.sender_id=e.sender_id),e.policy!=null&&(t.policy=e.policy);const s=await i.bridge.agentInvoke("claude_access_control",{verb:n.verb,payload:t},y);return{content:[{type:"text",text:typeof s=="string"?s:JSON.stringify(s)}]}}catch(n){return{content:[{type:"text",text:`${r} failed: ${n}`}],isError:!0}}}async function O(r,e,i){try{const n=k(r,e);if(!E.has(n.action))throw new Error(`Action not allowed: ${n.action}`);const t=await i.bridge.agentInvoke(n.action,n.params,y);if(t&&Number(t.code??0)!==0)throw new Error(String(t.msg??"invoke failed"));return{content:[{type:"text",text:t?.data!=null?typeof t.data=="string"?t.data:JSON.stringify(t.data):JSON.stringify(t)}]}}catch(n){return{content:[{type:"text",text:`${r} failed: ${n}`}],isError:!0}}}function c(r){return{content:[{type:"text",text:r}]}}export{q as registerClaudeTools};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{log as l}from"../../core/log/index.js";class c{controlURL="";token="";isConfigured(){return!!(this.controlURL&&this.token)}configure(
|
|
1
|
+
import{log as l}from"../../core/log/index.js";class c{controlURL="";token="";isConfigured(){return!!(this.controlURL&&this.token)}configure(r,e){this.controlURL=r.replace(/\/+$/,""),this.token=e.trim(),l.info("claude-worker-client",`Configured with control URL: ${this.controlURL}`)}async post(r,e,s){if(!this.isConfigured())throw new Error("worker control not configured");const i=new AbortController,o=setTimeout(()=>i.abort(),s);try{const t=await fetch(`${this.controlURL}${r}`,{method:"POST",headers:{"content-type":"application/json",authorization:`Bearer ${this.token}`},body:JSON.stringify(e),signal:i.signal}),n=await t.text(),a=n.trim()?JSON.parse(n):{};if(!t.ok)throw new Error(a.error||`worker control failed ${t.status}`);return a}finally{clearTimeout(o)}}isRetryableError(r){const e=r instanceof Error?r.message:String(r);return/fetch failed|network|ECONNRESET|ETIMEDOUT|EAI_AGAIN|socket hang up|aborted/i.test(e)}async postWithRetry(r,e,s,i=1){let o;for(let t=0;t<=i;t++)try{return t>0&&l.info("claude-worker-client",`Retrying ${r} attempt=${t+1}`),await this.post(r,e,s)}catch(n){if(o=n,t>=i||!this.isRetryableError(n))break;await new Promise(a=>setTimeout(a,150))}throw o instanceof Error?o:new Error(String(o))}async deliverEvent(r){return this.postWithRetry("/v1/worker/deliver-event",{payload:r},1e4,1)}async deliverStop(r){return this.postWithRetry("/v1/worker/deliver-stop",{payload:r},1e4,1)}async deliverLocalAction(r){return this.postWithRetry("/v1/worker/deliver-local-action",{payload:r},1e4,1)}async ping(){try{return await this.postWithRetry("/v1/worker/ping",{},5e3,1),!0}catch{return!1}}}export{c as ClaudeWorkerClient};
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{spawn as x,execSync as y}from"node:child_process";import{randomUUID as v}from"node:crypto";import{mkdir as S}from"node:fs/promises";import{readFileSync as C}from"node:fs";import{join as d}from"node:path";import{homedir as T,tmpdir as I}from"node:os";import{log as o}from"../../core/log/index.js";import{MCP_HTTP_CHANNEL_NAME as
|
|
2
|
-
`),"utf8"),{expectPath:a,pidPath:i}}function h(
|
|
1
|
+
import{spawn as x,execSync as y}from"node:child_process";import{randomUUID as v}from"node:crypto";import{mkdir as S}from"node:fs/promises";import{readFileSync as C}from"node:fs";import{join as d}from"node:path";import{homedir as T,tmpdir as I}from"node:os";import{log as o}from"../../core/log/index.js";import{MCP_HTTP_CHANNEL_NAME as m}from"./protocol-contract.js";function P(t){let e=null,r=0,n=!1,i=!1;const a=v(),s=t.gatewayUrl??"http://127.0.0.1:19580/mcp";return{async start(){await F(t.command,s,t.env);const c=E(t.grix),u=[...t.args??[],"--name",`grix-mcp-${t.name}`,"--session-id",a];t.fullAuto&&u.push("--dangerously-skip-permissions"),u.push("--dangerously-load-development-channels",`server:${m}`,"--append-system-prompt",c);const f=d(I(),`grix-mcp-claude-${t.name}`);await S(f,{recursive:!0});const{expectPath:$,pidPath:g}=await M(f,t.command,u),_={...process.env,...t.env??{}};e=x("/usr/bin/expect",[$],{cwd:t.cwd,env:_,stdio:["ignore","pipe","pipe"],detached:!0}),o.info("mcp-http-launcher",`\u542F\u52A8 Claude: name=${t.name} cwd=${t.cwd} pid=${e.pid}`),r=await k(g),n=!0,o.info("mcp-http-launcher",`Claude \u5B50\u8FDB\u7A0B PID: ${r}`),e.on("exit",(l,p)=>{o.info("mcp-http-launcher",`Claude \u9000\u51FA: code=${l} signal=${p}`),n=!1,e=null,r=0,i||(o.info("mcp-http-launcher","3 \u79D2\u540E\u81EA\u52A8\u91CD\u542F..."),setTimeout(()=>{i||this.start().catch(w=>{o.error("mcp-http-launcher",`\u91CD\u542F\u5931\u8D25: ${w}`)})},3e3))}),e.stdout?.on("data",l=>{const p=l.toString().trim();p&&o.info("mcp-http-launcher",`[stdout] ${p.slice(0,300)}`)}),e.stderr?.on("data",l=>{const p=l.toString().trim();p&&o.info("mcp-http-launcher",`[stderr] ${p.slice(0,300)}`)})},async stop(){if(i=!0,n=!1,r>0)try{process.kill(r,"SIGTERM")}catch{}if(e?.pid){try{process.kill(-e.pid,"SIGTERM")}catch{}await new Promise(c=>{const u=setTimeout(()=>{if(r>0)try{process.kill(r,"SIGKILL")}catch{}if(e?.pid)try{process.kill(-e.pid,"SIGKILL")}catch{}c()},5e3);e?.once("exit",()=>{clearTimeout(u),c()})})}e=null,r=0},getStatus(){return{name:t.name,alive:n,pid:r}}}}function E(t){return["You are connected to a chat via the grix MCP server.",`On startup, immediately call grix_authorize with: agentId="${t.agentId}", apiKey="${t.apiKey}", wsUrl="${t.wsUrl}", clientType="${t.clientType}".`,"When you receive a <channel> message, you MUST respond by calling the grix_reply tool (or the grix_complete tool if no response is needed).","Never write your reply as plain text \u2014 it will NOT reach the user. Only the grix_reply tool delivers your response to the chat.","The <channel> message contains event_id and session_id \u2014 pass them to grix_reply."].join(" ")}async function F(t,e,r){const n=d(T(),".claude.json");let i=null;try{const s=C(n,"utf8");i=JSON.parse(s)?.mcpServers?.[m]??null}catch{}if(i&&String(i.type??"").trim()==="http"&&String(i.url??"").trim()===e)return;o.info("mcp-http-launcher",`\u6CE8\u518C MCP Server: ${m} -> ${e}`);const a={...process.env,...r??{}};try{y(`${t} mcp remove -s user ${m}`,{encoding:"utf8",timeout:1e4,env:a,stdio:"pipe"})}catch{}y(`${t} mcp add --scope user --transport http ${m} ${e}`,{encoding:"utf8",timeout:1e4,env:a,stdio:"pipe"})}async function M(t,e,r){const{writeFile:n}=await import("node:fs/promises"),i=d(t,"claude.pid"),a=d(t,"claude.expect"),s=["log_user 1","set timeout -1","set startup_prompt_armed 1",`set claude_command [list {${h(e)}}${r.map(c=>` {${h(c)}}`).join("")}]`,"spawn -noecho {*}$claude_command",`set pid_file [open {${h(i)}} w]`,"puts $pid_file [exp_pid -i $spawn_id]","close $pid_file","expect {"," -re {(?i)(Quick.*safety.*check|trust.*folder)} {",' if {$startup_prompt_armed} { send -- "1\\r"; after 300 }; exp_continue'," }"," -re {(?i)I am using this for local development} {",' if {$startup_prompt_armed} { send -- "1\\r"; after 300 }; exp_continue'," }"," -re {(?i)(Enter.*confirm|Press.*Enter|Hit.*Enter)} {",' if {$startup_prompt_armed} { send -- "\\r"; after 300 }; exp_continue'," }"," -re {Listening for channel} {"," set startup_prompt_armed 0"," after 1000",' send -- "Call grix_authorize now as instructed in your system prompt.\\r"'," }"," -re {bypass permissions} {"," set startup_prompt_armed 0"," after 1000",' send -- "Call grix_authorize now as instructed in your system prompt.\\r"'," }"," eof {}","}","expect eof",""];return await n(i,"","utf8"),await n(a,s.join(`
|
|
2
|
+
`),"utf8"),{expectPath:a,pidPath:i}}function h(t){return t.replace(/[\\{}$\[\]"]/g,"\\$&")}async function k(t,e=1e4){const{readFile:r}=await import("node:fs/promises"),n=Math.ceil(e/100);for(let i=0;i<n;i++){try{const a=await r(t,"utf8"),s=parseInt(String(a).trim(),10);if(Number.isFinite(s)&&s>0)return s}catch{}await new Promise(a=>setTimeout(a,100))}return 0}export{P as createMcpHttpLauncher};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
class m{defaultTimeoutMs;onTimeout;timers=new Map;constructor(
|
|
1
|
+
class m{defaultTimeoutMs;onTimeout;timers=new Map;constructor(e){this.defaultTimeoutMs=e.defaultTimeoutMs??9e4,this.onTimeout=e.onTimeout}arm(e,t){this.cancel(e);const s=t?.timeoutMs??this.defaultTimeoutMs,i=Date.now()+s,o=setTimeout(()=>{this.timers.delete(e),this.onTimeout(e).catch(()=>{})},s);return this.timers.set(e,o),i}cancel(e){const t=this.timers.get(e);t&&(clearTimeout(t),this.timers.delete(e))}has(e){return this.timers.has(e)}close(){for(const e of this.timers.values())clearTimeout(e);this.timers.clear()}}export{m as ResultTimeoutManager};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import{closeSync as
|
|
2
|
-
`);for(let
|
|
3
|
-
`),stopReason:
|
|
4
|
-
`)){const
|
|
5
|
-
`);for(let
|
|
6
|
-
`),
|
|
7
|
-
`);let
|
|
1
|
+
import{closeSync as P,createReadStream as j,openSync as J,readFileSync as b,readSync as L,realpathSync as O,statSync as _}from"node:fs";import{createInterface as B}from"node:readline";import R from"node:path";import C from"node:os";import{log as A}from"../../core/log/index.js";function U(s){let a=s;try{a=O(s)}catch{}return a.replace(/[^a-zA-Z0-9]/g,"-")}function T(s,a){const e=R.join(C.homedir(),".claude"),l=R.join(e,"projects",U(a));return R.join(l,`${s}.jsonl`)}function w(){return{inputTokens:0,outputTokens:0,cacheReadInputTokens:0,cacheCreationInputTokens:0}}function I(s,a){s.inputTokens+=a.input_tokens??0,s.outputTokens+=a.output_tokens??0,s.cacheReadInputTokens+=a.cache_read_input_tokens??0,s.cacheCreationInputTokens+=a.cache_creation_input_tokens??0}async function H(s,a){const e=T(s,a);A.info("usage-parser",`Parsing session usage from ${e}`);const l=new Map,t=w();let n=0;try{const o=B({input:j(e,"utf8"),crlfDelay:1/0});for await(const r of o)if(r.trim())try{const f=JSON.parse(r);if(f.type!=="assistant")continue;const c=f.message?.usage;if(!c)continue;const i=f.message?.model??"unknown";n++,I(t,c);let p=l.get(i);p||(p={turns:0,usage:w()},l.set(i,p)),p.turns++,I(p.usage,c)}catch{}}catch(o){return o.code==="ENOENT"?(A.info("usage-parser",`Session JSONL not found: ${e}`),null):(A.error("usage-parser",`Failed to parse session usage: ${o}`),null)}return n===0?null:{models:[...l.entries()].map(([o,r])=>({model:o,turns:r.turns,total:r.usage})),total:t,turns:n}}const S=64*1024,z=9e4,D=new Set(["No response requested."]);function N(s){return D.has(s.trim())}function Q(s,a,e){const l=T(s,a);try{const t=b(l);let n;e!==void 0&&e>0?n=e<t.length?t.subarray(e):Buffer.alloc(0):n=t.length>S?t.subarray(t.length-S):t;const u=n.toString("utf8").split(`
|
|
2
|
+
`);let o;for(let r=u.length-1;r>=0;r--){const f=u[r].trim();if(!f)continue;let c;try{c=JSON.parse(f)}catch{continue}if(c.type!=="assistant")continue;const i=c.message;if(!i?.content)continue;const p=Array.isArray(i.content)?i.content:[{type:"text",text:String(i.content)}],y=[];let h=0;for(const g of p){const d=g;if(!(d.type!=="text"||!d.text?.trim())){if(N(d.text)){h++;continue}y.push(d.text)}}if(y.length>0)return{text:y.join(`
|
|
3
|
+
`),stopReason:o!==void 0?o:i.stop_reason??null};h>0&&o===void 0&&(o=i.stop_reason??null)}return o!==void 0?{text:"",stopReason:o,metaOnly:!0}:null}catch{return null}}function Y(s,a,e){const l=T(s,a),t=e>0?e:0;let n;try{const u=_(l).size;if(t>=u)return{segments:[],newOffset:t};const o=u-t,r=Buffer.alloc(o);n=J(l,"r");const f=L(n,r,0,o,t),c=r.subarray(0,f);let i=-1;for(let g=c.length-1;g>=0;g--)if(c[g]===10){i=g;break}if(i<0)return{segments:[],newOffset:t};const p=c.subarray(0,i+1),y=t+p.length,h=[];for(const g of p.toString("utf8").split(`
|
|
4
|
+
`)){const d=g.trim();if(!d)continue;let k;try{k=JSON.parse(d)}catch{continue}if(k.type!=="assistant")continue;const m=k.message;if(!m?.content)continue;const E=Array.isArray(m.content)?m.content:[{type:"text",text:String(m.content)}];for(const M of E){const x=M;x.type!=="text"||!x.text?.trim()||N(x.text)||h.push(x.text)}}return{segments:h,newOffset:y}}catch{return{segments:[],newOffset:t}}finally{if(n!==void 0)try{P(n)}catch{}}}function Z(s,a,e){const l=T(s,a);let t=null,n;try{const r=_(l);t=Date.now()-r.mtimeMs,n=b(l)}catch{return{lastStopReason:null,freshMs:null}}let u;e!==void 0&&e>0?u=e<n.length?n.subarray(e):Buffer.alloc(0):u=n.length>S?n.subarray(n.length-S):n;const o=u.toString("utf8").split(`
|
|
5
|
+
`);for(let r=o.length-1;r>=0;r--){const f=o[r].trim();if(!f)continue;let c;try{c=JSON.parse(f)}catch{continue}if(c.type!=="assistant")continue;const i=c.message;if(i)return{lastStopReason:i.stop_reason??null,freshMs:t}}return{lastStopReason:null,freshMs:t}}function G(s,a){const e=T(s,a);let l;try{l=b(e)}catch{return 0}const n=(l.length>S?l.subarray(l.length-S):l).toString("utf8").split(`
|
|
6
|
+
`),u=new Set,o=new Set,r=/<task-id>\s*([^<\s]+)\s*<\/task-id>/;for(const c of n){const i=c.trim();if(!i)continue;if(i.includes("task-notification")){const h=r.exec(i);h&&o.add(h[1]);continue}let p;try{p=JSON.parse(i)}catch{continue}const y=p.toolUseResult;y&&typeof y.backgroundTaskId=="string"&&u.add(y.backgroundTaskId)}let f=0;for(const c of u)o.has(c)||f++;return f}function K(s,a,e,l=!1){const t=T(s,a);try{const n=_(t);if(l&&Date.now()-n.mtimeMs<z)return!1;const u=b(t),r=(e>0&&e<u.length?u.subarray(e):u.length>S?u.subarray(u.length-S):u).toString("utf8").split(`
|
|
7
|
+
`);let f=!1,c=!1;for(const i of r){const p=i.trim();if(!p)continue;let y;try{y=JSON.parse(p)}catch{continue}if(y.type==="user"){const g=y.message?.content??y.content;(Array.isArray(g)?g:[]).some(m=>typeof m=="object"&&m!==null&&m.type==="tool_result")&&(f=!0,c=!1)}else y.type==="assistant"&&f&&(c=!0)}return f&&!c}catch{return!1}}export{G as countPendingBackgroundTasks,Q as extractLastAssistantEntry,Y as extractTextBlocksAfterOffset,K as hasTerminalToolResultAfterOffset,H as parseClaudeSessionUsage,Z as probeSessionTurnState,T as resolveSessionJsonlPath};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{log as o}from"../core/log/index.js";import{RespawnManager as d}from"./respawn-manager.js";import{EventQueue as p}from"./event-queue.js";class c extends Error{constructor(e){super(`adapter pool full (maxSize=${e})`),this.name="PoolFullError"}}const v=6e4;class m{slots=new Map;config;factory;sendAck;onEventState=null;onQueueComposing=null;onInternalError=null;onEventStarted=null;onEventDone=null;onSessionActivity=null;stopped=!1;sweepTimer=null;constructor(e,t,s){this.config=e,this.factory=t,this.sendAck=s}setEventStateHandler(e){this.onEventState=e}setQueueComposingHandler(e){this.onQueueComposing=e}setInternalErrorHandler(e){this.onInternalError=e}setEventStartedHandler(e){this.onEventStarted=e}setEventDoneHandler(e){this.onEventDone=e}setSessionActivityHandler(e){this.onSessionActivity=e}async deliverInboundEvent(e){const t=e.session_id,s=this.slots.get(t);if(s){s.startPromise&&await s.startPromise,s.lastActivityAt=Date.now(),this.sendAck(e.event_id,e.session_id),s.state==="stopped"&&o.info("adapter-pool",`Holding event ${e.event_id} in paused queue for restarting slot ${t}`),this.submitToSlotQueue(s,e);return}if(this.slots.size>=this.config.maxPoolSize&&!this.evictDeadSlot()&&!this.evictIdleSlot())throw new c(this.config.maxPoolSize);this.sendAck(e.event_id,e.session_id);const r=this.createSlot(t);try{await r.startPromise}catch(n){throw n}this.submitToSlotQueue(r,e)}submitToSlotQueue(e,t){e.eventQueue.submit(t)==="rejected"&&o.info("adapter-pool",`Event ${t.event_id} rejected by EventQueue (queue full)`)}eventComplete(e,t){const s=this.slots.get(t);s&&s.eventQueue.complete(e)}cancelEvent(e,t){const s=this.slots.get(t);return s?s.eventQueue.cancel(e):!1}removeQueuedEvent(e,t){const s=this.slots.get(e);return s?s.eventQueue.removeQueued(t):!1}clearQueue(e){const t=this.slots.get(e);return t?t.eventQueue.clear(e):[]}drainAllQueuedEvents(){const e=[];for(const t of this.slots.values())e.push(...t.eventQueue.drainQueuedForSession(t.sessionId));return e}drainQueuedForSession(e){if(!this.config.eventQueue)return[];const t=this.slots.get(e);return t?t.eventQueue.drainQueuedForSession(e):[]}getQueueSnapshot(e){if(!this.config.eventQueue)return null;const t=this.slots.get(e);return t?t.eventQueue.snapshot(e):null}deliverStopEvent(e,t){if(t){const s=this.slots.get(t);o.info("adapter-pool",`[stop-trace] pool.deliverStopEvent session=${t} event=${e} slotExists=${!!s} -> adapter.deliverStopEvent`),s?.adapter.deliverStopEvent(e,t);return}o.info("adapter-pool",`[stop-trace] pool.deliverStopEvent (broadcast, no session) event=${e} slots=${this.slots.size}`);for(const s of this.slots.values())s.adapter.deliverStopEvent(e)}collectActiveEventIds(){const e=[];for(const t of this.slots.values()){const s=t.adapter.getActiveEventIds;typeof s=="function"&&e.push(...s.call(t.adapter))}return e}clearActiveEventsForShutdown(){for(const e of this.slots.values()){const t=e.adapter.clearActiveEventForShutdown;typeof t=="function"&&t.call(e.adapter)}}async deliverLocalAction(e,t){const s=String((e.params??{}).session_id??"");let r=s?this.slots.get(s):void 0;if(!r&&s&&t?.autoCreateSlot&&!this.stopped&&(this.slots.size>=this.config.maxPoolSize&&(this.evictDeadSlot()||this.evictIdleSlotSync()),this.slots.size<this.config.maxPoolSize)){o.info("adapter-pool",`Auto-creating slot for session ${s} (local_action trigger)`);const i=this.createSlot(s);try{await i.startPromise,r=i}catch(u){o.error("adapter-pool",`Failed to auto-create slot for ${s}: ${u}`)}}return r?.adapter?.handleLocalAction?(r.lastActivityAt=Date.now(),r.adapter.handleLocalAction(e)):(await Promise.allSettled([...this.slots.values()].map(i=>i.adapter.handleLocalAction?.(e)??Promise.resolve({handled:!1,kind:""})))).find(i=>i.status==="fulfilled"&&i.value.handled)?.value??{handled:!1,kind:""}}getOrCreateSlot(e){const t=this.slots.get(e);if(t)return t;if(!this.stopped)return this.createSlot(e)}getSlot(e){return this.slots.get(e)}getAllSlots(){return[...this.slots.values()]}async removeSlot(e){const t=this.slots.get(e);t&&(this.onSessionActivity?.(e,!1),t.eventQueue.clear(e,"session slot removed"),t.eventQueue.destroy(),t.respawn.stopAll(),this.detachSlotEventListenersExceptDone(t),await t.adapter.stop().catch(()=>{}),t.adapter.removeAllListeners("eventDone"),this.slots.delete(e))}async stop(){this.stopped=!0,this.stopIdleSweep();const e=[...this.slots.values()];this.slots.clear(),await Promise.allSettled(e.map(async t=>{t.eventQueue.destroy(),t.respawn.stopAll(),this.detachAllSlotEventListeners(t),await t.adapter.stop().catch(()=>{})}))}startIdleSweep(){this.stopIdleSweep(),this.sweepTimer=setInterval(()=>this.sweepIdle(),v)}stopIdleSweep(){this.sweepTimer&&(clearInterval(this.sweepTimer),this.sweepTimer=null)}sweepIdle(){const e=Date.now();for(const[t,s]of this.slots){if(s.state==="stopped"&&s.respawn.exhausted){o.info("adapter-pool",`Removing dead slot for session ${t} (sweep)`),this.removeSlot(t).catch(()=>{});continue}if(s.state==="ready"){if(s.adapter.getStatus().busy){s.lastActivityAt=e;continue}e-s.lastActivityAt<=this.config.idleTimeoutMs||(s.adapter.hasBackgroundWork?s.adapter.hasBackgroundWork().then(r=>{r?(o.info("adapter-pool",`Deferred eviction for session ${t}: background grandchild processes detected`),s.lastActivityAt=Date.now()):(o.info("adapter-pool",`Evicting idle slot for session ${t}`),this.removeSlot(t).catch(n=>{o.error("adapter-pool",`Failed to evict slot ${t}: ${n}`)}))}).catch(()=>{o.info("adapter-pool",`Evicting idle slot for session ${t} (background probe failed)`),this.removeSlot(t).catch(r=>{o.error("adapter-pool",`Failed to evict slot ${t}: ${r}`)})}):(o.info("adapter-pool",`Evicting idle slot for session ${t}`),this.removeSlot(t).catch(r=>{o.error("adapter-pool",`Failed to evict slot ${t}: ${r}`)})))}}}evictDeadSlot(){for(const[e,t]of this.slots)if(t.state==="stopped"&&t.respawn.exhausted)return o.info("adapter-pool",`Removing dead slot for session ${e} (exhausted respawn)`),this.removeSlot(e).catch(()=>{}),!0;return!1}evictIdleSlot(){let e=null;for(const t of this.slots.values())t.state==="ready"&&(t.adapter.getStatus().busy||(!e||t.lastActivityAt<e.lastActivityAt)&&(e=t));return e?(o.info("adapter-pool",`Evicting idle slot for session ${e.sessionId} (pool full)`),this.removeSlot(e.sessionId).catch(()=>{}),!0):!1}evictIdleSlotSync(){let e=null,t="";for(const[s,r]of this.slots)r.state==="ready"&&(r.adapter.getStatus().busy||(!e||r.lastActivityAt<e.lastActivityAt)&&(e=r,t=s));return!e||!t?!1:(o.info("adapter-pool",`Evicting idle slot for session ${t} (pool full, sync)`),this.slots.delete(t),e.eventQueue.clear(t,"session slot removed"),e.eventQueue.destroy(),e.respawn.stopAll(),this.detachAllSlotEventListeners(e),e.adapter.stop().catch(()=>{}),!0)}getStatus(){let e=0,t=0;for(const s of this.slots.values())s.state==="ready"&&(e++,s.adapter.getStatus().busy&&t++);return{total:this.slots.size,ready:e,busy:t}}createSlot(e){const t=this.factory(e),s=new d,r=this.createEventQueue(e),n={sessionId:e,adapter:t,respawn:s,state:"starting",lastActivityAt:Date.now(),startPromise:null,eventQueue:r};this.slots.set(e,n);const a=(async()=>{try{this.wireSlotEvents(n),await t.start(),n.state="ready"}catch(i){throw this.detachAllSlotEventListeners(n),this.slots.delete(e),i}finally{n.startPromise=null}})();return n.startPromise=a,s.startHealthCheck(this.slotRespawnCtx(n)),n}wireSlotEvents(e){e.adapter.on("exit",t=>{this.stopped||(o.error("adapter-pool",`Slot ${e.sessionId} adapter exited (code=${t})`),e.state="stopped",e.eventQueue.pause("restart"),e.respawn.scheduleRespawn(this.slotRespawnCtx(e)))}),e.adapter.on("stuck",()=>{this.stopped||this.slots.get(e.sessionId)===e&&(o.error("adapter-pool",`Slot ${e.sessionId} adapter stuck, triggering respawn`),e.state="stopped",e.eventQueue.pause("restart"),e.respawn.scheduleRespawn(this.slotRespawnCtx(e)))}),e.adapter.on?.("error",t=>{if(this.stopped)return;const s=t instanceof Error?t.message:String(t);o.error("adapter-pool",`Slot ${e.sessionId} adapter error: ${s}`)}),e.adapter.on("internalError",t=>{if(!this.stopped){if(this.slots.get(e.sessionId)!==e){o.warn("adapter-pool",`Ignored internalError from detached slot session=${e.sessionId} event=${t.eventId}`);return}if(!this.onInternalError){o.warn("adapter-pool",`Slot ${e.sessionId} emitted internalError but no handler registered: event=${t.eventId} msg=${t.errorMsg}`);return}this.onInternalError(t)}}),e.adapter.on("eventStarted",(t,s)=>{this.stopped||this.slots.get(e.sessionId)===e&&this.onEventStarted?.(t,s)}),e.adapter.on("eventDone",t=>{this.stopped||this.slots.get(e.sessionId)===e&&this.onEventDone?.(t,e.sessionId)}),e.adapter.on("sessionActivity",(t,s,r)=>{this.stopped||this.slots.get(e.sessionId)===e&&this.onSessionActivity?.(t||e.sessionId,s,r)}),e.adapter.on("pauseIntake",t=>{this.stopped||this.slots.get(e.sessionId)===e&&e.eventQueue.pause(t)}),e.adapter.on("resumeIntake",t=>{this.stopped||this.slots.get(e.sessionId)===e&&e.eventQueue.resume(t)})}detachAllSlotEventListeners(e){e.adapter.removeAllListeners("exit"),e.adapter.removeAllListeners("error"),e.adapter.removeAllListeners("stuck"),e.adapter.removeAllListeners("internalError"),e.adapter.removeAllListeners("eventStarted"),e.adapter.removeAllListeners("eventDone"),e.adapter.removeAllListeners("sessionActivity"),e.adapter.removeAllListeners("pauseIntake"),e.adapter.removeAllListeners("resumeIntake")}detachSlotEventListenersExceptDone(e){e.adapter.removeAllListeners("exit"),e.adapter.removeAllListeners("error"),e.adapter.removeAllListeners("stuck"),e.adapter.removeAllListeners("internalError"),e.adapter.removeAllListeners("eventStarted"),e.adapter.removeAllListeners("sessionActivity"),e.adapter.removeAllListeners("pauseIntake"),e.adapter.removeAllListeners("resumeIntake")}slotRespawnCtx(e){const t=this,s=e.sessionId;return{name:`slot-${s}`,get stopped(){return t.stopped||!t.slots.has(s)},get agentAlive(){return e.adapter.isAlive()},pingAgent:r=>e.adapter.ping(r),onRespawnNeeded:async()=>{e.eventQueue.pause("restart"),this.detachAllSlotEventListeners(e),await e.adapter.stop().catch(()=>{}),e.adapter=t.factory(s),e.state="starting",t.wireSlotEvents(e),await e.adapter.start(),e.state="ready",e.respawn.resetAttempts(),e.respawn.resetHealthFailures(),e.respawn.stopSlowRetry(),e.respawn.startHealthCheck(t.slotRespawnCtx(e)),e.eventQueue.resume("compaction"),e.eventQueue.resume("barrier"),e.eventQueue.resume("restart")},onCleanupAgent:()=>{"cleanup"in e.adapter&&e.adapter.cleanup()},onAbortActiveRun:r=>{}}}createEventQueue(e){const t=this.config.eventQueue??{maxConcurrent:1,maxQueued:5,queueTimeoutMs:3e5,cancelableQueued:!0,cancelableRunning:!0},s={onDeliver:r=>{const n=this.slots.get(e);n&&n.adapter.deliverInboundEvent(r)},onStateChange:(r,n,a,i)=>{this.onEventState?.(r,n,a,i)},onCancelRunning:r=>{this.deliverStopEvent(r,e)},onRejected:(r,n)=>{this.onEventState?.(r.event_id,r.session_id,"failed",{reason:n})},onComposing:(r,n,a)=>{this.onQueueComposing?.(r,n,a)}};return new p(t,s)}}export{m as AdapterPool,c as PoolFullError};
|
|
1
|
+
import{log as o}from"../core/log/index.js";import{RespawnManager as d}from"./respawn-manager.js";import{EventQueue as p}from"./event-queue.js";class c extends Error{constructor(e){super(`adapter pool full (maxSize=${e})`),this.name="PoolFullError"}}const v=6e4;class g{slots=new Map;config;factory;sendAck;onEventState=null;onQueueComposing=null;onInternalError=null;onEventStarted=null;onEventDone=null;onSessionActivity=null;stopped=!1;sweepTimer=null;constructor(e,t,s){this.config=e,this.factory=t,this.sendAck=s}setEventStateHandler(e){this.onEventState=e}setQueueComposingHandler(e){this.onQueueComposing=e}setInternalErrorHandler(e){this.onInternalError=e}setEventStartedHandler(e){this.onEventStarted=e}setEventDoneHandler(e){this.onEventDone=e}setSessionActivityHandler(e){this.onSessionActivity=e}async deliverInboundEvent(e){const t=e.session_id,s=this.slots.get(t);if(s){s.startPromise&&await s.startPromise,s.lastActivityAt=Date.now(),this.sendAck(e.event_id,e.session_id),s.state==="stopped"&&o.info("adapter-pool",`Holding event ${e.event_id} in paused queue for restarting slot ${t}`),this.submitToSlotQueue(s,e);return}if(this.slots.size>=this.config.maxPoolSize&&!this.evictDeadSlot()&&!this.evictIdleSlot())throw new c(this.config.maxPoolSize);this.sendAck(e.event_id,e.session_id);const r=this.createSlot(t);try{await r.startPromise}catch(n){throw n}this.submitToSlotQueue(r,e)}submitToSlotQueue(e,t){e.eventQueue.submit(t)==="rejected"&&o.info("adapter-pool",`Event ${t.event_id} rejected by EventQueue (queue full)`)}eventComplete(e,t){const s=this.slots.get(t);s&&s.eventQueue.complete(e)}cancelEvent(e,t){const s=this.slots.get(t);return s?s.eventQueue.cancel(e):!1}removeQueuedEvent(e,t){const s=this.slots.get(e);return s?s.eventQueue.removeQueued(t):!1}clearQueue(e){const t=this.slots.get(e);return t?t.eventQueue.clear(e):[]}drainAllQueuedEvents(){const e=[];for(const t of this.slots.values())e.push(...t.eventQueue.drainQueuedForSession(t.sessionId));return e}drainQueuedForSession(e){if(!this.config.eventQueue)return[];const t=this.slots.get(e);return t?t.eventQueue.drainQueuedForSession(e):[]}getQueueSnapshot(e){if(!this.config.eventQueue)return null;const t=this.slots.get(e);return t?t.eventQueue.snapshot(e):null}deliverStopEvent(e,t){if(t){const s=this.slots.get(t);o.info("adapter-pool",`[stop-trace] pool.deliverStopEvent session=${t} event=${e} slotExists=${!!s} -> adapter.deliverStopEvent`),s?.adapter.deliverStopEvent(e,t);return}o.info("adapter-pool",`[stop-trace] pool.deliverStopEvent (broadcast, no session) event=${e} slots=${this.slots.size}`);for(const s of this.slots.values())s.adapter.deliverStopEvent(e)}collectActiveEventIds(){const e=[];for(const t of this.slots.values()){const s=t.adapter.getActiveEventIds;typeof s=="function"&&e.push(...s.call(t.adapter))}return e}clearActiveEventsForShutdown(){for(const e of this.slots.values()){const t=e.adapter.clearActiveEventForShutdown;typeof t=="function"&&t.call(e.adapter)}}async deliverLocalAction(e,t){const s=String((e.params??{}).session_id??"");let r=s?this.slots.get(s):void 0;if(!r&&s&&t?.autoCreateSlot&&!this.stopped&&(this.slots.size>=this.config.maxPoolSize&&(this.evictDeadSlot()||this.evictIdleSlotSync()),this.slots.size<this.config.maxPoolSize)){o.info("adapter-pool",`Auto-creating slot for session ${s} (local_action trigger)`);const i=this.createSlot(s);try{await i.startPromise,r=i}catch(u){o.error("adapter-pool",`Failed to auto-create slot for ${s}: ${u}`)}}return r?.adapter?.handleLocalAction?(r.lastActivityAt=Date.now(),r.adapter.handleLocalAction(e)):(await Promise.allSettled([...this.slots.values()].map(i=>i.adapter.handleLocalAction?.(e)??Promise.resolve({handled:!1,kind:""})))).find(i=>i.status==="fulfilled"&&i.value.handled)?.value??{handled:!1,kind:""}}getOrCreateSlot(e){const t=this.slots.get(e);if(t)return t;if(!this.stopped)return this.createSlot(e)}getSlot(e){return this.slots.get(e)}getAllSlots(){return[...this.slots.values()]}async removeSlot(e){const t=this.slots.get(e);t&&(this.onSessionActivity?.(e,!1),t.eventQueue.clear(e,"session slot removed"),t.eventQueue.destroy(),t.respawn.stopAll(),this.detachSlotEventListenersExceptDone(t),await t.adapter.stop().catch(()=>{}),t.adapter.removeAllListeners("eventDone"),this.slots.delete(e))}async stop(){this.stopped=!0,this.stopIdleSweep();const e=[...this.slots.values()];this.slots.clear(),await Promise.allSettled(e.map(async t=>{t.eventQueue.destroy(),t.respawn.stopAll(),this.detachAllSlotEventListeners(t),await t.adapter.stop().catch(()=>{})}))}startIdleSweep(){this.stopIdleSweep(),this.sweepTimer=setInterval(()=>this.sweepIdle(),v)}stopIdleSweep(){this.sweepTimer&&(clearInterval(this.sweepTimer),this.sweepTimer=null)}sweepIdle(){const e=Date.now();for(const[t,s]of this.slots){if(s.state==="stopped"&&s.respawn.exhausted){o.info("adapter-pool",`Removing dead slot for session ${t} (sweep)`),this.removeSlot(t).catch(()=>{});continue}if(s.state==="ready"){if(s.adapter.getStatus().busy){s.lastActivityAt=e;continue}e-s.lastActivityAt<=this.config.idleTimeoutMs||(s.adapter.hasBackgroundWork?s.adapter.hasBackgroundWork().then(r=>{r?(o.info("adapter-pool",`Deferred eviction for session ${t}: background grandchild processes detected`),s.lastActivityAt=Date.now()):(o.info("adapter-pool",`Evicting idle slot for session ${t}`),this.removeSlot(t).catch(n=>{o.error("adapter-pool",`Failed to evict slot ${t}: ${n}`)}))}).catch(()=>{o.info("adapter-pool",`Evicting idle slot for session ${t} (background probe failed)`),this.removeSlot(t).catch(r=>{o.error("adapter-pool",`Failed to evict slot ${t}: ${r}`)})}):(o.info("adapter-pool",`Evicting idle slot for session ${t}`),this.removeSlot(t).catch(r=>{o.error("adapter-pool",`Failed to evict slot ${t}: ${r}`)})))}}}evictDeadSlot(){for(const[e,t]of this.slots)if(t.state==="stopped"&&t.respawn.exhausted)return o.info("adapter-pool",`Removing dead slot for session ${e} (exhausted respawn)`),this.removeSlot(e).catch(()=>{}),!0;return!1}evictIdleSlot(){let e=null;for(const t of this.slots.values())t.state==="ready"&&(t.adapter.getStatus().busy||(!e||t.lastActivityAt<e.lastActivityAt)&&(e=t));return e?(o.info("adapter-pool",`Evicting idle slot for session ${e.sessionId} (pool full)`),this.removeSlot(e.sessionId).catch(()=>{}),!0):!1}evictIdleSlotSync(){let e=null,t="";for(const[s,r]of this.slots)r.state==="ready"&&(r.adapter.getStatus().busy||(!e||r.lastActivityAt<e.lastActivityAt)&&(e=r,t=s));return!e||!t?!1:(o.info("adapter-pool",`Evicting idle slot for session ${t} (pool full, sync)`),this.slots.delete(t),e.eventQueue.clear(t,"session slot removed"),e.eventQueue.destroy(),e.respawn.stopAll(),this.detachAllSlotEventListeners(e),e.adapter.stop().catch(()=>{}),!0)}getStatus(){let e=0,t=0;for(const s of this.slots.values())s.state==="ready"&&(e++,s.adapter.getStatus().busy&&t++);return{total:this.slots.size,ready:e,busy:t}}hasPendingWork(){for(const e of this.slots.values())if(e.eventQueue.runningCount>0||e.eventQueue.queuedCount>0||e.state==="ready"&&e.adapter.getStatus().busy)return!0;return!1}createSlot(e){const t=this.factory(e),s=new d,r=this.createEventQueue(e),n={sessionId:e,adapter:t,respawn:s,state:"starting",lastActivityAt:Date.now(),startPromise:null,eventQueue:r};this.slots.set(e,n);const a=(async()=>{try{this.wireSlotEvents(n),await t.start(),n.state="ready"}catch(i){throw this.detachAllSlotEventListeners(n),this.slots.delete(e),i}finally{n.startPromise=null}})();return n.startPromise=a,s.startHealthCheck(this.slotRespawnCtx(n)),n}wireSlotEvents(e){e.adapter.on("exit",t=>{this.stopped||(o.error("adapter-pool",`Slot ${e.sessionId} adapter exited (code=${t})`),e.state="stopped",e.eventQueue.pause("restart"),e.respawn.scheduleRespawn(this.slotRespawnCtx(e)))}),e.adapter.on("stuck",()=>{this.stopped||this.slots.get(e.sessionId)===e&&(o.error("adapter-pool",`Slot ${e.sessionId} adapter stuck, triggering respawn`),e.state="stopped",e.eventQueue.pause("restart"),e.respawn.scheduleRespawn(this.slotRespawnCtx(e)))}),e.adapter.on?.("error",t=>{if(this.stopped)return;const s=t instanceof Error?t.message:String(t);o.error("adapter-pool",`Slot ${e.sessionId} adapter error: ${s}`)}),e.adapter.on("internalError",t=>{if(!this.stopped){if(this.slots.get(e.sessionId)!==e){o.warn("adapter-pool",`Ignored internalError from detached slot session=${e.sessionId} event=${t.eventId}`);return}if(!this.onInternalError){o.warn("adapter-pool",`Slot ${e.sessionId} emitted internalError but no handler registered: event=${t.eventId} msg=${t.errorMsg}`);return}this.onInternalError(t)}}),e.adapter.on("eventStarted",(t,s)=>{this.stopped||this.slots.get(e.sessionId)===e&&this.onEventStarted?.(t,s)}),e.adapter.on("eventDone",t=>{this.stopped||this.slots.get(e.sessionId)===e&&this.onEventDone?.(t,e.sessionId)}),e.adapter.on("sessionActivity",(t,s,r)=>{this.stopped||this.slots.get(e.sessionId)===e&&this.onSessionActivity?.(t||e.sessionId,s,r)}),e.adapter.on("pauseIntake",t=>{this.stopped||this.slots.get(e.sessionId)===e&&e.eventQueue.pause(t)}),e.adapter.on("resumeIntake",t=>{this.stopped||this.slots.get(e.sessionId)===e&&e.eventQueue.resume(t)})}detachAllSlotEventListeners(e){e.adapter.removeAllListeners("exit"),e.adapter.removeAllListeners("error"),e.adapter.removeAllListeners("stuck"),e.adapter.removeAllListeners("internalError"),e.adapter.removeAllListeners("eventStarted"),e.adapter.removeAllListeners("eventDone"),e.adapter.removeAllListeners("sessionActivity"),e.adapter.removeAllListeners("pauseIntake"),e.adapter.removeAllListeners("resumeIntake")}detachSlotEventListenersExceptDone(e){e.adapter.removeAllListeners("exit"),e.adapter.removeAllListeners("error"),e.adapter.removeAllListeners("stuck"),e.adapter.removeAllListeners("internalError"),e.adapter.removeAllListeners("eventStarted"),e.adapter.removeAllListeners("sessionActivity"),e.adapter.removeAllListeners("pauseIntake"),e.adapter.removeAllListeners("resumeIntake")}slotRespawnCtx(e){const t=this,s=e.sessionId;return{name:`slot-${s}`,get stopped(){return t.stopped||!t.slots.has(s)},get agentAlive(){return e.adapter.isAlive()},pingAgent:r=>e.adapter.ping(r),onRespawnNeeded:async()=>{e.eventQueue.pause("restart"),this.detachAllSlotEventListeners(e),await e.adapter.stop().catch(()=>{}),e.adapter=t.factory(s),e.state="starting",t.wireSlotEvents(e),await e.adapter.start(),e.state="ready",e.respawn.resetAttempts(),e.respawn.resetHealthFailures(),e.respawn.stopSlowRetry(),e.respawn.startHealthCheck(t.slotRespawnCtx(e)),e.eventQueue.resume("compaction"),e.eventQueue.resume("barrier"),e.eventQueue.resume("restart")},onCleanupAgent:()=>{"cleanup"in e.adapter&&e.adapter.cleanup()},onAbortActiveRun:r=>{}}}createEventQueue(e){const t=this.config.eventQueue??{maxConcurrent:1,maxQueued:5,queueTimeoutMs:3e5,cancelableQueued:!0,cancelableRunning:!0},s={onDeliver:r=>{const n=this.slots.get(e);n&&n.adapter.deliverInboundEvent(r)},onStateChange:(r,n,a,i)=>{this.onEventState?.(r,n,a,i)},onCancelRunning:r=>{this.deliverStopEvent(r,e)},onRejected:(r,n)=>{this.onEventState?.(r.event_id,r.session_id,"failed",{reason:n})},onComposing:(r,n,a)=>{this.onQueueComposing?.(r,n,a)}};return new p(t,s)}}export{g as AdapterPool,c as PoolFullError};
|
package/dist/bridge/bridge.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import T from"node:path";import{realpath as j,stat as H}from"node:fs/promises";import{tmpdir as V}from"node:os";import{randomUUID as M}from"node:crypto";import{ConnectionManager as Y}from"../core/aibot/index.js";import{ClaudeAdapter as y}from"../adapter/claude/index.js";import{CodexAdapter as J}from"../adapter/codex/index.js";import{PiAdapter as X}from"../adapter/pi/index.js";import{AcpAdapter as A}from"../adapter/acp/index.js";import{OpenHumanAdapter as Z}from"../adapter/openhuman/index.js";import{CursorAdapter as I}from"../adapter/cursor/index.js";import{CodeWhaleAdapter as U}from"../adapter/codewhale/index.js";import{OpenCodeAdapter as ee}from"../adapter/opencode/index.js";import{AgyAdapter as O}from"../adapter/agy/index.js";import{getCachedAgyModels as te}from"../adapter/agy/model-list.js";import{getCachedAgyQuotaInfo as ie}from"../adapter/agy/quota.js";import{LOCAL_ACTION_ERROR_CODES as x,LOCAL_ACTION_TYPES as S,SESSION_CONTROL_ERROR_CODES as g,SESSION_CONTROL_VERBS as p,SESSION_MODE_IDS as C}from"../adapter/claude/protocol-contract.js";import{parseClaudeSessionUsage as ne}from"../adapter/claude/usage-parser.js";import{resolveSkillScanMode as se}from"../adapter/claude/skill-scanner.js";import{buildReportedSkills as oe}from"../default-skills/index.js";import{fetchAvailableModels as D,getCachedModels as Q,readSettingsEnv as re}from"../adapter/claude/model-list.js";import{parseAcpSessionUsage as ae}from"../adapter/acp/usage-parser.js";import{applyProxyEnv as de,getProxyManager as ce}from"../core/proxy/index.js";import{buildProviderEnv as le}from"../core/config/provider-env.js";import{parseCodexSessionUsage as ue}from"../adapter/codex/usage-parser.js";import{readCodexProviderSettings as he}from"../adapter/codex/codex-trust.js";import{scanCodexSessions as ge}from"../adapter/codex/session-scanner.js";import{scanClaudeSessions as me}from"../adapter/claude/session-scanner.js";import{scanAcpSessions as pe}from"../adapter/acp/session-scanner.js";import{parsePiSessionUsage as fe}from"../adapter/pi/usage-parser.js";import{SessionScanCache as L,resolveCodexLeafDirs as _e,resolveClaudeLeafDirs as ve,resolveAcpLeafDirs as be}from"./session-scan-cache.js";import{log as u,ConversationLog as Se,AgentApiPacketLog as we,BridgeEventLog as Ce}from"../core/log/index.js";import{RevokeHandler as Ae}from"./revoke-handler.js";import{AdapterPool as ke,PoolFullError as Re}from"./adapter-pool.js";import{parseSessionControlCommand as Ee,handleSessionControlCommand as q,handleSessionControlLocalAction as $e}from"./session-controller.js";import{handleAcpSetModel as W,handleAcpSetMode as N,resolveAcpInitialDefaults as ye}from"./acp-toolbar-persist.js";import{SessionBindingStore as xe}from"../core/persistence/session-binding-store.js";import{handleFileListAction as Te,handleCreateFolderAction as Le,serveLocalFile as Pe,realHomeDir as K}from"../core/files/index.js";import{getMachineName as He}from"../core/util/index.js";import{uploadReplyFileToAgentMedia as Me}from"../core/protocol/agent-api-media.js";import{AllowlistGate as Ie}from"../core/access/allowlist-gate.js";import{ActiveEventStore as De}from"../core/persistence/active-event-store.js";import{DEFAULT_CONNECTOR_RUNTIME_CONFIG as Qe,applyConnectorRuntimeConfigPatch as Be,extractConnectorRuntimeConfigPatch as Fe}from"./runtime-config.js";import{SendController as Ue}from"./send-controller.js";import{queryProviderQuota as z}from"../core/provider-quota/index.js";import{queryKiroQuota as B}from"../core/provider-quota/kiro.js";import{buildToolUseCard as E,buildToolResultCard as $,buildLocalGrixCardLink as Oe}from"./tool-card-utils.js";import{DeferredEventManager as qe}from"./deferred-events.js";import{buildAgentProbeResult as We,PROBE_CACHE_TTL_STATIC_MS as Ne,PROBE_CACHE_TTL_FULL_MS as Ke}from"./probe-helper.js";const ze=600*1e3,Ge=60*1e3,G=30*1e3,je=10*1e3,P=new Set(["claude","acp","agy","cursor","codex"]),Ve=new Set(["claude","codex","cursor","codewhale","opencode","pi","openhuman","agy","acp"]),F=3;class zt{config;name;aibotHandle;aibotConfig;pool;stopped=!1;revokeHandler=new Ae;sessionBindings=new Map;deferredMgr;sendCtrl=new Ue(Qe);bindingStore;globalConfigStore;upgradeTrigger=null;agentDeletedHandler=null;shareSetHandler=null;providerConfigHandler=null;agentProfile={agentName:"",introduction:""};allowlistGate;activeEventStore;cachedRateLimits=null;cachedRateLimitsSampledAtMs=null;cachedCodexContextWindow=null;cachedCodexTokenUsage=null;cachedCodexUsageSampledAtMs=null;cachedAcpContextWindow=null;cachedAcpContextWindowSampledAtMs=null;cachedClaudeRateLimitState=null;cachedProviderQuota=null;cachedProviderQuotaSampledAtMs=null;claudeWorkerStatus=new Map;lastReportedSkillsHash="";conversationLog=null;packetLog=null;kiroQuotaTimer=null;eventSessionIndex=new Map;inflightEvents=new Map;restartCount=new Map;selfDrivenSessions=new Set;selfDrivenLabels=new Map;probeCache=new Map;sessionScanCache;isRateLimitsCacheFresh(e){if(!Number.isFinite(e))return!1;const o=Number(e);return o>0&&Date.now()-o<=Ge}async maybeQueryProviderQuota(){if(this.config.aibot.clientType==="kiro"){if(this.isRateLimitsCacheFresh(this.cachedProviderQuotaSampledAtMs)&&this.cachedProviderQuota)return this.cachedProviderQuota;try{const t=await B();return this.cachedProviderQuota=t,this.cachedProviderQuotaSampledAtMs=Date.now(),u.info(this.name,`[provider-quota] kiro queried: success=${t.success}`+(t.balance?` balance=${t.balance.remaining} ${t.balance.unit}`:"")+(t.planName?` plan=${t.planName}`:"")+(t.error?` error=${t.error}`:"")),t}catch(t){return u.warn(this.name,`[provider-quota] kiro query failed: ${t instanceof Error?t.message:String(t)}`),null}}let e=this.config.providerBaseUrl,o=this.config.providerApiKey;if((!e||!o)&&(this.config.adapterType??"acp")==="claude"){const t=re();e||(e=(t.ANTHROPIC_BASE_URL??"").trim()||void 0),o||(o=(t.ANTHROPIC_API_KEY??"").trim()||(t.ANTHROPIC_AUTH_TOKEN??"").trim()||void 0)}if((!e||!o)&&(this.config.adapterType??"acp")==="codex"){const t=he();!e&&t.baseUrl&&(e=t.baseUrl),!o&&t.apiKey&&(o=t.apiKey)}if(!e||!o)return null;if(this.isRateLimitsCacheFresh(this.cachedProviderQuotaSampledAtMs)&&this.cachedProviderQuota)return this.cachedProviderQuota;try{const t=await z(e,o);return this.cachedProviderQuota=t,this.cachedProviderQuotaSampledAtMs=Date.now(),u.info(this.name,`[provider-quota] queried: provider=${t.provider} success=${t.success}`+(t.tiers.length>0?` tiers=${t.tiers.map(n=>`${n.name}=${n.usedPercent}%`).join(",")}`:"")+(t.balance?` balance=${t.balance.remaining} ${t.balance.unit}`:"")+(t.error?` error=${t.error}`:"")),t}catch(t){return u.warn(this.name,`[provider-quota] query failed: ${t instanceof Error?t.message:String(t)}`),null}}startKiroQuotaTimer(){this.kiroQuotaTimer||(this.kiroQuotaTimer=setInterval(()=>{if(this.stopped){this.stopKiroQuotaTimer();return}this.refreshAndPushKiroQuota().catch(()=>{})},G),u.info(this.name,`[kiro-quota-timer] started (interval=${G}ms)`))}stopKiroQuotaTimer(){this.kiroQuotaTimer&&(clearInterval(this.kiroQuotaTimer),this.kiroQuotaTimer=null,u.info(this.name,"[kiro-quota-timer] stopped"))}async refreshAndPushKiroQuota(){try{const e=await B();this.cachedProviderQuota=e,this.cachedProviderQuotaSampledAtMs=Date.now(),e.success&&this.pushKiroQuotaToBindings(e)}catch{}}pushKiroQuotaToBindings(e){const o=this.providerQuotaToRateLimits(e);for(const[t,n]of this.sessionBindings.entries()){if(!n)continue;const i={provider_quota:e};o&&(i.rate_limits=o);const s=this.cachedAcpContextWindow;s&&("usedPercentage"in s?i.context_window={usedPercentage:s.usedPercentage,remainingPercentage:100-s.usedPercentage}:i.context_window=s),this.aibotHandle.sendUpdateBindingCard({session_id:t,worker_status:"ready",cwd:n,meta:i})}}getFreshClaudeRateLimitState(){const e=this.cachedClaudeRateLimitState;return e&&this.isRateLimitsCacheFresh(e.sampledAt)?e:null}getFreshCodexGlobalRateLimitCache(){const e=this.cachedRateLimits,o=this.cachedCodexContextWindow,t=this.cachedCodexTokenUsage;return{sampledAt:Math.max(this.cachedRateLimitsSampledAtMs??0,this.cachedCodexUsageSampledAtMs??0)||null,rateLimits:e,contextWindow:o,tokenUsage:t,hasData:!!(e||o||t)}}getStatus(){const e=this.pool?.getStatus()??{total:0,ready:0,busy:0};return{name:this.name,alive:!this.stopped,busy:e.busy>0,exhausted:this.pool?[...this.pool.getAllSlots()].some(o=>o.respawn.exhausted):!1,adapterType:this.config.adapterType??"acp",clientType:this.config.aibot.clientType,pool:e}}async probe(e={}){const o=e.conversation?"full":"static",t=e.conversation?Ke:Ne;if(!e.fresh){const a=this.probeCache.get(o);if(a&&Date.now()-a.sampledAt<t)return{...a.result,cached:!0}}const n=this.config.adapterType??"acp",i=this.createAdapter(n,"__probe__"),s=e.conversation&&n==="acp"?()=>this.runAcpConversationProbe(i,e.timeoutMs??1e4):void 0;let r;try{r=await We({adapter:i,agentName:this.name,clientType:this.config.aibot.clientType,adapterType:n,providerBaseUrl:this.config.providerBaseUrl??null,opts:e,launchConversationProbe:s})}finally{i.stop().catch(()=>{})}return this.probeCache.set(o,{result:r,sampledAt:r.probed_at}),r}async runAcpConversationProbe(e,o){const t=Date.now(),n="__probe__",i=`probe-${Date.now()}-${Math.random().toString(36).slice(2,8)}`,s=this.config.agent.cwd||V(),r=()=>Date.now()-t;try{await e.start()}catch(h){return{attempted:!0,ok:!1,latency_ms:r(),error:{code:"conversation_failed",message:`start failed: ${h instanceof Error?h.message:String(h)}`}}}if(!e.isAlive())return{attempted:!0,ok:!1,latency_ms:r(),error:{code:"process_not_started",message:"agent process not alive"}};if(e instanceof A)try{await e.bindSession(n,s)}catch{}let a=null;const d=new Promise(h=>{a=m=>{m===i&&h()},e.on("eventDone",a)});e.deliverInboundEvent({event_id:i,session_id:n,content:"ping",msg_id:i}),e.deliverStopEvent(i,n);let c=null;const l=await Promise.race([d.then(()=>!1),new Promise(h=>{c=setTimeout(()=>h(!0),o)})]);return c&&clearTimeout(c),a&&e.removeListener("eventDone",a),l?{attempted:!0,ok:!1,latency_ms:r(),error:{code:"conversation_timeout",message:`no eventDone within ${o}ms`}}:{attempted:!0,ok:!0,latency_ms:r()}}constructor(e,o){this.config=e,this.name=e.name;const t=e.adapterType??"acp";this.aibotConfig={...e.aibot,...t==="claude"?{localActions:e.aibot.localActions??["session_control","claude_interaction_reply","get_session_usage","get_rate_limits","set_model","set_mode","thread_compact","get_agent_global_config"]}:{}},e.eventQueue&&(this.aibotConfig.concurrency={max_concurrent:e.eventQueue.maxConcurrent,max_queued:e.eventQueue.maxQueued,queue_timeout_ms:e.eventQueue.queueTimeoutMs,cancelable_queued:e.eventQueue.cancelableQueued,cancelable_running:e.eventQueue.cancelableRunning}),this.conversationLog=e.logDir?new Se(e.logDir):null,this.packetLog=e.logDir?new we(e.logDir):null,this.bindingStore=new xe(e.bindingsPath),this.bindingStore.load(),this.globalConfigStore=o??null,this.deferredMgr=new qe(this.name),this.allowlistGate=e.allowlistPath?new Ie(e.allowlistPath):null,this.activeEventStore=e.activeEventStorePath?new De(e.activeEventStorePath):null,t==="codex"?this.sessionScanCache=new L(ge,_e):t==="claude"?this.sessionScanCache=new L(me,ve):t==="agy"?this.sessionScanCache=new L(()=>[],()=>[]):this.sessionScanCache=new L(pe,be)}async start(){(this.config.adapterType??"acp")==="claude"&&(await D().catch(()=>{}),this.maybeQueryProviderQuota().catch(()=>{})),this.config.aibot.clientType==="kiro"&&(this.maybeQueryProviderQuota().catch(()=>{}),this.startKiroQuotaTimer()),(this.config.adapterType??"acp")==="codex"&&this.maybeQueryProviderQuota().catch(()=>{}),await this.connectAibot(),this.sendCtrl.bind(this.aibotHandle);const e=this.config.adapterType??"acp";this.pool=new ke({maxPoolSize:this.config.poolMaxSize??20,idleTimeoutMs:this.config.poolIdleTimeoutMs??18e5,eventQueue:this.config.eventQueue},o=>{const t=this.createAdapter(e,o);return t instanceof A&&t.on("acpSessionReady",n=>{this.bindingStore.setAcpSessionId(o,n),this.sessionScanCache.invalidate()}),t},(o,t)=>{this.aibotHandle.sendEventAck({event_id:o,session_id:t,received_at:Date.now()})}),this.pool.setEventStateHandler((o,t,n,i)=>{u.info(this.name,`[queue-debug] send event_state session=${t} event=${o} state=${n} queue_pos=${i?.queue_position??""} queue_total=${i?.queue_total??""}`),this.aibotHandle.sendEventState({event_id:o,session_id:t,state:n,content_preview:i?.content_preview,queue_position:i?.queue_position,queue_total:i?.queue_total,actions:i?.actions,reason:i?.reason,updated_at:Date.now()}),this.pushQueueSnapshotForSession(t),(n==="canceled"||n==="failed")&&this.aibotHandle.sendEventResult({event_id:o,status:n==="canceled"?"canceled":"failed",msg:i?.reason,updated_at:Date.now()})}),this.pool.setQueueComposingHandler((o,t,n)=>{this.aibotHandle.sendSessionActivitySet({session_id:o,kind:"composing",active:t,...t?{ttl_ms:3e4,ref_event_id:n}:{}})}),this.pool.setInternalErrorHandler(o=>{this.handleSessionInternalError(o).catch(t=>{u.error(this.name,`[recovery] handleSessionInternalError failed event=${o.eventId} session=${o.sessionId}: ${t instanceof Error?t.message:String(t)}`)})}),this.pool.setEventStartedHandler((o,t)=>{if(this.reportSessionSkills(t),this.config.adapterType!=="claude")return;this.claudeWorkerStatus.set(t,"busy");const n=this.bindingStore.get(t);n?.cwd&&this.aibotHandle.sendUpdateBindingCard({session_id:t,worker_status:"busy",cwd:n.cwd,meta:this.buildClaudeToolbarMeta(t)})}),this.pool.setEventDoneHandler((o,t)=>{const n=this.config.adapterType??"acp";if(n==="claude"){this.claudeWorkerStatus.set(t,"ready");const i=this.bindingStore.get(t);i?.cwd&&this.aibotHandle.sendUpdateBindingCard({session_id:t,worker_status:"ready",cwd:i.cwd,meta:this.buildClaudeToolbarMeta(t)})}else if(n==="agy"){const i=this.bindingStore.get(t);if(i?.cwd){const s=this.buildAgyToolbarMeta(t),r=s?.available_models??[];u.info(this.name,`[agy-toolbar-diag] eventDone push binding card session=${t} model_id=${String(s?.model_id??"")} available_models=${r.length} meta_keys=${s?Object.keys(s).join(","):"<none>"}`),this.aibotHandle.sendUpdateBindingCard({session_id:t,worker_status:"ready",cwd:i.cwd,meta:s})}else u.info(this.name,`[agy-toolbar-diag] eventDone skip binding card: no binding cwd session=${t}`)}}),this.pool.setSessionActivityHandler((o,t,n)=>{const i=this.selfDrivenSessions.has(o),s=this.selfDrivenLabels.get(o);t?(this.selfDrivenSessions.add(o),n&&this.selfDrivenLabels.set(o,n),this.aibotHandle.sendSessionActivitySet({session_id:o,kind:"composing",active:!0,ttl_ms:9e4})):(this.selfDrivenSessions.delete(o),this.selfDrivenLabels.delete(o),i&&this.aibotHandle.sendSessionActivitySet({session_id:o,kind:"composing",active:!1})),(i!==t||t&&n!==void 0&&n!==s)&&this.pushQueueSnapshotForSession(o)}),this.pool.startIdleSweep(),u.info(this.name,`Ready (adapter: ${e}, poolMax: ${this.config.poolMaxSize??20})`)}async stop(){this.stopped=!0,this.pool?.stopIdleSweep(),this.stopKiroQuotaTimer();const e=this.pool?.collectActiveEventIds()??[];e.length>0&&this.activeEventStore&&await this.activeEventStore.save(e);for(const n of e)u.info(this.name,`Canceling active event on shutdown: ${n}`),this.sendEventResultWithCleanup(n,"canceled","process shutting down");e.length>0&&await new Promise(n=>setTimeout(n,100)),this.pool?.clearActiveEventsForShutdown();const o=this.deferredMgr.getAllDeferredEvents();for(const n of o)u.info(this.name,`Failing deferred event on shutdown: ${n.event_id}`),this.sendEventResultWithCleanup(n.event_id,"failed","process shutting down");const t=this.pool?.drainAllQueuedEvents()??[];for(const n of t)u.info(this.name,`Failing queued event on shutdown: ${n.event_id}`),this.sendEventResultWithCleanup(n.event_id,"failed","process shutting down");this.deferredMgr.clearAll(),await this.pool?.stop(),this.aibotHandle?.disconnect(),e.length>0&&this.activeEventStore&&await this.activeEventStore.save([]),this.eventSessionIndex.clear(),this.inflightEvents.clear(),this.restartCount.clear()}resolveSpawnEnv(){const e=le(this.config.agent.clientType,this.config.agent.provider),o=this.config.agent.env,t=Object.keys(e).length>0?{...e,...o}:o;return de(t)}createAdapter(e,o){switch(e){case"claude":return this.createClaudeAdapter(o);case"codex":return this.createCodexAdapter(o);case"pi":return this.createPiAdapter(o);case"openhuman":return this.createOpenHumanAdapter(o);case"codewhale":return this.createCodeWhaleAdapter(o);case"cursor":return this.createCursorAdapter(o);case"opencode":return this.createOpenCodeAdapter(o);case"agy":return this.createAgyAdapter(o);default:return this.createAcpAdapter(o)}}createCursorAdapter(e){const o={...this.config.adapterOptions??{}};o.bindingStore=this.bindingStore,o.aibotSessionId=e;const t=this.bindingStore.get(e),n=t?.modelId??this.globalConfigStore?.get(this.name)?.modelId;n&&(o.model=n),t?.modeId&&(o.mode=t.modeId);const i={sendStreamChunk:(s,r,a,d,c)=>{this.sendStreamChunkByRuntimeConfig(s,r,a,d,c)},sendEventResult:(s,r,a)=>{this.sendEventResultWithCleanup(s,r,a)},sendEventAck:(s,r)=>{this.aibotHandle.sendEventAck({event_id:s,session_id:r,received_at:Date.now()})},sendRawEventEnvelope:(s,r,a)=>{this.aibotHandle.sendMsg({event_id:s,session_id:r,msg_type:1,content:"[cursor] raw_event",extra:{channel_data:{cursor:{raw_event:a}},agent_api_origin:!0}})},agentInvoke:async(s,r)=>this.platformInvoke(s,r),sendLocalActionResult:(s,r,a,d,c)=>{this.aibotHandle.sendLocalActionResult({action_id:s,status:r,...a!==void 0?{result:a}:{},...d?{error_code:d}:{},...c?{error_msg:c}:{}})}};return new I({command:this.config.agent.command,args:this.config.agent.args,env:this.resolveSpawnEnv(),options:o},i)}createClaudeAdapter(e){const o={sendReply:(i,s,r,a,d)=>{this.sendReplyByRuntimeConfig(i,s,r,a,d)},sendStreamChunk:(i,s,r,a,d,c,l)=>{this.sendStreamChunkByRuntimeConfig(i,s,r,a,d,c,l)},sendMedia:(i,s,r,a,d,c,l)=>{this.aibotHandle.sendMedia({event_id:i,session_id:s,content:r,msg_type:2,quoted_message_id:d||void 0,client_msg_id:c||void 0,extra:l?{media_caption:a,...l}:{media_caption:a}})},sendEventResult:(i,s,r,a)=>{this.sendEventResultWithCleanup(i,s,r,a)},sendEventAck:(i,s)=>{this.aibotHandle.sendEventAck({event_id:i,session_id:s,received_at:Date.now()})},agentInvoke:async(i,s,r)=>this.platformInvoke(i,s,r),sendLocalActionResult:(i,s,r,a,d)=>{this.aibotHandle.sendLocalActionResult({action_id:i,status:s,...r!==void 0?{result:r}:{},...a?{error_code:a}:{},...d?{error_msg:d}:{}})},sendToolUse:(i,s,r,a)=>{this.sendToolExecutionCard(i,s,E(r,a))},sendToolResult:(i,s,r,a)=>{this.sendToolExecutionCard(i,s,$(r,a))},getWsUrl:()=>this.config.aibot.url,getAgentId:()=>this.config.aibot.agentId,getAgentProfile:()=>this.agentProfile,getApiKey:()=>this.config.aibot.apiKey,getActiveEventCount:()=>0,getPendingPermissionCount:()=>0,getPendingElicitationCount:()=>0,sendAgentQuestionCard:(i,s,r)=>{const a=r.questions.map(c=>c.header).join(", "),d=Oe(`[Agent Question] ${r.request_id}`,"agent_question",r);this.aibotHandle.sendText({event_id:i,session_id:s,content:d,msg_type:1,extra:{card_type:"agent_question",summary_text:a}})},sendPermissionCard:i=>{this.aibotHandle.sendMsg({event_id:i.eventId,session_id:i.sessionId,client_msg_id:`perm_${M()}`,msg_type:1,content:i.toolTitle?`Permission required: ${i.toolTitle}`:"Permission request",extra:{channel_data:{execApproval:{approvalId:i.approvalId,approvalSlug:i.toolName},grix:{execApproval:{approval_command_id:i.approvalId,command:i.toolTitle||i.toolName,host:"claude"}}},agent_api_origin:!0}})},sendDirectMessage:i=>{this.aibotHandle.sendMsg({session_id:i.sessionId,msg_type:1,content:i.content,...i.clientMsgId?{client_msg_id:i.clientMsgId}:{},...i.quotedMessageId?{quoted_message_id:i.quotedMessageId}:{}})},onStatusLineUpdated:i=>{(i.rateLimits?.fiveHour||i.rateLimits?.sevenDay)&&(this.cachedClaudeRateLimitState=i);const s=this.bindingStore.get(e);s?.cwd&&this.aibotHandle.sendUpdateBindingCard({session_id:e,worker_status:this.claudeWorkerStatus.get(e)??"ready",cwd:s.cwd,meta:this.buildClaudeToolbarMeta(e)})},sendMcpFrame:i=>{this.aibotHandle.sendMcpFrame(e,i)}},t=this.config.adapterOptions??{},n={...t,sessionRuntimeResolver:()=>{const i=this.bindingStore.get(e);return{cwd:i?.cwd,modeId:i?.modeId??C.fullAuto,modelId:i?.modelId??this.globalConfigStore?.get(this.name)?.modelId,pluginDir:t.pluginDir,claudeSessionId:i?.claudeSessionId,onSessionIdAssigned:s=>{this.bindingStore.setClaudeSessionId(e,s),this.sessionScanCache.invalidate()}}}};return new y({command:this.config.agent.command,args:this.config.agent.args,env:this.resolveSpawnEnv(),options:n},o)}createCodexAdapter(e){let o=null;const t={sendEventResult:(s,r,a)=>{this.sendEventResultWithCleanup(s,r,a)},sendEventAck:(s,r)=>this.aibotHandle.sendEventAck({event_id:s,session_id:r,received_at:Date.now()}),sendCodexEvent:s=>{this.shouldDropCodexDisplayEvent(s.event_id,s.codex_method)||(this.aibotHandle.sendCodexEvent(s),this.logCodexEventToConversation(s))},sendCodexEventReliable:async s=>{if(!this.shouldDropCodexDisplayEvent(s.event_id,s.codex_method)){try{await this.aibotHandle.sendCodexEventReliable(s)}catch(r){u.warn("bridge",`[codex] sendCodexEventReliable ACK failed event=${s.event_id}: ${r}`)}u.info("bridge",`[codex] sendCodexEventReliable done event=${s.event_id} method=${s.codex_method}`),this.logCodexEventToConversation(s)}},sendRunError:(s,r,a)=>{this.sendStreamChunkByRuntimeConfig(s,r,`
|
|
1
|
+
import T from"node:path";import{realpath as j,stat as H}from"node:fs/promises";import{tmpdir as V}from"node:os";import{randomUUID as M}from"node:crypto";import{ConnectionManager as Y}from"../core/aibot/index.js";import{ClaudeAdapter as y}from"../adapter/claude/index.js";import{CodexAdapter as J}from"../adapter/codex/index.js";import{PiAdapter as X}from"../adapter/pi/index.js";import{AcpAdapter as A}from"../adapter/acp/index.js";import{OpenHumanAdapter as Z}from"../adapter/openhuman/index.js";import{CursorAdapter as I}from"../adapter/cursor/index.js";import{CodeWhaleAdapter as U}from"../adapter/codewhale/index.js";import{OpenCodeAdapter as ee}from"../adapter/opencode/index.js";import{AgyAdapter as O}from"../adapter/agy/index.js";import{getCachedAgyModels as te}from"../adapter/agy/model-list.js";import{getCachedAgyQuotaInfo as ie}from"../adapter/agy/quota.js";import{LOCAL_ACTION_ERROR_CODES as x,LOCAL_ACTION_TYPES as S,SESSION_CONTROL_ERROR_CODES as g,SESSION_CONTROL_VERBS as p,SESSION_MODE_IDS as C}from"../adapter/claude/protocol-contract.js";import{parseClaudeSessionUsage as ne}from"../adapter/claude/usage-parser.js";import{resolveSkillScanMode as se}from"../adapter/claude/skill-scanner.js";import{buildReportedSkills as oe}from"../default-skills/index.js";import{fetchAvailableModels as D,getCachedModels as Q,readSettingsEnv as re}from"../adapter/claude/model-list.js";import{parseAcpSessionUsage as ae}from"../adapter/acp/usage-parser.js";import{applyProxyEnv as de,getProxyManager as ce}from"../core/proxy/index.js";import{buildProviderEnv as le}from"../core/config/provider-env.js";import{parseCodexSessionUsage as ue}from"../adapter/codex/usage-parser.js";import{readCodexProviderSettings as he}from"../adapter/codex/codex-trust.js";import{scanCodexSessions as ge}from"../adapter/codex/session-scanner.js";import{scanClaudeSessions as me}from"../adapter/claude/session-scanner.js";import{scanAcpSessions as pe}from"../adapter/acp/session-scanner.js";import{parsePiSessionUsage as fe}from"../adapter/pi/usage-parser.js";import{SessionScanCache as L,resolveCodexLeafDirs as _e,resolveClaudeLeafDirs as ve,resolveAcpLeafDirs as be}from"./session-scan-cache.js";import{log as u,ConversationLog as Se,AgentApiPacketLog as we,BridgeEventLog as Ce}from"../core/log/index.js";import{RevokeHandler as Ae}from"./revoke-handler.js";import{AdapterPool as ke,PoolFullError as Re}from"./adapter-pool.js";import{parseSessionControlCommand as Ee,handleSessionControlCommand as q,handleSessionControlLocalAction as $e}from"./session-controller.js";import{handleAcpSetModel as W,handleAcpSetMode as N,resolveAcpInitialDefaults as ye}from"./acp-toolbar-persist.js";import{SessionBindingStore as xe}from"../core/persistence/session-binding-store.js";import{handleFileListAction as Te,handleCreateFolderAction as Le,serveLocalFile as Pe,realHomeDir as K}from"../core/files/index.js";import{getMachineName as He}from"../core/util/index.js";import{uploadReplyFileToAgentMedia as Me}from"../core/protocol/agent-api-media.js";import{AllowlistGate as Ie}from"../core/access/allowlist-gate.js";import{ActiveEventStore as De}from"../core/persistence/active-event-store.js";import{DEFAULT_CONNECTOR_RUNTIME_CONFIG as Qe,applyConnectorRuntimeConfigPatch as Be,extractConnectorRuntimeConfigPatch as Fe}from"./runtime-config.js";import{SendController as Ue}from"./send-controller.js";import{queryProviderQuota as z}from"../core/provider-quota/index.js";import{queryKiroQuota as B}from"../core/provider-quota/kiro.js";import{buildToolUseCard as E,buildToolResultCard as $,buildLocalGrixCardLink as Oe}from"./tool-card-utils.js";import{DeferredEventManager as qe}from"./deferred-events.js";import{buildAgentProbeResult as We,PROBE_CACHE_TTL_STATIC_MS as Ne,PROBE_CACHE_TTL_FULL_MS as Ke}from"./probe-helper.js";const ze=600*1e3,Ge=60*1e3,G=30*1e3,je=10*1e3,P=new Set(["claude","acp","agy","cursor","codex"]),Ve=new Set(["claude","codex","cursor","codewhale","opencode","pi","openhuman","agy","acp"]),F=3;class zt{config;name;aibotHandle;aibotConfig;pool;stopped=!1;revokeHandler=new Ae;sessionBindings=new Map;deferredMgr;sendCtrl=new Ue(Qe);bindingStore;globalConfigStore;upgradeTrigger=null;agentDeletedHandler=null;shareSetHandler=null;providerConfigHandler=null;agentProfile={agentName:"",introduction:""};allowlistGate;activeEventStore;cachedRateLimits=null;cachedRateLimitsSampledAtMs=null;cachedCodexContextWindow=null;cachedCodexTokenUsage=null;cachedCodexUsageSampledAtMs=null;cachedAcpContextWindow=null;cachedAcpContextWindowSampledAtMs=null;cachedClaudeRateLimitState=null;cachedProviderQuota=null;cachedProviderQuotaSampledAtMs=null;claudeWorkerStatus=new Map;lastReportedSkillsHash="";conversationLog=null;packetLog=null;kiroQuotaTimer=null;eventSessionIndex=new Map;inflightEvents=new Map;restartCount=new Map;selfDrivenSessions=new Set;selfDrivenLabels=new Map;probeCache=new Map;sessionScanCache;isRateLimitsCacheFresh(e){if(!Number.isFinite(e))return!1;const o=Number(e);return o>0&&Date.now()-o<=Ge}async maybeQueryProviderQuota(){if(this.config.aibot.clientType==="kiro"){if(this.isRateLimitsCacheFresh(this.cachedProviderQuotaSampledAtMs)&&this.cachedProviderQuota)return this.cachedProviderQuota;try{const t=await B();return this.cachedProviderQuota=t,this.cachedProviderQuotaSampledAtMs=Date.now(),u.info(this.name,`[provider-quota] kiro queried: success=${t.success}`+(t.balance?` balance=${t.balance.remaining} ${t.balance.unit}`:"")+(t.planName?` plan=${t.planName}`:"")+(t.error?` error=${t.error}`:"")),t}catch(t){return u.warn(this.name,`[provider-quota] kiro query failed: ${t instanceof Error?t.message:String(t)}`),null}}let e=this.config.providerBaseUrl,o=this.config.providerApiKey;if((!e||!o)&&(this.config.adapterType??"acp")==="claude"){const t=re();e||(e=(t.ANTHROPIC_BASE_URL??"").trim()||void 0),o||(o=(t.ANTHROPIC_API_KEY??"").trim()||(t.ANTHROPIC_AUTH_TOKEN??"").trim()||void 0)}if((!e||!o)&&(this.config.adapterType??"acp")==="codex"){const t=he();!e&&t.baseUrl&&(e=t.baseUrl),!o&&t.apiKey&&(o=t.apiKey)}if(!e||!o)return null;if(this.isRateLimitsCacheFresh(this.cachedProviderQuotaSampledAtMs)&&this.cachedProviderQuota)return this.cachedProviderQuota;try{const t=await z(e,o);return this.cachedProviderQuota=t,this.cachedProviderQuotaSampledAtMs=Date.now(),u.info(this.name,`[provider-quota] queried: provider=${t.provider} success=${t.success}`+(t.tiers.length>0?` tiers=${t.tiers.map(n=>`${n.name}=${n.usedPercent}%`).join(",")}`:"")+(t.balance?` balance=${t.balance.remaining} ${t.balance.unit}`:"")+(t.error?` error=${t.error}`:"")),t}catch(t){return u.warn(this.name,`[provider-quota] query failed: ${t instanceof Error?t.message:String(t)}`),null}}startKiroQuotaTimer(){this.kiroQuotaTimer||(this.kiroQuotaTimer=setInterval(()=>{if(this.stopped){this.stopKiroQuotaTimer();return}this.refreshAndPushKiroQuota().catch(()=>{})},G),u.info(this.name,`[kiro-quota-timer] started (interval=${G}ms)`))}stopKiroQuotaTimer(){this.kiroQuotaTimer&&(clearInterval(this.kiroQuotaTimer),this.kiroQuotaTimer=null,u.info(this.name,"[kiro-quota-timer] stopped"))}async refreshAndPushKiroQuota(){try{const e=await B();this.cachedProviderQuota=e,this.cachedProviderQuotaSampledAtMs=Date.now(),e.success&&this.pushKiroQuotaToBindings(e)}catch{}}pushKiroQuotaToBindings(e){const o=this.providerQuotaToRateLimits(e);for(const[t,n]of this.sessionBindings.entries()){if(!n)continue;const i={provider_quota:e};o&&(i.rate_limits=o);const s=this.cachedAcpContextWindow;s&&("usedPercentage"in s?i.context_window={usedPercentage:s.usedPercentage,remainingPercentage:100-s.usedPercentage}:i.context_window=s),this.aibotHandle.sendUpdateBindingCard({session_id:t,worker_status:"ready",cwd:n,meta:i})}}getFreshClaudeRateLimitState(){const e=this.cachedClaudeRateLimitState;return e&&this.isRateLimitsCacheFresh(e.sampledAt)?e:null}getFreshCodexGlobalRateLimitCache(){const e=this.cachedRateLimits,o=this.cachedCodexContextWindow,t=this.cachedCodexTokenUsage;return{sampledAt:Math.max(this.cachedRateLimitsSampledAtMs??0,this.cachedCodexUsageSampledAtMs??0)||null,rateLimits:e,contextWindow:o,tokenUsage:t,hasData:!!(e||o||t)}}getStatus(){const e=this.pool?.getStatus()??{total:0,ready:0,busy:0};return{name:this.name,alive:!this.stopped,busy:e.busy>0,exhausted:this.pool?[...this.pool.getAllSlots()].some(o=>o.respawn.exhausted):!1,adapterType:this.config.adapterType??"acp",clientType:this.config.aibot.clientType,pool:e}}hasPendingWork(){return this.pool?.hasPendingWork()??!1}async probe(e={}){const o=e.conversation?"full":"static",t=e.conversation?Ke:Ne;if(!e.fresh){const a=this.probeCache.get(o);if(a&&Date.now()-a.sampledAt<t)return{...a.result,cached:!0}}const n=this.config.adapterType??"acp",i=this.createAdapter(n,"__probe__"),s=e.conversation&&n==="acp"?()=>this.runAcpConversationProbe(i,e.timeoutMs??1e4):void 0;let r;try{r=await We({adapter:i,agentName:this.name,clientType:this.config.aibot.clientType,adapterType:n,providerBaseUrl:this.config.providerBaseUrl??null,opts:e,launchConversationProbe:s})}finally{i.stop().catch(()=>{})}return this.probeCache.set(o,{result:r,sampledAt:r.probed_at}),r}async runAcpConversationProbe(e,o){const t=Date.now(),n="__probe__",i=`probe-${Date.now()}-${Math.random().toString(36).slice(2,8)}`,s=this.config.agent.cwd||V(),r=()=>Date.now()-t;try{await e.start()}catch(h){return{attempted:!0,ok:!1,latency_ms:r(),error:{code:"conversation_failed",message:`start failed: ${h instanceof Error?h.message:String(h)}`}}}if(!e.isAlive())return{attempted:!0,ok:!1,latency_ms:r(),error:{code:"process_not_started",message:"agent process not alive"}};if(e instanceof A)try{await e.bindSession(n,s)}catch{}let a=null;const d=new Promise(h=>{a=m=>{m===i&&h()},e.on("eventDone",a)});e.deliverInboundEvent({event_id:i,session_id:n,content:"ping",msg_id:i}),e.deliverStopEvent(i,n);let c=null;const l=await Promise.race([d.then(()=>!1),new Promise(h=>{c=setTimeout(()=>h(!0),o)})]);return c&&clearTimeout(c),a&&e.removeListener("eventDone",a),l?{attempted:!0,ok:!1,latency_ms:r(),error:{code:"conversation_timeout",message:`no eventDone within ${o}ms`}}:{attempted:!0,ok:!0,latency_ms:r()}}constructor(e,o){this.config=e,this.name=e.name;const t=e.adapterType??"acp";this.aibotConfig={...e.aibot,...t==="claude"?{localActions:e.aibot.localActions??["session_control","claude_interaction_reply","get_session_usage","get_rate_limits","set_model","set_mode","thread_compact","get_agent_global_config"]}:{}},e.eventQueue&&(this.aibotConfig.concurrency={max_concurrent:e.eventQueue.maxConcurrent,max_queued:e.eventQueue.maxQueued,queue_timeout_ms:e.eventQueue.queueTimeoutMs,cancelable_queued:e.eventQueue.cancelableQueued,cancelable_running:e.eventQueue.cancelableRunning}),this.conversationLog=e.logDir?new Se(e.logDir):null,this.packetLog=e.logDir?new we(e.logDir):null,this.bindingStore=new xe(e.bindingsPath),this.bindingStore.load(),this.globalConfigStore=o??null,this.deferredMgr=new qe(this.name),this.allowlistGate=e.allowlistPath?new Ie(e.allowlistPath):null,this.activeEventStore=e.activeEventStorePath?new De(e.activeEventStorePath):null,t==="codex"?this.sessionScanCache=new L(ge,_e):t==="claude"?this.sessionScanCache=new L(me,ve):t==="agy"?this.sessionScanCache=new L(()=>[],()=>[]):this.sessionScanCache=new L(pe,be)}async start(){(this.config.adapterType??"acp")==="claude"&&(await D().catch(()=>{}),this.maybeQueryProviderQuota().catch(()=>{})),this.config.aibot.clientType==="kiro"&&(this.maybeQueryProviderQuota().catch(()=>{}),this.startKiroQuotaTimer()),(this.config.adapterType??"acp")==="codex"&&this.maybeQueryProviderQuota().catch(()=>{}),await this.connectAibot(),this.sendCtrl.bind(this.aibotHandle);const e=this.config.adapterType??"acp";this.pool=new ke({maxPoolSize:this.config.poolMaxSize??20,idleTimeoutMs:this.config.poolIdleTimeoutMs??18e5,eventQueue:this.config.eventQueue},o=>{const t=this.createAdapter(e,o);return t instanceof A&&t.on("acpSessionReady",n=>{this.bindingStore.setAcpSessionId(o,n),this.sessionScanCache.invalidate()}),t},(o,t)=>{this.aibotHandle.sendEventAck({event_id:o,session_id:t,received_at:Date.now()})}),this.pool.setEventStateHandler((o,t,n,i)=>{u.info(this.name,`[queue-debug] send event_state session=${t} event=${o} state=${n} queue_pos=${i?.queue_position??""} queue_total=${i?.queue_total??""}`),this.aibotHandle.sendEventState({event_id:o,session_id:t,state:n,content_preview:i?.content_preview,queue_position:i?.queue_position,queue_total:i?.queue_total,actions:i?.actions,reason:i?.reason,updated_at:Date.now()}),this.pushQueueSnapshotForSession(t),(n==="canceled"||n==="failed")&&this.aibotHandle.sendEventResult({event_id:o,status:n==="canceled"?"canceled":"failed",msg:i?.reason,updated_at:Date.now()})}),this.pool.setQueueComposingHandler((o,t,n)=>{this.aibotHandle.sendSessionActivitySet({session_id:o,kind:"composing",active:t,...t?{ttl_ms:3e4,ref_event_id:n}:{}})}),this.pool.setInternalErrorHandler(o=>{this.handleSessionInternalError(o).catch(t=>{u.error(this.name,`[recovery] handleSessionInternalError failed event=${o.eventId} session=${o.sessionId}: ${t instanceof Error?t.message:String(t)}`)})}),this.pool.setEventStartedHandler((o,t)=>{if(this.reportSessionSkills(t),this.config.adapterType!=="claude")return;this.claudeWorkerStatus.set(t,"busy");const n=this.bindingStore.get(t);n?.cwd&&this.aibotHandle.sendUpdateBindingCard({session_id:t,worker_status:"busy",cwd:n.cwd,meta:this.buildClaudeToolbarMeta(t)})}),this.pool.setEventDoneHandler((o,t)=>{const n=this.config.adapterType??"acp";if(n==="claude"){this.claudeWorkerStatus.set(t,"ready");const i=this.bindingStore.get(t);i?.cwd&&this.aibotHandle.sendUpdateBindingCard({session_id:t,worker_status:"ready",cwd:i.cwd,meta:this.buildClaudeToolbarMeta(t)})}else if(n==="agy"){const i=this.bindingStore.get(t);if(i?.cwd){const s=this.buildAgyToolbarMeta(t),r=s?.available_models??[];u.info(this.name,`[agy-toolbar-diag] eventDone push binding card session=${t} model_id=${String(s?.model_id??"")} available_models=${r.length} meta_keys=${s?Object.keys(s).join(","):"<none>"}`),this.aibotHandle.sendUpdateBindingCard({session_id:t,worker_status:"ready",cwd:i.cwd,meta:s})}else u.info(this.name,`[agy-toolbar-diag] eventDone skip binding card: no binding cwd session=${t}`)}}),this.pool.setSessionActivityHandler((o,t,n)=>{const i=this.selfDrivenSessions.has(o),s=this.selfDrivenLabels.get(o);t?(this.selfDrivenSessions.add(o),n&&this.selfDrivenLabels.set(o,n),this.aibotHandle.sendSessionActivitySet({session_id:o,kind:"composing",active:!0,ttl_ms:9e4})):(this.selfDrivenSessions.delete(o),this.selfDrivenLabels.delete(o),i&&this.aibotHandle.sendSessionActivitySet({session_id:o,kind:"composing",active:!1})),(i!==t||t&&n!==void 0&&n!==s)&&this.pushQueueSnapshotForSession(o)}),this.pool.startIdleSweep(),u.info(this.name,`Ready (adapter: ${e}, poolMax: ${this.config.poolMaxSize??20})`)}async stop(){this.stopped=!0,this.pool?.stopIdleSweep(),this.stopKiroQuotaTimer();const e=this.pool?.collectActiveEventIds()??[];e.length>0&&this.activeEventStore&&await this.activeEventStore.save(e);for(const n of e)u.info(this.name,`Canceling active event on shutdown: ${n}`),this.sendEventResultWithCleanup(n,"canceled","process shutting down");e.length>0&&await new Promise(n=>setTimeout(n,100)),this.pool?.clearActiveEventsForShutdown();const o=this.deferredMgr.getAllDeferredEvents();for(const n of o)u.info(this.name,`Failing deferred event on shutdown: ${n.event_id}`),this.sendEventResultWithCleanup(n.event_id,"failed","process shutting down");const t=this.pool?.drainAllQueuedEvents()??[];for(const n of t)u.info(this.name,`Failing queued event on shutdown: ${n.event_id}`),this.sendEventResultWithCleanup(n.event_id,"failed","process shutting down");this.deferredMgr.clearAll(),await this.pool?.stop(),this.aibotHandle?.disconnect(),e.length>0&&this.activeEventStore&&await this.activeEventStore.save([]),this.eventSessionIndex.clear(),this.inflightEvents.clear(),this.restartCount.clear()}resolveSpawnEnv(){const e=le(this.config.agent.clientType,this.config.agent.provider),o=this.config.agent.env,t=Object.keys(e).length>0?{...e,...o}:o;return de(t)}createAdapter(e,o){switch(e){case"claude":return this.createClaudeAdapter(o);case"codex":return this.createCodexAdapter(o);case"pi":return this.createPiAdapter(o);case"openhuman":return this.createOpenHumanAdapter(o);case"codewhale":return this.createCodeWhaleAdapter(o);case"cursor":return this.createCursorAdapter(o);case"opencode":return this.createOpenCodeAdapter(o);case"agy":return this.createAgyAdapter(o);default:return this.createAcpAdapter(o)}}createCursorAdapter(e){const o={...this.config.adapterOptions??{}};o.bindingStore=this.bindingStore,o.aibotSessionId=e;const t=this.bindingStore.get(e),n=t?.modelId??this.globalConfigStore?.get(this.name)?.modelId;n&&(o.model=n),t?.modeId&&(o.mode=t.modeId);const i={sendStreamChunk:(s,r,a,d,c)=>{this.sendStreamChunkByRuntimeConfig(s,r,a,d,c)},sendEventResult:(s,r,a)=>{this.sendEventResultWithCleanup(s,r,a)},sendEventAck:(s,r)=>{this.aibotHandle.sendEventAck({event_id:s,session_id:r,received_at:Date.now()})},sendRawEventEnvelope:(s,r,a)=>{this.aibotHandle.sendMsg({event_id:s,session_id:r,msg_type:1,content:"[cursor] raw_event",extra:{channel_data:{cursor:{raw_event:a}},agent_api_origin:!0}})},agentInvoke:async(s,r)=>this.platformInvoke(s,r),sendLocalActionResult:(s,r,a,d,c)=>{this.aibotHandle.sendLocalActionResult({action_id:s,status:r,...a!==void 0?{result:a}:{},...d?{error_code:d}:{},...c?{error_msg:c}:{}})}};return new I({command:this.config.agent.command,args:this.config.agent.args,env:this.resolveSpawnEnv(),options:o},i)}createClaudeAdapter(e){const o={sendReply:(i,s,r,a,d)=>{this.sendReplyByRuntimeConfig(i,s,r,a,d)},sendStreamChunk:(i,s,r,a,d,c,l)=>{this.sendStreamChunkByRuntimeConfig(i,s,r,a,d,c,l)},sendMedia:(i,s,r,a,d,c,l)=>{this.aibotHandle.sendMedia({event_id:i,session_id:s,content:r,msg_type:2,quoted_message_id:d||void 0,client_msg_id:c||void 0,extra:l?{media_caption:a,...l}:{media_caption:a}})},sendEventResult:(i,s,r,a)=>{this.sendEventResultWithCleanup(i,s,r,a)},sendEventAck:(i,s)=>{this.aibotHandle.sendEventAck({event_id:i,session_id:s,received_at:Date.now()})},agentInvoke:async(i,s,r)=>this.platformInvoke(i,s,r),sendLocalActionResult:(i,s,r,a,d)=>{this.aibotHandle.sendLocalActionResult({action_id:i,status:s,...r!==void 0?{result:r}:{},...a?{error_code:a}:{},...d?{error_msg:d}:{}})},sendToolUse:(i,s,r,a)=>{this.sendToolExecutionCard(i,s,E(r,a))},sendToolResult:(i,s,r,a)=>{this.sendToolExecutionCard(i,s,$(r,a))},getWsUrl:()=>this.config.aibot.url,getAgentId:()=>this.config.aibot.agentId,getAgentProfile:()=>this.agentProfile,getApiKey:()=>this.config.aibot.apiKey,getActiveEventCount:()=>0,getPendingPermissionCount:()=>0,getPendingElicitationCount:()=>0,sendAgentQuestionCard:(i,s,r)=>{const a=r.questions.map(c=>c.header).join(", "),d=Oe(`[Agent Question] ${r.request_id}`,"agent_question",r);this.aibotHandle.sendText({event_id:i,session_id:s,content:d,msg_type:1,extra:{card_type:"agent_question",summary_text:a}})},sendPermissionCard:i=>{this.aibotHandle.sendMsg({event_id:i.eventId,session_id:i.sessionId,client_msg_id:`perm_${M()}`,msg_type:1,content:i.toolTitle?`Permission required: ${i.toolTitle}`:"Permission request",extra:{channel_data:{execApproval:{approvalId:i.approvalId,approvalSlug:i.toolName},grix:{execApproval:{approval_command_id:i.approvalId,command:i.toolTitle||i.toolName,host:"claude"}}},agent_api_origin:!0}})},sendDirectMessage:i=>{this.aibotHandle.sendMsg({session_id:i.sessionId,msg_type:1,content:i.content,...i.clientMsgId?{client_msg_id:i.clientMsgId}:{},...i.quotedMessageId?{quoted_message_id:i.quotedMessageId}:{}})},onStatusLineUpdated:i=>{(i.rateLimits?.fiveHour||i.rateLimits?.sevenDay)&&(this.cachedClaudeRateLimitState=i);const s=this.bindingStore.get(e);s?.cwd&&this.aibotHandle.sendUpdateBindingCard({session_id:e,worker_status:this.claudeWorkerStatus.get(e)??"ready",cwd:s.cwd,meta:this.buildClaudeToolbarMeta(e)})},sendMcpFrame:i=>{this.aibotHandle.sendMcpFrame(e,i)}},t=this.config.adapterOptions??{},n={...t,sessionRuntimeResolver:()=>{const i=this.bindingStore.get(e);return{cwd:i?.cwd,modeId:i?.modeId??C.fullAuto,modelId:i?.modelId??this.globalConfigStore?.get(this.name)?.modelId,pluginDir:t.pluginDir,claudeSessionId:i?.claudeSessionId,onSessionIdAssigned:s=>{this.bindingStore.setClaudeSessionId(e,s),this.sessionScanCache.invalidate()}}}};return new y({command:this.config.agent.command,args:this.config.agent.args,env:this.resolveSpawnEnv(),options:n},o)}createCodexAdapter(e){let o=null;const t={sendEventResult:(s,r,a)=>{this.sendEventResultWithCleanup(s,r,a)},sendEventAck:(s,r)=>this.aibotHandle.sendEventAck({event_id:s,session_id:r,received_at:Date.now()}),sendCodexEvent:s=>{this.shouldDropCodexDisplayEvent(s.event_id,s.codex_method)||(this.aibotHandle.sendCodexEvent(s),this.logCodexEventToConversation(s))},sendCodexEventReliable:async s=>{if(!this.shouldDropCodexDisplayEvent(s.event_id,s.codex_method)){try{await this.aibotHandle.sendCodexEventReliable(s)}catch(r){u.warn("bridge",`[codex] sendCodexEventReliable ACK failed event=${s.event_id}: ${r}`)}u.info("bridge",`[codex] sendCodexEventReliable done event=${s.event_id} method=${s.codex_method}`),this.logCodexEventToConversation(s)}},sendRunError:(s,r,a)=>{this.sendStreamChunkByRuntimeConfig(s,r,`
|
|
2
2
|
|
|
3
3
|
Error: ${a}`,1,!1)},sendUpdateBindingCard:(s,r,a,d)=>{const c={...d??{}};if(!c.rate_limits&&this.cachedProviderQuota?.success){this.isRateLimitsCacheFresh(this.cachedProviderQuotaSampledAtMs)||this.maybeQueryProviderQuota().catch(()=>{});const l=this.providerQuotaToCodexRateLimits(this.cachedProviderQuota);l&&(c.rate_limits=l.rateLimits,c.rate_limit_primary_percent=l.primaryPercent,c.rate_limit_secondary_percent=l.secondaryPercent,c.rate_limit_primary_window_min=l.primaryWindowMin,c.rate_limit_secondary_window_min=l.secondaryWindowMin)}!c.provider_quota&&this.cachedProviderQuota?.success&&(c.provider_quota=this.cachedProviderQuota),this.aibotHandle.sendUpdateBindingCard({session_id:s,worker_status:r,cwd:a,...Object.keys(c).length>0?{meta:c}:{}})},agentInvoke:async(s,r)=>this.platformInvoke(s,r),sendLocalActionResult:(s,r,a,d,c)=>this.aibotHandle.sendLocalActionResult({action_id:s,status:r,...a!==void 0?{result:a}:{},...d?{error_code:d}:{},...c?{error_msg:c}:{}}),sendSessionActivitySet:(s,r,a,d)=>{this.aibotHandle.sendSessionActivitySet({session_id:s,kind:r,active:a,...d??{}})},getConversationLog:()=>this.conversationLog,getAgentProfile:()=>this.agentProfile,onRateLimitsUpdated:s=>{this.cachedRateLimits=s,this.cachedRateLimitsSampledAtMs=Date.now(),this.isRateLimitsCacheFresh(this.cachedProviderQuotaSampledAtMs)||this.maybeQueryProviderQuota().catch(()=>{});const r=this.bindingStore.get(e);if(r?.cwd){const a=this.cachedRateLimitsSampledAtMs,d={rate_limits:{primary:s.primary,secondary:s.secondary,sampledAt:a},rate_limit_primary_percent:s.primary.usedPercent,rate_limit_secondary_percent:s.secondary.usedPercent,rate_limit_primary_window_min:s.primary.windowMinutes,rate_limit_secondary_window_min:s.secondary.windowMinutes,...o?.getEffortMeta()};this.cachedProviderQuota?.success&&(d.provider_quota=this.cachedProviderQuota),this.aibotHandle.sendUpdateBindingCard({session_id:e,worker_status:"ready",cwd:r.cwd,meta:d})}},onContextWindowUpdated:s=>{if(!s)return;this.cachedCodexContextWindow=s,this.cachedCodexUsageSampledAtMs=Date.now();const r=this.bindingStore.get(e);r?.cwd&&this.aibotHandle.sendUpdateBindingCard({session_id:e,worker_status:"ready",cwd:r.cwd,meta:{context_window:s,...o?.getEffortMeta()}})},onTokenUsageUpdated:s=>{s&&(this.cachedCodexTokenUsage=s,this.cachedCodexUsageSampledAtMs=Date.now())}},n=this.config.adapterOptions??{},i=this.globalConfigStore?.get(this.name);return o=new J({command:this.config.agent.command,args:this.config.agent.args,env:this.resolveSpawnEnv(),options:{...n,model:this.bindingStore.getCodexModelId(e)??n.model,collaborationMode:this.bindingStore.getCodexModeId(e)??n.collaborationMode,reasoningEffort:i?.codexReasoningEffort??n.reasoningEffort,sandboxMode:i?.codexSandboxMode??n.sandboxMode,aibotSessionId:e,bindingStore:this.bindingStore}},t),o}createCodeWhaleAdapter(e){const o={sendEventResult:(n,i,s)=>{this.sendEventResultWithCleanup(n,i,s)},sendEventAck:(n,i)=>this.aibotHandle.sendEventAck({event_id:n,session_id:i,received_at:Date.now()}),sendStreamChunk:(n,i,s,r,a,d)=>{this.sendStreamChunkByRuntimeConfig(n,i,s,r,a,d)},sendUpdateBindingCard:(n,i,s,r)=>this.aibotHandle.sendUpdateBindingCard({session_id:n,worker_status:i,cwd:s,...r?{meta:r}:{}}),sendLocalActionResult:(n,i,s,r,a)=>this.aibotHandle.sendLocalActionResult({action_id:n,status:i,...s!==void 0?{result:s}:{},...r?{error_code:r}:{},...a?{error_msg:a}:{}}),sendSessionActivitySet:(n,i,s,r)=>{this.aibotHandle.sendSessionActivitySet({session_id:n,kind:i,active:s,...r??{}})},sendToolUse:(n,i,s,r)=>{this.sendToolExecutionCard(n,i,E(s,r))},sendToolResult:(n,i,s,r)=>{this.sendToolExecutionCard(n,i,$(s,r))},agentInvoke:async(n,i)=>this.platformInvoke(n,i),getConversationLog:()=>this.conversationLog,getAgentProfile:()=>this.agentProfile},t=this.config.adapterOptions??{};return new U({command:this.config.agent.command,args:this.config.agent.args,env:this.resolveSpawnEnv(),options:{...t,aibotSessionId:e,bindingStore:this.bindingStore}},o)}createPiAdapter(e){const o={sendEventResult:(n,i,s)=>{this.sendEventResultWithCleanup(n,i,s),u.info("bridge",`[pi] sendEventResult event=${n} status=${i}`)},sendEventAck:(n,i)=>this.aibotHandle.sendEventAck({event_id:n,session_id:i,received_at:Date.now()}),sendUpdateBindingCard:(n,i,s,r)=>this.aibotHandle.sendUpdateBindingCard({session_id:n,worker_status:i,cwd:s,...r?{meta:r}:{}}),agentInvoke:async(n,i)=>this.platformInvoke(n,i),sendLocalActionResult:(n,i,s,r,a)=>{this.aibotHandle.sendLocalActionResult({action_id:n,status:i,...s!==void 0?{result:s}:{},...r?{error_code:r}:{},...a?{error_msg:a}:{}})},sendSessionActivitySet:(n,i,s,r)=>{this.aibotHandle.sendSessionActivitySet({session_id:n,kind:i,active:s,...r??{}})},sendToolUse:(n,i,s,r)=>{this.sendToolExecutionCard(n,i,E(s,r))},sendToolResult:(n,i,s,r)=>{this.sendToolExecutionCard(n,i,$(s,r))},sendStreamChunk:(n,i,s,r,a,d)=>{this.sendStreamChunkByRuntimeConfig(n,i,s,r,a,d),a&&u.info("bridge",`[pi] sendFinalStreamChunk event=${n} seq=${r}`)},sendFinalStreamChunkReliable:async(n,i,s,r)=>{if(this.finalizeThinking(n,i),this.resolveEventRuntimeConfig(n).responseDelivery==="single_message"&&n){this.bufferStreamChunk(n,i,"",!0);return}try{await this.aibotHandle.sendStreamChunkRequest({event_id:n,session_id:i,delta_content:"",chunk_seq:s,is_finish:!0,...r?{client_msg_id:r}:{}})}catch(d){u.warn("bridge",`[pi] sendFinalStreamChunkReliable ACK failed event=${n}: ${d}`)}u.info("bridge",`[pi] sendFinalStreamChunkReliable done event=${n} seq=${s}`)},sendThinking:(n,i,s)=>{this.sendThinkingByRuntimeConfig(n,i,s)},sendRunError:(n,i,s,r,a)=>{this.sendStreamChunkByRuntimeConfig(n,i,`
|
|
4
4
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{readdir as r,stat as m}from"node:fs/promises";import{join as l,extname as d}from"node:path";const x={pdf:"application/pdf",doc:"application/msword",docx:"application/vnd.openxmlformats-officedocument.wordprocessingml.document",xls:"application/vnd.ms-excel",xlsx:"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",ppt:"application/vnd.ms-powerpoint",pptx:"application/vnd.openxmlformats-officedocument.presentationml.presentation",txt:"text/plain",md:"text/markdown",csv:"text/csv",json:"application/json",xml:"application/xml",yaml:"text/yaml",yml:"text/yaml",html:"text/html",css:"text/css",js:"text/javascript",ts:"text/typescript",zip:"application/zip",rar:"application/x-rar-compressed","7z":"application/x-7z-compressed",tar:"application/x-tar",gz:"application/gzip",jpg:"image/jpeg",jpeg:"image/jpeg",png:"image/png",gif:"image/gif",webp:"image/webp",svg:"image/svg+xml",mp4:"video/mp4",mov:"video/quicktime",avi:"video/x-msvideo",mkv:"video/x-matroska",webm:"video/webm",mp3:"audio/mpeg",wav:"audio/wav",flac:"audio/flac",aac:"audio/aac"};function n(a){const p=d(a).slice(1).toLowerCase();return x[p]}async function f(a,p=!1){const c=await r(a,{withFileTypes:!0}),s=[];for(const
|
|
1
|
+
import{readdir as r,stat as m}from"node:fs/promises";import{join as l,extname as d}from"node:path";const x={pdf:"application/pdf",doc:"application/msword",docx:"application/vnd.openxmlformats-officedocument.wordprocessingml.document",xls:"application/vnd.ms-excel",xlsx:"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",ppt:"application/vnd.ms-powerpoint",pptx:"application/vnd.openxmlformats-officedocument.presentationml.presentation",txt:"text/plain",md:"text/markdown",csv:"text/csv",json:"application/json",xml:"application/xml",yaml:"text/yaml",yml:"text/yaml",html:"text/html",css:"text/css",js:"text/javascript",ts:"text/typescript",zip:"application/zip",rar:"application/x-rar-compressed","7z":"application/x-7z-compressed",tar:"application/x-tar",gz:"application/gzip",jpg:"image/jpeg",jpeg:"image/jpeg",png:"image/png",gif:"image/gif",webp:"image/webp",svg:"image/svg+xml",mp4:"video/mp4",mov:"video/quicktime",avi:"video/x-msvideo",mkv:"video/x-matroska",webm:"video/webm",mp3:"audio/mpeg",wav:"audio/wav",flac:"audio/flac",aac:"audio/aac"};function n(a){const p=d(a).slice(1).toLowerCase();return x[p]}async function f(a,p=!1){const c=await r(a,{withFileTypes:!0}),s=[];for(const i of c){if(!p&&i.name.startsWith("."))continue;const t=l(a,i.name),e={id:t,name:i.name,is_directory:i.isDirectory()};try{if(i.isDirectory()){const o=await m(t);e.modified_at=o.mtime.toISOString()}else{const o=await m(t);e.size=o.size,e.modified_at=o.mtime.toISOString(),e.mime_type=n(i.name)}}catch{}s.push(e)}return s.sort((i,t)=>i.is_directory!==t.is_directory?i.is_directory?-1:1:i.name.localeCompare(t.name)),s}export{f as listFiles,n as resolveMimeType};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{existsSync as
|
|
1
|
+
import{existsSync as h,mkdirSync as U,readFileSync as w,renameSync as C,writeFileSync as M}from"node:fs";import{join as g,dirname as $}from"node:path";import{spawn as R}from"node:child_process";import{log as o}from"../log/index.js";import{GRIX_PATHS as m}from"../log/index.js";import{resolveClientVersion as _}from"../util/client-version.js";import{UpgradeError as L,collectEnvInfo as O,getUpgradeLogTail as D,isVersionAvailable as N,npmInstall as F,pendingExists as v,preflightCheck as H,readPending as x,removePending as u,upgradeLog as s,verifyInstalledVersion as z,writePending as B}from"./npm-upgrader.js";import{isRetriableRegistryError as G}from"../installer/npm-registry.js";const V=360*60*1e3,j=300*1e3,T=1800*1e3,K=2,X=3,S="grix-connector",k=1e4,A=15,q=2e3,J=8e3,Y=1e3;function b(l){return new URL(l.replace(/^wss:/,"https:").replace(/^ws:/,"http:")).origin}function P(){return g(m.data,"upgrade-state.json")}function E(){const l=P();if(!h(l))return{daily_attempts:{},version_attempts:{}};try{return JSON.parse(w(l,"utf-8"))}catch{return{daily_attempts:{},version_attempts:{}}}}function Z(l){const t=P();U($(t),{recursive:!0});const e=t+".tmp";M(e,JSON.stringify(l),"utf-8"),C(e,t)}function I(){return new Date().toISOString().slice(0,10)}class ot{agentConfigs;isBusy;timer=null;initialTimer=null;running=!1;stopped=!1;constructor(t,e){this.agentConfigs=t,this.isBusy=e}async start(){await this.handlePendingOnStartup(),this.initialTimer=setTimeout(()=>{this.stopped||(this.runCheck(),!this.stopped&&(this.timer=setInterval(()=>this.runCheck(),V)))},j)}stop(){this.stopped=!0,this.initialTimer&&(clearTimeout(this.initialTimer),this.initialTimer=null),this.timer&&(clearInterval(this.timer),this.timer=null)}triggerCheck(){this.stopped||this.runCheck()}async checkForUpdate(){return this.agentConfigs.length===0?{available:!1}:(await Promise.all(this.agentConfigs.map(i=>this.queryUpgrade(i)))).find(i=>i.available&&i.release)??{available:!1}}async handlePendingOnStartup(){if(!v())return;const t=x();if(!t){u();return}const e=_(),i=e===t.target_version,r=i?{from_version:t.from_version,to_version:t.target_version,status:"success"}:{from_version:t.from_version,to_version:t.target_version,status:"rolled_back",error_code:"STARTUP_CRASH",crash_count:t.crash_count};if(s(i?`daemon startup: version matches target ${t.target_version}, upgrade succeeded`:`daemon startup: version ${e} != target ${t.target_version}, rolled back`),i){const a=await this.confirmHealthz(J);s(a?"healthz confirmed healthy, reporting success":"healthz not confirmed within window; reporting success on version-match (guardian still guards crashes)")}if(await this.reportUpgrade(r)){u();return}this.retryStartupReport(r)}async retryStartupReport(t){for(let e=2;e<=A;e++){if(this.stopped)return;const i=q*(e-1)+Math.floor(Math.random()*1e3);if(await new Promise(r=>setTimeout(r,i)),this.stopped)return;if(await this.reportUpgrade(t)){u();return}}s(`startup report (${t.status}) not delivered after ${A} attempts; clearing pending to unblock future checks`),u()}async confirmHealthz(t){let e="19579";try{const a=g(m.data,"health-port");h(a)&&(e=w(a,"utf-8").trim()||e)}catch{}const i=`http://127.0.0.1:${e}/healthz`,r=Date.now()+t;for(;Date.now()<r&&!this.stopped;){try{if((await fetch(i,{signal:AbortSignal.timeout(2e3)})).ok)return!0}catch{}await new Promise(a=>setTimeout(a,Y))}return!1}async runCheck(){if(!this.running){this.running=!0;try{await this.check()}catch(t){o.error("upgrade",`Check failed: ${t instanceof Error?t.message:t}`)}finally{this.running=!1}}}async check(){if(v()||this.agentConfigs.length===0)return;const i=(await Promise.all(this.agentConfigs.map(n=>this.queryUpgrade(n)))).find(n=>n.available&&n.release)?.release;if(!i)return;const r=i.version,a=_();if(!i.force&&!this.checkRateLimit(r))return;const p=H();if(!p.ok){await this.reportUpgrade({from_version:a,to_version:r,status:"failed",error_code:p.errorCode,error_msg:p.errorMsg});return}if(!await N(S,r)){s(`target ${r} not resolvable on any registry yet; skipping this cycle (no failure recorded)`);return}s(`upgrade start: ${a} -> ${r}`);const y=Date.now();try{if(B(a,r),await F(S,r),z(r),this.startGuardian(),s("npm install verified, reporting installed"),await this.reportUpgrade({from_version:a,to_version:r,status:"installed",duration_ms:Date.now()-y}),s("shutting down for restart"),!await this.waitUntilIdle()){s("upgrade aborted: checker stopped during wait");return}process.kill(process.pid,"SIGTERM")}catch(n){const d=n instanceof L?n.code:"NPM_INSTALL_FAILED",f=n instanceof Error?n.message:String(n);s(`upgrade failed: ${d} ${f}`),u(),G(f)||d==="NPM_TIMEOUT"?s(`failure is retriable (${d}); skipping cooldown, will retry next cycle`):this.recordFailure(r),await this.reportUpgrade({from_version:a,to_version:r,status:"failed",error_code:d,error_msg:f,duration_ms:Date.now()-y,upgrade_log:D()})}}async waitUntilIdle(){if(!this.isBusy?.())return!this.stopped;s("active tasks detected, waiting until idle before restart (no max wait)");const t=5e3,e=600*1e3;let i=Date.now();for(;this.isBusy()&&!this.stopped;)await new Promise(r=>setTimeout(r,t)),Date.now()-i>=e&&(i=Date.now(),s("still waiting for active tasks to finish before restart"));return this.stopped?!1:(s("all tasks completed, proceeding with restart"),!0)}checkRateLimit(t){const e=E();if(e.last_failure_at){const c=Date.now()-new Date(e.last_failure_at).getTime();if(c<T)return o.info("upgrade",`In cooldown, ${(T-c)/6e4}m remaining`),!1}const i=e.version_attempts[t]??0;if(i>=X)return o.info("upgrade",`Version ${t} already tried ${i} times, skipping`),!1;const r=I(),a=e.daily_attempts[r]??0;return a>=K?(o.info("upgrade",`Already ${a} attempts today, skipping`),!1):!0}recordFailure(t){const e=E(),i=I();e.last_failure_at=new Date().toISOString(),e.last_failure_version=t,e.daily_attempts[i]=(e.daily_attempts[i]??0)+1,e.version_attempts[t]=(e.version_attempts[t]??0)+1,Z(e)}async queryUpgrade(t){const i=`${b(t.wsUrl)}/v1/agent-api/upgrade/check?`+new URLSearchParams({client_type:"grix-connector",client_version:_(),channel:t.channel??"stable",platform:process.platform,arch:process.arch}).toString();try{const r=await fetch(i,{headers:{Authorization:`Bearer ${t.apiKey}`},signal:AbortSignal.timeout(k)});if(!r.ok)return o.warn("upgrade",`Check API returned ${r.status}`),{available:!1};const a=await r.json();return a.code!==0?{available:!1}:a.data}catch(r){return o.warn("upgrade",`Check API error: ${r instanceof Error?r.message:r}`),{available:!1}}}async reportUpgrade(t){if(this.agentConfigs.length===0)return!1;const e={client_type:"grix-connector",...t.npm_version?t:{...t,...O()}};return(await Promise.all(this.agentConfigs.map(async r=>{const c=`${b(r.wsUrl)}/v1/agent-api/upgrade/report`;try{return(await fetch(c,{method:"POST",headers:{Authorization:`Bearer ${r.apiKey}`,"Content-Type":"application/json"},body:JSON.stringify(e),signal:AbortSignal.timeout(k)})).ok}catch{return!1}}))).some(r=>r)}startGuardian(){const t=g(m.base,"bin","upgrade-guardian.sh");if(process.platform==="win32"||!h(t)){o.info("upgrade","Guardian not available on this platform, skipping");return}try{const e=R(t,[],{detached:!0,stdio:"ignore"});e.unref(),s(`guardian started (pid ${e.pid})`)}catch(e){o.warn("upgrade",`Failed to start guardian: ${e instanceof Error?e.message:e}`)}}}export{ot as UpgradeChecker};
|
package/dist/log.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import{createWriteStream as g,mkdirSync as l,existsSync as f}from"node:fs";import{join as
|
|
2
|
-
`)},error(o,r,...
|
|
1
|
+
import{createWriteStream as g,mkdirSync as l,existsSync as f}from"node:fs";import{join as i}from"node:path";import{homedir as m}from"node:os";const n=i(m(),".grix"),s={base:n,config:i(n,"config"),log:i(n,"log"),data:i(n,"data")};function S(){for(const o of Object.values(s))f(o)||l(o,{recursive:!0})}let a=null;function $(){const o=new Date().toISOString().slice(0,10),r=i(s.log,`grix-acp-${o}.log`);a=g(r,{flags:"a"})}function c(){return new Date().toISOString().slice(11,19)}const u={info(o,r,...t){const e=`${c()} [${o}] ${r}${t.length?" "+t.map(String).join(" "):""}`;console.log(e),a?.write(e+`
|
|
2
|
+
`)},error(o,r,...t){const e=`${c()} [${o}] ERROR ${r}${t.length?" "+t.map(String).join(" "):""}`;console.error(e),a?.write(e+`
|
|
3
3
|
`)}};export{s as GRIX_PATHS,S as ensureGrixDirs,$ as initLogger,u as log};
|
package/dist/manager.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{readFileSync as L,readdirSync as Q,writeFileSync as K}from"node:fs";import{join as b}from"node:path";import{AgentInstance as $}from"./bridge/bridge.js";import{buildSharedInstanceConfig as z,diffSharedOwners as G,sharedInstanceKey as W,SharedOwnersCache as J}from"./manager-share-config.js";import{isAgentDeletedError as j}from"./core/aibot/errors.js";import{GRIX_PATHS as A,log as d}from"./core/log/index.js";import{resolveClientVersion as V}from"./core/util/client-version.js";import{UpgradeChecker as X}from"./core/upgrade/upgrade-checker.js";import{AgentGlobalConfigStore as Y}from"./core/persistence/agent-global-config-store.js";import{resolveSkillScanMode as Z}from"./adapter/claude/skill-scanner.js";import{buildReportedSkills as ee,logDefaultSkillsCheck as te,cleanupProjectedSkills as D}from"./default-skills/index.js";import{resolveCopilotCommand as E}from"./core/runtime/copilot-resolve.js";import{getCliVersion as ne,resolveCliPath as ae}from"./core/util/cli-probe.js";import{AgentInstaller as re}from"./core/installer/installer.js";import{reportInstallFailure as se}from"./core/observability/sentry.js";const oe=8e3;function ie(){const n=E();return[{clientType:"openclaw",command:"openclaw"},{clientType:"claude",command:"claude"},{clientType:"codex",command:"codex"},{clientType:"gemini",command:"gemini"},{clientType:"qwen",command:"qwen"},{clientType:"hermes",command:"hermes"},{clientType:"reasonix",command:"reasonix"},{clientType:"codewhale",command:"codewhale"},{clientType:"opencode",command:"opencode"},{clientType:"cursor",command:"agent"},{clientType:"pi",command:"pi"},{clientType:"openhuman",command:"openhuman-core"},{clientType:"kiro",command:"kiro-cli"},{clientType:"copilot",command:n.command},{clientType:"agy",command:"agy"}]}async function ce(){return Promise.all(ie().map(async n=>{const e=await ae(n.command);if(!e)return{client_type:n.clientType,command:n.command,installed:!1,path:null,version:null};const t=await ne(e,n.versionArgs??["--version"]);return{client_type:n.clientType,command:n.command,installed:!0,path:e,version:t.version,error:t.error}}))}function le(n){switch(n){case"claude":return{adapterType:"claude",command:"claude"};case"codex":return{adapterType:"codex",command:"codex",options:{sandboxMode:"danger-full-access"}};case"gemini":return{adapterType:"acp",command:"gemini",autoInjectArgs:{acp:!0},enableSessionBinding:!0};case"qwen":return{adapterType:"acp",command:"qwen",adapterHint:"qwen/base",autoInjectArgs:{acp:!0},enableSessionBinding:!0};case"pi":return{adapterType:"pi",command:"pi"};case"cursor":return{adapterType:"cursor",command:"agent"};case"reasonix":return{adapterType:"acp",command:"reasonix",args:["acp"],enableSessionBinding:!0};case"codewhale":return{adapterType:"codewhale",command:"codewhale",enableSessionBinding:!0};case"openhuman":return{adapterType:"openhuman",command:"openhuman-core",enableSessionBinding:!0};case"kiro":return{adapterType:"acp",command:"kiro-cli",args:["acp"],enableSessionBinding:!0};case"opencode":return{adapterType:"opencode",command:"opencode",args:["serve"],enableSessionBinding:!0};case"copilot":{const e=E();return{adapterType:"acp",command:e.command,args:[...e.prefixArgs,"--acp"],enableSessionBinding:!0}}case"agy":return{adapterType:"agy",command:"agy",enableSessionBinding:!0};case"hermes":throw new Error('client_type "hermes" is not handled by grix-connector. Hermes runs as a separate project \u2014 see https://github.com/askie/grix-hermes-python');default:throw new Error(`Unsupported client_type: ${n}`)}}function O(n,e){return String(n??"").trim().toLowerCase().replace(/[^a-z0-9._-]+/g,"-").replace(/-+/g,"-").replace(/^-|-$/g,"")||String(e??"").trim().toLowerCase().replace(/[^a-z0-9._-]+/g,"-").replace(/-+/g,"-").replace(/^-|-$/g,"")||"default"}function de(n,e){return b(A.data,`session-bindings-${O(n,e)}.json`)}function pe(n,e){return b(A.data,`active-events-${O(n,e)}.json`)}function ue(...n){const e=[],t=new Set;for(const s of n)for(const a of s??[]){const r=String(a??"").trim(),o=r.toLowerCase();!r||t.has(o)||(t.add(o),e.push(r))}return e.length>0?e:void 0}function me(n,e){const t={claude:{maxConcurrent:1,maxQueued:5,queueTimeoutMs:0},codex:{maxConcurrent:1,maxQueued:5,queueTimeoutMs:0},cursor:{maxConcurrent:1,maxQueued:3,queueTimeoutMs:0},acp:{maxConcurrent:1,maxQueued:3,queueTimeoutMs:0},pi:{maxConcurrent:1,maxQueued:5,queueTimeoutMs:0},codewhale:{maxConcurrent:1,maxQueued:3,queueTimeoutMs:0},openhuman:{maxConcurrent:1,maxQueued:3,queueTimeoutMs:0},opencode:{maxConcurrent:1,maxQueued:3,queueTimeoutMs:0},agy:{maxConcurrent:1,maxQueued:3,queueTimeoutMs:0}},s=t[n]??t.acp;return{maxConcurrent:e?.max_concurrent??s.maxConcurrent??1,maxQueued:e?.max_queued??s.maxQueued??3,queueTimeoutMs:e?.queue_timeout_ms??s.queueTimeoutMs??0,cancelableQueued:!0,cancelableRunning:!0}}function T(n){const e=V(),t=String(n.client_type??"").trim().toLowerCase(),s=le(t),a=String(n.ws_url??"").trim(),r="get_session_usage",o="get_rate_limits",i="get_agent_global_config";if(!n.name?.trim())throw new Error("agent name is required");if(!a)throw new Error(`agent ${n.name}: ws_url is required`);if(!n.agent_id?.trim())throw new Error(`agent ${n.name}: agent_id is required`);if(!n.api_key?.trim())throw new Error(`agent ${n.name}: api_key is required`);const c=s.adapterType,p=c==="acp",g=t==="qwen",f={...s.options??{}},w=c==="codex"?{capabilities:["local_action_v1","agent_invoke"],localActions:["session_control","get_context","set_model","set_mode","set_reasoning_effort","set_sandbox_mode","exec_approve","exec_reject","file_list","create_folder","turn_interrupt","permission_approve","permission_reject","thread_compact",r,o]}:null,l=c==="claude"?{localActions:["session_control","set_mode","set_model","claude_interaction_reply","exec_approve","exec_reject","file_list","create_folder","thread_compact",r,o]}:null,m=g?{capabilities:["stream_chunk","local_action_v1"],localActions:["exec_approve","exec_reject","permission_approve","permission_reject","session_control","set_model","set_mode","file_list","create_folder",r],adapterHint:"qwen/base"}:null,h=c==="pi"?{adapterHint:"pi/base",capabilities:["local_action_v1"],localActions:["session_control","set_model","get_context","file_list","create_folder",r]}:null,u=c==="openhuman"?{adapterHint:"openhuman/base",capabilities:["local_action_v1"],localActions:["session_control","set_model","file_list","create_folder",r]}:null,_=c==="cursor"?{adapterHint:"cursor/base",capabilities:["stream_chunk","local_action_v1"],localActions:["session_control","set_model","set_mode","get_context","file_list","create_folder",r,o]}:null,y=c==="codewhale"?{capabilities:["stream_chunk","local_action_v1"],localActions:["session_control","set_model","file_list","create_folder",r]}:null,v=c==="opencode"?{adapterHint:"opencode/base",capabilities:["stream_chunk","local_action_v1"],localActions:["exec_approve","exec_reject","permission_approve","permission_reject","session_control","set_model","set_mode","file_list","create_folder",r]}:null,C=c==="agy"?{adapterHint:"agy/base",capabilities:["stream_chunk","local_action_v1"],localActions:["session_control","set_model","file_list","create_folder",r]}:null,q=p&&!g?{localActions:["exec_approve","exec_reject","permission_approve","permission_reject","session_control","set_model","set_mode","file_list","create_folder",r]}:null,H=t==="kiro"?{localActions:["exec_approve","exec_reject","permission_approve","permission_reject","session_control","set_model","set_mode","file_list","create_folder","thread_compact",r,o]}:null,N=c==="codex"||c==="claude"||t==="gemini"?["session_control","set_model","set_mode"]:void 0,F=[r,o];p&&f.raw_transport===void 0&&(f.raw_transport=t==="gemini");const U=`${t}/base`,I=Z(c,t),B=I==="kiro"?void 0:process.cwd(),k=ee({mode:I,projectDir:B}),M=k.filter(x=>x.source==="connector").map(x=>x.name);M.length>0&&d.info("manager",`[${n.name}] injecting connector skills: [${M.join(", ")}]`);let R=k.length>0?k:void 0;return{name:n.name,adapterType:c,aibot:{url:a,agentId:n.agent_id,apiKey:n.api_key,clientType:t,clientVersion:e,adapterHint:s.adapterHint??m?.adapterHint??h?.adapterHint??u?.adapterHint??_?.adapterHint??v?.adapterHint??C?.adapterHint??U,capabilities:w?.capabilities??y?.capabilities??h?.capabilities??u?.capabilities??_?.capabilities??v?.capabilities??C?.capabilities??m?.capabilities??["stream_chunk","local_action_v1","connector_upgrade"],localActions:ue(w?.localActions??y?.localActions??l?.localActions??h?.localActions??u?.localActions??_?.localActions??v?.localActions??C?.localActions??m?.localActions??H?.localActions??q?.localActions??["exec_approve","exec_reject"],N,F,["connector_rollback","connector_upgrade_push",i,"configure_gateway_provider"]),skills:R},agent:{command:s.command,args:[...s.args??[],...n.args??[]],env:n.env,clientType:t,...n.provider?{provider:{...n.provider.base_url?.trim()?{baseUrl:n.provider.base_url.trim()}:{},...n.provider.api_key?.trim()?{apiKey:n.provider.api_key.trim()}:{},...n.provider.model?.trim()?{model:n.provider.model.trim()}:{}}}:{}},adapterOptions:f,acpAuthMethod:f.auth_method,acpInitialMode:f.initial_mode,acpMcpTools:f.acp_mcp_tools,promptTimeoutMs:n.prompt_timeout_ms,bindingsPath:de(n.name,n.agent_id),activeEventStorePath:pe(n.name,n.agent_id),...s.enableSessionBinding||p?{enableSessionBinding:!0}:{},...s.autoInjectArgs?{autoInjectArgs:s.autoInjectArgs}:{},poolMaxSize:n.pool?.maxSize,poolIdleTimeoutMs:n.pool?.idleTimeoutMs,eventQueue:me(c,n.event_queue),logDir:A.log,providerBaseUrl:n.provider_base_url?.trim()||void 0,providerApiKey:n.provider_api_key?.trim()||void 0}}function ge(){const n=process.env.GRIX_AGENT_STARTUP_WAIT_MS,e=Number(n);return Number.isFinite(e)&&e>=500?Math.floor(e):oe}function P(n){const e=[],t=[],s=Q(n).filter(a=>a.endsWith(".json")).sort();for(const a of s)try{const r=JSON.parse(L(b(n,a),"utf-8"));if(Array.isArray(r.agents)){if(r.agents.length===0){t.push({file:a,error:"agents array is empty"});continue}for(const o of r.agents)e.push({entry:o,file:a})}else t.push({file:a,error:"unrecognized config format"})}catch(r){t.push({file:a,error:r instanceof Error?r.message:String(r)})}return{entries:e,fileErrors:t}}function S(n){if(n===null||typeof n!="object")return JSON.stringify(n)??"null";if(Array.isArray(n))return`[${n.map(S).join(",")}]`;const e=n;return`{${Object.keys(e).sort().map(s=>`${JSON.stringify(s)}:${S(e[s])}`).join(",")}}`}function he(n,e){return S(n)===S(e)}class Ee{instances=[];configMap=new Map;rawConfigMap=new Map;reloadChain=Promise.resolve();sharedInstances=new Map;shareSyncChains=new Map;stopping=!1;deletedAgentCleanups=new Set;upgradeChecker=null;globalConfigStore;configDir=A.config;sharedOwnersCache=new J(A.data);installer=new re;async start(e){const t=e??A.config;this.configDir=t,d.info("manager",`Loading configs from ${t}`),te(),D(),this.globalConfigStore=new Y(b(A.data,"agent-global-configs.json")),this.globalConfigStore.load();const{entries:s,fileErrors:a}=P(t);for(const c of a)d.error("manager",`Failed to load ${c.file}: ${c.error}`);if(s.length===0&&a.length===0)throw new Error(`No config files found in ${t}`);const r=[];let o=a.length;for(const{entry:c,file:p}of s)try{const g=T(c);r.push({config:g,entry:c,file:p}),d.info("manager",`Loaded ${g.name} (${g.adapterType??"acp"}) from ${p}`)}catch(g){const f=typeof c?.name=="string"?c.name:"<unknown>";d.error("manager",`Invalid agent config in ${p} (name=${f}): ${g}`),o++}let i=0;if(r.length>0){const c=ge();d.info("manager",`Starting ${r.length} agent(s), startup wait=${c}ms`);const p=()=>this.upgradeChecker?.triggerCheck(),g=l=>{this.instances=this.instances.filter(m=>m!==l)},f=r.map(({config:l,entry:m,file:h})=>{const u=new $(l,this.globalConfigStore);return u.setUpgradeTrigger(p),u.setShareSetHandler(_=>this.onShareSet(l,_)),u.setAgentDeletedHandler(()=>this.onAgentDeletedByPlatform(l.name)),this.instances.push(u),this.configMap.set(l.name,l),this.rawConfigMap.set(l.name,{entry:m,file:h}),{config:l,instance:u,startPromise:u.start()}}),w=await Promise.all(f.map(async l=>{const m=await new Promise(h=>{let u=!1;const _=setTimeout(()=>{u||(u=!0,h({kind:"timeout"}))},c);l.startPromise.then(()=>{u||(u=!0,clearTimeout(_),h({kind:"started"}))}).catch(y=>{u||(u=!0,clearTimeout(_),h({kind:"failed",error:y}))})});return{task:l,outcome:m}}));for(const{task:l,outcome:m}of w){if(m.kind==="started"){this.restoreCachedSharedInstances(l.config);continue}if(m.kind==="failed"){if(j(m.error)){this.onAgentDeletedByPlatform(l.config.name);continue}g(l.instance),d.error("manager",`Failed to start ${l.config.name}: ${m.error}`);continue}i++,d.warn("manager",`Startup pending for ${l.config.name}, continue retrying in background`),l.startPromise.then(()=>{d.info("manager",`Delayed start succeeded: ${l.config.name}`),this.restoreCachedSharedInstances(l.config)}).catch(h=>{if(j(h)){this.onAgentDeletedByPlatform(l.config.name);return}g(l.instance),d.error("manager",`Delayed start failed: ${l.config.name}: ${h}`)})}if(this.instances.length>0){const l=Math.max(0,this.instances.length-i);d.info("manager",`${l}/${r.length} agent(s) running now`)}i>0&&d.warn("manager",`${i} agent(s) still connecting in background`)}if(this.instances.length===0&&r.length>0)throw new Error("All agent configurations failed to start");r.length>0&&(this.upgradeChecker=new X(r.map(({config:c})=>({apiKey:c.aibot.apiKey,wsUrl:c.aibot.url})),()=>this.instances.some(c=>c.getStatus().busy)),await this.upgradeChecker.start())}async stop(){d.info("manager","Stopping all agents..."),this.stopping=!0,this.upgradeChecker?.stop(),await Promise.allSettled([...this.shareSyncChains.values()]),this.shareSyncChains.clear();const e=[...this.sharedInstances.values()];this.sharedInstances.clear(),await Promise.allSettled([...this.instances.map(t=>t.stop()),...e.map(t=>t.stop())]),await this.globalConfigStore?.flush(),this.instances=[],D(),d.info("manager","All stopped")}onShareSet(e,t){this.sharedOwnersCache.save(e.name,t);const a=(this.shareSyncChains.get(e.name)??Promise.resolve()).catch(()=>{}).then(()=>this.syncSharedInstances(e,t));this.shareSyncChains.set(e.name,a)}async syncSharedInstances(e,t){if(this.stopping)return;const{toAdd:s,toRemove:a}=G(e.name,this.sharedInstances.keys(),t);for(const r of s){if(this.stopping)break;const o=W(e.name,r);try{const i=z(e,r),c=new $(i,this.globalConfigStore);this.sharedInstances.set(o,c),await c.start(),d.info("manager",`shared instance started: ${o}`)}catch(i){this.sharedInstances.delete(o);const c=i instanceof Error?i.message:String(i);/Auth failed/i.test(c)?(this.sharedOwnersCache.remove(e.name,r),d.warn("manager",`shared instance auth rejected, removed from cache: ${o}`)):d.error("manager",`start shared instance failed ${o}: ${i}`)}}for(const{key:r}of a){const o=this.sharedInstances.get(r);o&&(this.sharedInstances.delete(r),o.stop().catch(i=>d.error("manager",`stop shared instance failed ${r}: ${i}`)),d.info("manager",`shared instance stopped: ${r}`))}}restoreCachedSharedInstances(e){const t=this.sharedOwnersCache.load(e.name);t.length!==0&&(d.info("manager",`restoring ${t.length} cached shared instance(s) for ${e.name}`),this.onShareSet(e,t))}getAgentsStatus(){return this.instances.map(e=>e.getStatus())}async addAgent(e){await this.addAgentInternal(e,"agents.json"),this.persistAgentsConfig(),d.info("manager",`Added agent: ${e.name}`)}async addAgentInternal(e,t){const s=T(e);if(this.instances.some(a=>a.name===s.name))throw new Error(`Agent "${s.name}" already exists`);await this.startInstanceFromConfig(s,e,t)}async startInstanceFromConfig(e,t,s){const a=new $(e,this.globalConfigStore);a.setUpgradeTrigger(()=>this.upgradeChecker?.triggerCheck()),a.setShareSetHandler(r=>this.onShareSet(e,r)),a.setAgentDeletedHandler(()=>this.onAgentDeletedByPlatform(e.name)),a.setProviderConfigHandler(r=>this.setAgentProvider(e.name,r)),await a.start(),this.instances.push(a),this.configMap.set(e.name,e),this.rawConfigMap.set(e.name,{entry:t,file:s})}async removeAgent(e){if(!this.instances.some(t=>t.name===e))throw Object.assign(new Error(`Agent "${e}" not found`),{code:"NOT_FOUND"});await this.removeAgentInternal(e),this.persistAgentsConfig(),d.info("manager",`Removed agent: ${e}`)}async removeAgentInternal(e,t){const s=this.instances.findIndex(i=>i.name===e),a=s===-1?void 0:this.instances[s];s!==-1&&this.instances.splice(s,1),this.configMap.delete(e),this.rawConfigMap.delete(e);const r=this.shareSyncChains.get(e);this.shareSyncChains.delete(e),r&&await r.catch(()=>{});const o=`${e}#shared:`;for(const[i,c]of[...this.sharedInstances.entries()])i.startsWith(o)&&(this.sharedInstances.delete(i),c.stop().catch(p=>d.error("manager",`stop shared instance failed ${i}: ${p}`)));t?.keepShareCache||this.sharedOwnersCache.delete(e),a&&await a.stop()}onAgentDeletedByPlatform(e){this.stopping||this.deletedAgentCleanups.has(e)||(this.deletedAgentCleanups.add(e),(async()=>{try{if(!(this.rawConfigMap.has(e)||this.instances.some(s=>s.name===e)))return;d.warn("manager",`Agent "${e}" \u5DF2\u5728\u5E73\u53F0\u5220\u9664\uFF0C\u505C\u6B62\u5B9E\u4F8B\u5E76\u6E05\u7406\u672C\u5730\u914D\u7F6E`),await this.removeAgentInternal(e),this.persistAgentsConfig(),d.info("manager",`Agent "${e}" \u5DF2\u5728\u5E73\u53F0\u5220\u9664\uFF0C\u672C\u5730\u914D\u7F6E\u5DF2\u6E05\u7406\uFF08agents.json \u5DF2\u79FB\u9664\u8BE5\u6761\u76EE\uFF09`)}catch(t){d.error("manager",`cleanup for deleted agent "${e}" failed: ${t}`)}finally{this.deletedAgentCleanups.delete(e)}})())}persistAgentsConfig(){const e=b(this.configDir,"agents.json");try{const t=[];for(const[,a]of this.configMap){const r=this.rawConfigMap.get(a.name)?.entry.provider;t.push({name:a.name,ws_url:a.aibot.url,agent_id:a.aibot.agentId,api_key:a.aibot.apiKey,client_type:a.aibot.clientType,...r?{provider:r}:{}})}K(e,JSON.stringify({agents:t},null,4)+`
|
|
1
|
+
import{readFileSync as L,readdirSync as Q,writeFileSync as K}from"node:fs";import{join as b}from"node:path";import{AgentInstance as $}from"./bridge/bridge.js";import{buildSharedInstanceConfig as W,diffSharedOwners as z,sharedInstanceKey as G,SharedOwnersCache as J}from"./manager-share-config.js";import{isAgentDeletedError as j}from"./core/aibot/errors.js";import{GRIX_PATHS as A,log as d}from"./core/log/index.js";import{resolveClientVersion as V}from"./core/util/client-version.js";import{UpgradeChecker as X}from"./core/upgrade/upgrade-checker.js";import{AgentGlobalConfigStore as Y}from"./core/persistence/agent-global-config-store.js";import{resolveSkillScanMode as Z}from"./adapter/claude/skill-scanner.js";import{buildReportedSkills as ee,logDefaultSkillsCheck as te,cleanupProjectedSkills as D}from"./default-skills/index.js";import{resolveCopilotCommand as E}from"./core/runtime/copilot-resolve.js";import{getCliVersion as ne,resolveCliPath as ae}from"./core/util/cli-probe.js";import{AgentInstaller as re}from"./core/installer/installer.js";import{reportInstallFailure as se}from"./core/observability/sentry.js";const oe=8e3;function ie(){const n=E();return[{clientType:"openclaw",command:"openclaw"},{clientType:"claude",command:"claude"},{clientType:"codex",command:"codex"},{clientType:"gemini",command:"gemini"},{clientType:"qwen",command:"qwen"},{clientType:"hermes",command:"hermes"},{clientType:"reasonix",command:"reasonix"},{clientType:"codewhale",command:"codewhale"},{clientType:"opencode",command:"opencode"},{clientType:"cursor",command:"agent"},{clientType:"pi",command:"pi"},{clientType:"openhuman",command:"openhuman-core"},{clientType:"kiro",command:"kiro-cli"},{clientType:"copilot",command:n.command},{clientType:"agy",command:"agy"}]}async function ce(){return Promise.all(ie().map(async n=>{const e=await ae(n.command);if(!e)return{client_type:n.clientType,command:n.command,installed:!1,path:null,version:null};const t=await ne(e,n.versionArgs??["--version"]);return{client_type:n.clientType,command:n.command,installed:!0,path:e,version:t.version,error:t.error}}))}function le(n){switch(n){case"claude":return{adapterType:"claude",command:"claude"};case"codex":return{adapterType:"codex",command:"codex",options:{sandboxMode:"danger-full-access"}};case"gemini":return{adapterType:"acp",command:"gemini",autoInjectArgs:{acp:!0},enableSessionBinding:!0};case"qwen":return{adapterType:"acp",command:"qwen",adapterHint:"qwen/base",autoInjectArgs:{acp:!0},enableSessionBinding:!0};case"pi":return{adapterType:"pi",command:"pi"};case"cursor":return{adapterType:"cursor",command:"agent"};case"reasonix":return{adapterType:"acp",command:"reasonix",args:["acp"],enableSessionBinding:!0};case"codewhale":return{adapterType:"codewhale",command:"codewhale",enableSessionBinding:!0};case"openhuman":return{adapterType:"openhuman",command:"openhuman-core",enableSessionBinding:!0};case"kiro":return{adapterType:"acp",command:"kiro-cli",args:["acp"],enableSessionBinding:!0};case"opencode":return{adapterType:"opencode",command:"opencode",args:["serve"],enableSessionBinding:!0};case"copilot":{const e=E();return{adapterType:"acp",command:e.command,args:[...e.prefixArgs,"--acp"],enableSessionBinding:!0}}case"agy":return{adapterType:"agy",command:"agy",enableSessionBinding:!0};case"hermes":throw new Error('client_type "hermes" is not handled by grix-connector. Hermes runs as a separate project \u2014 see https://github.com/askie/grix-hermes-python');default:throw new Error(`Unsupported client_type: ${n}`)}}function O(n,e){return String(n??"").trim().toLowerCase().replace(/[^a-z0-9._-]+/g,"-").replace(/-+/g,"-").replace(/^-|-$/g,"")||String(e??"").trim().toLowerCase().replace(/[^a-z0-9._-]+/g,"-").replace(/-+/g,"-").replace(/^-|-$/g,"")||"default"}function de(n,e){return b(A.data,`session-bindings-${O(n,e)}.json`)}function pe(n,e){return b(A.data,`active-events-${O(n,e)}.json`)}function ue(...n){const e=[],t=new Set;for(const s of n)for(const a of s??[]){const r=String(a??"").trim(),o=r.toLowerCase();!r||t.has(o)||(t.add(o),e.push(r))}return e.length>0?e:void 0}function me(n,e){const t={claude:{maxConcurrent:1,maxQueued:5,queueTimeoutMs:0},codex:{maxConcurrent:1,maxQueued:5,queueTimeoutMs:0},cursor:{maxConcurrent:1,maxQueued:3,queueTimeoutMs:0},acp:{maxConcurrent:1,maxQueued:3,queueTimeoutMs:0},pi:{maxConcurrent:1,maxQueued:5,queueTimeoutMs:0},codewhale:{maxConcurrent:1,maxQueued:3,queueTimeoutMs:0},openhuman:{maxConcurrent:1,maxQueued:3,queueTimeoutMs:0},opencode:{maxConcurrent:1,maxQueued:3,queueTimeoutMs:0},agy:{maxConcurrent:1,maxQueued:3,queueTimeoutMs:0}},s=t[n]??t.acp;return{maxConcurrent:e?.max_concurrent??s.maxConcurrent??1,maxQueued:e?.max_queued??s.maxQueued??3,queueTimeoutMs:e?.queue_timeout_ms??s.queueTimeoutMs??0,cancelableQueued:!0,cancelableRunning:!0}}function T(n){const e=V(),t=String(n.client_type??"").trim().toLowerCase(),s=le(t),a=String(n.ws_url??"").trim(),r="get_session_usage",o="get_rate_limits",i="get_agent_global_config";if(!n.name?.trim())throw new Error("agent name is required");if(!a)throw new Error(`agent ${n.name}: ws_url is required`);if(!n.agent_id?.trim())throw new Error(`agent ${n.name}: agent_id is required`);if(!n.api_key?.trim())throw new Error(`agent ${n.name}: api_key is required`);const c=s.adapterType,p=c==="acp",g=t==="qwen",f={...s.options??{}},w=c==="codex"?{capabilities:["local_action_v1","agent_invoke"],localActions:["session_control","get_context","set_model","set_mode","set_reasoning_effort","set_sandbox_mode","exec_approve","exec_reject","file_list","create_folder","turn_interrupt","permission_approve","permission_reject","thread_compact",r,o]}:null,l=c==="claude"?{localActions:["session_control","set_mode","set_model","claude_interaction_reply","exec_approve","exec_reject","file_list","create_folder","thread_compact",r,o]}:null,m=g?{capabilities:["stream_chunk","local_action_v1"],localActions:["exec_approve","exec_reject","permission_approve","permission_reject","session_control","set_model","set_mode","file_list","create_folder",r],adapterHint:"qwen/base"}:null,h=c==="pi"?{adapterHint:"pi/base",capabilities:["local_action_v1"],localActions:["session_control","set_model","get_context","file_list","create_folder",r]}:null,u=c==="openhuman"?{adapterHint:"openhuman/base",capabilities:["local_action_v1"],localActions:["session_control","set_model","file_list","create_folder",r]}:null,_=c==="cursor"?{adapterHint:"cursor/base",capabilities:["stream_chunk","local_action_v1"],localActions:["session_control","set_model","set_mode","get_context","file_list","create_folder",r,o]}:null,y=c==="codewhale"?{capabilities:["stream_chunk","local_action_v1"],localActions:["session_control","set_model","file_list","create_folder",r]}:null,v=c==="opencode"?{adapterHint:"opencode/base",capabilities:["stream_chunk","local_action_v1"],localActions:["exec_approve","exec_reject","permission_approve","permission_reject","session_control","set_model","set_mode","file_list","create_folder",r]}:null,C=c==="agy"?{adapterHint:"agy/base",capabilities:["stream_chunk","local_action_v1"],localActions:["session_control","set_model","file_list","create_folder",r]}:null,q=p&&!g?{localActions:["exec_approve","exec_reject","permission_approve","permission_reject","session_control","set_model","set_mode","file_list","create_folder",r]}:null,H=t==="kiro"?{localActions:["exec_approve","exec_reject","permission_approve","permission_reject","session_control","set_model","set_mode","file_list","create_folder","thread_compact",r,o]}:null,N=c==="codex"||c==="claude"||t==="gemini"?["session_control","set_model","set_mode"]:void 0,F=[r,o];p&&f.raw_transport===void 0&&(f.raw_transport=t==="gemini");const U=`${t}/base`,I=Z(c,t),B=I==="kiro"?void 0:process.cwd(),k=ee({mode:I,projectDir:B}),M=k.filter(x=>x.source==="connector").map(x=>x.name);M.length>0&&d.info("manager",`[${n.name}] injecting connector skills: [${M.join(", ")}]`);let R=k.length>0?k:void 0;return{name:n.name,adapterType:c,aibot:{url:a,agentId:n.agent_id,apiKey:n.api_key,clientType:t,clientVersion:e,adapterHint:s.adapterHint??m?.adapterHint??h?.adapterHint??u?.adapterHint??_?.adapterHint??v?.adapterHint??C?.adapterHint??U,capabilities:w?.capabilities??y?.capabilities??h?.capabilities??u?.capabilities??_?.capabilities??v?.capabilities??C?.capabilities??m?.capabilities??["stream_chunk","local_action_v1","connector_upgrade"],localActions:ue(w?.localActions??y?.localActions??l?.localActions??h?.localActions??u?.localActions??_?.localActions??v?.localActions??C?.localActions??m?.localActions??H?.localActions??q?.localActions??["exec_approve","exec_reject"],N,F,["connector_rollback","connector_upgrade_push",i,"configure_gateway_provider"]),skills:R},agent:{command:s.command,args:[...s.args??[],...n.args??[]],env:n.env,clientType:t,...n.provider?{provider:{...n.provider.base_url?.trim()?{baseUrl:n.provider.base_url.trim()}:{},...n.provider.api_key?.trim()?{apiKey:n.provider.api_key.trim()}:{},...n.provider.model?.trim()?{model:n.provider.model.trim()}:{}}}:{}},adapterOptions:f,acpAuthMethod:f.auth_method,acpInitialMode:f.initial_mode,acpMcpTools:f.acp_mcp_tools,promptTimeoutMs:n.prompt_timeout_ms,bindingsPath:de(n.name,n.agent_id),activeEventStorePath:pe(n.name,n.agent_id),...s.enableSessionBinding||p?{enableSessionBinding:!0}:{},...s.autoInjectArgs?{autoInjectArgs:s.autoInjectArgs}:{},poolMaxSize:n.pool?.maxSize,poolIdleTimeoutMs:n.pool?.idleTimeoutMs,eventQueue:me(c,n.event_queue),logDir:A.log,providerBaseUrl:n.provider_base_url?.trim()||void 0,providerApiKey:n.provider_api_key?.trim()||void 0}}function ge(){const n=process.env.GRIX_AGENT_STARTUP_WAIT_MS,e=Number(n);return Number.isFinite(e)&&e>=500?Math.floor(e):oe}function P(n){const e=[],t=[],s=Q(n).filter(a=>a.endsWith(".json")).sort();for(const a of s)try{const r=JSON.parse(L(b(n,a),"utf-8"));if(Array.isArray(r.agents)){if(r.agents.length===0){t.push({file:a,error:"agents array is empty"});continue}for(const o of r.agents)e.push({entry:o,file:a})}else t.push({file:a,error:"unrecognized config format"})}catch(r){t.push({file:a,error:r instanceof Error?r.message:String(r)})}return{entries:e,fileErrors:t}}function S(n){if(n===null||typeof n!="object")return JSON.stringify(n)??"null";if(Array.isArray(n))return`[${n.map(S).join(",")}]`;const e=n;return`{${Object.keys(e).sort().map(s=>`${JSON.stringify(s)}:${S(e[s])}`).join(",")}}`}function he(n,e){return S(n)===S(e)}class Ee{instances=[];configMap=new Map;rawConfigMap=new Map;reloadChain=Promise.resolve();sharedInstances=new Map;shareSyncChains=new Map;stopping=!1;deletedAgentCleanups=new Set;upgradeChecker=null;globalConfigStore;configDir=A.config;sharedOwnersCache=new J(A.data);installer=new re;async start(e){const t=e??A.config;this.configDir=t,d.info("manager",`Loading configs from ${t}`),te(),D(),this.globalConfigStore=new Y(b(A.data,"agent-global-configs.json")),this.globalConfigStore.load();const{entries:s,fileErrors:a}=P(t);for(const c of a)d.error("manager",`Failed to load ${c.file}: ${c.error}`);if(s.length===0&&a.length===0)throw new Error(`No config files found in ${t}`);const r=[];let o=a.length;for(const{entry:c,file:p}of s)try{const g=T(c);r.push({config:g,entry:c,file:p}),d.info("manager",`Loaded ${g.name} (${g.adapterType??"acp"}) from ${p}`)}catch(g){const f=typeof c?.name=="string"?c.name:"<unknown>";d.error("manager",`Invalid agent config in ${p} (name=${f}): ${g}`),o++}let i=0;if(r.length>0){const c=ge();d.info("manager",`Starting ${r.length} agent(s), startup wait=${c}ms`);const p=()=>this.upgradeChecker?.triggerCheck(),g=l=>{this.instances=this.instances.filter(m=>m!==l)},f=r.map(({config:l,entry:m,file:h})=>{const u=new $(l,this.globalConfigStore);return u.setUpgradeTrigger(p),u.setShareSetHandler(_=>this.onShareSet(l,_)),u.setAgentDeletedHandler(()=>this.onAgentDeletedByPlatform(l.name)),this.instances.push(u),this.configMap.set(l.name,l),this.rawConfigMap.set(l.name,{entry:m,file:h}),{config:l,instance:u,startPromise:u.start()}}),w=await Promise.all(f.map(async l=>{const m=await new Promise(h=>{let u=!1;const _=setTimeout(()=>{u||(u=!0,h({kind:"timeout"}))},c);l.startPromise.then(()=>{u||(u=!0,clearTimeout(_),h({kind:"started"}))}).catch(y=>{u||(u=!0,clearTimeout(_),h({kind:"failed",error:y}))})});return{task:l,outcome:m}}));for(const{task:l,outcome:m}of w){if(m.kind==="started"){this.restoreCachedSharedInstances(l.config);continue}if(m.kind==="failed"){if(j(m.error)){this.onAgentDeletedByPlatform(l.config.name);continue}g(l.instance),d.error("manager",`Failed to start ${l.config.name}: ${m.error}`);continue}i++,d.warn("manager",`Startup pending for ${l.config.name}, continue retrying in background`),l.startPromise.then(()=>{d.info("manager",`Delayed start succeeded: ${l.config.name}`),this.restoreCachedSharedInstances(l.config)}).catch(h=>{if(j(h)){this.onAgentDeletedByPlatform(l.config.name);return}g(l.instance),d.error("manager",`Delayed start failed: ${l.config.name}: ${h}`)})}if(this.instances.length>0){const l=Math.max(0,this.instances.length-i);d.info("manager",`${l}/${r.length} agent(s) running now`)}i>0&&d.warn("manager",`${i} agent(s) still connecting in background`)}if(this.instances.length===0&&r.length>0)throw new Error("All agent configurations failed to start");r.length>0&&(this.upgradeChecker=new X(r.map(({config:c})=>({apiKey:c.aibot.apiKey,wsUrl:c.aibot.url})),()=>this.instances.some(c=>c.hasPendingWork())),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}=z(e.name,this.sharedInstances.keys(),t);for(const r of s){if(this.stopping)break;const o=G(e.name,r);try{const i=W(e,r),c=new $(i,this.globalConfigStore);this.sharedInstances.set(o,c),await c.start(),d.info("manager",`shared instance started: ${o}`)}catch(i){this.sharedInstances.delete(o);const c=i instanceof Error?i.message:String(i);/Auth failed/i.test(c)?(this.sharedOwnersCache.remove(e.name,r),d.warn("manager",`shared instance auth rejected, removed from cache: ${o}`)):d.error("manager",`start shared instance failed ${o}: ${i}`)}}for(const{key:r}of a){const o=this.sharedInstances.get(r);o&&(this.sharedInstances.delete(r),o.stop().catch(i=>d.error("manager",`stop shared instance failed ${r}: ${i}`)),d.info("manager",`shared instance stopped: ${r}`))}}restoreCachedSharedInstances(e){const t=this.sharedOwnersCache.load(e.name);t.length!==0&&(d.info("manager",`restoring ${t.length} cached shared instance(s) for ${e.name}`),this.onShareSet(e,t))}getAgentsStatus(){return this.instances.map(e=>e.getStatus())}async addAgent(e){await this.addAgentInternal(e,"agents.json"),this.persistAgentsConfig(),d.info("manager",`Added agent: ${e.name}`)}async addAgentInternal(e,t){const s=T(e);if(this.instances.some(a=>a.name===s.name))throw new Error(`Agent "${s.name}" already exists`);await this.startInstanceFromConfig(s,e,t)}async startInstanceFromConfig(e,t,s){const a=new $(e,this.globalConfigStore);a.setUpgradeTrigger(()=>this.upgradeChecker?.triggerCheck()),a.setShareSetHandler(r=>this.onShareSet(e,r)),a.setAgentDeletedHandler(()=>this.onAgentDeletedByPlatform(e.name)),a.setProviderConfigHandler(r=>this.setAgentProvider(e.name,r)),await a.start(),this.instances.push(a),this.configMap.set(e.name,e),this.rawConfigMap.set(e.name,{entry:t,file:s})}async removeAgent(e){if(!this.instances.some(t=>t.name===e))throw Object.assign(new Error(`Agent "${e}" not found`),{code:"NOT_FOUND"});await this.removeAgentInternal(e),this.persistAgentsConfig(),d.info("manager",`Removed agent: ${e}`)}async removeAgentInternal(e,t){const s=this.instances.findIndex(i=>i.name===e),a=s===-1?void 0:this.instances[s];s!==-1&&this.instances.splice(s,1),this.configMap.delete(e),this.rawConfigMap.delete(e);const r=this.shareSyncChains.get(e);this.shareSyncChains.delete(e),r&&await r.catch(()=>{});const o=`${e}#shared:`;for(const[i,c]of[...this.sharedInstances.entries()])i.startsWith(o)&&(this.sharedInstances.delete(i),c.stop().catch(p=>d.error("manager",`stop shared instance failed ${i}: ${p}`)));t?.keepShareCache||this.sharedOwnersCache.delete(e),a&&await a.stop()}onAgentDeletedByPlatform(e){this.stopping||this.deletedAgentCleanups.has(e)||(this.deletedAgentCleanups.add(e),(async()=>{try{if(!(this.rawConfigMap.has(e)||this.instances.some(s=>s.name===e)))return;d.warn("manager",`Agent "${e}" \u5DF2\u5728\u5E73\u53F0\u5220\u9664\uFF0C\u505C\u6B62\u5B9E\u4F8B\u5E76\u6E05\u7406\u672C\u5730\u914D\u7F6E`),await this.removeAgentInternal(e),this.persistAgentsConfig(),d.info("manager",`Agent "${e}" \u5DF2\u5728\u5E73\u53F0\u5220\u9664\uFF0C\u672C\u5730\u914D\u7F6E\u5DF2\u6E05\u7406\uFF08agents.json \u5DF2\u79FB\u9664\u8BE5\u6761\u76EE\uFF09`)}catch(t){d.error("manager",`cleanup for deleted agent "${e}" failed: ${t}`)}finally{this.deletedAgentCleanups.delete(e)}})())}persistAgentsConfig(){const e=b(this.configDir,"agents.json");try{const t=[];for(const[,a]of this.configMap){const r=this.rawConfigMap.get(a.name)?.entry.provider;t.push({name:a.name,ws_url:a.aibot.url,agent_id:a.aibot.agentId,api_key:a.aibot.apiKey,client_type:a.aibot.clientType,...r?{provider:r}:{}})}K(e,JSON.stringify({agents:t},null,4)+`
|
|
2
2
|
`,"utf-8")}catch(t){d.error("manager",`Failed to persist agents config: ${t}`)}}async restartAgent(e){const t=this.rawConfigMap.get(e);if(!t)throw Object.assign(new Error(`Agent "${e}" not found`),{code:"NOT_FOUND"});await this.replaceInstance(e,t.entry,t.file),d.info("manager",`Restarted agent: ${e}`)}async setAgentProvider(e,t){const s=this.rawConfigMap.get(e);if(!s)throw Object.assign(new Error(`Agent "${e}" not found`),{code:"NOT_FOUND"});const a={...s.entry,provider:t};await this.replaceInstance(e,a,s.file),this.persistAgentsConfig(),d.info("manager",`Updated provider config for agent: ${e}`)}async replaceInstance(e,t,s){const a=T(t);await this.removeAgentInternal(e,{keepShareCache:!0}),await this.startInstanceFromConfig(a,t,s),this.restoreCachedSharedInstances(a)}async reload(){const e=this.reloadChain.catch(()=>{}).then(()=>this.doReload());return this.reloadChain=e.catch(()=>{}),e}async doReload(){if(this.stopping)throw Object.assign(new Error("manager is stopping"),{code:"RELOAD_UNSAFE"});const{entries:e,fileErrors:t}=P(this.configDir);if(t.length>0){const o=t.map(i=>`${i.file}: ${i.error}`).join("; ");throw Object.assign(new Error(`reload aborted: ${t.length} config file(s) failed to parse [${o}]`),{code:"RELOAD_UNSAFE"})}if(e.length===0)throw Object.assign(new Error("reload aborted: no valid agent config found"),{code:"RELOAD_UNSAFE"});const s=new Map;for(const o of e){const i=String(o.entry?.name??"").trim();i&&(s.has(i)&&d.warn("manager",`reload: duplicate agent name "${i}", last one wins`),s.set(i,o))}const a={added:[],removed:[],restarted:[],unchanged:[],failed:[]},r=new Set(this.rawConfigMap.keys());for(const o of r)if(!s.has(o))try{await this.removeAgentInternal(o),a.removed.push(o)}catch(i){a.failed.push({name:o,error:i instanceof Error?i.message:String(i)})}for(const[o,i]of s){const c=this.rawConfigMap.get(o);if(c)if(he(c.entry,i.entry))this.rawConfigMap.set(o,i),a.unchanged.push(o);else try{await this.replaceInstance(o,i.entry,i.file),a.restarted.push(o)}catch(p){a.failed.push({name:o,error:p instanceof Error?p.message:String(p)})}else try{await this.addAgentInternal(i.entry,i.file),a.added.push(o)}catch(p){a.failed.push({name:o,error:p instanceof Error?p.message:String(p)})}}return d.info("manager",`reload done: +${a.added.length} -${a.removed.length} ~${a.restarted.length} =${a.unchanged.length} !${a.failed.length}`),a}async checkUpgrade(){return this.upgradeChecker?this.upgradeChecker.checkForUpdate():{available:!1}}triggerUpgrade(){this.upgradeChecker?.triggerCheck()}async probeAll(e={}){return fe(this.instances,e)}async probeOne(e,t={}){return _e(this.instances,e,t)}listInstallable(){return this.installer.listInstallable()}async installAgent(e){const t=await this.installer.install(e);return se(t),t}getInstallProgress(e){return this.installer.getProgress(e)??null}}async function fe(n,e){const t=e.concurrency??4,s=Date.now(),a=new Array(n.length);await new Promise(p=>{let g=0,f=0;const w=n.length;if(w===0){p();return}function l(u){const _=n[u];_.probe(e).then(y=>{a[u]=y,m()},y=>{a[u]={agent_name:_.name,client_type:"unknown",adapter_type:"acp",ok:!1,status:"error",probed_at:Date.now(),duration_ms:0,cached:!1,cli:{command:"",installed:!1,path:null,version:null,error:{code:"internal",message:y?.message??String(y)}},conversation:{attempted:!1,ok:!1,latency_ms:null},config:{model:null,base_url:null,source:{model:"unknown",base_url:"unknown"}},process:{started:!1,alive:!1,busy:!1}},m()})}function m(){f++,g<w?l(g++):f===w&&p()}const h=Math.min(t,w);for(let u=0;u<h;u++)l(g++)});const r=a.filter(p=>p.status==="healthy").length,o=a.filter(p=>p.status==="degraded").length,i=a.filter(p=>p.status==="unavailable").length,c=await ce();return{ok:r===a.length&&a.length>0,total:a.length,healthy:r,degraded:o,unavailable:i,installed_clients:c,agents:a,probed_at:s,duration_ms:Date.now()-s}}async function _e(n,e,t){const s=n.find(a=>a.name===e);if(!s)throw Object.assign(new Error(`Agent "${e}" not found`),{code:"NOT_FOUND"});return s.probe(t)}export{Ee as Manager,ce as probeInstalledClientCommands,fe as probeInstances,_e as probeOneInstance};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import*as
|
|
1
|
+
import*as n from"node:net";const i={bind:"127.0.0.1",port:0,endpoint:"/mcp",sessionTimeoutMs:18e5,invokeTimeoutMs:3e4};function s(u){const e={bind:u?.bind??i.bind,port:u?.port??i.port,endpoint:u?.endpoint??i.endpoint,sessionTimeoutMs:u?.sessionTimeoutMs??i.sessionTimeoutMs,invokeTimeoutMs:u?.invokeTimeoutMs??i.invokeTimeoutMs,allowedOrigins:u?.allowedOrigins,allowedHosts:u?.allowedHosts};return t(e.bind),e.port!==0&&o(e.port),r(e.sessionTimeoutMs),e}function t(u){if(!u||!n.isIPv4(u)&&!n.isIPv6(u))throw new Error(`\u914D\u7F6E\u6821\u9A8C\u5931\u8D25: bind \u5730\u5740 "${u}" \u4E0D\u662F\u5408\u6CD5\u7684 IPv4 \u6216 IPv6 \u5730\u5740`)}function o(u){if(!Number.isInteger(u)||u<1||u>65535)throw new Error(`\u914D\u7F6E\u6821\u9A8C\u5931\u8D25: port \u503C ${u} \u4E0D\u5728\u5408\u6CD5\u8303\u56F4 1-65535 \u5185\u6216\u4E0D\u662F\u6574\u6570`)}function r(u){if(!Number.isInteger(u)||u<1e3||u>864e5)throw new Error(`\u914D\u7F6E\u6821\u9A8C\u5931\u8D25: session_timeout_ms \u503C ${u} \u4E0D\u5728\u5408\u6CD5\u8303\u56F4 1000-86400000 \u5185`)}export{s as createDefaultGatewayConfig};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
const
|
|
1
|
+
const a=3e4;class c{connectionManager;onDisconnected;bindings=new Map;constructor(n,i){this.connectionManager=n,this.onDisconnected=i}async bind(n,i){if(this.bindings.has(n))throw new Error(`Session ${n} is already bound to a connection`);const e=await this.connectWithTimeout(i),t=[],s=e.onDisconnected(()=>{this.removeBinding(n),this.onDisconnected(n)});return t.push(s),this.bindings.set(n,{sessionId:n,handle:e,subscriptions:t}),e}getHandle(n){return this.bindings.get(n)?.handle}unbind(n){const i=this.bindings.get(n);if(i){this.bindings.delete(n);for(const e of i.subscriptions)e();i.handle.disconnect()}}unbindAll(){const n=[...this.bindings.keys()];for(const i of n)this.unbind(i)}connectWithTimeout(n){return new Promise((i,e)=>{let t=!1;const s=setTimeout(()=>{t||(t=!0,e(new Error("Connection bind timeout after 30000ms")))},3e4);this.connectionManager.connect({agentId:n.agentId,apiKey:n.apiKey,url:n.wsUrl,clientType:n.clientType,capabilities:["agent_invoke"],adapterHint:`${n.clientType}/base`},{maxRetries:0}).then(o=>{t?o.disconnect():(t=!0,clearTimeout(s),i(o))}).catch(o=>{t||(t=!0,clearTimeout(s),e(o))})})}removeBinding(n){const i=this.bindings.get(n);if(i){this.bindings.delete(n);for(const e of i.subscriptions)e()}}}export{c as ConnectionBindingImpl};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
function a(
|
|
1
|
+
function a(e){const t=new Set([`http://127.0.0.1:${e.serverPort}`,`http://localhost:${e.serverPort}`,...e.allowedOrigins]),o=new Set([`127.0.0.1:${e.serverPort}`,`localhost:${e.serverPort}`,...e.allowedHosts]);return{validateRequest(s){const r=i(s,t);if(!r.ok)return r;const n=l(s,o);return n.ok?{ok:!0}:n}}}function i(e,t){const o=e.headers.origin;return o?t.has(o)?{ok:!0}:{ok:!1,statusCode:403,message:`Origin not allowed: ${o}`}:{ok:!0}}function l(e,t){const o=e.headers.host;return o?t.has(o)?{ok:!0}:{ok:!1,statusCode:403,message:`Host not allowed: ${o}`}:{ok:!1,statusCode:403,message:"Missing Host header"}}export{a as createSecurityPolicy};
|