grix-connector 2.0.1 → 2.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- import{createServer as g}from"node:http";import{log as c}from"../log/logger.js";class m{server=null;token;handler=null;upgradeHandler=null;probeHandler=null;installHandler=null;constructor(e){this.token=e}setAgentHandler(e){this.handler=e}setUpgradeHandler(e){this.upgradeHandler=e}setProbeHandler(e){this.probeHandler=e}setInstallHandler(e){this.installHandler=e}async start(e){return new Promise((n,t)=>{this.server=g((r,s)=>this.handleRequest(r,s)),this.server.listen(e,"127.0.0.1",()=>{c.info("admin",`Listening on 127.0.0.1:${e}`),n()}),this.server.on("error",t)})}async stop(){if(this.server)return new Promise(e=>{this.server.close(()=>e())})}handleRequest(e,n){const t=e.method??"",r=e.url??"";if(r==="/api/agents"&&t==="GET")this.handleList(n);else if(r==="/api/agents"&&t==="POST")this.readBody(e).then(s=>this.handleAdd(n,s)).catch(s=>this.error(n,s));else if(t==="DELETE"&&r.startsWith("/api/agents/")){const s=decodeURIComponent(r.slice(12));this.handleRemove(n,s)}else if(t==="POST"&&r.match(/^\/api\/agents\/[^/]+\/restart$/)){const s=decodeURIComponent(r.slice(12,r.lastIndexOf("/restart")));this.handleRestart(n,s)}else if(r==="/api/upgrade"&&t==="GET")this.handleCheckUpgrade(n);else if(r==="/api/upgrade"&&t==="POST")this.handleTriggerUpgrade(n);else if(t==="GET"&&r.startsWith("/api/probe"))this.handleProbe(e,n,r);else if(r==="/api/install"&&t==="GET")this.handleInstallList(n);else if(r==="/api/install"&&t==="POST")this.readBody(e).then(s=>this.handleInstall(n,s)).catch(s=>this.error(n,s));else if(t==="GET"&&r.startsWith("/api/install/")){const s=decodeURIComponent(r.slice(13));this.handleInstallProgress(n,s)}else this.json(n,404,{error:"not_found"})}handleList(e){try{const n=this.handler?.list()??[];this.json(e,200,n)}catch(n){this.error(e,n)}}async handleAdd(e,n){try{const t=await this.handler.add(n);this.json(e,201,t??{ok:!0})}catch(t){this.error(e,t)}}handleRemove(e,n){this.handler.remove(n).then(()=>{e.writeHead(204),e.end()}).catch(t=>this.error(e,t))}handleRestart(e,n){this.handler.restart(n).then(()=>{this.json(e,200,{ok:!0})}).catch(t=>this.error(e,t))}handleCheckUpgrade(e){if(!this.upgradeHandler){this.json(e,501,{error:"upgrade not configured"});return}this.upgradeHandler.check().then(n=>{this.json(e,200,n)}).catch(n=>this.error(e,n))}handleTriggerUpgrade(e){if(!this.upgradeHandler){this.json(e,501,{error:"upgrade not configured"});return}this.upgradeHandler.trigger(),this.json(e,200,{ok:!0,message:"upgrade check triggered"})}error(e,n){const t=n;t.code==="NOT_FOUND"?this.json(e,404,{error:t.message??"not found"}):t.code==="UNKNOWN_AGENT"||t.code==="UNSUPPORTED_OS"?this.json(e,400,{error:t.message,code:t.code}):t.code==="ALREADY_INSTALLED"||t.code==="INSTALL_IN_PROGRESS"?this.json(e,409,{error:t.message,code:t.code}):t.code==="INSTALL_FAILED"||t.code==="INSTALL_TIMEOUT"||t.code==="PREFLIGHT_FAILED"||t.code==="VERIFICATION_FAILED"||t.code==="PREREQ_MISSING"||t.code==="PREREQ_INSTALL_FAILED"||t.code==="FALLBACK_EXHAUSTED"||t.code==="ENVIRONMENT_UNSUPPORTED"?this.json(e,500,{error:t.message,code:t.code}):(c.error("admin",`Handler error: ${t.message??n}`),this.json(e,500,{error:t.message??"internal error"}))}json(e,n,t){const r=JSON.stringify(t);e.writeHead(n,{"Content-Type":"application/json"}),e.end(r)}readBody(e){return new Promise((n,t)=>{let r="";e.setEncoding("utf8"),e.on("data",s=>{r+=s}),e.on("end",()=>{try{n(JSON.parse(r))}catch{t(new Error("invalid JSON body"))}}),e.on("error",t)})}handleProbe(e,n,t){if(!this.probeHandler){this.json(n,501,{error:"probe not configured"});return}const r=t.indexOf("?"),s=r>=0?t.slice(0,r):t,a=r>=0?new URLSearchParams(t.slice(r+1)):new URLSearchParams,i={};a.get("conversation")==="true"&&(i.conversation=!0),a.get("fresh")==="true"&&(i.fresh=!0);const l=Number(a.get("timeoutMs"));Number.isFinite(l)&&l>0&&(i.timeoutMs=l);const d=s.match(/^\/api\/probe\/(.+)$/);if(d){const o=decodeURIComponent(d[1]);this.probeHandler.probeOne(o,i).then(h=>{this.json(n,200,h)}).catch(h=>this.error(n,h));return}this.probeHandler.probeAll(i).then(o=>{this.json(n,200,o)}).catch(o=>this.error(n,o))}handleInstallList(e){if(!this.installHandler){this.json(e,501,{error:"install not configured"});return}try{const n=this.installHandler.listInstallable();this.json(e,200,n)}catch(n){this.error(e,n)}}handleInstallProgress(e,n){if(!this.installHandler){this.json(e,501,{error:"install not configured"});return}const t=this.installHandler.getInstallProgress(n);this.json(e,200,{agentType:n,inProgress:t!=null,progress:t})}async handleInstall(e,n){if(!this.installHandler){this.json(e,501,{error:"install not configured"});return}try{const t=n;if(!t||typeof t.agentType!="string"||!t.agentType){this.json(e,400,{error:"agentType is required"});return}const r=await this.installHandler.installAgent(t);if(r.ok)this.json(e,200,r);else{const s=r.error?.code;s==="UNKNOWN_AGENT"||s==="UNSUPPORTED_OS"?this.json(e,400,r):s==="INSTALL_IN_PROGRESS"?this.json(e,409,r):this.json(e,500,r)}}catch(t){this.error(e,t)}}}export{m as AdminServer};
1
+ import{createServer as g}from"node:http";import{log as c}from"../log/logger.js";class m{server=null;token;handler=null;upgradeHandler=null;probeHandler=null;installHandler=null;constructor(e){this.token=e}setAgentHandler(e){this.handler=e}setUpgradeHandler(e){this.upgradeHandler=e}setProbeHandler(e){this.probeHandler=e}setInstallHandler(e){this.installHandler=e}async start(e){return new Promise((r,t)=>{this.server=g((n,s)=>this.handleRequest(n,s)),this.server.listen(e,"127.0.0.1",()=>{c.info("admin",`Listening on 127.0.0.1:${e}`),r()}),this.server.on("error",t)})}async stop(){if(this.server)return new Promise(e=>{this.server.close(()=>e())})}handleRequest(e,r){const t=e.method??"",n=e.url??"";if(n==="/api/agents"&&t==="GET")this.handleList(r);else if(n==="/api/agents"&&t==="POST")this.readBody(e).then(s=>this.handleAdd(r,s)).catch(s=>this.error(r,s));else if(t==="DELETE"&&n.startsWith("/api/agents/")){const s=decodeURIComponent(n.slice(12));this.handleRemove(r,s)}else if(t==="POST"&&n.match(/^\/api\/agents\/[^/]+\/restart$/)){const s=decodeURIComponent(n.slice(12,n.lastIndexOf("/restart")));this.handleRestart(r,s)}else if(n==="/api/upgrade"&&t==="GET")this.handleCheckUpgrade(r);else if(n==="/api/upgrade"&&t==="POST")this.handleTriggerUpgrade(r);else if(t==="GET"&&n.startsWith("/api/probe"))this.handleProbe(e,r,n);else if(n==="/api/install"&&t==="GET")this.handleInstallList(r);else if(n==="/api/install"&&t==="POST")this.readBody(e).then(s=>this.handleInstall(r,s)).catch(s=>this.error(r,s));else if(t==="GET"&&n.startsWith("/api/install/")){const s=decodeURIComponent(n.slice(13));this.handleInstallProgress(r,s)}else this.json(r,404,{error:"not_found"})}handleList(e){try{const r=this.handler?.list()??[];this.json(e,200,r)}catch(r){this.error(e,r)}}async handleAdd(e,r){try{const t=await this.handler.add(r);this.json(e,201,t??{ok:!0})}catch(t){this.error(e,t)}}handleRemove(e,r){this.handler.remove(r).then(()=>{e.writeHead(204),e.end()}).catch(t=>this.error(e,t))}handleRestart(e,r){this.handler.restart(r).then(()=>{this.json(e,200,{ok:!0})}).catch(t=>this.error(e,t))}handleCheckUpgrade(e){if(!this.upgradeHandler){this.json(e,501,{error:"upgrade not configured"});return}this.upgradeHandler.check().then(r=>{this.json(e,200,r)}).catch(r=>this.error(e,r))}handleTriggerUpgrade(e){if(!this.upgradeHandler){this.json(e,501,{error:"upgrade not configured"});return}this.upgradeHandler.trigger(),this.json(e,200,{ok:!0,message:"upgrade check triggered"})}error(e,r){const t=r;t.code==="NOT_FOUND"?this.json(e,404,{error:t.message??"not found"}):t.code==="UNKNOWN_AGENT"||t.code==="UNSUPPORTED_OS"?this.json(e,400,{error:t.message,code:t.code}):t.code==="ALREADY_INSTALLED"||t.code==="INSTALL_IN_PROGRESS"?this.json(e,409,{error:t.message,code:t.code}):t.code==="INSTALL_FAILED"||t.code==="INSTALL_TIMEOUT"||t.code==="PREFLIGHT_FAILED"||t.code==="VERIFICATION_FAILED"||t.code==="PREREQ_MISSING"||t.code==="PREREQ_INSTALL_FAILED"||t.code==="FALLBACK_EXHAUSTED"||t.code==="ENVIRONMENT_UNSUPPORTED"?this.json(e,500,{error:t.message,code:t.code}):(c.error("admin",`Handler error: ${t.message??r}`),this.json(e,500,{error:t.message??"internal error"}))}json(e,r,t){const n=JSON.stringify(t);e.writeHead(r,{"Content-Type":"application/json"}),e.end(n)}readBody(e){return new Promise((r,t)=>{let n="";e.setEncoding("utf8"),e.on("data",s=>{n+=s}),e.on("end",()=>{try{r(JSON.parse(n))}catch{t(new Error("invalid JSON body"))}}),e.on("error",t)})}handleProbe(e,r,t){if(!this.probeHandler){this.json(r,501,{error:"probe not configured"});return}const n=t.indexOf("?"),s=n>=0?t.slice(0,n):t,a=n>=0?new URLSearchParams(t.slice(n+1)):new URLSearchParams,i={};a.get("conversation")==="true"&&(i.conversation=!0),a.get("fresh")==="true"&&(i.fresh=!0);const l=Number(a.get("timeoutMs"));Number.isFinite(l)&&l>0&&(i.timeoutMs=l);const d=s.match(/^\/api\/probe\/(.+)$/);if(d){const o=decodeURIComponent(d[1]);this.probeHandler.probeOne(o,i).then(h=>{this.json(r,200,h)}).catch(h=>this.error(r,h));return}this.probeHandler.probeAll(i).then(o=>{this.json(r,200,o)}).catch(o=>this.error(r,o))}handleInstallList(e){if(!this.installHandler){this.json(e,501,{error:"install not configured"});return}try{const r=this.installHandler.listInstallable();this.json(e,200,r)}catch(r){this.error(e,r)}}handleInstallProgress(e,r){if(!this.installHandler){this.json(e,501,{error:"install not configured"});return}const t=this.installHandler.getInstallProgress(r);if(!t){this.json(e,200,{agentType:r,status:"unknown",inProgress:!1,progress:null});return}let n,s,a;switch(t.phase){case"completed":n="done",s="\u5B89\u88C5\u5B8C\u6210";break;case"failed":n="error",a=t.outputTail||"\u5B89\u88C5\u5931\u8D25";break;case"preflight":n="pending",s="\u68C0\u67E5\u524D\u7F6E\u4F9D\u8D56...";break;case"installing_prereq":n="downloading",s=t.currentPrereq?`\u6B63\u5728\u5B89\u88C5\u524D\u7F6E\u4F9D\u8D56: ${t.currentPrereq}`:"\u6B63\u5728\u5B89\u88C5\u524D\u7F6E\u4F9D\u8D56...";break;case"installing":n="installing",s=`\u6B63\u5728\u5B89\u88C5 ${r}...`;break;case"verifying":n="installing",s="\u9A8C\u8BC1\u5B89\u88C5...";break;default:n="unknown"}this.json(e,200,{agentType:r,status:n,inProgress:!0,progress:t.elapsedMs?Math.min(.9,t.elapsedMs/3e4):.1,message:s,error:a})}async handleInstall(e,r){if(!this.installHandler){this.json(e,501,{error:"install not configured"});return}try{const t=r;if(!t||typeof t.agentType!="string"||!t.agentType){this.json(e,400,{error:"agentType is required"});return}const n=await this.installHandler.installAgent(t);if(n.ok)this.json(e,200,n);else{const s=n.error?.code;s==="UNKNOWN_AGENT"||s==="UNSUPPORTED_OS"?this.json(e,400,n):s==="INSTALL_IN_PROGRESS"?this.json(e,409,n):this.json(e,500,n)}}catch(t){this.error(e,t)}}}export{m as AdminServer};
@@ -1,2 +1,2 @@
1
- import{EventEmitter as g}from"node:events";import{randomUUID as k}from"node:crypto";import f from"node:os";import _ from"ws";import{log as o}from"../log/index.js";import{detectTailnetIPv4 as p,ensureServerAndGetPort as v}from"../files/file-serve.js";function $(m){return m.replace(/(?<=[\[:,\[]\s*)(\d{16,})(?=\s*[,}\]\n])/g,'"$1"')}function b(m){const e=[...m??["stream_chunk","local_action_v1","agent_invoke"]];return e.includes("agent_invoke")||e.push("agent_invoke"),e.includes("event_result_ack")||e.push("event_result_ack"),e}const w="aibot-agent-api-v1",q=1;class h extends g{static DROPPABLE_COMMANDS=new Set(["update_binding_card"]);static BUFFER_OVERFLOW_RETAIN_COMMANDS=new Set(["event_result","codex_event","client_stream_chunk"]);static MAX_OUTBOUND_BUFFER_SIZE=1e3;static BACKPRESSURE_THRESHOLD=64*1024;ws=null;seq=0;heartbeatTimer=null;heartbeatSec=30;connected=!1;reconnecting=!1;reconnectAttempts=0;everConnected=!1;config;packetLog;pendingInvokes=new Map;seqEventMap=new Map;pendingRequests=new Map;outboundBuffer=[];ackPolicy=null;constructor(e,t){super(),this.packetLog=t?.packetLog??null,this.config={url:e.url,agentId:e.agentId,apiKey:e.apiKey,clientType:e.clientType,clientVersion:e.clientVersion??"",adapterHint:e.adapterHint??"",capabilities:b(e.capabilities),localActions:e.localActions??["exec_approve","exec_reject"],skills:e.skills}}get isConnected(){return this.connected}async connect(){const e=await p();let t;if(e!==void 0)try{t=await v(e)}catch(n){o.warn("aibot",`file server pre-start failed: ${n}`)}return new Promise((n,c)=>{const r=new _(this.config.url);this.ws=r;const u=setTimeout(()=>{c(new Error("Auth timeout: no auth_ack received within 15s")),this.cleanupSocket()},15e3),a=++this.seq,d=setTimeout(()=>{this.pendingRequests.delete(a),c(new Error("Auth request timeout")),this.cleanupSocket()},15e3);this.pendingRequests.set(a,{expected:["auth_ack"],resolve:i=>{clearTimeout(u);const s=i.payload;s.code===0?(this.connected=!0,this.everConnected=!0,this.reconnectAttempts=0,s.heartbeat_sec&&(this.heartbeatSec=s.heartbeat_sec),s.ack_policy&&(this.ackPolicy=s.ack_policy,o.info("aibot",`ack_policy received: push_ack_timeout_ms=${s.ack_policy.push_ack_timeout_ms??"default"} max_retries=${s.ack_policy.max_retries??"default"} timeout_action=${s.ack_policy.timeout_action??"default"}`)),this.startHeartbeat(),this.flushOutboundBuffer(),this.emit("auth",s),n(s)):c(new Error(`Auth failed: code=${s.code} msg=${s.msg}`))},reject:i=>{clearTimeout(u),c(i)},timer:d}),r.on("open",()=>{const i={agent_id:this.config.agentId,api_key:this.config.apiKey,client_type:this.config.clientType,protocol_version:w,contract_version:q,capabilities:this.config.capabilities??[],local_actions:this.config.localActions,skills:this.config.skills};this.config.clientVersion&&(i.client="grix-connector",i.client_version=this.config.clientVersion,i.host_type=this.config.clientType,i.host_version=this.config.clientVersion),this.config.adapterHint&&(i.adapter_hint=this.config.adapterHint),i.host_meta={hostname:f.hostname(),platform:f.platform(),arch:f.arch(),os_release:f.release(),...e!==void 0&&{tailnet_ip:e},...t!==void 0&&t>0&&{file_server_port:t}},this.config.concurrency&&(i.concurrency=this.config.concurrency),this.sendPacket("auth",i,a)}),r.on("message",i=>{let s;try{s=JSON.parse($(i.toString()))}catch{return}try{this.handlePacket(s)}catch(l){this.emitClientError(new Error(`handlePacket error: ${l}`))}}),r.on("close",(i,s)=>{this.connected=!1,this.stopHeartbeat(),this.rejectAllPendingRequests("websocket closed"),this.emit("close",i,s.toString());const l=i!==1e3&&this.everConnected;o.info("aibot",`ws closed agent=${this.config.clientType}:${this.config.agentId} code=${i} reason=${s.toString()||"<none>"} everConnected=${this.everConnected} reconnecting=${this.reconnecting} willReconnect=${l}`),l&&this.attemptReconnect()}),r.on("error",i=>{this.emitClientError(i instanceof Error?i:new Error(String(i))),this.connected||c(i)})})}handlePacket(e){if(this.packetLog?.logInboundPacket(e.cmd,e.seq,e.payload),e.seq>0&&this.pendingRequests.has(e.seq)){const t=this.pendingRequests.get(e.seq);this.pendingRequests.delete(e.seq),clearTimeout(t.timer),t.expected.includes(e.cmd)?t.resolve(e):t.reject(new Error(`unexpected response: got ${e.cmd}, expected ${t.expected.join("/")}`));return}switch(e.cmd){case"auth_ack":break;case"ping":{this.sendPacket("pong",e.payload??{});break}case"event_msg":{this.emit("event",e.payload);break}case"local_action":{this.emit("localAction",e.payload);break}case"event_stop":{this.emit("stop",e.payload);break}case"event_revoke":{this.emit("revoke",e.payload);break}case"event_edit":{this.emit("edit",e.payload);break}case"event_cancel":{this.emit("eventCancel",e.payload);break}case"queue_clear":{this.emit("queueClear",e.payload);break}case"queue_snapshot_query":{this.emit("queueSnapshotQuery",e.payload);break}case"kicked":{if(this.emit("kicked",e.payload),this.connected=!1,this.stopHeartbeat(),this.rejectAllPendingRequests("kicked"),this.outboundBuffer.length=0,this.reconnectAttempts=Math.max(this.reconnectAttempts,3),this.ws){try{this.ws.close(4001,"kicked")}catch{}this.ws=null}break}case"error":{const t=e.payload,n=[t.ref_cmd?`ref_cmd=${t.ref_cmd}`:"",t.ref_id?`ref_id=${t.ref_id}`:""].filter(Boolean).join(" ");this.emitClientError(new Error(`Server error: code=${t.code} msg=${t.msg}${n?` ${n}`:""}`));break}case"agent_invoke_result":{this.handleInvokeResult(e.payload);break}case"mcp_frame":{const t=e.payload;this.emit("mcpFrame",t.session_id??"",t.frame??null);break}case"send_ack":break;case"send_nack":{const t=e.payload;if(t.code===4003&&e.seq>0){const n=this.seqEventMap.get(e.seq);n&&(this.seqEventMap.delete(e.seq),this.purgeBufferedStreamChunks(n),o.warn("aibot",`stream chunk rejected (4003), purging buffered chunks for event=${n}`),this.emit("streamRejected",n,t.code))}break}case"local_action_ack":break;default:break}}sendEventAck(e){this.sendPacket("event_ack",e)||o.warn("aibot",`event_ack NOT sent (ws not open) event=${e.event_id} ws=${this.ws?`state=${this.ws.readyState}`:"null"}`)}sendStreamChunk(e){!e.delta_content&&!e.is_finish&&(o.warn("aibot",`stream_chunk delta_content empty, patched to newline event=${e.event_id??""} session=${e.session_id} chunk_seq=${e.chunk_seq} is_finish=${e.is_finish}`),e={...e,delta_content:`
2
- `}),this.sendPacket("client_stream_chunk",e)}sendMsg(e){this.sendPacket("send_msg",e)||o.warn("aibot",`send_msg NOT sent (ws not open) event=${e.event_id??""} session=${e.session_id??""} ws=${this.ws?`state=${this.ws.readyState}`:"null"}`)}editMsg(e){this.sendPacket("edit_msg",e)}sendEventResult(e){if(!this.ws||this.ws.readyState!==_.OPEN){this.sendPacket("event_result",e);return}this.sendEventResultReliable(e)}sendLocalActionResult(e){this.sendPacket("local_action_result",e)}sendEventStopAck(e){this.sendPacket("event_stop_ack",e)}sendEventStopResult(e){this.sendPacket("event_stop_result",e)}sendSessionActivitySet(e){this.sendPacket("session_activity_set",e)}sendCodexEvent(e){this.sendPacket("codex_event",e)}sendUpdateBindingCard(e){this.sendPacket("update_binding_card",e)}sendSkillsUpdate(e){this.sendPacket("agent_skills_update",e)}sendPing(){this.sendPacket("ping",{})}sendEventState(e){this.sendPacket("event_state",e)}sendEventCancelResult(e){this.sendPacket("event_cancel_result",e)}sendQueueClearResult(e){this.sendPacket("queue_clear_result",e)}sendQueueSnapshot(e){this.sendPacket("queue_snapshot",e)}agentInvoke(e,t,n=15e3){return new Promise((c,r)=>{const u=k(),a=Math.max(1e3,Math.min(n,6e4)),d=setTimeout(()=>{this.pendingInvokes.delete(u),r(new Error(`agent_invoke timeout: ${e}`))},a);this.pendingInvokes.set(u,{resolve:c,reject:r,timer:d}),this.sendPacket("agent_invoke",{invoke_id:u,action:e,params:t,timeout_ms:a})})}sendMcpFrame(e,t){this.sendPacket("mcp_frame",{session_id:e,frame:t})}request(e,t,n){return new Promise((c,r)=>{const u=++this.seq,a=setTimeout(()=>{this.pendingRequests.delete(u),r(new Error(`request timeout: ${e} (expected ${n.expected.join("/")})`))},n.timeoutMs);this.pendingRequests.set(u,{expected:n.expected,resolve:c,reject:r,timer:a}),this.sendPacket(e,t,u)||(this.pendingRequests.delete(u),clearTimeout(a),r(new Error(`send failed: ${e}`)))})}async sendStreamChunkRequest(e,t=2e4){return this.request("client_stream_chunk",e,{expected:["send_ack","send_nack","error"],timeoutMs:t})}async sendText(e,t=2e4){return this.request("send_msg",{msg_type:1,...e},{expected:["send_ack","send_nack","error"],timeoutMs:t})}async sendMedia(e,t=2e4){return this.request("send_msg",{...e,msg_type:2},{expected:["send_ack","send_nack","error"],timeoutMs:t})}async editMessage(e,t=2e4){return this.request("edit_msg",e,{expected:["send_ack","send_nack","error"],timeoutMs:t})}async deleteMessage(e,t,n=2e4){return this.request("delete_msg",{session_id:e,msg_id:t},{expected:["send_ack","send_nack","error"],timeoutMs:n})}async sendEventResultRequest(e,t=5e3){return this.request("event_result",e,{expected:["send_ack","send_nack","error"],timeoutMs:t})}disconnect(){o.info("aibot",`disconnect() agent=${this.config.clientType}:${this.config.agentId} wasConnected=${this.connected} reconnecting=${this.reconnecting} reconnectAttempts=${this.reconnectAttempts}`),this.connected=!1,this.everConnected=!1,this.reconnecting=!1,this.reconnectAttempts=0,this.stopHeartbeat(),this.rejectAllPendingInvokes("disconnect"),this.rejectAllPendingRequests("disconnect"),this.outboundBuffer.length=0,this.ws&&(this.ws.close(1e3,"client disconnect"),this.ws=null)}async attemptReconnect(){if(!this.reconnecting)for(this.reconnecting=!0,o.info("aibot",`attemptReconnect start agent=${this.config.clientType}:${this.config.agentId} fromAttempts=${this.reconnectAttempts}`),this.emit("disconnected");this.reconnecting;){const e=Math.min(1e3*2**this.reconnectAttempts,3e4),t=Math.floor(e*.2*Math.random());if(this.reconnectAttempts++,await new Promise(n=>setTimeout(n,e+t)),!this.reconnecting)return;try{const n=await this.connect(),c=this.reconnectAttempts;this.reconnectAttempts=0,this.reconnecting=!1,o.info("aibot",`reconnect succeeded agent=${this.config.clientType}:${this.config.agentId} attempt=${c}`),this.emit("auth",n);return}catch(n){o.warn("aibot",`reconnect failed agent=${this.config.clientType}:${this.config.agentId} attempt=${this.reconnectAttempts} err=${n instanceof Error?n.message:n}`)}}}sendPacket(e,t,n){if(this.ws&&this.ws.readyState===_.OPEN){const c=this.ws.bufferedAmount>h.BACKPRESSURE_THRESHOLD;if(!c||!h.DROPPABLE_COMMANDS.has(e)){if(c&&h.DROPPABLE_COMMANDS.has(e))return!1;const r=n??++this.seq;if(e==="client_stream_chunk"&&t&&typeof t=="object"){const a=t.event_id;if(a&&(this.seqEventMap.set(r,a),this.seqEventMap.size>200)){const d=this.seqEventMap.keys().next().value;d!==void 0&&this.seqEventMap.delete(d)}}const u={cmd:e,seq:r,payload:t};this.packetLog?.logOutboundPacket(e,r,t,"sent");try{const a=this.ws.readyState,d=this.ws.bufferedAmount;return this.ws.send(JSON.stringify(u),i=>{if(e==="event_result"){const s=t;i?o.warn("aibot",`event_result ws send callback failed event=${s.event_id??""} status=${s.status??""} seq=${r} readyState=${a} bufferedAmount=${d} err=${i.message}`):o.info("aibot",`event_result ws send callback ok event=${s.event_id??""} status=${s.status??""} seq=${r} readyState=${a} bufferedAmount=${d}`)}else if(e==="client_stream_chunk"){const s=t;i?o.warn("aibot",`stream_chunk ws send failed event=${s.event_id??""} session=${s.session_id??""} seq=${r} chunk_seq=${s.chunk_seq??""} is_finish=${s.is_finish??""} readyState=${a} bufferedAmount=${d} err=${i.message}`):o.info("aibot",`stream_chunk ws send ok event=${s.event_id??""} session=${s.session_id??""} seq=${r} chunk_seq=${s.chunk_seq??""} is_finish=${s.is_finish??""} readyState=${a} bufferedAmount=${d}`)}else if(e==="event_ack"){const s=t;i?o.warn("aibot",`event_ack ws send failed event=${s.event_id??""} seq=${r} readyState=${a} bufferedAmount=${d} err=${i.message}`):o.info("aibot",`event_ack ws send ok event=${s.event_id??""} seq=${r} readyState=${a} bufferedAmount=${d}`)}else if(e==="send_msg"){const s=t;i?o.warn("aibot",`send_msg ws send failed event=${s.event_id??""} session=${s.session_id??""} seq=${r} readyState=${a} bufferedAmount=${d} err=${i.message}`):o.info("aibot",`send_msg ws send ok event=${s.event_id??""} session=${s.session_id??""} seq=${r} readyState=${a} bufferedAmount=${d}`)}else if(i){const s=t;o.warn("aibot",`${e} ws send failed seq=${r} session=${s.session_id??""} event=${s.event_id??""} client_msg_id=${s.client_msg_id??""} readyState=${a} bufferedAmount=${d} err=${i.message}`)}}),!0}catch(a){return this.emitClientError(new Error(`sendPacket failed: ${a}`)),!1}}}if(h.DROPPABLE_COMMANDS.has(e))return this.packetLog?.logOutboundPacket(e,n??0,t,"dropped"),!1;if(n!==void 0)return this.packetLog?.logOutboundPacket(e,n,t,"dropped"),!1;if(this.outboundBuffer.length>=h.MAX_OUTBOUND_BUFFER_SIZE&&(this.outboundBuffer=this.outboundBuffer.filter(c=>h.BUFFER_OVERFLOW_RETAIN_COMMANDS.has(c.cmd)),this.outboundBuffer.length>=h.MAX_OUTBOUND_BUFFER_SIZE&&this.outboundBuffer.shift()),this.outboundBuffer.push({cmd:e,payload:t}),this.packetLog?.logOutboundPacket(e,n??0,t,"buffered"),e==="client_stream_chunk"){const c=t;o.info("aibot",`stream_chunk buffered (ws not open) event=${c.event_id??""} session=${c.session_id??""} chunk_seq=${c.chunk_seq??""} is_finish=${c.is_finish??""} ws=${this.ws?`state=${this.ws.readyState}`:"null"}`)}return!1}async sendEventResultReliable(e){const t=this.ackPolicy?.max_retries??3,n=this.ackPolicy?.push_ack_timeout_ms??5e3,c=750;for(let r=1;r<=t;r++){const u=this.ws?.readyState??-1,a=this.ws?.bufferedAmount??0;o.info("aibot",`event_result send attempt event=${e.event_id} status=${e.status} attempt=${r}/${t} readyState=${u} bufferedAmount=${a}`);try{const d=await this.sendEventResultRequest(e,n);if(d.cmd==="send_ack"){const s=d.payload;o.info("aibot",`event_result ack event=${e.event_id} status=${e.status} attempt=${r}/${t} ack_event=${s.event_id??""} ack_status=${s.status??""}`);return}const i=d.payload;if(o.warn("aibot",`event_result rejected event=${e.event_id} status=${e.status} attempt=${r}/${t} cmd=${d.cmd} code=${i.code??""} msg=${i.msg??""}${i.ref_cmd?` ref_cmd=${i.ref_cmd}`:""}${i.ref_id?` ref_id=${i.ref_id}`:""}`),i.code===4003){o.warn("aibot",`event_result stopping retries: 4003 ownership denied event=${e.event_id}`);return}return}catch(d){const i=d instanceof Error?d.message:String(d);if(o.warn("aibot",`event_result attempt failed event=${e.event_id} status=${e.status} attempt=${r}/${t} err=${i}`),r===t){this.emitClientError(new Error(`event_result ack failed after ${t} attempts: event=${e.event_id} status=${e.status}`));return}await new Promise(s=>setTimeout(s,c*r))}}}purgeBufferedStreamChunks(e){const t=this.outboundBuffer.length;this.outboundBuffer=this.outboundBuffer.filter(n=>n.cmd!=="client_stream_chunk"?!0:n.payload?.event_id!==e),this.outboundBuffer.length<t&&o.info("aibot",`purged ${t-this.outboundBuffer.length} buffered stream chunks for event=${e}`)}emitClientError(e){if(this.listenerCount("error")===0){o.warn("aibot",`Client error (no listeners): ${e.message}`);return}this.emit("error",e)}flushOutboundBuffer(){if(this.outboundBuffer.length===0||!this.ws||this.ws.readyState!==_.OPEN)return;const e=this.outboundBuffer;this.outboundBuffer=[];for(const{cmd:t,payload:n}of e){const c=++this.seq;if(t==="client_stream_chunk"&&n&&typeof n=="object"){const u=n.event_id;u&&this.seqEventMap.set(c,u)}const r={cmd:t,seq:c,payload:n};try{this.ws.send(JSON.stringify(r))}catch{break}}if(this.seqEventMap.size>200){const t=[...this.seqEventMap.entries()].sort((n,c)=>n[0]-c[0]);this.seqEventMap.clear();for(const[n,c]of t.slice(-100))this.seqEventMap.set(n,c)}}handleInvokeResult(e){const t=this.pendingInvokes.get(e.invoke_id);t&&(this.pendingInvokes.delete(e.invoke_id),clearTimeout(t.timer),e.code===0?t.resolve(e.data??null):t.reject(new Error(`agent_invoke error code=${e.code}: ${e.msg??""}`)))}rejectAllPendingInvokes(e){for(const[,t]of this.pendingInvokes)clearTimeout(t.timer),t.reject(new Error(`agent_invoke canceled: ${e}`));this.pendingInvokes.clear()}rejectAllPendingRequests(e){for(const[,t]of this.pendingRequests)clearTimeout(t.timer),t.reject(new Error(`request canceled: ${e}`));this.pendingRequests.clear()}cleanupSocket(){if(this.ws){try{this.ws.close()}catch{}this.ws=null}}startHeartbeat(){this.stopHeartbeat(),this.heartbeatTimer=setInterval(()=>{this.connected&&this.request("ping",{ts:Date.now()},{expected:["pong"],timeoutMs:5e3}).catch(()=>{this.connected&&(this.cleanupSocket(),this.attemptReconnect())})},this.heartbeatSec*1e3)}stopHeartbeat(){this.heartbeatTimer&&(clearInterval(this.heartbeatTimer),this.heartbeatTimer=null)}}export{h as AibotClient};
1
+ import{EventEmitter as k}from"node:events";import{randomUUID as p}from"node:crypto";import _ from"node:os";import m from"ws";import{log as c}from"../log/index.js";import{detectTailnetIPv4 as v,ensureServerAndGetPort as $}from"../files/file-serve.js";function b(g){return g.replace(/(?<=[\[:,\[]\s*)(\d{16,})(?=\s*[,}\]\n])/g,'"$1"')}function w(g){const e=[...g??["stream_chunk","local_action_v1","agent_invoke"]];return e.includes("agent_invoke")||e.push("agent_invoke"),e.includes("event_result_ack")||e.push("event_result_ack"),e}const q="aibot-agent-api-v1",E=1;class l extends k{static DROPPABLE_COMMANDS=new Set(["update_binding_card"]);static BUFFER_OVERFLOW_RETAIN_COMMANDS=new Set(["event_result","codex_event","client_stream_chunk"]);static MAX_OUTBOUND_BUFFER_SIZE=1e3;static BACKPRESSURE_THRESHOLD=64*1024;ws=null;seq=0;heartbeatTimer=null;heartbeatSec=30;connected=!1;reconnecting=!1;reconnectAttempts=0;everConnected=!1;config;packetLog;pendingInvokes=new Map;seqEventMap=new Map;pendingRequests=new Map;outboundBuffer=[];ackPolicy=null;constructor(e,t){super(),this.packetLog=t?.packetLog??null,this.config={url:e.url,agentId:e.agentId,apiKey:e.apiKey,clientType:e.clientType,clientVersion:e.clientVersion??"",adapterHint:e.adapterHint??"",capabilities:w(e.capabilities),localActions:e.localActions??["exec_approve","exec_reject"],skills:e.skills}}get isConnected(){return this.connected}async connect(){let e,t;const n=(async()=>{try{if(e=await v(),e!==void 0)try{t=await $(e)}catch(r){c.warn("aibot",`file server pre-start failed: ${r}`)}}catch(r){c.warn("aibot",`tailnet detect failed: ${r}`)}})();return new Promise((r,i)=>{const u=new m(this.config.url);this.ws=u;const a=setTimeout(()=>{i(new Error("Auth timeout: no auth_ack received within 15s")),this.cleanupSocket()},15e3),o=++this.seq,d=setTimeout(()=>{this.pendingRequests.delete(o),i(new Error("Auth request timeout")),this.cleanupSocket()},15e3);this.pendingRequests.set(o,{expected:["auth_ack"],resolve:s=>{clearTimeout(a);const h=s.payload;h.code===0?(this.connected=!0,this.everConnected=!0,this.reconnectAttempts=0,h.heartbeat_sec&&(this.heartbeatSec=h.heartbeat_sec),h.ack_policy&&(this.ackPolicy=h.ack_policy,c.info("aibot",`ack_policy received: push_ack_timeout_ms=${h.ack_policy.push_ack_timeout_ms??"default"} max_retries=${h.ack_policy.max_retries??"default"} timeout_action=${h.ack_policy.timeout_action??"default"}`)),this.startHeartbeat(),this.flushOutboundBuffer(),this.emit("auth",h),r(h)):i(new Error(`Auth failed: code=${h.code} msg=${h.msg}`))},reject:s=>{clearTimeout(a),i(s)},timer:d}),u.on("open",async()=>{await n;const s={agent_id:this.config.agentId,api_key:this.config.apiKey,client_type:this.config.clientType,protocol_version:q,contract_version:E,capabilities:this.config.capabilities??[],local_actions:this.config.localActions,skills:this.config.skills};this.config.clientVersion&&(s.client="grix-connector",s.client_version=this.config.clientVersion,s.host_type=this.config.clientType,s.host_version=this.config.clientVersion),this.config.adapterHint&&(s.adapter_hint=this.config.adapterHint),s.host_meta={hostname:_.hostname(),platform:_.platform(),arch:_.arch(),os_release:_.release(),...e!==void 0&&{tailnet_ip:e},...t!==void 0&&t>0&&{file_server_port:t}},this.config.concurrency&&(s.concurrency=this.config.concurrency),this.sendPacket("auth",s,o)}),u.on("message",s=>{let h;try{h=JSON.parse(b(s.toString()))}catch{return}try{this.handlePacket(h)}catch(f){this.emitClientError(new Error(`handlePacket error: ${f}`))}}),u.on("close",(s,h)=>{this.connected=!1,this.stopHeartbeat(),this.rejectAllPendingRequests("websocket closed"),this.emit("close",s,h.toString());const f=s!==1e3&&this.everConnected;c.info("aibot",`ws closed agent=${this.config.clientType}:${this.config.agentId} code=${s} reason=${h.toString()||"<none>"} everConnected=${this.everConnected} reconnecting=${this.reconnecting} willReconnect=${f}`),f&&this.attemptReconnect()}),u.on("error",s=>{this.emitClientError(s instanceof Error?s:new Error(String(s))),this.connected||i(s)})})}handlePacket(e){if(this.packetLog?.logInboundPacket(e.cmd,e.seq,e.payload),e.seq>0&&this.pendingRequests.has(e.seq)){const t=this.pendingRequests.get(e.seq);this.pendingRequests.delete(e.seq),clearTimeout(t.timer),t.expected.includes(e.cmd)?t.resolve(e):t.reject(new Error(`unexpected response: got ${e.cmd}, expected ${t.expected.join("/")}`));return}switch(e.cmd){case"auth_ack":break;case"ping":{this.sendPacket("pong",e.payload??{});break}case"event_msg":{this.emit("event",e.payload);break}case"local_action":{this.emit("localAction",e.payload);break}case"event_stop":{this.emit("stop",e.payload);break}case"event_revoke":{this.emit("revoke",e.payload);break}case"event_edit":{this.emit("edit",e.payload);break}case"event_cancel":{this.emit("eventCancel",e.payload);break}case"queue_clear":{this.emit("queueClear",e.payload);break}case"queue_snapshot_query":{this.emit("queueSnapshotQuery",e.payload);break}case"kicked":{if(this.emit("kicked",e.payload),this.connected=!1,this.stopHeartbeat(),this.rejectAllPendingRequests("kicked"),this.outboundBuffer.length=0,this.reconnectAttempts=Math.max(this.reconnectAttempts,3),this.ws){try{this.ws.close(4001,"kicked")}catch{}this.ws=null}break}case"error":{const t=e.payload,n=[t.ref_cmd?`ref_cmd=${t.ref_cmd}`:"",t.ref_id?`ref_id=${t.ref_id}`:""].filter(Boolean).join(" ");this.emitClientError(new Error(`Server error: code=${t.code} msg=${t.msg}${n?` ${n}`:""}`));break}case"agent_invoke_result":{this.handleInvokeResult(e.payload);break}case"mcp_frame":{const t=e.payload;this.emit("mcpFrame",t.session_id??"",t.frame??null);break}case"send_ack":break;case"send_nack":{const t=e.payload;if(t.code===4003&&e.seq>0){const n=this.seqEventMap.get(e.seq);n&&(this.seqEventMap.delete(e.seq),this.purgeBufferedStreamChunks(n),c.warn("aibot",`stream chunk rejected (4003), purging buffered chunks for event=${n}`),this.emit("streamRejected",n,t.code))}break}case"local_action_ack":break;default:break}}sendEventAck(e){this.sendPacket("event_ack",e)||c.warn("aibot",`event_ack NOT sent (ws not open) event=${e.event_id} ws=${this.ws?`state=${this.ws.readyState}`:"null"}`)}sendStreamChunk(e){!e.delta_content&&!e.is_finish&&(c.warn("aibot",`stream_chunk delta_content empty, patched to newline event=${e.event_id??""} session=${e.session_id} chunk_seq=${e.chunk_seq} is_finish=${e.is_finish}`),e={...e,delta_content:`
2
+ `}),this.sendPacket("client_stream_chunk",e)}sendMsg(e){this.sendPacket("send_msg",e)||c.warn("aibot",`send_msg NOT sent (ws not open) event=${e.event_id??""} session=${e.session_id??""} ws=${this.ws?`state=${this.ws.readyState}`:"null"}`)}editMsg(e){this.sendPacket("edit_msg",e)}sendEventResult(e){if(!this.ws||this.ws.readyState!==m.OPEN){this.sendPacket("event_result",e);return}this.sendEventResultReliable(e)}sendLocalActionResult(e){this.sendPacket("local_action_result",e)}sendEventStopAck(e){this.sendPacket("event_stop_ack",e)}sendEventStopResult(e){this.sendPacket("event_stop_result",e)}sendSessionActivitySet(e){this.sendPacket("session_activity_set",e)}sendCodexEvent(e){this.sendPacket("codex_event",e)}sendUpdateBindingCard(e){this.sendPacket("update_binding_card",e)}sendSkillsUpdate(e){this.sendPacket("agent_skills_update",e)}sendPing(){this.sendPacket("ping",{})}sendEventState(e){this.sendPacket("event_state",e)}sendEventCancelResult(e){this.sendPacket("event_cancel_result",e)}sendQueueClearResult(e){this.sendPacket("queue_clear_result",e)}sendQueueSnapshot(e){this.sendPacket("queue_snapshot",e)}agentInvoke(e,t,n=15e3){return new Promise((r,i)=>{const u=p(),a=Math.max(1e3,Math.min(n,6e4)),o=setTimeout(()=>{this.pendingInvokes.delete(u),i(new Error(`agent_invoke timeout: ${e}`))},a);this.pendingInvokes.set(u,{resolve:r,reject:i,timer:o}),this.sendPacket("agent_invoke",{invoke_id:u,action:e,params:t,timeout_ms:a})})}sendMcpFrame(e,t){this.sendPacket("mcp_frame",{session_id:e,frame:t})}request(e,t,n){return new Promise((r,i)=>{const u=++this.seq,a=setTimeout(()=>{this.pendingRequests.delete(u),i(new Error(`request timeout: ${e} (expected ${n.expected.join("/")})`))},n.timeoutMs);this.pendingRequests.set(u,{expected:n.expected,resolve:r,reject:i,timer:a}),this.sendPacket(e,t,u)||(this.pendingRequests.delete(u),clearTimeout(a),i(new Error(`send failed: ${e}`)))})}async sendStreamChunkRequest(e,t=2e4){return this.request("client_stream_chunk",e,{expected:["send_ack","send_nack","error"],timeoutMs:t})}async sendText(e,t=2e4){return this.request("send_msg",{msg_type:1,...e},{expected:["send_ack","send_nack","error"],timeoutMs:t})}async sendMedia(e,t=2e4){return this.request("send_msg",{...e,msg_type:2},{expected:["send_ack","send_nack","error"],timeoutMs:t})}async editMessage(e,t=2e4){return this.request("edit_msg",e,{expected:["send_ack","send_nack","error"],timeoutMs:t})}async deleteMessage(e,t,n=2e4){return this.request("delete_msg",{session_id:e,msg_id:t},{expected:["send_ack","send_nack","error"],timeoutMs:n})}async sendEventResultRequest(e,t=5e3){return this.request("event_result",e,{expected:["send_ack","send_nack","error"],timeoutMs:t})}disconnect(){c.info("aibot",`disconnect() agent=${this.config.clientType}:${this.config.agentId} wasConnected=${this.connected} reconnecting=${this.reconnecting} reconnectAttempts=${this.reconnectAttempts}`),this.connected=!1,this.everConnected=!1,this.reconnecting=!1,this.reconnectAttempts=0,this.stopHeartbeat(),this.rejectAllPendingInvokes("disconnect"),this.rejectAllPendingRequests("disconnect"),this.outboundBuffer.length=0,this.ws&&(this.ws.close(1e3,"client disconnect"),this.ws=null)}async attemptReconnect(){if(!this.reconnecting)for(this.reconnecting=!0,c.info("aibot",`attemptReconnect start agent=${this.config.clientType}:${this.config.agentId} fromAttempts=${this.reconnectAttempts}`),this.emit("disconnected");this.reconnecting;){const e=Math.min(1e3*2**this.reconnectAttempts,3e4),t=Math.floor(e*.2*Math.random());if(this.reconnectAttempts++,await new Promise(n=>setTimeout(n,e+t)),!this.reconnecting)return;try{const n=await this.connect(),r=this.reconnectAttempts;this.reconnectAttempts=0,this.reconnecting=!1,c.info("aibot",`reconnect succeeded agent=${this.config.clientType}:${this.config.agentId} attempt=${r}`),this.emit("auth",n);return}catch(n){c.warn("aibot",`reconnect failed agent=${this.config.clientType}:${this.config.agentId} attempt=${this.reconnectAttempts} err=${n instanceof Error?n.message:n}`)}}}sendPacket(e,t,n){if(this.ws&&this.ws.readyState===m.OPEN){const r=this.ws.bufferedAmount>l.BACKPRESSURE_THRESHOLD;if(!r||!l.DROPPABLE_COMMANDS.has(e)){if(r&&l.DROPPABLE_COMMANDS.has(e))return!1;const i=n??++this.seq;if(e==="client_stream_chunk"&&t&&typeof t=="object"){const a=t.event_id;if(a&&(this.seqEventMap.set(i,a),this.seqEventMap.size>200)){const o=this.seqEventMap.keys().next().value;o!==void 0&&this.seqEventMap.delete(o)}}const u={cmd:e,seq:i,payload:t};this.packetLog?.logOutboundPacket(e,i,t,"sent");try{const a=this.ws.readyState,o=this.ws.bufferedAmount;return this.ws.send(JSON.stringify(u),d=>{if(e==="event_result"){const s=t;d?c.warn("aibot",`event_result ws send callback failed event=${s.event_id??""} status=${s.status??""} seq=${i} readyState=${a} bufferedAmount=${o} err=${d.message}`):c.info("aibot",`event_result ws send callback ok event=${s.event_id??""} status=${s.status??""} seq=${i} readyState=${a} bufferedAmount=${o}`)}else if(e==="client_stream_chunk"){const s=t;d?c.warn("aibot",`stream_chunk ws send failed event=${s.event_id??""} session=${s.session_id??""} seq=${i} chunk_seq=${s.chunk_seq??""} is_finish=${s.is_finish??""} readyState=${a} bufferedAmount=${o} err=${d.message}`):c.info("aibot",`stream_chunk ws send ok event=${s.event_id??""} session=${s.session_id??""} seq=${i} chunk_seq=${s.chunk_seq??""} is_finish=${s.is_finish??""} readyState=${a} bufferedAmount=${o}`)}else if(e==="event_ack"){const s=t;d?c.warn("aibot",`event_ack ws send failed event=${s.event_id??""} seq=${i} readyState=${a} bufferedAmount=${o} err=${d.message}`):c.info("aibot",`event_ack ws send ok event=${s.event_id??""} seq=${i} readyState=${a} bufferedAmount=${o}`)}else if(e==="send_msg"){const s=t;d?c.warn("aibot",`send_msg ws send failed event=${s.event_id??""} session=${s.session_id??""} seq=${i} readyState=${a} bufferedAmount=${o} err=${d.message}`):c.info("aibot",`send_msg ws send ok event=${s.event_id??""} session=${s.session_id??""} seq=${i} readyState=${a} bufferedAmount=${o}`)}else if(d){const s=t;c.warn("aibot",`${e} ws send failed seq=${i} session=${s.session_id??""} event=${s.event_id??""} client_msg_id=${s.client_msg_id??""} readyState=${a} bufferedAmount=${o} err=${d.message}`)}}),!0}catch(a){return this.emitClientError(new Error(`sendPacket failed: ${a}`)),!1}}}if(l.DROPPABLE_COMMANDS.has(e))return this.packetLog?.logOutboundPacket(e,n??0,t,"dropped"),!1;if(n!==void 0)return this.packetLog?.logOutboundPacket(e,n,t,"dropped"),!1;if(this.outboundBuffer.length>=l.MAX_OUTBOUND_BUFFER_SIZE&&(this.outboundBuffer=this.outboundBuffer.filter(r=>l.BUFFER_OVERFLOW_RETAIN_COMMANDS.has(r.cmd)),this.outboundBuffer.length>=l.MAX_OUTBOUND_BUFFER_SIZE&&this.outboundBuffer.shift()),this.outboundBuffer.push({cmd:e,payload:t}),this.packetLog?.logOutboundPacket(e,n??0,t,"buffered"),e==="client_stream_chunk"){const r=t;c.info("aibot",`stream_chunk buffered (ws not open) event=${r.event_id??""} session=${r.session_id??""} chunk_seq=${r.chunk_seq??""} is_finish=${r.is_finish??""} ws=${this.ws?`state=${this.ws.readyState}`:"null"}`)}return!1}async sendEventResultReliable(e){const t=this.ackPolicy?.max_retries??3,n=this.ackPolicy?.push_ack_timeout_ms??5e3,r=750;for(let i=1;i<=t;i++){const u=this.ws?.readyState??-1,a=this.ws?.bufferedAmount??0;c.info("aibot",`event_result send attempt event=${e.event_id} status=${e.status} attempt=${i}/${t} readyState=${u} bufferedAmount=${a}`);try{const o=await this.sendEventResultRequest(e,n);if(o.cmd==="send_ack"){const s=o.payload;c.info("aibot",`event_result ack event=${e.event_id} status=${e.status} attempt=${i}/${t} ack_event=${s.event_id??""} ack_status=${s.status??""}`);return}const d=o.payload;if(c.warn("aibot",`event_result rejected event=${e.event_id} status=${e.status} attempt=${i}/${t} cmd=${o.cmd} code=${d.code??""} msg=${d.msg??""}${d.ref_cmd?` ref_cmd=${d.ref_cmd}`:""}${d.ref_id?` ref_id=${d.ref_id}`:""}`),d.code===4003){c.warn("aibot",`event_result stopping retries: 4003 ownership denied event=${e.event_id}`);return}return}catch(o){const d=o instanceof Error?o.message:String(o);if(c.warn("aibot",`event_result attempt failed event=${e.event_id} status=${e.status} attempt=${i}/${t} err=${d}`),i===t){this.emitClientError(new Error(`event_result ack failed after ${t} attempts: event=${e.event_id} status=${e.status}`));return}await new Promise(s=>setTimeout(s,r*i))}}}purgeBufferedStreamChunks(e){const t=this.outboundBuffer.length;this.outboundBuffer=this.outboundBuffer.filter(n=>n.cmd!=="client_stream_chunk"?!0:n.payload?.event_id!==e),this.outboundBuffer.length<t&&c.info("aibot",`purged ${t-this.outboundBuffer.length} buffered stream chunks for event=${e}`)}emitClientError(e){if(this.listenerCount("error")===0){c.warn("aibot",`Client error (no listeners): ${e.message}`);return}this.emit("error",e)}flushOutboundBuffer(){if(this.outboundBuffer.length===0||!this.ws||this.ws.readyState!==m.OPEN)return;const e=this.outboundBuffer;this.outboundBuffer=[];for(const{cmd:t,payload:n}of e){const r=++this.seq;if(t==="client_stream_chunk"&&n&&typeof n=="object"){const u=n.event_id;u&&this.seqEventMap.set(r,u)}const i={cmd:t,seq:r,payload:n};try{this.ws.send(JSON.stringify(i))}catch{break}}if(this.seqEventMap.size>200){const t=[...this.seqEventMap.entries()].sort((n,r)=>n[0]-r[0]);this.seqEventMap.clear();for(const[n,r]of t.slice(-100))this.seqEventMap.set(n,r)}}handleInvokeResult(e){const t=this.pendingInvokes.get(e.invoke_id);t&&(this.pendingInvokes.delete(e.invoke_id),clearTimeout(t.timer),e.code===0?t.resolve(e.data??null):t.reject(new Error(`agent_invoke error code=${e.code}: ${e.msg??""}`)))}rejectAllPendingInvokes(e){for(const[,t]of this.pendingInvokes)clearTimeout(t.timer),t.reject(new Error(`agent_invoke canceled: ${e}`));this.pendingInvokes.clear()}rejectAllPendingRequests(e){for(const[,t]of this.pendingRequests)clearTimeout(t.timer),t.reject(new Error(`request canceled: ${e}`));this.pendingRequests.clear()}cleanupSocket(){if(this.ws){try{this.ws.close()}catch{}this.ws=null}}startHeartbeat(){this.stopHeartbeat(),this.heartbeatTimer=setInterval(()=>{this.connected&&this.request("ping",{ts:Date.now()},{expected:["pong"],timeoutMs:5e3}).catch(()=>{this.connected&&(this.cleanupSocket(),this.attemptReconnect())})},this.heartbeatSec*1e3)}stopHeartbeat(){this.heartbeatTimer&&(clearInterval(this.heartbeatTimer),this.heartbeatTimer=null)}}export{l as AibotClient};
@@ -1,8 +1,8 @@
1
- import{execFile as P,spawn as D}from"node:child_process";import{existsSync as k}from"node:fs";import{join as T}from"node:path";import{log as h}from"../log/logger.js";import{resolveCliPath as v,getCliVersion as S}from"../util/cli-probe.js";import{getInstallCommand as y,getCliBinary as A,isKnownAgent as O,detectPlatformOS as U}from"./registry.js";import{checkPrerequisites as x,getMissingPrerequisites as L}from"./preflight.js";import{installMissingPrerequisites as V}from"./prereq-installer.js";import{detectEnvironment as C,formatEnvironmentInfo as q,isEnvironmentSupported as F}from"./env-detect.js";import{generateManualGuide as b}from"./manual-guide.js";import{npmInstallWithMirror as G}from"./npm-registry.js";import{getAllAgentInstallInfo as B}from"./registry.js";class a extends Error{code;constructor(n,t){super(t),this.name="InstallerError",this.code=n}}const $=64*1024,W=20,N=2,j=3e3;class se{os;activeInstalls=new Map;constructor(){this.os=U()}listInstallable(){return{platform:this.os,agents:B(this.os)}}getProgress(n){return this.activeInstalls.get(n)}isInProgress(n){return this.activeInstalls.has(n)}async install(n){const{agentType:t}=n,e=Date.now();if(this.activeInstalls.has(t))return this.fail(t,"preflight",e,new a("INSTALL_IN_PROGRESS",`${t} is already being installed`));try{return await this._doInstall(n,e)}catch(i){this.activeInstalls.delete(t);const r=i instanceof Error?i.message:String(i);return h.error("installer",`${t} install unexpected error: ${r}`),{agentType:t,ok:!1,phase:"failed",error:{code:"INTERNAL",message:`Unexpected error: ${r}`},durationMs:Date.now()-e,output:""}}}async _doInstall(n,t){const{agentType:e}=n;let i;try{i=await C()}catch(l){const u=l instanceof Error?l.message:String(l);return this.fail(e,"preflight",t,new a("INTERNAL",`Environment detection failed: ${u}`))}h.info("installer",`Install request: ${e}
2
- ${q(i)}`);const r=F(i);if(!r.supported)return this.fail(e,"preflight",t,new a("ENVIRONMENT_UNSUPPORTED",`Current environment is not supported for automatic installation: ${r.reason}. Please install ${e} manually.`),i);if(this.setProgress(e,"preflight",t),!O(e))return this.fail(e,"preflight",t,new a("UNKNOWN_AGENT",`Unknown agent type: ${e}`),i);const s=y(e,this.os);if(!s)return this.fail(e,"preflight",t,new a("UNSUPPORTED_OS",`Installation of ${e} is not supported on ${this.os}`),i);const c=A(e),m=await v(c),p=m?(await S(m)).version:null;if(m&&!n.force){this.activeInstalls.delete(e);const l=Date.now()-t;return h.info("installer",`${e} already installed at ${m}${p?` (v${p})`:""}`),{agentType:e,ok:!0,phase:"completed",installedPath:m,installedVersion:p,durationMs:l,output:"",environment:i}}const f=s.prerequisites??[];let o=[];if(f.length>0){h.info("installer",`Checking prerequisites for ${e}: ${f.join(", ")}`),o=await x(f,this.os),h.info("installer",`Prerequisites: ${o.map(u=>`${u.label}=${u.met?u.version:"missing"}`).join(", ")}`);const l=L(o);if(l.length>0){const u=l.map(d=>`${d.label}${d.minVersion?` >= ${d.minVersion}`:""}`).join(", ");if(!n.dryRun){if(n.skipPrereqInstall)return this.fail(e,"preflight",t,new a("PREREQ_MISSING",`Missing prerequisites: ${u}. Install them first or retry without skipPrereqInstall.`),i,o);const d=l.map(I=>`${I.label}${I.minVersion?` >= ${I.minVersion}`:""}`);h.info("installer",`Will auto-install prerequisites: ${d.join(", ")}`),this.setProgress(e,"installing_prereq",t,l[0].label,d);const w=await V(l,this.os);if(!w.allOk){const I=w.results.find(_=>!_.ok),R=I?`Failed to install prerequisite ${I.prereq.label}: ${I.output}`:"Prerequisite installation failed";return this.fail(e,"installing_prereq",t,new a("PREREQ_INSTALL_FAILED",R),i,o)}h.info("installer",`All prerequisites installed for ${e}`),o=await x(f,this.os)}}}if(n.dryRun){this.activeInstalls.delete(e);const l=L(o),u=this.getManualHint(e,this.os),d={agentType:e,environment:i,canInstall:!0,alreadyInstalled:!!m,installedPath:m,installedVersion:p,installCommand:s.command,installMode:s.mode,prerequisites:o,missingPrerequisites:l,fallbackCommand:s.fallback?.command??null,manualHint:u},w=b({agentType:e,os:this.os,env:i,missingPrereqs:l});return{agentType:e,ok:!0,phase:"completed",durationMs:Date.now()-t,output:"dry-run: no commands executed",environment:i,dryRun:d,manualGuide:w}}this.setProgress(e,"installing",t),h.info("installer",`Installing ${e}: ${s.command}`);let g;try{g=await this.executeWithRetry(s,e,t,n.timeoutMs)}catch(l){if(s.fallback&&l instanceof a&&(l.code==="INSTALL_FAILED"||l.code==="INSTALL_TIMEOUT")){h.info("installer",`Primary install failed after retries, trying fallback: ${s.fallback.command}`),this.setProgress(e,"installing",t);try{g=await this.executeWithRetry(s.fallback,e,t,n.timeoutMs),g=`[primary failed, fallback succeeded]
3
- ${g}`}catch(u){const d=l.message,w=u instanceof a?u.message:String(u);return this.fail(e,"installing",t,new a("FALLBACK_EXHAUSTED",`Both primary and fallback install methods failed.
1
+ import{execFile as k,spawn as D}from"node:child_process";import{existsSync as P}from"node:fs";import{join as T}from"node:path";import{log as h}from"../log/logger.js";import{resolveCliPath as v,getCliVersion as S}from"../util/cli-probe.js";import{getInstallCommand as y,getCliBinary as A,isKnownAgent as O,detectPlatformOS as U}from"./registry.js";import{checkPrerequisites as x,getMissingPrerequisites as L}from"./preflight.js";import{installMissingPrerequisites as V}from"./prereq-installer.js";import{detectEnvironment as C,formatEnvironmentInfo as q,isEnvironmentSupported as F}from"./env-detect.js";import{generateManualGuide as b}from"./manual-guide.js";import{npmInstallWithMirror as G}from"./npm-registry.js";import{getAllAgentInstallInfo as B}from"./registry.js";class a extends Error{code;constructor(n,t){super(t),this.name="InstallerError",this.code=n}}const w=64*1024,H=20,N=2,W=3e3;class se{os;activeInstalls=new Map;constructor(){this.os=U()}listInstallable(){return{platform:this.os,agents:B(this.os)}}getProgress(n){return this.activeInstalls.get(n)}isInProgress(n){return this.activeInstalls.has(n)}async install(n){const{agentType:t}=n,e=Date.now();if(this.activeInstalls.has(t))return this.fail(t,"preflight",e,new a("INSTALL_IN_PROGRESS",`${t} is already being installed`));try{return await this._doInstall(n,e)}catch(i){this.activeInstalls.delete(t);const o=i instanceof Error?i.message:String(i);return h.error("installer",`${t} install unexpected error: ${o}`),{agentType:t,ok:!1,phase:"failed",error:{code:"INTERNAL",message:`Unexpected error: ${o}`},durationMs:Date.now()-e,output:""}}}async _doInstall(n,t){const{agentType:e}=n;let i;try{i=await C()}catch(l){const c=l instanceof Error?l.message:String(l);return this.fail(e,"preflight",t,new a("INTERNAL",`Environment detection failed: ${c}`))}h.info("installer",`Install request: ${e}
2
+ ${q(i)}`);const o=F(i);if(!o.supported)return this.fail(e,"preflight",t,new a("ENVIRONMENT_UNSUPPORTED",`Current environment is not supported for automatic installation: ${o.reason}. Please install ${e} manually.`),i);if(this.setProgress(e,"preflight",t),!O(e))return this.fail(e,"preflight",t,new a("UNKNOWN_AGENT",`Unknown agent type: ${e}`),i);const s=y(e,this.os);if(!s){const l=this.getManualHint(e,this.os),c=l?`Installation of ${e} is not supported on ${this.os}. ${l}`:`Installation of ${e} is not supported on ${this.os}`;return this.fail(e,"preflight",t,new a("UNSUPPORTED_OS",c),i)}const u=A(e),m=await v(u),p=m?(await S(m)).version:null;if(m&&!n.force){this.activeInstalls.delete(e);const l=Date.now()-t;return h.info("installer",`${e} already installed at ${m}${p?` (v${p})`:""}`),{agentType:e,ok:!0,phase:"completed",installedPath:m,installedVersion:p,durationMs:l,output:"",environment:i}}const f=s.prerequisites??[];let r=[];if(f.length>0){h.info("installer",`Checking prerequisites for ${e}: ${f.join(", ")}`),r=await x(f,this.os),h.info("installer",`Prerequisites: ${r.map(c=>`${c.label}=${c.met?c.version:"missing"}`).join(", ")}`);const l=L(r);if(l.length>0){const c=l.map(d=>`${d.label}${d.minVersion?` >= ${d.minVersion}`:""}`).join(", ");if(!n.dryRun){if(n.skipPrereqInstall)return this.fail(e,"preflight",t,new a("PREREQ_MISSING",`Missing prerequisites: ${c}. Install them first or retry without skipPrereqInstall.`),i,r);const d=l.map(I=>`${I.label}${I.minVersion?` >= ${I.minVersion}`:""}`);h.info("installer",`Will auto-install prerequisites: ${d.join(", ")}`),this.setProgress(e,"installing_prereq",t,l[0].label,d);const $=await V(l,this.os);if(!$.allOk){const I=$.results.find(_=>!_.ok),R=I?`Failed to install prerequisite ${I.prereq.label}: ${I.output}`:"Prerequisite installation failed";return this.fail(e,"installing_prereq",t,new a("PREREQ_INSTALL_FAILED",R),i,r)}h.info("installer",`All prerequisites installed for ${e}`),r=await x(f,this.os)}}}if(n.dryRun){this.activeInstalls.delete(e);const l=L(r),c=this.getManualHint(e,this.os),d={agentType:e,environment:i,canInstall:!0,alreadyInstalled:!!m,installedPath:m,installedVersion:p,installCommand:s.command,installMode:s.mode,prerequisites:r,missingPrerequisites:l,fallbackCommand:s.fallback?.command??null,manualHint:c},$=b({agentType:e,os:this.os,env:i,missingPrereqs:l});return{agentType:e,ok:!0,phase:"completed",durationMs:Date.now()-t,output:"dry-run: no commands executed",environment:i,dryRun:d,manualGuide:$}}this.setProgress(e,"installing",t),h.info("installer",`Installing ${e}: ${s.command}`);let g;try{g=await this.executeWithRetry(s,e,t,n.timeoutMs)}catch(l){if(s.fallback&&l instanceof a&&(l.code==="INSTALL_FAILED"||l.code==="INSTALL_TIMEOUT")){h.info("installer",`Primary install failed after retries, trying fallback: ${s.fallback.command}`),this.setProgress(e,"installing",t);try{g=await this.executeWithRetry(s.fallback,e,t,n.timeoutMs),g=`[primary failed, fallback succeeded]
3
+ ${g}`}catch(c){const d=l.message,$=c instanceof a?c.message:String(c);return this.fail(e,"installing",t,new a("FALLBACK_EXHAUSTED",`Both primary and fallback install methods failed.
4
4
  Primary: ${d}
5
- Fallback: ${w}`),i,o)}}else return l instanceof a?this.fail(e,"installing",t,new a(l.code,l.message),i,o):this.fail(e,"installing",t,new a("INTERNAL",l instanceof Error?l.message:String(l)),i,o)}if(!n.skipVerify){this.setProgress(e,"verifying",t),h.info("installer",`Verifying ${e} installation...`);let l=await v(c);if(l||(l=await this.resolveViaNpmBin(c)),!l)return this.fail(e,"verifying",t,new a("VERIFICATION_FAILED",`${c} not found on PATH after installation. You may need to open a new terminal or run: source ~/.zshrc (or ~/.bashrc)`),i,o);const{version:u}=await S(l),d=Date.now()-t;return this.activeInstalls.delete(e),h.info("installer",`${e} installed successfully at ${l} (v${u??"unknown"}, ${d}ms)`),{agentType:e,ok:!0,phase:"completed",installedPath:l,installedVersion:u,durationMs:d,output:g,prerequisites:o.length>0?o:void 0,environment:i}}const E=Date.now()-t;return this.activeInstalls.delete(e),h.info("installer",`${e} install command completed (${E}ms, verification skipped)`),{agentType:e,ok:!0,phase:"completed",installedPath:null,installedVersion:null,durationMs:E,output:g,prerequisites:o.length>0?o:void 0,environment:i}}getManualHint(n,t){const i=y(n,t)?.fallback,s={claude:"https://docs.anthropic.com/en/docs/claude-code/overview",codex:"https://github.com/openai/codex",gemini:"https://github.com/google-gemini/gemini-cli",qwen:"https://github.com/QwenLM/qwen-code",cursor:"https://cursor.com/docs/cli/installation",copilot:"https://docs.github.com/en/copilot/managing-copilot/configure-personal-settings/installing-github-copilot-in-the-cli",kiro:"https://kiro.dev/docs/cli/",openclaw:"https://github.com/openclaw/openclaw",reasonix:"https://github.com/esengine/DeepSeek-Reasonix"}[n],c=[];return s&&c.push(`Docs: ${s}`),i&&c.push(`Alternative: ${i.command}`),c.length>0?c.join(" | "):null}setProgress(n,t,e,i,r){this.activeInstalls.set(n,{agentType:n,phase:t,startedAt:e,elapsedMs:Date.now()-e,...i?{currentPrereq:i}:{},...r?{pendingPrereqs:r}:{}})}fail(n,t,e,i,r,s,c){const m=c??this.activeInstalls.get(n)?.outputTail??"";this.activeInstalls.delete(n),h.error("installer",`${n} install failed at ${t}: ${i.message}`);const p=s?L(s):[],f=b({agentType:n,os:this.os,env:r??{platform:this.os,osVersion:"unknown",arch:process.arch,shell:process.env.SHELL??null,nodeVersion:null,npmVersion:null,isDocker:!1,isCI:!1},missingPrereqs:p,primaryFailed:t==="installing",fallbackFailed:i.code==="FALLBACK_EXHAUSTED",error:i.message});return{agentType:n,ok:!1,phase:"failed",error:{code:i.code,message:i.message},durationMs:Date.now()-e,output:m,environment:r,prerequisites:s,manualGuide:f}}async executeWithRetry(n,t,e,i){let r=null;for(let s=0;s<=N;s++)try{return s>0&&(h.info("installer",`Retry ${s}/${N} for ${t}...`),await this.sleep(j)),await this.executeCommand(n,t,e,i)}catch(c){if(r=c instanceof a?c:new a("INTERNAL",String(c)),!(r.code==="INSTALL_TIMEOUT")||s>=N)throw r;h.info("installer",`Attempt ${s+1} failed (retryable): ${r.message}`)}throw r??new a("INTERNAL","Unexpected retry loop exit")}sleep(n){return new Promise(t=>setTimeout(t,n))}executeCommand(n,t,e,i){const r=i??n.timeoutMs;switch(n.mode){case"npm":return this.executeNpm(n.npmPackage,r,t,e);case"shell":return this.executeShell(n.command,r,t,e);case"exec":return this.executeExec(n.command,n.execArgs??[],r,t,e);default:return Promise.reject(new a("INTERNAL",`Unknown install mode: ${n.mode}`))}}async executeNpm(n,t,e,i){try{const{output:r,registry:s}=await G(n,t,$);return h.info("installer",`npm install ${n} succeeded via ${s}`),r}catch(r){const s=r instanceof Error?r.message:String(r);throw s.includes("timed out")||s.includes("ETIMEDOUT")?new a("INSTALL_TIMEOUT",`npm install timed out (tried all mirrors): ${s}`):new a("INSTALL_FAILED",`npm install failed (tried all mirrors): ${s}`)}}executeShell(n,t,e,i){return new Promise((r,s)=>{const c=process.platform==="win32",m=c?"cmd.exe":"sh",p=c?["/d","/s","/c",n]:["-c",n];h.info("installer",`exec: ${m} ${p.join(" ")}`);const f=D(m,p,{timeout:t,stdio:["ignore","pipe","pipe"],env:{...process.env,NONINTERACTIVE:"1",DEBIAN_FRONTEND:"noninteractive"}});let o="",g=!1;const E=setTimeout(()=>{g=!0;try{f.kill("SIGTERM")}catch{}setTimeout(()=>{try{f.kill("SIGKILL")}catch{}},5e3).unref()},t);f.stdout?.on("data",l=>{const u=l.toString("utf-8");o+=u,o.length>$&&(o=o.slice(-$)),this.updateOutputTail(e,i,o)}),f.stderr?.on("data",l=>{const u=l.toString("utf-8");o+=u,o.length>$&&(o=o.slice(-$)),this.updateOutputTail(e,i,o)}),f.on("error",l=>{clearTimeout(E),s(new a("INSTALL_FAILED",`Spawn error: ${l.message}`))}),f.on("close",l=>{if(clearTimeout(E),g){s(new a("INSTALL_TIMEOUT",`Install timed out after ${t/1e3}s`));return}if(l!==0){const u=o.slice(-1024);s(new a("INSTALL_FAILED",`Process exited with code ${l}: ${u}`));return}r(o.trim())})})}executeExec(n,t,e,i,r){return new Promise((s,c)=>{h.info("installer",`exec: ${n} ${t.join(" ")}`);const m=P(n,t,{timeout:e,maxBuffer:$},(p,f,o)=>{if(p){if(p.killed)c(new a("INSTALL_TIMEOUT",`${n} timed out after ${e/1e3}s`));else{const g=o?.trim()||p.message;c(new a("INSTALL_FAILED",`${n} failed: ${g}`))}return}s(`${f??""}
6
- ${o??""}`.trim())});this.trackOutput(m,i,r)})}trackOutput(n,t,e){n.on("close",()=>{const i=this.activeInstalls.get(t);i&&(i.elapsedMs=Date.now()-e)})}async resolveViaNpmBin(n){return new Promise(t=>{const e=process.platform==="win32";P(e?"cmd.exe":"npm",e?["/c","npm","prefix","-g"]:["prefix","-g"],{timeout:5e3,encoding:"utf-8"},(s,c)=>{if(s){t(null);return}const m=c.trim();if(!m){t(null);return}const p=this.os==="windows"?T(m,`${n}.cmd`):T(m,"bin",n);if(k(p)){t(p);return}const f=T(m,n);if(k(f)){t(f);return}t(null)})})}updateOutputTail(n,t,e){const r=e.split(`
7
- `).slice(-W).join(`
8
- `),s=this.activeInstalls.get(n);s&&(s.outputTail=r,s.elapsedMs=Date.now()-t)}}export{se as AgentInstaller,a as InstallerError};
5
+ Fallback: ${$}`),i,r)}}else return l instanceof a?this.fail(e,"installing",t,new a(l.code,l.message),i,r):this.fail(e,"installing",t,new a("INTERNAL",l instanceof Error?l.message:String(l)),i,r)}if(!n.skipVerify&&!s.skipVerification){this.setProgress(e,"verifying",t),h.info("installer",`Verifying ${e} installation...`);let l=await v(u);if(l||(l=await this.resolveViaNpmBin(u)),!l)return this.fail(e,"verifying",t,new a("VERIFICATION_FAILED",`${u} not found on PATH after installation. You may need to open a new terminal or run: source ~/.zshrc (or ~/.bashrc)`),i,r);const{version:c}=await S(l),d=Date.now()-t;return this.activeInstalls.delete(e),h.info("installer",`${e} installed successfully at ${l} (v${c??"unknown"}, ${d}ms)`),{agentType:e,ok:!0,phase:"completed",installedPath:l,installedVersion:c,durationMs:d,output:g,prerequisites:r.length>0?r:void 0,environment:i}}const E=Date.now()-t;return this.activeInstalls.delete(e),h.info("installer",`${e} install command completed (${E}ms, verification skipped)`),{agentType:e,ok:!0,phase:"completed",installedPath:null,installedVersion:null,durationMs:E,output:g,prerequisites:r.length>0?r:void 0,environment:i}}getManualHint(n,t){const i=y(n,t)?.fallback,s={claude:"https://docs.anthropic.com/en/docs/claude-code/overview",codex:"https://github.com/openai/codex",gemini:"https://github.com/google-gemini/gemini-cli",qwen:"https://github.com/QwenLM/qwen-code",cursor:"https://cursor.com/docs/cli/installation",copilot:"https://docs.github.com/en/copilot/managing-copilot/configure-personal-settings/installing-github-copilot-in-the-cli",kiro:"https://kiro.dev/docs/cli/",openclaw:"https://github.com/openclaw/openclaw",reasonix:"https://github.com/esengine/DeepSeek-Reasonix",openhuman:"https://github.com/tinyhumansai/openhuman/issues/128"}[n],u=[];return s&&u.push(`Docs: ${s}`),i&&u.push(`Alternative: ${i.command}`),u.length>0?u.join(" | "):null}setProgress(n,t,e,i,o){this.activeInstalls.set(n,{agentType:n,phase:t,startedAt:e,elapsedMs:Date.now()-e,...i?{currentPrereq:i}:{},...o?{pendingPrereqs:o}:{}})}fail(n,t,e,i,o,s,u){const m=u??this.activeInstalls.get(n)?.outputTail??"";this.activeInstalls.delete(n),h.error("installer",`${n} install failed at ${t}: ${i.message}`);const p=s?L(s):[],f=b({agentType:n,os:this.os,env:o??{platform:this.os,osVersion:"unknown",arch:process.arch,shell:process.env.SHELL??null,nodeVersion:null,npmVersion:null,isDocker:!1,isCI:!1},missingPrereqs:p,primaryFailed:t==="installing",fallbackFailed:i.code==="FALLBACK_EXHAUSTED",error:i.message});return{agentType:n,ok:!1,phase:"failed",error:{code:i.code,message:i.message},durationMs:Date.now()-e,output:m,environment:o,prerequisites:s,manualGuide:f}}async executeWithRetry(n,t,e,i){let o=null;for(let s=0;s<=N;s++)try{return s>0&&(h.info("installer",`Retry ${s}/${N} for ${t}...`),await this.sleep(W)),await this.executeCommand(n,t,e,i)}catch(u){if(o=u instanceof a?u:new a("INTERNAL",String(u)),!(o.code==="INSTALL_TIMEOUT")||s>=N)throw o;h.info("installer",`Attempt ${s+1} failed (retryable): ${o.message}`)}throw o??new a("INTERNAL","Unexpected retry loop exit")}sleep(n){return new Promise(t=>setTimeout(t,n))}executeCommand(n,t,e,i){const o=i??n.timeoutMs;switch(n.mode){case"npm":return this.executeNpm(n.npmPackage,o,t,e);case"shell":return this.executeShell(n.command,o,t,e);case"exec":return this.executeExec(n.command,n.execArgs??[],o,t,e);default:return Promise.reject(new a("INTERNAL",`Unknown install mode: ${n.mode}`))}}async executeNpm(n,t,e,i){try{const{output:o,registry:s}=await G(n,t,w);return h.info("installer",`npm install ${n} succeeded via ${s}`),o}catch(o){const s=o instanceof Error?o.message:String(o);throw s.includes("timed out")||s.includes("ETIMEDOUT")?new a("INSTALL_TIMEOUT",`npm install timed out (tried all mirrors): ${s}`):new a("INSTALL_FAILED",`npm install failed (tried all mirrors): ${s}`)}}executeShell(n,t,e,i){return new Promise((o,s)=>{const u=process.platform==="win32",m=u?"cmd.exe":"sh",p=u?["/d","/s","/c",n]:["-c",n];h.info("installer",`exec: ${m} ${p.join(" ")}`);const f=D(m,p,{timeout:t,stdio:["ignore","pipe","pipe"],env:{...process.env,NONINTERACTIVE:"1",DEBIAN_FRONTEND:"noninteractive"}});let r="",g=!1;const E=setTimeout(()=>{g=!0;try{f.kill("SIGTERM")}catch{}setTimeout(()=>{try{f.kill("SIGKILL")}catch{}},5e3).unref()},t);f.stdout?.on("data",l=>{const c=l.toString("utf-8");r+=c,r.length>w&&(r=r.slice(-w)),this.updateOutputTail(e,i,r)}),f.stderr?.on("data",l=>{const c=l.toString("utf-8");r+=c,r.length>w&&(r=r.slice(-w)),this.updateOutputTail(e,i,r)}),f.on("error",l=>{clearTimeout(E),s(new a("INSTALL_FAILED",`Spawn error: ${l.message}`))}),f.on("close",l=>{if(clearTimeout(E),g){s(new a("INSTALL_TIMEOUT",`Install timed out after ${t/1e3}s`));return}if(l!==0){const c=r.slice(-1024);s(new a("INSTALL_FAILED",`Process exited with code ${l}: ${c}`));return}o(r.trim())})})}executeExec(n,t,e,i,o){return new Promise((s,u)=>{h.info("installer",`exec: ${n} ${t.join(" ")}`);const m=k(n,t,{timeout:e,maxBuffer:w},(p,f,r)=>{if(p){if(p.killed)u(new a("INSTALL_TIMEOUT",`${n} timed out after ${e/1e3}s`));else{const g=r?.trim()||p.message;u(new a("INSTALL_FAILED",`${n} failed: ${g}`))}return}s(`${f??""}
6
+ ${r??""}`.trim())});this.trackOutput(m,i,o)})}trackOutput(n,t,e){n.on("close",()=>{const i=this.activeInstalls.get(t);i&&(i.elapsedMs=Date.now()-e)})}async resolveViaNpmBin(n){return new Promise(t=>{const e=process.platform==="win32";k(e?"cmd.exe":"npm",e?["/c","npm","prefix","-g"]:["prefix","-g"],{timeout:5e3,encoding:"utf-8"},(s,u)=>{if(s){t(null);return}const m=u.trim();if(!m){t(null);return}const p=this.os==="windows"?T(m,`${n}.cmd`):T(m,"bin",n);if(P(p)){t(p);return}const f=T(m,n);if(P(f)){t(f);return}t(null)})})}updateOutputTail(n,t,e){const o=e.split(`
7
+ `).slice(-H).join(`
8
+ `),s=this.activeInstalls.get(n);s&&(s.outputTail=o,s.elapsedMs=Date.now()-t)}}export{se as AgentInstaller,a as InstallerError};
@@ -1,3 +1,4 @@
1
- import{execFile as h}from"node:child_process";import{join as l}from"node:path";import{promisify as v}from"node:util";import{log as o}from"../log/logger.js";const u=v(h),m=5e3;function f(e){const t=e.replace(/^[^0-9]*/,"").split(".");return[parseInt(t[0]??"0",10)||0,parseInt(t[1]??"0",10)||0]}function g(e,n){const[t,r]=f(e),[c,i]=f(n);return t>c||t===c&&r>=i}async function s(e,n=["--version"]){try{const{stdout:t}=await u(e,n,{timeout:m,encoding:"utf-8"});return{found:!0,version:(t||"").trim().split(`
2
- `)[0]?.trim()||null}}catch{return{found:!1,version:null}}}async function w(){const e=await s("node",["--version"]);return e.version?{found:!0,version:e.version.replace(/^v/,"")}:e}async function b(){if(process.platform==="win32"){o.info("preflight","[npm-detect] Starting Windows npm detection");const e=["npm",l(process.env.ProgramFiles||"C:\\Program Files","nodejs","npm.cmd"),l(process.env.APPDATA||"","npm","npm.cmd")];try{o.info("preflight","[npm-detect] Trying to locate node.exe via where.exe");const{stdout:n}=await u("where.exe",["node"],{timeout:m,encoding:"utf-8"}),t=l(n.trim().split(`
3
- `)[0],".."),r=l(t,"npm.cmd");e.push(r),o.info("preflight",`[npm-detect] Inferred npm path from node: ${r}`)}catch(n){o.warn("preflight",`[npm-detect] Failed to locate node.exe: ${n instanceof Error?n.message:String(n)}`)}o.info("preflight",`[npm-detect] Will try ${e.length} paths: ${e.join(", ")}`);for(const n of e)try{o.info("preflight",`[npm-detect] Trying: ${n}`);const{stdout:t}=await u("cmd.exe",["/c",n,"--version"],{timeout:m,encoding:"utf-8"}),r=t.trim();return o.info("preflight",`[npm-detect] SUCCESS at ${n}, version: ${r}`),{found:!0,version:r}}catch(t){o.warn("preflight",`[npm-detect] Failed at ${n}: ${t instanceof Error?t.message:String(t)}`)}return o.error("preflight","[npm-detect] All paths failed, npm not found"),{found:!1,version:null}}return s("npm",["--version"])}async function y(){const e=await s("curl",["--version"]);return e.version?{found:!0,version:e.version.split(" ")[0]?.trim()??null}:e}async function P(){const e=await s("gh",["--version"]);return e.version?{found:!0,version:e.version.match(/(\d+\.\d+\.\d+)/)?.[1]??e.version}:e}async function x(){const e=await s("brew",["--version"]);return e.version?{found:!0,version:e.version.match(/(\d+\.\d+\.\d+)/)?.[1]??e.version}:e}function V(e){return[{id:"node",label:"Node.js",minVersion:"18.0",detect:w},{id:"npm",label:"npm",minVersion:"9.0",detect:b},{id:"curl",label:"curl",minVersion:null,detect:y},{id:"gh",label:"GitHub CLI (gh)",minVersion:null,detect:P},...e!=="windows"?[{id:"brew",label:"Homebrew",minVersion:null,detect:x}]:[]]}async function k(e,n){const r=V(n).find(p=>p.id===e);if(!r)return{id:e,label:e,met:!1,version:null,minVersion:null,autoInstallable:!1};const{found:c,version:i}=await r.detect();let a=c;a&&r.minVersion&&i&&(a=g(i,r.minVersion));const d=C(e,n);return{id:e,label:r.label,met:a,version:i,minVersion:r.minVersion,autoInstallable:a?!1:d}}async function A(e,n){return await Promise.all(e.map(r=>k(r,n)))}function F(e){return e.every(n=>n.met)}function M(e){return e.filter(n=>!n.met)}function C(e,n){switch(e){case"node":case"npm":return!0;case"curl":return!0;case"gh":return!0;case"brew":return n==="macos";default:return!1}}export{F as allPrerequisitesMet,k as checkPrerequisite,A as checkPrerequisites,M as getMissingPrerequisites};
1
+ import{execFile as h}from"node:child_process";import{join as c}from"node:path";import{promisify as v}from"node:util";import{log as o}from"../log/logger.js";const u=v(h),m=5e3;function f(e){const t=e.replace(/^[^0-9]*/,"").split(".");return[parseInt(t[0]??"0",10)||0,parseInt(t[1]??"0",10)||0]}function g(e,n){const[t,r]=f(e),[i,s]=f(n);return t>i||t===i&&r>=s}async function a(e,n=["--version"]){try{const{stdout:t}=await u(e,n,{timeout:m,encoding:"utf-8"});return{found:!0,version:(t||"").trim().split(`
2
+ `)[0]?.trim()||null}}catch{return{found:!1,version:null}}}async function w(){const e=await a("node",["--version"]);return e.version?{found:!0,version:e.version.replace(/^v/,"")}:e}async function b(){if(process.platform==="win32"){o.info("preflight","[npm-detect] Starting Windows npm detection");const e=["npm",c(process.env.ProgramFiles||"C:\\Program Files","nodejs","npm.cmd"),c(process.env.APPDATA||"","npm","npm.cmd")];try{o.info("preflight","[npm-detect] Trying to locate node.exe via where.exe");const{stdout:n}=await u("where.exe",["node"],{timeout:m,encoding:"utf-8"}),t=c(n.trim().split(`
3
+ `)[0],".."),r=c(t,"npm.cmd");e.push(r),o.info("preflight",`[npm-detect] Inferred npm path from node: ${r}`)}catch(n){o.warn("preflight",`[npm-detect] Failed to locate node.exe: ${n instanceof Error?n.message:String(n)}`)}o.info("preflight",`[npm-detect] Will try ${e.length} paths: ${e.join(", ")}`);for(const n of e)try{o.info("preflight",`[npm-detect] Trying: ${n}`);const{stdout:t}=await u("cmd.exe",["/c",n,"--version"],{timeout:m,encoding:"utf-8"}),r=t.trim();return o.info("preflight",`[npm-detect] SUCCESS at ${n}, version: ${r}`),{found:!0,version:r}}catch(t){o.warn("preflight",`[npm-detect] Failed at ${n}: ${t instanceof Error?t.message:String(t)}`)}return o.error("preflight","[npm-detect] All paths failed, npm not found"),{found:!1,version:null}}return a("npm",["--version"])}async function P(){const e=await a("curl",["--version"]);return e.version?{found:!0,version:e.version.split(" ")[0]?.trim()??null}:e}async function y(){if(process.platform==="win32"){const n=["gh",c(process.env.ProgramFiles||"C:\\Program Files","GitHub CLI","gh.exe"),c(process.env["ProgramFiles(x86)"]||"C:\\Program Files (x86)","GitHub CLI","gh.exe")];for(const t of n)try{const{stdout:r}=await u(t,["--version"],{timeout:m,encoding:"utf-8"}),i=(r||"").trim();return{found:!0,version:i.match(/(\d+\.\d+\.\d+)/)?.[1]??(i.split(`
4
+ `)[0]?.trim()||null)}}catch{}return{found:!1,version:null}}const e=await a("gh",["--version"]);return e.version?{found:!0,version:e.version.match(/(\d+\.\d+\.\d+)/)?.[1]??e.version}:e}async function x(){const e=await a("brew",["--version"]);return e.version?{found:!0,version:e.version.match(/(\d+\.\d+\.\d+)/)?.[1]??e.version}:e}function C(e){return[{id:"node",label:"Node.js",minVersion:"18.0",detect:w},{id:"npm",label:"npm",minVersion:"9.0",detect:b},{id:"curl",label:"curl",minVersion:null,detect:P},{id:"gh",label:"GitHub CLI (gh)",minVersion:null,detect:y},...e!=="windows"?[{id:"brew",label:"Homebrew",minVersion:null,detect:x}]:[]]}async function I(e,n){const r=C(n).find(p=>p.id===e);if(!r)return{id:e,label:e,met:!1,version:null,minVersion:null,autoInstallable:!1};const{found:i,version:s}=await r.detect();let l=i;l&&r.minVersion&&s&&(l=g(s,r.minVersion));const d=V(e,n);return{id:e,label:r.label,met:l,version:s,minVersion:r.minVersion,autoInstallable:l?!1:d}}async function S(e,n){return await Promise.all(e.map(r=>I(r,n)))}function A(e){return e.every(n=>n.met)}function M(e){return e.filter(n=>!n.met)}function V(e,n){switch(e){case"node":case"npm":return!0;case"curl":return!0;case"gh":return!0;case"brew":return n==="macos";default:return!1}}export{A as allPrerequisitesMet,I as checkPrerequisite,S as checkPrerequisites,M as getMissingPrerequisites};
@@ -1 +1 @@
1
- function i(e,n){return{command:`npm install -g ${e}`,mode:"npm",npmPackage:e,timeoutMs:n?.timeoutMs??12e4,prerequisites:["node","npm"],minNodeVersion:n?.minNodeVersion}}function l(e,n){return{command:e,mode:"shell",timeoutMs:n?.timeoutMs??12e4,prerequisites:n?.prerequisites}}function r(e,n,s){return{command:e,mode:"exec",execArgs:n,timeoutMs:s?.timeoutMs??6e4,prerequisites:s?.prerequisites}}const o={claude:{cliBinary:"claude",macos:i("@anthropic-ai/claude-code",{minNodeVersion:"18.0"}),linux:i("@anthropic-ai/claude-code",{minNodeVersion:"18.0"}),windows:i("@anthropic-ai/claude-code",{minNodeVersion:"18.0"})},codex:{cliBinary:"codex",macos:{...l("curl -fsSL https://chatgpt.com/codex/install.sh | sh",{prerequisites:["curl"]}),fallback:i("@openai/codex")},linux:{...l("curl -fsSL https://chatgpt.com/codex/install.sh | sh",{prerequisites:["curl"]}),fallback:i("@openai/codex")},windows:{...l('powershell -ExecutionPolicy ByPass -c "irm https://chatgpt.com/codex/install.ps1 | iex"'),fallback:i("@openai/codex")}},gemini:{cliBinary:"gemini",macos:{...i("@google/gemini-cli",{minNodeVersion:"18.0"}),fallback:l("npx @google/gemini-cli --yes",{timeoutMs:12e4})},linux:i("@google/gemini-cli",{minNodeVersion:"18.0"}),windows:i("@google/gemini-cli",{minNodeVersion:"18.0"})},qwen:{cliBinary:"qwen",macos:{...l('bash -c "$(curl -fsSL https://qwen-code-assets.oss-cn-hangzhou.aliyuncs.com/installation/install-qwen.sh)"',{prerequisites:["curl"]}),fallback:i("@qwen-code/qwen-code@latest",{minNodeVersion:"22.0"})},linux:{...l('bash -c "$(curl -fsSL https://qwen-code-assets.oss-cn-hangzhou.aliyuncs.com/installation/install-qwen.sh)"',{prerequisites:["curl"]}),fallback:i("@qwen-code/qwen-code@latest",{minNodeVersion:"22.0"})},windows:{...l(`powershell -Command "Invoke-WebRequest 'https://qwen-code-assets.oss-cn-hangzhou.aliyuncs.com/installation/install-qwen.bat' -OutFile (Join-Path $env:TEMP 'install-qwen.bat'); & (Join-Path $env:TEMP 'install-qwen.bat')"`),fallback:i("@qwen-code/qwen-code@latest",{minNodeVersion:"22.0"})}},cursor:{cliBinary:"agent",macos:l("curl https://cursor.com/install -fsS | bash",{prerequisites:["curl"]}),linux:l("curl https://cursor.com/install -fsS | bash",{prerequisites:["curl"]}),windows:l("irm 'https://cursor.com/install?win32=true' | iex")},copilot:{cliBinary:"copilot",macos:r("gh",["extension","install","github/gh-copilot"],{prerequisites:["gh"]}),linux:r("gh",["extension","install","github/gh-copilot"],{prerequisites:["gh"]}),windows:r("gh",["extension","install","github/gh-copilot"],{prerequisites:["gh"]})},kiro:{cliBinary:"kiro-cli",macos:l("curl -fsSL https://cli.kiro.dev/install | bash",{prerequisites:["curl"]}),linux:l("curl -fsSL https://cli.kiro.dev/install | bash",{prerequisites:["curl"]}),windows:null},openclaw:{cliBinary:"openclaw",macos:i("openclaw@latest",{minNodeVersion:"22.0"}),linux:i("openclaw@latest",{minNodeVersion:"22.0"}),windows:i("openclaw@latest",{minNodeVersion:"22.0"})},reasonix:{cliBinary:"reasonix",macos:i("reasonix"),linux:i("reasonix"),windows:i("reasonix")},pi:{cliBinary:"pi",macos:i("@earendil-works/pi-coding-agent",{minNodeVersion:"22.19"}),linux:i("@earendil-works/pi-coding-agent",{minNodeVersion:"22.19"}),windows:i("@earendil-works/pi-coding-agent",{minNodeVersion:"22.19"})},agy:{cliBinary:"agy",macos:l("curl -fsSL https://antigravity.google/cli/install.sh | bash",{prerequisites:["curl"]}),linux:l("curl -fsSL https://antigravity.google/cli/install.sh | bash",{prerequisites:["curl"]}),windows:l('powershell -ExecutionPolicy ByPass -c "irm https://antigravity.google/cli/install.ps1 | iex"')},hermes:{cliBinary:"hermes",macos:null,linux:null,windows:null},codewhale:{cliBinary:"codewhale",macos:null,linux:null,windows:null},opencode:{cliBinary:"opencode",macos:null,linux:null,windows:null},openhuman:{cliBinary:"openhuman-core",macos:null,linux:null,windows:null}};function a(){switch(process.platform){case"darwin":return"macos";case"linux":return"linux";case"win32":return"windows";default:return"linux"}}function u(e,n){const s=o[e];return s?s[n]:null}function d(e){return o[e]?.cliBinary??null}function c(e,n){const s=o[e];if(!s)return null;const t=s[n];return{agentType:e,cliBinary:s.cliBinary,supported:t!==null,installCommand:t?.command??null,prerequisites:t?.prerequisites}}function m(e){return Object.keys(o).sort().map(n=>c(n,e)).filter(n=>n!==null)}function p(e){return e in o}export{a as detectPlatformOS,c as getAgentInstallInfo,m as getAllAgentInstallInfo,d as getCliBinary,u as getInstallCommand,p as isKnownAgent};
1
+ function i(n,s){return{command:`npm install -g ${n}`,mode:"npm",npmPackage:n,timeoutMs:s?.timeoutMs??12e4,prerequisites:["node","npm"],minNodeVersion:s?.minNodeVersion}}function e(n,s){return{command:n,mode:"shell",timeoutMs:s?.timeoutMs??12e4,prerequisites:s?.prerequisites}}function r(n,s,o){return{command:n,mode:"exec",execArgs:s,timeoutMs:o?.timeoutMs??6e4,prerequisites:o?.prerequisites,skipVerification:o?.skipVerification}}const l={claude:{cliBinary:"claude",macos:i("@anthropic-ai/claude-code",{minNodeVersion:"18.0"}),linux:i("@anthropic-ai/claude-code",{minNodeVersion:"18.0"}),windows:i("@anthropic-ai/claude-code",{minNodeVersion:"18.0"})},codex:{cliBinary:"codex",macos:{...e("curl -fsSL https://chatgpt.com/codex/install.sh | sh",{prerequisites:["curl"]}),fallback:i("@openai/codex")},linux:{...e("curl -fsSL https://chatgpt.com/codex/install.sh | sh",{prerequisites:["curl"]}),fallback:i("@openai/codex")},windows:{...e('powershell -ExecutionPolicy ByPass -c "irm https://chatgpt.com/codex/install.ps1 | iex"'),fallback:i("@openai/codex")}},gemini:{cliBinary:"gemini",macos:{...i("@google/gemini-cli",{minNodeVersion:"18.0"}),fallback:e("npx @google/gemini-cli --yes",{timeoutMs:12e4})},linux:i("@google/gemini-cli",{minNodeVersion:"18.0"}),windows:i("@google/gemini-cli",{minNodeVersion:"18.0"})},qwen:{cliBinary:"qwen",macos:{...e('bash -c "$(curl -fsSL https://qwen-code-assets.oss-cn-hangzhou.aliyuncs.com/installation/install-qwen.sh)"',{prerequisites:["curl"]}),fallback:i("@qwen-code/qwen-code@latest",{minNodeVersion:"22.0"})},linux:{...e('bash -c "$(curl -fsSL https://qwen-code-assets.oss-cn-hangzhou.aliyuncs.com/installation/install-qwen.sh)"',{prerequisites:["curl"]}),fallback:i("@qwen-code/qwen-code@latest",{minNodeVersion:"22.0"})},windows:{...e(`powershell -Command "Invoke-WebRequest 'https://qwen-code-assets.oss-cn-hangzhou.aliyuncs.com/installation/install-qwen.bat' -OutFile (Join-Path $env:TEMP 'install-qwen.bat'); & (Join-Path $env:TEMP 'install-qwen.bat')"`),fallback:i("@qwen-code/qwen-code@latest",{minNodeVersion:"22.0"})}},cursor:{cliBinary:"agent",macos:e("curl https://cursor.com/install -fsS | bash",{prerequisites:["curl"]}),linux:e("curl https://cursor.com/install -fsS | bash",{prerequisites:["curl"]}),windows:e("irm 'https://cursor.com/install?win32=true' | iex")},copilot:{cliBinary:"copilot",macos:r("gh",["extension","install","github/gh-copilot"],{prerequisites:["gh"],skipVerification:!0}),linux:r("gh",["extension","install","github/gh-copilot"],{prerequisites:["gh"],skipVerification:!0}),windows:r("gh",["extension","install","github/gh-copilot"],{prerequisites:["gh"],skipVerification:!0})},kiro:{cliBinary:"kiro-cli",macos:e("curl -fsSL https://cli.kiro.dev/install | bash",{prerequisites:["curl"]}),linux:e("curl -fsSL https://cli.kiro.dev/install | bash",{prerequisites:["curl"]}),windows:null},openclaw:{cliBinary:"openclaw",macos:i("openclaw@latest",{minNodeVersion:"22.0"}),linux:i("openclaw@latest",{minNodeVersion:"22.0"}),windows:i("openclaw@latest",{minNodeVersion:"22.0"})},reasonix:{cliBinary:"reasonix",macos:i("reasonix"),linux:i("reasonix"),windows:i("reasonix")},pi:{cliBinary:"pi",macos:i("@earendil-works/pi-coding-agent",{minNodeVersion:"22.19"}),linux:i("@earendil-works/pi-coding-agent",{minNodeVersion:"22.19"}),windows:i("@earendil-works/pi-coding-agent",{minNodeVersion:"22.19"})},agy:{cliBinary:"agy",macos:e("curl -fsSL https://antigravity.google/cli/install.sh | bash",{prerequisites:["curl"]}),linux:e("curl -fsSL https://antigravity.google/cli/install.sh | bash",{prerequisites:["curl"]}),windows:e('powershell -ExecutionPolicy ByPass -c "irm https://antigravity.google/cli/install.ps1 | iex"')},hermes:{cliBinary:"hermes",macos:null,linux:null,windows:null},codewhale:{cliBinary:"codewhale",macos:i("codewhale"),linux:i("codewhale"),windows:i("codewhale")},opencode:{cliBinary:"opencode",macos:{...e("curl -fsSL https://opencode.ai/install | bash",{prerequisites:["curl"]}),fallback:i("opencode-ai")},linux:{...e("curl -fsSL https://opencode.ai/install | bash",{prerequisites:["curl"]}),fallback:i("opencode-ai")},windows:i("opencode-ai")},openhuman:{cliBinary:"openhuman-core",macos:e("curl -fsSL https://raw.githubusercontent.com/tinyhumansai/openhuman/main/scripts/install.sh | bash",{prerequisites:["curl"]}),linux:e("curl -fsSL https://raw.githubusercontent.com/tinyhumansai/openhuman/main/scripts/install.sh | bash",{prerequisites:["curl"]}),windows:null}};function c(){switch(process.platform){case"darwin":return"macos";case"linux":return"linux";case"win32":return"windows";default:return"linux"}}function u(n,s){const o=l[n];return o?o[s]:null}function p(n){return l[n]?.cliBinary??null}function a(n,s){const o=l[n];if(!o)return null;const t=o[s];return{agentType:n,cliBinary:o.cliBinary,supported:t!==null,installCommand:t?.command??null,prerequisites:t?.prerequisites}}function d(n){return Object.keys(l).sort().map(s=>a(s,n)).filter(s=>s!==null)}function m(n){return n in l}export{c as detectPlatformOS,a as getAgentInstallInfo,d as getAllAgentInstallInfo,p as getCliBinary,u as getInstallCommand,m as isKnownAgent};
@@ -1 +1 @@
1
- const a=[{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"],description:"Query action type."},id:{type:"string",description:"Contact ID (contact_search) or Session ID (session_search)."},keyword:{type:"string",description:"Search keyword."},limit:{type:"integer",description:"Max results."},offset:{type:"integer",description:"Result offset."},sessionId:{type:"string",description:"Session ID (message_history, message_search)."},beforeId:{type:"string",description:"Pagination cursor (message_history, message_search)."}},required:["action"]},validation:{required:["action"],properties:{action:{type:"string",enum:["contact_search","session_search","message_history","message_search"]},id:{type:"string"},keyword:{type:"string",maxLength:200},limit:{type:"integer",minimum:1,maximum:100},offset:{type:"integer",minimum:0},sessionId:{type:"string"},beforeId:{type:"string"}}}},{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"],description:"Group action type."},sessionId:{type:"string",description:"Group session ID."},name:{type:"string",description:"Group name (create)."},memberIds:{type:"array",items:{type:"string"},description:"Member IDs to add/remove."},memberTypes:{type:"array",items:{type:"integer",enum:[1,2]},description:"Member types (1=user, 2=agent)."},memberId:{type:"string",description:"Target member ID."},role:{type:"integer",enum:[1,2],description:"New role (1=admin, 2=member)."},memberType:{type:"integer",description:"Member type."},allMembersMuted:{type:"boolean",description:"Whether to mute all members."},isSpeakMuted:{type:"boolean",description:"Whether member is muted."},canSpeakWhenAllMuted:{type:"boolean",description:"Allow speaking when all muted."}},required:["action"]},validation:{required:["action"],properties:{action:{type:"string",enum:["create","detail","leave","add_members","remove_members","update_member_role","update_all_members_muted","update_member_speaking","dissolve"]},sessionId:{type:"string"},name:{type:"string",maxLength:128},memberIds:{type:"array",items:{type:"string"},maxItems:100},memberTypes:{type:"array",items:{type:"integer",enum:[1,2]}},memberId:{type:"string"},role:{type:"integer",enum:[1,2]},memberType:{type:"integer"},allMembersMuted:{type:"boolean"},isSpeakMuted:{type:"boolean"},canSpeakWhenAllMuted:{type:"boolean"}}}},{name:"grix_message_send",description:"Send a message to a session in the Grix/AIBot platform.",inputSchema:{type:"object",properties:{sessionId:{type:"string",description:"Target session ID"},content:{type:"string",description:"Message content"},msgType:{type:"integer",description:"Message type (1=text, default 1)"},quotedMessageId:{type:"string",description:"Message ID to reply to"},threadId:{type:"string",description:"Thread ID for threaded reply"}},required:["sessionId","content"]},validation:{required:["sessionId","content"],properties:{sessionId:{type:"string"},content:{type:"string",maxLength:1e4},msgType:{type:"integer"},quotedMessageId:{type:"string"},threadId:{type:"string"}}}},{name:"grix_message_unsend",description:"Recall/unsend a message in the Grix/AIBot platform.",inputSchema:{type:"object",properties:{sessionId:{type:"string",description:"Session ID"},msgId:{type:"string",description:"Message ID to unsend"}},required:["sessionId","msgId"]},validation:{required:["sessionId","msgId"],properties:{sessionId:{type:"string"},msgId:{type:"string"}}}},{name:"grix_file_link",description:"Create a direct, tailnet-only download link for a local file on this host. Use this whenever the user asks you to send, share, give, or deliver a file that exists on the machine where you run (a report, log, build artifact, export, or any local path). It returns a ready-to-use Markdown link in the `markdown` field \u2014 include that exact Markdown link in your reply so the user can click and download the file directly over the shared Tailscale network. Each link is one-time and expires, so call this again to produce a fresh link every time you deliver a file. Requires this host to be on a tailnet (Tailscale running).",inputSchema:{type:"object",properties:{file_path:{type:"string",description:"Absolute path to a local file on this host to share with the user."},ttl_ms:{type:"integer",description:"Optional link lifetime in milliseconds (default 10 minutes)."}},required:["file_path"]},validation:{required:["file_path"],properties:{file_path:{type:"string",maxLength:4096},ttl_ms:{type:"integer",minimum:1e4,maximum:864e5}}}},{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"],description:"Admin action type."},agentName:{type:"string",description:"Agent name (create_agent)."},introduction:{type:"string",description:"Agent introduction (create_agent)."},isMain:{type:"boolean",description:"Set as main agent (create_agent)."},agentId:{type:"string",description:"Agent ID (assign_category, rotate_api_key)."},categoryId:{type:"string",description:"Category ID (create_agent, update_category, assign_category)."},name:{type:"string",description:"Category name (create_category, update_category)."},parentId:{type:"string",description:"Parent category ID (create_category, update_category)."},sortOrder:{type:"integer",description:"Sort order (create_category, update_category)."}},required:["action"]},validation:{required:["action"],properties:{action:{type:"string",enum:["create_agent","list_categories","create_category","update_category","assign_category","rotate_api_key"]},agentName:{type:"string"},introduction:{type:"string"},isMain:{type:"boolean"},agentId:{type:"string"},categoryId:{type:"string"},name:{type:"string"},parentId:{type:"string"},sortOrder:{type:"integer"}}}},{name:"grix_call_owner",description:"Call your owner into this session to talk by voice. Use this when, during your work, you need to reach your owner \u2014 to discuss something or to get an approval/review. It sends the owner an offline notification; when they tap it they land directly in this conversation and a voice-brain call is started automatically. Requires the owner to have configured a voice brain. Rate-limited per session.",inputSchema:{type:"object",properties:{session_id:{type:"string",description:"The session ID to call the owner into."}},required:["session_id"]},validation:{required:["session_id"],properties:{session_id:{type:"string"}}}},{name:"grix_agent_update",description:"Update the text introduction of one of your owner's agents, identified by its numeric agent ID.",inputSchema:{type:"object",properties:{agent_id:{type:"string",description:"Target agent's numeric ID, passed as a string."},introduction:{type:"string",description:"New text introduction (max 300 characters)."}},required:["agent_id","introduction"]},validation:{required:["agent_id","introduction"],properties:{agent_id:{type:"string"},introduction:{type:"string",maxLength:300}}}},{name:"grix_dispatch_agent",description:"Dispatch one of your owner's agents to do work in a given working directory. Provide the target agent numeric ID, the working directory, and a text description of the task. The backend opens (or reuses) a private session between the owner and that agent, binds the working directory when the agent type requires it (claude/codex/etc.), and sends the task into the session as the owner so the agent starts working.",inputSchema:{type:"object",properties:{agent_id:{type:"string",description:"Target agent's numeric ID, passed as a string."},cwd:{type:"string",description:"Absolute working directory where the agent should do the work."},task:{type:"string",description:"Text description of the task to perform."}},required:["agent_id","cwd","task"]},validation:{required:["agent_id","cwd","task"],properties:{agent_id:{type:"string"},cwd:{type:"string",maxLength:4096},task:{type:"string",maxLength:1e4}}}},{name:"grix_session_send",description:"Send a message into a session as the owner (not as yourself). Use this to speak on the owner's behalf in a session the owner is a member of. The owner must be a member of the target session.",inputSchema:{type:"object",properties:{session_id:{type:"string",description:"Target session ID."},content:{type:"string",description:"Message content to send as the owner."}},required:["session_id","content"]},validation:{required:["session_id","content"],properties:{session_id:{type:"string"},content:{type:"string",maxLength:1e4}}}},{name:"grix_task_query",description:"Query the session-level task states of all your owner's sessions. Takes no parameters \u2014 owner and agent are resolved from your authenticated connection. Returns one entry per session with a single mutually-exclusive state: running (working), waiting_approval (blocked on your owner to approve/deny), waiting_question (asked the owner a question, awaiting their reply), completed, failed, or idle (no task / stopped). Use this to see at a glance which tasks are done, still running, or waiting on the owner.",inputSchema:{type:"object",properties:{}},validation:{required:[],properties:{}}}],d=[{name:"grix_reply",description:"Send a reply message to the specified session. Supports streaming in chunks; the frontend will automatically aggregate them into one complete message.",inputSchema:{type:"object",properties:{event_id:{type:"string",description:"Associated event ID from the inbound event."},session_id:{type:"string",description:"Target session ID."},text:{type:"string",description:"Reply text content."},quoted_message_id:{type:"string",description:"Quoted message ID (optional)."},is_final:{type:"boolean",description:"Whether this is a stage-final reply. Advisory only \u2014 does not trigger event completion; completion is handled by the complete tool or Stop hook."}},required:["session_id","text"]},validation:{required:["session_id","text"],properties:{event_id:{type:"string"},session_id:{type:"string"},text:{type:"string",maxLength:5e4},quoted_message_id:{type:"string"},is_final:{type:"boolean"}}}},{name:"grix_complete",description:"Mark event processing as complete, notifying the backend that no more replies are expected.",inputSchema:{type:"object",properties:{event_id:{type:"string",description:"The event ID to complete."},status:{type:"string",enum:["responded","canceled","failed"],description:"Completion status."},msg:{type:"string",description:"Additional note (optional)."}},required:["event_id","status"]},validation:{required:["event_id","status"],properties:{event_id:{type:"string"},status:{type:"string",enum:["responded","canceled","failed"]},msg:{type:"string",maxLength:500}}}},{name:"grix_event_ack",description:"Acknowledge event receipt (usually done automatically by the Dispatcher; agents typically do not need to call this manually).",inputSchema:{type:"object",properties:{event_id:{type:"string",description:"The event ID to acknowledge."},session_id:{type:"string",description:"Session ID."}},required:["event_id"]},validation:{required:["event_id"],properties:{event_id:{type:"string"},session_id:{type:"string"}}}},{name:"grix_composing",description:'Set the "typing" indicator status for a session.',inputSchema:{type:"object",properties:{session_id:{type:"string",description:"Session ID."},active:{type:"boolean",description:"true = typing, false = stopped."},event_id:{type:"string",description:"Associated event ID (optional)."}},required:["session_id","active"]},validation:{required:["session_id","active"],properties:{session_id:{type:"string"},active:{type:"boolean"},event_id:{type:"string"}}}},{name:"grix_access_control",description:"Manage sender access control: pair approval, allow/remove senders, set policy.",inputSchema:{type:"object",properties:{action:{type:"string",enum:["pair_approve","pair_deny","allow_sender","remove_sender","set_policy"],description:"Access control action type."},code:{type:"string",description:"Pairing code (required for pair_approve/pair_deny)."},sender_id:{type:"string",description:"Sender ID (required for allow_sender/remove_sender)."},policy:{type:"string",enum:["allowlist","open","disabled"],description:"Access policy (required for set_policy)."}},required:["action"]},validation:{required:["action"],properties:{action:{type:"string",enum:["pair_approve","pair_deny","allow_sender","remove_sender","set_policy"]},code:{type:"string"},sender_id:{type:"string"},policy:{type:"string",enum:["allowlist","open","disabled"]}}}},{name:"grix_status",description:"Query the Grix connection status of the current MCP session.",inputSchema:{type:"object",properties:{}},validation:{required:[],properties:{}}}],p=[{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."},final:{type:"boolean",description:"Advisory flag only. It does not complete the event; completion is handled by complete tool or Stop hook."}},required:["chat_id","event_id","text"]}},{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"]},msg:{type:"string"},code:{type:"string"}},required:["event_id","status"]}}],c=new Set(p.map(e=>e.name)),b=new Set(a.map(e=>e.name)),v=new Set(d.map(e=>e.name)),I=/([A-Za-z0-9._-]+:[A-Za-z0-9._-]+:[A-Za-z0-9._-]+:[A-Za-z0-9._-]+)/,k=/[A-Za-z0-9._-]+/;function C(e){return!!(b.has(e)||v.has(e)||c.has(e)||e.startsWith("mcp__grix"))}const w=[...a,...d],x=[...w,...p],P=new Map(x.map(e=>[e.name,e]));function z(e,t){return e==="reply"?{name:"grix_reply",args:u("grix_reply",{event_id:t.event_id,session_id:t.chat_id,text:t.text,quoted_message_id:t.reply_to,is_final:t.final})}:e==="complete"?{name:"grix_complete",args:u("grix_complete",{event_id:t.event_id,status:t.status,msg:t.msg,code:t.code})}:{name:e,args:t}}function l(e){const t=String(e??"").trim();return t?t.match(I)?.[1]:void 0}function m(e){const t=String(e??"").trim();if(!t)return;const n=l(t);if(n)return _(n);const i=t.match(/(?:chat_id|session_id)\s*=\s*"([A-Za-z0-9._-]+)"/)?.[1];if(i)return i;const r=t.match(/[A-Za-z0-9._-]+/g)??[];for(const s of r)if(!(s==="event_id"||s==="chat_id"||s==="session_id")&&s.length>0)return s;return t.match(k)?.[0]}function _(e){if(!e)return;const t=e.split(":",1)[0]?.trim();if(t)return m(t)}function u(e,t){if(e!=="grix_reply"&&e!=="grix_complete")return t;const n={...t},i=l(n.event_id);if(i&&(n.event_id=i),e==="grix_reply"){const r=_(i),o=String(n.session_id??""),s=m(n.session_id),f=/\bevent_id\b|["'<>\s]/.test(o);s&&!(f&&r)?n.session_id=s:r&&(n.session_id=r)}return n}function U(e){return c.has(e)}function G(e,t){switch(e){case"grix_query":return S(t);case"grix_group":return A(t);case"grix_message_send":return q(t);case"grix_message_unsend":return T(t);case"grix_file_link":return M(t);case"grix_admin":return N(t);case"grix_call_owner":return O(t);case"grix_agent_update":return D(t);case"grix_dispatch_agent":return E(t);case"grix_session_send":return L(t);case"grix_task_query":return j();default:throw new Error(`Unknown tool: ${e}`)}}const g={contact_search:"contact_search",session_search:"session_search",message_history:"message_history",message_search:"message_search"};function S(e){const t=String(e.action??""),n=g[t];if(!n)throw new Error(`Unknown grix_query action: ${t}`);const i={};return e.id!=null&&(i.id=e.id),e.keyword!=null&&(i.keyword=e.keyword),e.limit!=null&&(i.limit=e.limit),e.offset!=null&&(i.offset=e.offset),e.sessionId!=null&&(i.session_id=e.sessionId),e.beforeId!=null&&(i.before_id=e.beforeId),{action:n,params:i}}const y={create:"group_create",detail:"group_detail_read",leave:"group_leave_self",add_members:"group_member_add",remove_members:"group_member_remove",update_member_role:"group_member_role_update",update_all_members_muted:"group_all_members_muted_update",update_member_speaking:"group_member_speaking_update",dissolve:"group_dissolve"};function A(e){const t=String(e.action??""),n=y[t];if(!n)throw new Error(`Unknown grix_group action: ${t}`);const i={};return e.sessionId!=null&&(i.session_id=e.sessionId),e.name!=null&&(i.name=e.name),e.memberIds!=null&&(i.member_ids=e.memberIds),e.memberTypes!=null&&(i.member_types=e.memberTypes),e.memberId!=null&&(i.member_id=e.memberId),e.role!=null&&(i.role=e.role),e.memberType!=null&&(i.member_type=e.memberType),e.allMembersMuted!=null&&(i.all_members_muted=e.allMembersMuted),e.isSpeakMuted!=null&&(i.is_speak_muted=e.isSpeakMuted),e.canSpeakWhenAllMuted!=null&&(i.can_speak_when_all_muted=e.canSpeakWhenAllMuted),{action:n,params:i}}function q(e){const t={session_id:e.sessionId,msg_type:e.msgType??1,content:e.content};return e.quotedMessageId!=null&&(t.quoted_message_id=e.quotedMessageId),e.threadId!=null&&(t.thread_id=e.threadId),{action:"send_msg",params:t}}function T(e){return{action:"delete_msg",params:{session_id:e.sessionId,msg_id:e.msgId}}}function M(e){const t={file_path:e.file_path};return e.ttl_ms!=null&&(t.ttl_ms=e.ttl_ms),{action:"file_link",params:t}}const h={create_agent:"agent_api_create",list_categories:"agent_category_list",create_category:"agent_category_create",update_category:"agent_category_update",assign_category:"agent_category_assign",rotate_api_key:"agent_api_key_rotate"};function O(e){return{action:"call_owner",params:{session_id:e.session_id}}}function D(e){return{action:"agent_introduction_update",params:{agent_id:e.agent_id,introduction:e.introduction}}}function E(e){return{action:"dispatch_agent",params:{agent_id:e.agent_id,cwd:e.cwd,task:e.task}}}function L(e){return{action:"session_send",params:{session_id:e.session_id,content:e.content}}}function j(){return{action:"agent_task_query",params:{}}}function N(e){const t=String(e.action??""),n=h[t];if(!n)throw new Error(`Unknown grix_admin action: ${t}`);const i={};return e.agentName!=null&&(i.agent_name=e.agentName),e.introduction!=null&&(i.introduction=e.introduction),e.isMain!=null&&(i.is_main=e.isMain),e.agentId!=null&&(i.agent_id=e.agentId),e.categoryId!=null&&(i.category_id=e.categoryId),e.name!=null&&(i.name=e.name),e.parentId!=null&&(i.parent_id=e.parentId),e.sortOrder!=null&&(i.sort_order=e.sortOrder),{action:n,params:i}}const R=new Set([...Object.values(g),...Object.values(y),...Object.values(h),"send_msg","delete_msg","file_link","call_owner","agent_introduction_update","dispatch_agent","session_send","agent_task_query"]),W={pair_approve:"pair_approve",pair_deny:"pair_deny",allow_sender:"sender_allow",remove_sender:"sender_remove",set_policy:"policy_set"};export{W as ACCESS_CONTROL_ACTION_MAP,w as ALL_TOOLS,d as EVENT_TOOLS,x as EXPOSED_TOOLS,R as PHASE1_INVOKE_ACTIONS,b as PHASE1_TOOL_NAMES,v as PHASE2_TOOL_NAMES,a as TOOLS,p as TOOL_ALIASES,P as TOOL_MAP,U as isAlias,C as isGrixInternalToolName,z as mapToolAlias,u as normalizeEventToolArgs,G as toolCallToInvoke};
1
+ const a=[{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"],description:"Query action type."},id:{type:"string",description:"Contact ID (contact_search) or Session ID (session_search)."},keyword:{type:"string",description:"Search keyword."},limit:{type:"integer",description:"Max results."},offset:{type:"integer",description:"Result offset."},sessionId:{type:"string",description:"Session ID (message_history, message_search)."},beforeId:{type:"string",description:"Pagination cursor (message_history, message_search)."}},required:["action"]},validation:{required:["action"],properties:{action:{type:"string",enum:["contact_search","session_search","message_history","message_search"]},id:{type:"string"},keyword:{type:"string",maxLength:200},limit:{type:"integer",minimum:1,maximum:100},offset:{type:"integer",minimum:0},sessionId:{type:"string"},beforeId:{type:"string"}}}},{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"],description:"Group action type."},sessionId:{type:"string",description:"Group session ID."},name:{type:"string",description:"Group name (create)."},memberIds:{type:"array",items:{type:"string"},description:"Member IDs to add/remove."},memberTypes:{type:"array",items:{type:"integer",enum:[1,2]},description:"Member types (1=user, 2=agent)."},memberId:{type:"string",description:"Target member ID."},role:{type:"integer",enum:[1,2],description:"New role (1=admin, 2=member)."},memberType:{type:"integer",description:"Member type."},allMembersMuted:{type:"boolean",description:"Whether to mute all members."},isSpeakMuted:{type:"boolean",description:"Whether member is muted."},canSpeakWhenAllMuted:{type:"boolean",description:"Allow speaking when all muted."}},required:["action"]},validation:{required:["action"],properties:{action:{type:"string",enum:["create","detail","leave","add_members","remove_members","update_member_role","update_all_members_muted","update_member_speaking","dissolve"]},sessionId:{type:"string"},name:{type:"string",maxLength:128},memberIds:{type:"array",items:{type:"string"},maxItems:100},memberTypes:{type:"array",items:{type:"integer",enum:[1,2]}},memberId:{type:"string"},role:{type:"integer",enum:[1,2]},memberType:{type:"integer"},allMembersMuted:{type:"boolean"},isSpeakMuted:{type:"boolean"},canSpeakWhenAllMuted:{type:"boolean"}}}},{name:"grix_message_send",description:"Send a message to a session in the Grix/AIBot platform.",inputSchema:{type:"object",properties:{sessionId:{type:"string",description:"Target session ID"},content:{type:"string",description:"Message content"},msgType:{type:"integer",description:"Message type (1=text, default 1)"},quotedMessageId:{type:"string",description:"Message ID to reply to"},threadId:{type:"string",description:"Thread ID for threaded reply"}},required:["sessionId","content"]},validation:{required:["sessionId","content"],properties:{sessionId:{type:"string"},content:{type:"string",maxLength:1e4},msgType:{type:"integer"},quotedMessageId:{type:"string"},threadId:{type:"string"}}}},{name:"grix_message_unsend",description:"Recall/unsend a message in the Grix/AIBot platform.",inputSchema:{type:"object",properties:{sessionId:{type:"string",description:"Session ID"},msgId:{type:"string",description:"Message ID to unsend"}},required:["sessionId","msgId"]},validation:{required:["sessionId","msgId"],properties:{sessionId:{type:"string"},msgId:{type:"string"}}}},{name:"grix_file_link",description:"Create a direct, tailnet-only download link for a local file on this host. Use this whenever the user asks you to send, share, give, or deliver a file that exists on the machine where you run (a report, log, build artifact, export, or any local path). It returns a ready-to-use Markdown link in the `markdown` field \u2014 include that exact Markdown link in your reply so the user can click and download the file directly over the shared Tailscale network. Each link is one-time and expires, so call this again to produce a fresh link every time you deliver a file. Requires this host to be on a tailnet (Tailscale running).",inputSchema:{type:"object",properties:{file_path:{type:"string",description:"Absolute path to a local file on this host to share with the user."},ttl_ms:{type:"integer",description:"Optional link lifetime in milliseconds (default 10 minutes)."}},required:["file_path"]},validation:{required:["file_path"],properties:{file_path:{type:"string",maxLength:4096},ttl_ms:{type:"integer",minimum:1e4,maximum:864e5}}}},{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"],description:"Admin action type."},agentName:{type:"string",description:"Agent name (create_agent)."},introduction:{type:"string",description:"Agent introduction (create_agent)."},isMain:{type:"boolean",description:"Set as main agent (create_agent)."},agentId:{type:"string",description:"Agent ID (assign_category, rotate_api_key)."},categoryId:{type:"string",description:"Category ID (create_agent, update_category, assign_category)."},name:{type:"string",description:"Category name (create_category, update_category)."},parentId:{type:"string",description:"Parent category ID (create_category, update_category)."},sortOrder:{type:"integer",description:"Sort order (create_category, update_category)."}},required:["action"]},validation:{required:["action"],properties:{action:{type:"string",enum:["create_agent","list_categories","create_category","update_category","assign_category","rotate_api_key"]},agentName:{type:"string"},introduction:{type:"string"},isMain:{type:"boolean"},agentId:{type:"string"},categoryId:{type:"string"},name:{type:"string"},parentId:{type:"string"},sortOrder:{type:"integer"}}}},{name:"grix_call_owner",description:"Call your owner into this session to talk by voice. Use this when, during your work, you need to reach your owner \u2014 to discuss something or to get an approval/review. It sends the owner an offline notification; when they tap it they land directly in this conversation and a voice-brain call is started automatically. Requires the owner to have configured a voice brain. Rate-limited per session.",inputSchema:{type:"object",properties:{session_id:{type:"string",description:"The session ID to call the owner into."}},required:["session_id"]},validation:{required:["session_id"],properties:{session_id:{type:"string"}}}},{name:"grix_agent_update",description:"Update the text introduction of one of your owner's agents, identified by its numeric agent ID.",inputSchema:{type:"object",properties:{agent_id:{type:"string",description:"Target agent's numeric ID, passed as a string."},introduction:{type:"string",description:"New text introduction (max 300 characters)."}},required:["agent_id","introduction"]},validation:{required:["agent_id","introduction"],properties:{agent_id:{type:"string"},introduction:{type:"string",maxLength:300}}}},{name:"grix_dispatch_agent",description:"Dispatch one of your owner's agents to do work in a given working directory. Provide the target agent numeric ID, the working directory, and a text description of the task. The backend opens (or reuses) a private session between the owner and that agent, binds the working directory when the agent type requires it (claude/codex/etc.), and sends the task into the session as the owner so the agent starts working.",inputSchema:{type:"object",properties:{agent_id:{type:"string",description:"Target agent's numeric ID, passed as a string."},cwd:{type:"string",description:"Absolute working directory where the agent should do the work."},task:{type:"string",description:"Text description of the task to perform."}},required:["agent_id","cwd","task"]},validation:{required:["agent_id","cwd","task"],properties:{agent_id:{type:"string"},cwd:{type:"string",maxLength:4096},task:{type:"string",maxLength:1e4}}}},{name:"grix_session_send",description:"Send a message into a session as the owner (not as yourself). Use this to speak on the owner's behalf in a session the owner is a member of. The owner must be a member of the target session.",inputSchema:{type:"object",properties:{session_id:{type:"string",description:"Target session ID."},content:{type:"string",description:"Message content to send as the owner."}},required:["session_id","content"]},validation:{required:["session_id","content"],properties:{session_id:{type:"string"},content:{type:"string",maxLength:1e4}}}},{name:"grix_task_query",description:"Query the session-level task states of all your owner's sessions. Takes no parameters \u2014 owner and agent are resolved from your authenticated connection. Returns one entry per session with a single mutually-exclusive state: running (working), waiting_approval (blocked on your owner to approve/deny), waiting_question (asked the owner a question, awaiting their reply), completed, failed, or idle (no task / stopped). Use this to see at a glance which tasks are done, still running, or waiting on the owner.",inputSchema:{type:"object",properties:{}},validation:{required:[],properties:{}}}],d=[{name:"grix_reply",description:"Send a reply message to the specified session. Supports streaming in chunks; the frontend will automatically aggregate them into one complete message. Any content meant for the user \u2014 including your final conclusion at the end of a turn \u2014 MUST be sent through this tool; text written outside this tool is not delivered to the user.",inputSchema:{type:"object",properties:{event_id:{type:"string",description:"Associated event ID from the inbound event."},session_id:{type:"string",description:"Target session ID."},text:{type:"string",description:"Reply text content."},quoted_message_id:{type:"string",description:"Quoted message ID (optional)."},is_final:{type:"boolean",description:"Whether this is a stage-final reply. Advisory only \u2014 does not trigger event completion; completion is handled by the complete tool or Stop hook."}},required:["session_id","text"]},validation:{required:["session_id","text"],properties:{event_id:{type:"string"},session_id:{type:"string"},text:{type:"string",maxLength:5e4},quoted_message_id:{type:"string"},is_final:{type:"boolean"}}}},{name:"grix_complete",description:"Mark event processing as complete, notifying the backend that no more replies are expected.",inputSchema:{type:"object",properties:{event_id:{type:"string",description:"The event ID to complete."},status:{type:"string",enum:["responded","canceled","failed"],description:"Completion status."},msg:{type:"string",description:"Additional note (optional)."}},required:["event_id","status"]},validation:{required:["event_id","status"],properties:{event_id:{type:"string"},status:{type:"string",enum:["responded","canceled","failed"]},msg:{type:"string",maxLength:500}}}},{name:"grix_event_ack",description:"Acknowledge event receipt (usually done automatically by the Dispatcher; agents typically do not need to call this manually).",inputSchema:{type:"object",properties:{event_id:{type:"string",description:"The event ID to acknowledge."},session_id:{type:"string",description:"Session ID."}},required:["event_id"]},validation:{required:["event_id"],properties:{event_id:{type:"string"},session_id:{type:"string"}}}},{name:"grix_composing",description:'Set the "typing" indicator status for a session.',inputSchema:{type:"object",properties:{session_id:{type:"string",description:"Session ID."},active:{type:"boolean",description:"true = typing, false = stopped."},event_id:{type:"string",description:"Associated event ID (optional)."}},required:["session_id","active"]},validation:{required:["session_id","active"],properties:{session_id:{type:"string"},active:{type:"boolean"},event_id:{type:"string"}}}},{name:"grix_access_control",description:"Manage sender access control: pair approval, allow/remove senders, set policy.",inputSchema:{type:"object",properties:{action:{type:"string",enum:["pair_approve","pair_deny","allow_sender","remove_sender","set_policy"],description:"Access control action type."},code:{type:"string",description:"Pairing code (required for pair_approve/pair_deny)."},sender_id:{type:"string",description:"Sender ID (required for allow_sender/remove_sender)."},policy:{type:"string",enum:["allowlist","open","disabled"],description:"Access policy (required for set_policy)."}},required:["action"]},validation:{required:["action"],properties:{action:{type:"string",enum:["pair_approve","pair_deny","allow_sender","remove_sender","set_policy"]},code:{type:"string"},sender_id:{type:"string"},policy:{type:"string",enum:["allowlist","open","disabled"]}}}},{name:"grix_status",description:"Query the Grix connection status of the current MCP session.",inputSchema:{type:"object",properties:{}},validation:{required:[],properties:{}}}],p=[{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."},final:{type:"boolean",description:"Advisory flag only. It does not complete the event; completion is handled by complete tool or Stop hook."}},required:["chat_id","event_id","text"]}},{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"]},msg:{type:"string"},code:{type:"string"}},required:["event_id","status"]}}],c=new Set(p.map(e=>e.name)),b=new Set(a.map(e=>e.name)),v=new Set(d.map(e=>e.name)),I=/([A-Za-z0-9._-]+:[A-Za-z0-9._-]+:[A-Za-z0-9._-]+:[A-Za-z0-9._-]+)/,k=/[A-Za-z0-9._-]+/;function C(e){return!!(b.has(e)||v.has(e)||c.has(e)||e.startsWith("mcp__grix"))}const w=[...a,...d],x=[...w,...p],P=new Map(x.map(e=>[e.name,e]));function U(e,t){return e==="reply"?{name:"grix_reply",args:_("grix_reply",{event_id:t.event_id,session_id:t.chat_id,text:t.text,quoted_message_id:t.reply_to,is_final:t.final})}:e==="complete"?{name:"grix_complete",args:_("grix_complete",{event_id:t.event_id,status:t.status,msg:t.msg,code:t.code})}:{name:e,args:t}}function l(e){const t=String(e??"").trim();return t?t.match(I)?.[1]:void 0}function m(e){const t=String(e??"").trim();if(!t)return;const n=l(t);if(n)return u(n);const i=t.match(/(?:chat_id|session_id)\s*=\s*"([A-Za-z0-9._-]+)"/)?.[1];if(i)return i;const r=t.match(/[A-Za-z0-9._-]+/g)??[];for(const s of r)if(!(s==="event_id"||s==="chat_id"||s==="session_id")&&s.length>0)return s;return t.match(k)?.[0]}function u(e){if(!e)return;const t=e.split(":",1)[0]?.trim();if(t)return m(t)}function _(e,t){if(e!=="grix_reply"&&e!=="grix_complete")return t;const n={...t},i=l(n.event_id);if(i&&(n.event_id=i),e==="grix_reply"){const r=u(i),o=String(n.session_id??""),s=m(n.session_id),f=/\bevent_id\b|["'<>\s]/.test(o);s&&!(f&&r)?n.session_id=s:r&&(n.session_id=r)}return n}function z(e){return c.has(e)}function G(e,t){switch(e){case"grix_query":return S(t);case"grix_group":return A(t);case"grix_message_send":return q(t);case"grix_message_unsend":return T(t);case"grix_file_link":return M(t);case"grix_admin":return N(t);case"grix_call_owner":return O(t);case"grix_agent_update":return D(t);case"grix_dispatch_agent":return E(t);case"grix_session_send":return L(t);case"grix_task_query":return j();default:throw new Error(`Unknown tool: ${e}`)}}const g={contact_search:"contact_search",session_search:"session_search",message_history:"message_history",message_search:"message_search"};function S(e){const t=String(e.action??""),n=g[t];if(!n)throw new Error(`Unknown grix_query action: ${t}`);const i={};return e.id!=null&&(i.id=e.id),e.keyword!=null&&(i.keyword=e.keyword),e.limit!=null&&(i.limit=e.limit),e.offset!=null&&(i.offset=e.offset),e.sessionId!=null&&(i.session_id=e.sessionId),e.beforeId!=null&&(i.before_id=e.beforeId),{action:n,params:i}}const y={create:"group_create",detail:"group_detail_read",leave:"group_leave_self",add_members:"group_member_add",remove_members:"group_member_remove",update_member_role:"group_member_role_update",update_all_members_muted:"group_all_members_muted_update",update_member_speaking:"group_member_speaking_update",dissolve:"group_dissolve"};function A(e){const t=String(e.action??""),n=y[t];if(!n)throw new Error(`Unknown grix_group action: ${t}`);const i={};return e.sessionId!=null&&(i.session_id=e.sessionId),e.name!=null&&(i.name=e.name),e.memberIds!=null&&(i.member_ids=e.memberIds),e.memberTypes!=null&&(i.member_types=e.memberTypes),e.memberId!=null&&(i.member_id=e.memberId),e.role!=null&&(i.role=e.role),e.memberType!=null&&(i.member_type=e.memberType),e.allMembersMuted!=null&&(i.all_members_muted=e.allMembersMuted),e.isSpeakMuted!=null&&(i.is_speak_muted=e.isSpeakMuted),e.canSpeakWhenAllMuted!=null&&(i.can_speak_when_all_muted=e.canSpeakWhenAllMuted),{action:n,params:i}}function q(e){const t={session_id:e.sessionId,msg_type:e.msgType??1,content:e.content};return e.quotedMessageId!=null&&(t.quoted_message_id=e.quotedMessageId),e.threadId!=null&&(t.thread_id=e.threadId),{action:"send_msg",params:t}}function T(e){return{action:"delete_msg",params:{session_id:e.sessionId,msg_id:e.msgId}}}function M(e){const t={file_path:e.file_path};return e.ttl_ms!=null&&(t.ttl_ms=e.ttl_ms),{action:"file_link",params:t}}const h={create_agent:"agent_api_create",list_categories:"agent_category_list",create_category:"agent_category_create",update_category:"agent_category_update",assign_category:"agent_category_assign",rotate_api_key:"agent_api_key_rotate"};function O(e){return{action:"call_owner",params:{session_id:e.session_id}}}function D(e){return{action:"agent_introduction_update",params:{agent_id:e.agent_id,introduction:e.introduction}}}function E(e){return{action:"dispatch_agent",params:{agent_id:e.agent_id,cwd:e.cwd,task:e.task}}}function L(e){return{action:"session_send",params:{session_id:e.session_id,content:e.content}}}function j(){return{action:"agent_task_query",params:{}}}function N(e){const t=String(e.action??""),n=h[t];if(!n)throw new Error(`Unknown grix_admin action: ${t}`);const i={};return e.agentName!=null&&(i.agent_name=e.agentName),e.introduction!=null&&(i.introduction=e.introduction),e.isMain!=null&&(i.is_main=e.isMain),e.agentId!=null&&(i.agent_id=e.agentId),e.categoryId!=null&&(i.category_id=e.categoryId),e.name!=null&&(i.name=e.name),e.parentId!=null&&(i.parent_id=e.parentId),e.sortOrder!=null&&(i.sort_order=e.sortOrder),{action:n,params:i}}const R=new Set([...Object.values(g),...Object.values(y),...Object.values(h),"send_msg","delete_msg","file_link","call_owner","agent_introduction_update","dispatch_agent","session_send","agent_task_query"]),W={pair_approve:"pair_approve",pair_deny:"pair_deny",allow_sender:"sender_allow",remove_sender:"sender_remove",set_policy:"policy_set"};export{W as ACCESS_CONTROL_ACTION_MAP,w as ALL_TOOLS,d as EVENT_TOOLS,x as EXPOSED_TOOLS,R as PHASE1_INVOKE_ACTIONS,b as PHASE1_TOOL_NAMES,v as PHASE2_TOOL_NAMES,a as TOOLS,p as TOOL_ALIASES,P as TOOL_MAP,z as isAlias,C as isGrixInternalToolName,U as mapToolAlias,_ as normalizeEventToolArgs,G as toolCallToInvoke};
@@ -1 +1 @@
1
- import*as i from"@sentry/node";import{log as s}from"../log/logger.js";import{resolveClientVersion as c}from"../util/client-version.js";let a=!1;const p="https://e8e202d7625372b1314b3ff4e85a7ff9@o119262.ingest.us.sentry.io/4511410543329280";function f(){if(!process.env.GRIX_SENTRY_DISABLE)return process.env.SENTRY_DSN||process.env.GRIX_SENTRY_DSN||p}function T(){const e=f();if(!e){s.info("sentry","Sentry \u9519\u8BEF\u4E0A\u62A5\u5DF2\u7981\u7528\uFF08GRIX_SENTRY_DISABLE\uFF09");return}try{i.init({dsn:e,release:`grix-connector@${c()}`,environment:process.env.SENTRY_ENVIRONMENT||process.env.NODE_ENV||"production",sendDefaultPii:!1,initialScope:{tags:{component:"grix-connector"}},defaultIntegrations:!1,tracesSampleRate:0}),a=!0,s.info("sentry","Sentry \u9519\u8BEF\u4E0A\u62A5\u5DF2\u542F\u7528")}catch(n){s.error("sentry",`Sentry \u521D\u59CB\u5316\u5931\u8D25: ${n instanceof Error?n.message:String(n)}`)}}function g(){return a}const E=new Set(["INSTALL_FAILED","INSTALL_TIMEOUT","FALLBACK_EXHAUSTED","PREREQ_INSTALL_FAILED","VERIFICATION_FAILED","ENVIRONMENT_UNSUPPORTED","INTERNAL"]);function S(e){if(e.ok)return!1;const n=e.error?.code;return!!n&&E.has(n)}function I(e){if(!a||!S(e))return;const n=e.error?.code??"INTERNAL",t=e.error?.message??"unknown install failure",r=e.environment;try{i.withScope(o=>{o.setLevel("error"),o.setTags({agent_type:e.agentType,error_code:n,phase:e.phase}),o.setContext("install",{agentType:e.agentType,code:n,phase:e.phase,durationMs:e.durationMs,os:r?.platform,osVersion:r?.osVersion,arch:r?.arch,nodeVersion:r?.nodeVersion,npmVersion:r?.npmVersion,isDocker:r?.isDocker,isCI:r?.isCI,outputTail:(e.output??"").slice(-2e3)}),o.setFingerprint(["agent-install-failure",e.agentType,n]),i.captureException(new Error(`Agent install failed [${e.agentType}/${n}]: ${t}`))}),s.info("sentry",`\u5DF2\u4E0A\u62A5\u5B89\u88C5\u5931\u8D25: ${e.agentType}/${n}`)}catch(o){s.error("sentry",`\u4E0A\u62A5\u5B89\u88C5\u5931\u8D25\u65F6\u51FA\u9519: ${o instanceof Error?o.message:String(o)}`)}}function N(e,n){if(a)try{i.withScope(t=>{t.setLevel("fatal"),t.setTag("crash_type",n);const r=e instanceof Error?e:new Error(String(e));i.captureException(r)}),s.info("sentry",`\u5DF2\u4E0A\u62A5\u5D29\u6E83: ${n}`)}catch(t){s.error("sentry",`\u4E0A\u62A5\u5D29\u6E83\u65F6\u51FA\u9519: ${t instanceof Error?t.message:String(t)}`)}}async function u(e=2e3){if(a)try{await i.close(e)}catch{}}export{u as closeSentry,T as initSentry,g as isSentryEnabled,N as reportFatal,I as reportInstallFailure,S as shouldReportInstallFailure};
1
+ import{log as i}from"../log/logger.js";import{resolveClientVersion as c}from"../util/client-version.js";let r=null,a=!1;const p="https://e8e202d7625372b1314b3ff4e85a7ff9@o119262.ingest.us.sentry.io/4511410543329280";function f(){if(!process.env.GRIX_SENTRY_DISABLE)return process.env.SENTRY_DSN||process.env.GRIX_SENTRY_DSN||p}async function g(){const e=f();if(!e){i.info("sentry","Sentry \u9519\u8BEF\u4E0A\u62A5\u5DF2\u7981\u7528\uFF08GRIX_SENTRY_DISABLE\uFF09");return}try{r=await import("@sentry/node")}catch(n){i.warn("sentry",`Sentry \u6A21\u5757\u52A0\u8F7D\u5931\u8D25\uFF08\u5E73\u53F0\u4E0D\u517C\u5BB9\uFF09\uFF0C\u9519\u8BEF\u4E0A\u62A5\u5DF2\u7981\u7528: ${n instanceof Error?n.message:String(n)}`);return}try{r.init({dsn:e,release:`grix-connector@${c()}`,environment:process.env.SENTRY_ENVIRONMENT||process.env.NODE_ENV||"production",sendDefaultPii:!1,initialScope:{tags:{component:"grix-connector"}},defaultIntegrations:!1,tracesSampleRate:0}),a=!0,i.info("sentry","Sentry \u9519\u8BEF\u4E0A\u62A5\u5DF2\u542F\u7528")}catch(n){i.error("sentry",`Sentry \u521D\u59CB\u5316\u5931\u8D25: ${n instanceof Error?n.message:String(n)}`)}}function T(){return a}const E=new Set(["INSTALL_FAILED","INSTALL_TIMEOUT","FALLBACK_EXHAUSTED","PREREQ_INSTALL_FAILED","VERIFICATION_FAILED","ENVIRONMENT_UNSUPPORTED","INTERNAL"]);function S(e){if(e.ok)return!1;const n=e.error?.code;return!!n&&E.has(n)}function I(e){if(!a||!r||!S(e))return;const n=e.error?.code??"INTERNAL",o=e.error?.message??"unknown install failure",t=e.environment;try{r.withScope(s=>{s.setLevel("error"),s.setTags({agent_type:e.agentType,error_code:n,phase:e.phase}),s.setContext("install",{agentType:e.agentType,code:n,phase:e.phase,durationMs:e.durationMs,os:t?.platform,osVersion:t?.osVersion,arch:t?.arch,nodeVersion:t?.nodeVersion,npmVersion:t?.npmVersion,isDocker:t?.isDocker,isCI:t?.isCI,outputTail:(e.output??"").slice(-2e3)}),s.setFingerprint(["agent-install-failure",e.agentType,n]),r.captureException(new Error(`Agent install failed [${e.agentType}/${n}]: ${o}`))}),i.info("sentry",`\u5DF2\u4E0A\u62A5\u5B89\u88C5\u5931\u8D25: ${e.agentType}/${n}`)}catch(s){i.error("sentry",`\u4E0A\u62A5\u5B89\u88C5\u5931\u8D25\u65F6\u51FA\u9519: ${s instanceof Error?s.message:String(s)}`)}}function N(e,n){if(!(!a||!r))try{r.withScope(o=>{o.setLevel("fatal"),o.setTag("crash_type",n);const t=e instanceof Error?e:new Error(String(e));r.captureException(t)}),i.info("sentry",`\u5DF2\u4E0A\u62A5\u5D29\u6E83: ${n}`)}catch(o){i.error("sentry",`\u4E0A\u62A5\u5D29\u6E83\u65F6\u51FA\u9519: ${o instanceof Error?o.message:String(o)}`)}}async function u(e=2e3){if(!(!a||!r))try{await r.close(e)}catch{}}export{u as closeSentry,g as initSentry,T as isSentryEnabled,N as reportFatal,I as reportInstallFailure,S as shouldReportInstallFailure};
@@ -1 +1 @@
1
- import{readJSONFile as c,writeJSONFileAtomic as l}from"../util/json-file.js";import{log as g}from"../log/index.js";import{SESSION_MODE_IDS as s}from"../../adapter/claude/protocol-contract.js";const a=s.fullAuto;function n(r){const e=String(r??"").trim().toLowerCase();return e===s.approval||e===s.fullAuto?e:a}class x{bindings=new Map;filePath;writePromise=Promise.resolve();constructor(e){this.filePath=e??null}load(){if(!this.filePath)return;const e=c(this.filePath);if(Array.isArray(e)){this.bindings.clear();for(const d of e)d.aibotSessionId&&this.bindings.set(d.aibotSessionId,{...d,modeId:n(d.modeId)})}}set(e,d,t){const i=this.bindings.get(e),o=n(t?.modeId??i?.modeId);this.bindings.set(e,{aibotSessionId:e,cwd:d,acpSessionId:i?.acpSessionId,claudeSessionId:i?.claudeSessionId,codexThreadId:i?.codexThreadId,codexModelId:i?.codexModelId,codexModeId:i?.codexModeId,codewhaleThreadId:i?.codewhaleThreadId,codexReasoningEffort:i?.codexReasoningEffort,codexSandboxMode:i?.codexSandboxMode,piSessionPath:i?.piSessionPath,modeId:o,modelId:i?.modelId,acpModelId:i?.acpModelId,acpModeId:i?.acpModeId,updatedAt:Date.now()}),this.scheduleWrite()}setAcpSessionId(e,d){this.updateBinding(e,{acpSessionId:d})}setClaudeSessionId(e,d){this.updateBinding(e,{claudeSessionId:d})}getClaudeSessionId(e){return this.bindings.get(e)?.claudeSessionId}setCodexThreadId(e,d){this.updateBinding(e,{codexThreadId:d})}getCodexThreadId(e){return this.bindings.get(e)?.codexThreadId}setCodexContext(e,d){this.updateBinding(e,{codexModelId:d.modelId,codexModeId:d.modeId,codexReasoningEffort:d.reasoningEffort,codexSandboxMode:d.sandboxMode})}getCodexModelId(e){return this.bindings.get(e)?.codexModelId}getCodexModeId(e){return this.bindings.get(e)?.codexModeId}getCodexReasoningEffort(e){return this.bindings.get(e)?.codexReasoningEffort}getCodexSandboxMode(e){return this.bindings.get(e)?.codexSandboxMode}setPiSessionPath(e,d){this.updateBinding(e,{piSessionPath:d})}getPiSessionPath(e){return this.bindings.get(e)?.piSessionPath}setCodeWhaleThreadId(e,d){this.updateBinding(e,{codewhaleThreadId:d})}getCodeWhaleThreadId(e){return this.bindings.get(e)?.codewhaleThreadId}setModeId(e,d){const t=this.bindings.get(e);if(!t)return;const i=n(d);t.modeId!==i&&(this.bindings.set(e,{...t,modeId:i,updatedAt:Date.now()}),this.scheduleWrite())}ensureModeId(e,d=a){const t=this.bindings.get(e);t&&(t.modeId||(this.bindings.set(e,{...t,modeId:n(d),updatedAt:Date.now()}),this.scheduleWrite()))}getAcpSessionId(e){return this.bindings.get(e)?.acpSessionId}getModeId(e){const d=this.bindings.get(e)?.modeId;return d?n(d):void 0}setModelId(e,d){const t=this.bindings.get(e);if(!t)return;const i=d.trim();t.modelId!==i&&(this.bindings.set(e,{...t,modelId:i,updatedAt:Date.now()}),this.scheduleWrite())}getModelId(e){return this.bindings.get(e)?.modelId}setAcpModelId(e,d){const t=this.bindings.get(e);if(!t)return;const i=String(d??"").trim(),o=i===""?void 0:i;t.acpModelId!==o&&(this.bindings.set(e,{...t,acpModelId:o,updatedAt:Date.now()}),this.scheduleWrite())}getAcpModelId(e){return this.bindings.get(e)?.acpModelId}setAcpModeId(e,d){const t=this.bindings.get(e);if(!t)return;const i=String(d??"").trim(),o=i===""?void 0:i;t.acpModeId!==o&&(this.bindings.set(e,{...t,acpModeId:o,updatedAt:Date.now()}),this.scheduleWrite())}getAcpModeId(e){return this.bindings.get(e)?.acpModeId}get(e){return this.bindings.get(e)}getMostRecentlyUpdatedSessionId(e){const d=e?.requireCwd!==!1;let t,i=-1;for(const o of this.bindings.values())d&&!String(o.cwd??"").trim()||o.updatedAt>i&&(i=o.updatedAt,t=o.aibotSessionId);return t}delete(e){this.bindings.delete(e),this.scheduleWrite()}entries(){return this.bindings.entries()}async flush(){await this.writePromise}scheduleWrite(){this.filePath&&(this.writePromise=this.writePromise.then(()=>this.write()).catch(e=>{g.error("session-binding-store",`Persist failed: ${e instanceof Error?e.message:e}`)}))}async write(){if(!this.filePath)return;const e=Array.from(this.bindings.values());await l(this.filePath,e)}updateBinding(e,d){const t=this.bindings.get(e),i={aibotSessionId:e,acpSessionId:Object.prototype.hasOwnProperty.call(d,"acpSessionId")?d.acpSessionId:t?.acpSessionId,claudeSessionId:Object.prototype.hasOwnProperty.call(d,"claudeSessionId")?d.claudeSessionId:t?.claudeSessionId,codexThreadId:Object.prototype.hasOwnProperty.call(d,"codexThreadId")?d.codexThreadId:t?.codexThreadId,codexModelId:Object.prototype.hasOwnProperty.call(d,"codexModelId")?d.codexModelId:t?.codexModelId,codexModeId:Object.prototype.hasOwnProperty.call(d,"codexModeId")?d.codexModeId:t?.codexModeId,codewhaleThreadId:Object.prototype.hasOwnProperty.call(d,"codewhaleThreadId")?d.codewhaleThreadId:t?.codewhaleThreadId,codexReasoningEffort:Object.prototype.hasOwnProperty.call(d,"codexReasoningEffort")?d.codexReasoningEffort:t?.codexReasoningEffort,codexSandboxMode:Object.prototype.hasOwnProperty.call(d,"codexSandboxMode")?d.codexSandboxMode:t?.codexSandboxMode,piSessionPath:Object.prototype.hasOwnProperty.call(d,"piSessionPath")?d.piSessionPath:t?.piSessionPath,cwd:t?.cwd,modeId:n(t?.modeId),modelId:t?.modelId,acpModelId:t?.acpModelId,acpModeId:t?.acpModeId,updatedAt:Date.now()};this.bindings.set(e,i),this.scheduleWrite()}}export{x as SessionBindingStore};
1
+ import{readJSONFile as c,writeJSONFileAtomic as g}from"../util/json-file.js";import{log as l}from"../log/index.js";import{SESSION_MODE_IDS as s}from"../../adapter/claude/protocol-contract.js";const a=s.fullAuto;function n(r){const e=String(r??"").trim().toLowerCase();return e===s.approval||e===s.fullAuto?e:a}class p{bindings=new Map;filePath;writePromise=Promise.resolve();constructor(e){this.filePath=e??null}load(){if(!this.filePath)return;const e=c(this.filePath);if(Array.isArray(e)){this.bindings.clear();for(const d of e)d.aibotSessionId&&this.bindings.set(d.aibotSessionId,{...d,modeId:n(d.modeId)})}}set(e,d,t){const i=this.bindings.get(e),o=n(t?.modeId??i?.modeId);this.bindings.set(e,{aibotSessionId:e,cwd:d,acpSessionId:i?.acpSessionId,claudeSessionId:i?.claudeSessionId,codexThreadId:i?.codexThreadId,codexModelId:i?.codexModelId,codexModeId:i?.codexModeId,codewhaleThreadId:i?.codewhaleThreadId,agyConversationId:i?.agyConversationId,codexReasoningEffort:i?.codexReasoningEffort,codexSandboxMode:i?.codexSandboxMode,piSessionPath:i?.piSessionPath,modeId:o,modelId:i?.modelId,acpModelId:i?.acpModelId,acpModeId:i?.acpModeId,updatedAt:Date.now()}),this.scheduleWrite()}setAcpSessionId(e,d){this.updateBinding(e,{acpSessionId:d})}setClaudeSessionId(e,d){this.updateBinding(e,{claudeSessionId:d})}getClaudeSessionId(e){return this.bindings.get(e)?.claudeSessionId}setCodexThreadId(e,d){this.updateBinding(e,{codexThreadId:d})}getCodexThreadId(e){return this.bindings.get(e)?.codexThreadId}setAgyConversationId(e,d){this.updateBinding(e,{agyConversationId:d})}getAgyConversationId(e){return this.bindings.get(e)?.agyConversationId}setCodexContext(e,d){this.updateBinding(e,{codexModelId:d.modelId,codexModeId:d.modeId,codexReasoningEffort:d.reasoningEffort,codexSandboxMode:d.sandboxMode})}getCodexModelId(e){return this.bindings.get(e)?.codexModelId}getCodexModeId(e){return this.bindings.get(e)?.codexModeId}getCodexReasoningEffort(e){return this.bindings.get(e)?.codexReasoningEffort}getCodexSandboxMode(e){return this.bindings.get(e)?.codexSandboxMode}setPiSessionPath(e,d){this.updateBinding(e,{piSessionPath:d})}getPiSessionPath(e){return this.bindings.get(e)?.piSessionPath}setCodeWhaleThreadId(e,d){this.updateBinding(e,{codewhaleThreadId:d})}getCodeWhaleThreadId(e){return this.bindings.get(e)?.codewhaleThreadId}setModeId(e,d){const t=this.bindings.get(e);if(!t)return;const i=n(d);t.modeId!==i&&(this.bindings.set(e,{...t,modeId:i,updatedAt:Date.now()}),this.scheduleWrite())}ensureModeId(e,d=a){const t=this.bindings.get(e);t&&(t.modeId||(this.bindings.set(e,{...t,modeId:n(d),updatedAt:Date.now()}),this.scheduleWrite()))}getAcpSessionId(e){return this.bindings.get(e)?.acpSessionId}getModeId(e){const d=this.bindings.get(e)?.modeId;return d?n(d):void 0}setModelId(e,d){const t=this.bindings.get(e);if(!t)return;const i=d.trim();t.modelId!==i&&(this.bindings.set(e,{...t,modelId:i,updatedAt:Date.now()}),this.scheduleWrite())}getModelId(e){return this.bindings.get(e)?.modelId}setAcpModelId(e,d){const t=this.bindings.get(e);if(!t)return;const i=String(d??"").trim(),o=i===""?void 0:i;t.acpModelId!==o&&(this.bindings.set(e,{...t,acpModelId:o,updatedAt:Date.now()}),this.scheduleWrite())}getAcpModelId(e){return this.bindings.get(e)?.acpModelId}setAcpModeId(e,d){const t=this.bindings.get(e);if(!t)return;const i=String(d??"").trim(),o=i===""?void 0:i;t.acpModeId!==o&&(this.bindings.set(e,{...t,acpModeId:o,updatedAt:Date.now()}),this.scheduleWrite())}getAcpModeId(e){return this.bindings.get(e)?.acpModeId}get(e){return this.bindings.get(e)}getMostRecentlyUpdatedSessionId(e){const d=e?.requireCwd!==!1;let t,i=-1;for(const o of this.bindings.values())d&&!String(o.cwd??"").trim()||o.updatedAt>i&&(i=o.updatedAt,t=o.aibotSessionId);return t}delete(e){this.bindings.delete(e),this.scheduleWrite()}entries(){return this.bindings.entries()}async flush(){await this.writePromise}scheduleWrite(){this.filePath&&(this.writePromise=this.writePromise.then(()=>this.write()).catch(e=>{l.error("session-binding-store",`Persist failed: ${e instanceof Error?e.message:e}`)}))}async write(){if(!this.filePath)return;const e=Array.from(this.bindings.values());await g(this.filePath,e)}updateBinding(e,d){const t=this.bindings.get(e),i={aibotSessionId:e,acpSessionId:Object.prototype.hasOwnProperty.call(d,"acpSessionId")?d.acpSessionId:t?.acpSessionId,claudeSessionId:Object.prototype.hasOwnProperty.call(d,"claudeSessionId")?d.claudeSessionId:t?.claudeSessionId,codexThreadId:Object.prototype.hasOwnProperty.call(d,"codexThreadId")?d.codexThreadId:t?.codexThreadId,codexModelId:Object.prototype.hasOwnProperty.call(d,"codexModelId")?d.codexModelId:t?.codexModelId,codexModeId:Object.prototype.hasOwnProperty.call(d,"codexModeId")?d.codexModeId:t?.codexModeId,codewhaleThreadId:Object.prototype.hasOwnProperty.call(d,"codewhaleThreadId")?d.codewhaleThreadId:t?.codewhaleThreadId,agyConversationId:Object.prototype.hasOwnProperty.call(d,"agyConversationId")?d.agyConversationId:t?.agyConversationId,codexReasoningEffort:Object.prototype.hasOwnProperty.call(d,"codexReasoningEffort")?d.codexReasoningEffort:t?.codexReasoningEffort,codexSandboxMode:Object.prototype.hasOwnProperty.call(d,"codexSandboxMode")?d.codexSandboxMode:t?.codexSandboxMode,piSessionPath:Object.prototype.hasOwnProperty.call(d,"piSessionPath")?d.piSessionPath:t?.piSessionPath,cwd:t?.cwd,modeId:n(t?.modeId),modelId:t?.modelId,acpModelId:t?.acpModelId,acpModeId:t?.acpModeId,updatedAt:Date.now()};this.bindings.set(e,i),this.scheduleWrite()}}export{p as SessionBindingStore};
@@ -1,2 +1,2 @@
1
- import{execFileSync as p}from"node:child_process";import{existsSync as c,readFileSync as m,renameSync as _,statfsSync as S,unlinkSync as E,writeFileSync as N}from"node:fs";import{join as u}from"node:path";import{GRIX_PATHS as f}from"../log/index.js";import{appendRotatingFileSync as I}from"../log/rotation.js";import{resolveClientVersion as w}from"../util/client-version.js";import{npmInstallWithMirror as y}from"../installer/npm-registry.js";class i extends Error{code;constructor(r,n){super(n),this.name="UpgradeError",this.code=r}}function d(){return u(f.log,"upgrade.log")}function a(){return u(f.data,"upgrade-pending.json")}function T(){return c(a())}function k(){const e=a();if(!c(e))return null;try{return JSON.parse(m(e,"utf-8"))}catch{return null}}function $(e,r){const n={from_version:e,target_version:r,upgraded_at:new Date().toISOString(),crash_count:0},s=a(),o=s+".tmp";N(o,JSON.stringify(n),"utf-8"),_(o,s)}function b(){const e=a();if(c(e))try{E(e)}catch{}}function l(e){const r=`[${new Date().toISOString()}] ${e}
2
- `;try{I(d(),r)}catch{}}function O(e=4096){const r=d();if(!c(r))return"";try{const n=m(r,"utf-8");return n.length<=e?n:n.slice(-e)}catch{return""}}function g(){try{const e=process.platform==="win32";return p(e?"cmd.exe":"npm",e?["/c","npm","--version"]:["--version"],{encoding:"utf-8",timeout:1e4}).trim()}catch{throw new i("NPM_NOT_FOUND","npm is not available or timed out")}}function h(){let e;try{const r=process.platform==="win32";e=p(r?"cmd.exe":"npm",r?["/c","npm","prefix","-g"]:["prefix","-g"],{encoding:"utf-8",timeout:1e4}).trim()}catch{return Number.MAX_SAFE_INTEGER}try{if(process.platform==="win32")return Number.MAX_SAFE_INTEGER;const r=S(e);return Math.floor(r.bsize*r.bavail/(1024*1024))}catch{return Number.MAX_SAFE_INTEGER}}function D(){let e;try{e=g()}catch(n){return{ok:!1,errorCode:"NPM_NOT_FOUND",errorMsg:n instanceof Error?n.message:"npm not available"}}const r=h();return r<100?{ok:!1,errorCode:"DISK_FULL",errorMsg:`Only ${r}MB free disk space (need >= 100MB)`,npmVersion:e,diskFreeMb:r}:{ok:!0,npmVersion:e,diskFreeMb:r}}function U(){let e="";try{e=g()}catch{}let r=Number.MAX_SAFE_INTEGER;try{r=h()}catch{}return{npm_version:e,node_version:process.version,disk_free_mb:r,platform:process.platform,arch:process.arch}}async function C(e,r,n=12e4){const s=`${e}@${r}`;l(`npm install -g ${s} starting (with mirror fallback)`);try{const{registry:o}=await y(s,n,10485760);l(`npm install succeeded via ${o}`)}catch(o){const t=o instanceof Error?o.message:String(o);throw l(`npm install failed: ${t}`),/timed out|ETIMEDOUT/i.test(t)?new i("NPM_TIMEOUT",`npm install timed out after ${n/1e3}s (tried all mirrors): ${t}`):t.includes("EACCES")||t.includes("permission denied")?new i("NPM_INSTALL_FAILED",`Permission denied: ${t}`):t.includes("ENOSPC")||t.includes("no space left")?new i("DISK_FULL",`Disk full: ${t}`):t.includes("404")||t.includes("not found")?new i("NPM_INSTALL_FAILED",`Package not found: ${t}`):new i("NPM_INSTALL_FAILED",t)}}function R(e){const r=w();if(r!==e)throw new i("VERSION_MISMATCH",`Installed version ${r} does not match expected ${e}`);return r}export{i as UpgradeError,h as checkDiskSpace,g as checkNpmAvailable,U as collectEnvInfo,O as getUpgradeLogTail,C as npmInstall,T as pendingExists,D as preflightCheck,k as readPending,b as removePending,l as upgradeLog,R as verifyInstalledVersion,$ as writePending};
1
+ import{execFileSync as p}from"node:child_process";import{existsSync as s,readFileSync as m,renameSync as _,unlinkSync as S,writeFileSync as E}from"node:fs";import{join as u}from"node:path";import{GRIX_PATHS as f}from"../log/index.js";import{appendRotatingFileSync as N}from"../log/rotation.js";import{resolveClientVersion as I}from"../util/client-version.js";import{npmInstallWithMirror as w}from"../installer/npm-registry.js";class i extends Error{code;constructor(r,n){super(n),this.name="UpgradeError",this.code=r}}function d(){return u(f.log,"upgrade.log")}function a(){return u(f.data,"upgrade-pending.json")}function P(){return s(a())}function k(){const e=a();if(!s(e))return null;try{return JSON.parse(m(e,"utf-8"))}catch{return null}}function T(e,r){const n={from_version:e,target_version:r,upgraded_at:new Date().toISOString(),crash_count:0},c=a(),o=c+".tmp";E(o,JSON.stringify(n),"utf-8"),_(o,c)}function $(){const e=a();if(s(e))try{S(e)}catch{}}function l(e){const r=`[${new Date().toISOString()}] ${e}
2
+ `;try{N(d(),r)}catch{}}function O(e=4096){const r=d();if(!s(r))return"";try{const n=m(r,"utf-8");return n.length<=e?n:n.slice(-e)}catch{return""}}function g(){try{const e=process.platform==="win32";return p(e?"cmd.exe":"npm",e?["/c","npm","--version"]:["--version"],{encoding:"utf-8",timeout:1e4}).trim()}catch{throw new i("NPM_NOT_FOUND","npm is not available or timed out")}}function h(){let e;try{const r=process.platform==="win32";e=p(r?"cmd.exe":"npm",r?["/c","npm","prefix","-g"]:["prefix","-g"],{encoding:"utf-8",timeout:1e4}).trim()}catch{return Number.MAX_SAFE_INTEGER}return Number.MAX_SAFE_INTEGER}function D(){let e;try{e=g()}catch(n){return{ok:!1,errorCode:"NPM_NOT_FOUND",errorMsg:n instanceof Error?n.message:"npm not available"}}const r=h();return r<100?{ok:!1,errorCode:"DISK_FULL",errorMsg:`Only ${r}MB free disk space (need >= 100MB)`,npmVersion:e,diskFreeMb:r}:{ok:!0,npmVersion:e,diskFreeMb:r}}function b(){let e="";try{e=g()}catch{}let r=Number.MAX_SAFE_INTEGER;try{r=h()}catch{}return{npm_version:e,node_version:process.version,disk_free_mb:r,platform:process.platform,arch:process.arch}}async function U(e,r,n=12e4){const c=`${e}@${r}`;l(`npm install -g ${c} starting (with mirror fallback)`);try{const{registry:o}=await w(c,n,10485760);l(`npm install succeeded via ${o}`)}catch(o){const t=o instanceof Error?o.message:String(o);throw l(`npm install failed: ${t}`),/timed out|ETIMEDOUT/i.test(t)?new i("NPM_TIMEOUT",`npm install timed out after ${n/1e3}s (tried all mirrors): ${t}`):t.includes("EACCES")||t.includes("permission denied")?new i("NPM_INSTALL_FAILED",`Permission denied: ${t}`):t.includes("ENOSPC")||t.includes("no space left")?new i("DISK_FULL",`Disk full: ${t}`):t.includes("404")||t.includes("not found")?new i("NPM_INSTALL_FAILED",`Package not found: ${t}`):new i("NPM_INSTALL_FAILED",t)}}function C(e){const r=I();if(r!==e)throw new i("VERSION_MISMATCH",`Installed version ${r} does not match expected ${e}`);return r}export{i as UpgradeError,h as checkDiskSpace,g as checkNpmAvailable,b as collectEnvInfo,O as getUpgradeLogTail,U as npmInstall,P as pendingExists,D as preflightCheck,k as readPending,$ as removePending,l as upgradeLog,C as verifyInstalledVersion,T as writePending};
@@ -0,0 +1,31 @@
1
+ ---
2
+ name: grix-access-control
3
+ description: Manage sender access control with the typed `grix_access_control` tool — approve/deny a pairing code, allow/remove a sender, or set the access policy. Trigger when the user asks to approve a pairing request, allowlist or block a sender, or change who is allowed to message the agent.
4
+ trigger: 当用户要批准/拒绝配对码、允许或移除某个发送者、或调整谁可以给 Agent 发消息的访问策略时
5
+ ---
6
+
7
+ # Grix Access Control
8
+
9
+ Use the `grix_access_control` tool to manage who may message this agent.
10
+
11
+ ## Tool contract
12
+
13
+ Call `grix_access_control` with one `action`:
14
+
15
+ - `pair_approve` / `pair_deny` — approve or deny a pairing request. Requires the
16
+ `code` from the pairing request.
17
+ - `allow_sender` — add a sender to the allowlist. Requires `sender_id`.
18
+ - `remove_sender` — remove a sender. Requires `sender_id`.
19
+ - `set_policy` — set the access policy. Requires `policy`, one of:
20
+ - `allowlist` — only allowlisted senders may message
21
+ - `open` — anyone may message
22
+ - `disabled` — access control off
23
+
24
+ ## Rules
25
+
26
+ 1. Pick exactly one `action` and supply only the field it needs (`code` for
27
+ pairing, `sender_id` for allow/remove, `policy` for set_policy).
28
+ 2. These actions change who can reach the agent — confirm with the user before
29
+ approving an unknown pairing code or switching the policy to `open`.
30
+ 3. On failure, report the exact reason (e.g. expired/invalid code) instead of
31
+ retrying with a guessed value.
@@ -0,0 +1,35 @@
1
+ ---
2
+ name: grix-admin
3
+ description: Use the typed `grix_admin` tool to manage agents and categories on the Grix platform — create agents, list/create/update categories, assign an agent to a category, and rotate an agent's API key. Trigger when the user asks to create a new agent, organize agents into categories, or rotate an agent key.
4
+ trigger: 当用户要在 Grix 平台创建 Agent、管理分类、给 Agent 分配分类、或轮换 Agent 的 API key 时
5
+ ---
6
+
7
+ # Grix Admin
8
+
9
+ Use the `grix_admin` tool for agent and category management on the Grix
10
+ platform. This skill covers only platform-side management — it does not touch
11
+ any local connector configuration or binding.
12
+
13
+ ## Tool contract
14
+
15
+ Call `grix_admin` with one `action`:
16
+
17
+ - `create_agent` — create an agent. Requires `agentName`; optional
18
+ `introduction`, `isMain`, `categoryId`.
19
+ - `list_categories` — list all categories. No extra fields.
20
+ - `create_category` — requires `name`; optional `parentId`, `sortOrder`.
21
+ - `update_category` — requires `categoryId`; optional `name`, `parentId`,
22
+ `sortOrder`.
23
+ - `assign_category` — assign an agent to a category. Requires `agentId` and
24
+ `categoryId`.
25
+ - `rotate_api_key` — rotate an agent's API key. Requires `agentId`.
26
+
27
+ ## Rules
28
+
29
+ 1. Pick exactly one `action` per call and supply only the fields that action
30
+ needs.
31
+ 2. For `assign_category` / `rotate_api_key` you need the target `agentId`; for
32
+ category edits you need the `categoryId`. Resolve unknown IDs first
33
+ (`list_categories`, or `grix_query` for agents) rather than guessing.
34
+ 3. `rotate_api_key` invalidates the old key. Confirm the target agent with the
35
+ user before rotating, and report the new key handling per platform policy.
@@ -0,0 +1,35 @@
1
+ ---
2
+ name: grix-agent-dispatch
3
+ description: Dispatch one of the owner's other agents to do work in a given directory (`grix_dispatch_agent`), and update an agent's text introduction (`grix_agent_update`). Trigger when the user asks to hand a task to another agent, run work in a specific directory via a sibling agent, or change an agent's introduction.
4
+ trigger: 当用户要把任务派发给 owner 名下的另一个 Agent、让某个 Agent 在指定目录干活、或修改某个 Agent 的简介时
5
+ ---
6
+
7
+ # Grix Agent Dispatch
8
+
9
+ Manage and delegate to the owner's other agents.
10
+
11
+ ## Dispatch a task — `grix_dispatch_agent`
12
+
13
+ Hand work to another of the owner's agents. The backend opens (or reuses) a
14
+ private session between the owner and that agent, binds the working directory
15
+ when the agent type requires it (claude/codex/etc.), and sends the task in as
16
+ the owner so the agent starts working.
17
+
18
+ - `agent_id` (required) — target agent's numeric ID, as a string.
19
+ - `cwd` (required) — absolute working directory for the task.
20
+ - `task` (required) — text description of what to do.
21
+
22
+ ## Update an introduction — `grix_agent_update`
23
+
24
+ Change the text introduction of one of the owner's agents.
25
+
26
+ - `agent_id` (required) — target agent's numeric ID, as a string.
27
+ - `introduction` (required) — new introduction text (max 300 chars).
28
+
29
+ ## Rules
30
+
31
+ 1. You need the exact numeric `agent_id`. Resolve it with `grix_query` first if
32
+ you only have a name; never guess an ID.
33
+ 2. `cwd` must be an absolute path the target agent can access.
34
+ 3. Dispatch runs the task as the owner in a separate session — confirm the
35
+ target agent and directory with the user when the task is consequential.