grix-connector 3.9.1 → 3.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/adapter/acp/acp-adapter.js +11 -11
- package/dist/adapter/cursor/cursor-adapter.js +5 -5
- package/dist/adapter/opencode/opencode-adapter.js +4 -4
- package/dist/adapter/pi/pi-adapter.js +3 -3
- package/dist/bridge/adapter-pool.js +1 -1
- package/dist/bridge/bridge.js +8 -8
- package/dist/bridge/event-queue.js +1 -1
- package/dist/bridge/send-controller.js +3 -3
- package/dist/core/aibot/client.js +2 -2
- package/dist/core/aibot/connection-handle.js +1 -1
- package/dist/core/aibot/errors.js +1 -1
- package/dist/core/config/paths.js +1 -1
- package/dist/core/installer/installer.js +4 -4
- package/dist/core/installer/manual-guide.js +1 -1
- package/dist/core/installer/registry.js +1 -1
- package/dist/core/log/logger.js +6 -6
- package/dist/core/mcp/tools.js +1 -1
- package/dist/core/skill-sync/skill-syncer.js +1 -0
- package/dist/default-skills/grix-agent-dispatch/SKILL.md +11 -5
- package/dist/default-skills/index.js +1 -1
- package/dist/manager.js +2 -2
- package/dist/mcp/stream-http/security.js +1 -1
- package/openclaw-plugin/index.js +65 -9
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
const
|
|
1
|
+
const h=25e3;class d{config;callbacks;running=new Map;queued=[];timers=new Map;composingTimers=new Map;adapterDoneEventIds=new Set;pauseReasons=new Set;get ready(){return this.pauseReasons.size===0}constructor(e,t){this.config=e,this.callbacks=t}pause(e){this.pauseReasons.add(e)}resume(e){this.pauseReasons.delete(e)&&this.ready&&this.drainNext()}submit(e){return this.running.has(e.event_id)||this.queued.some(t=>t.event_id===e.event_id)?"accepted":this.ready&&this.running.size<this.config.maxConcurrent?(this.startRunning(e),"accepted"):this.config.maxQueued<=0||this.queued.length>=this.config.maxQueued?(this.callbacks.onRejected(e,"queue full"),"rejected"):(this.enqueue(e),"accepted")}cancel(e){const t=this.queued.findIndex(i=>i.event_id===e);if(t>=0){if(!this.config.cancelableQueued)return!1;const[i]=this.queued.splice(t,1);return this.clearTimer(e),this.callbacks.onStateChange(e,i.session_id,"canceled",{reason:"canceled by user"}),this.broadcastQueuePositions(),this.drainNext(),this.checkStopComposing(i.session_id),!0}return this.running.has(e)&&this.config.cancelableRunning?(this.callbacks.onCancelRunning(e),!0):!1}removeQueued(e){const t=this.queued.findIndex(n=>n.event_id===e);if(t<0)return!1;const[i]=this.queued.splice(t,1);return this.clearTimer(e),this.broadcastQueuePositions(),this.checkStopComposing(i.session_id),!0}complete(e){const i=this.running.get(e)?.session_id;this.running.delete(e),this.adapterDoneEventIds.delete(e),this.clearTimer(e),queueMicrotask(()=>this.drainNext()),i&&this.checkStopComposing(i)}clear(e,t="queue cleared"){const i=[],n=[];for(const o of this.queued)o.session_id===e?(this.clearTimer(o.event_id),this.callbacks.onStateChange(o.event_id,e,"canceled",{reason:t}),i.push(o.event_id)):n.push(o);return this.queued=n,i.length>0&&this.broadcastQueuePositions(),this.checkStopComposing(e),i}reorder(e,t){const i=[],n=[];if(this.queued.forEach((u,r)=>{u.session_id===e&&(i.push(r),n.push(u))}),n.length===0)return[];const o=new Map(n.map(u=>[u.event_id,u])),s=[];for(const u of t){const r=o.get(u);r&&(o.delete(u),s.push(r))}for(const u of n)o.has(u.event_id)&&s.push(u);return s.some((u,r)=>u!==n[r])&&(i.forEach((u,r)=>{this.queued[u]=s[r]}),this.broadcastQueuePositions()),s.map(u=>u.event_id)}drainQueuedForSession(e){const t=[],i=[];for(const n of this.queued)n.session_id===e?(this.clearTimer(n.event_id),t.push(n)):i.push(n);return this.queued=i,t}snapshot(e){const t=[...this.running.values()].filter(s=>s.session_id===e),i=t.map(s=>s.event_id),n=t.map(s=>({event_id:s.event_id,content_preview:this.buildQueueItemTitle(s.content),title:this.buildQueueItemTitle(s.content),summary:this.buildQueueItemTitle(s.content)})),o=this.queued.flatMap((s,c)=>s.session_id===e?[{event_id:s.event_id,position:c+1,content_preview:this.buildQueueItemTitle(s.content),title:this.buildQueueItemTitle(s.content),summary:this.buildQueueItemTitle(s.content)}]:[]);return{running:i,running_items:n,queued:o}}hasCapacity(){return this.running.size<this.config.maxConcurrent}get runningCount(){return this.running.size}get queuedCount(){return this.queued.length}destroy(){for(const e of this.timers.values())clearTimeout(e);this.timers.clear();for(const e of this.composingTimers.values())clearInterval(e);this.composingTimers.clear(),this.queued=[],this.running.clear(),this.pauseReasons.clear()}enqueue(e){this.queued.push(e);const t=this.queued.length;if(this.callbacks.onStateChange(e.event_id,e.session_id,"queued",{queue_position:t,queue_total:t,actions:this.config.cancelableQueued?[{type:"cancel"}]:[],content_preview:this.buildQueueItemTitle(e.content)}),this.config.queueTimeoutMs>0){const i=setTimeout(()=>{this.timeoutEvent(e.event_id)},this.config.queueTimeoutMs);i.unref(),this.timers.set(e.event_id,i)}this.ensureComposing(e.session_id)}timeoutEvent(e){const t=this.queued.findIndex(n=>n.event_id===e);if(t<0)return;const[i]=this.queued.splice(t,1);this.timers.delete(e),this.callbacks.onStateChange(e,i.session_id,"failed",{reason:"queue timeout"}),this.broadcastQueuePositions(),this.drainNext(),this.checkStopComposing(i.session_id)}startRunning(e){this.running.set(e.event_id,e),this.callbacks.onStateChange(e.event_id,e.session_id,"running",{actions:this.config.cancelableRunning?[{type:"stop"}]:[],content_preview:this.buildQueueItemTitle(e.content)}),this.ensureComposing(e.session_id),this.callbacks.onDeliver(e)}drainNext(){for(;this.ready&&this.running.size<this.config.maxConcurrent&&this.queued.length>0;){const e=this.queued.shift();this.clearTimer(e.event_id),this.startRunning(e)}this.queued.length>0&&this.broadcastQueuePositions()}broadcastQueuePositions(){const e=this.queued.length;for(let t=0;t<e;t++){const i=this.queued[t];this.callbacks.onStateChange(i.event_id,i.session_id,"queued",{queue_position:t+1,queue_total:e,actions:this.config.cancelableQueued?[{type:"cancel"}]:[],content_preview:this.buildQueueItemTitle(i.content)})}}clearTimer(e){const t=this.timers.get(e);t&&(clearTimeout(t),this.timers.delete(e))}buildQueueItemTitle(e){const t=String(e??"").replace(/\s+/g," ").trim();return t?t.length>64?`${t.slice(0,64)}...`:t:"Message"}ensureComposing(e){if(!this.callbacks.onComposing||this.composingTimers.has(e))return;this.callbacks.onComposing(e,!0,this.getFirstRunningEventId(e));const t=setInterval(()=>{this.sessionHasEvents(e)?this.callbacks.onComposing(e,!0,this.getFirstRunningEventId(e)):this.stopComposing(e)},25e3);t.unref(),this.composingTimers.set(e,t)}checkStopComposing(e){this.sessionHasEvents(e)||this.stopComposing(e)}markAdapterDone(e){const t=this.running.get(e);t&&(this.adapterDoneEventIds.add(e),this.sessionHasEvents(t.session_id)||this.stopComposing(t.session_id))}stopComposing(e){const t=this.composingTimers.get(e);t&&(clearInterval(t),this.composingTimers.delete(e)),this.callbacks.onComposing?.(e,!1)}sessionHasEvents(e){for(const t of this.running.values())if(t.session_id===e&&!this.adapterDoneEventIds.has(t.event_id))return!0;return this.queued.some(t=>t.session_id===e)}getFirstRunningEventId(e){for(const t of this.running.values())if(t.session_id===e&&!this.adapterDoneEventIds.has(t.event_id))return t.event_id}}export{d as EventQueue};
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import{log as
|
|
2
|
-
`:"");if(!r&&!n)return;const
|
|
3
|
-
`);const u=`${e}:${o??"default"}`;for(let
|
|
1
|
+
import{isRequestTimeoutError as C}from"../core/aibot/errors.js";import{log as h}from"../core/log/index.js";import{splitTextForAibotProtocol as p,AIBOT_PROTOCOL_MAX_RUNES as E}from"../core/protocol/protocol-text.js";import{isThinkingEventDropped as k,isToolEventDropped as S,normalizeRuntimeConfigSnapshot as T}from"./runtime-config.js";const q={claude:!1,acp:!1,codex:!0,codewhale:!0,pi:!0,openhuman:!1,cursor:!0,opencode:!0,agy:!0};class g{handle;connectorRuntimeConfig;eventRuntimeConfigs=new Map;eventQuotedMessageIds=new Map;defaultQuoteTrigger=!1;thinkingSeqMap=new Map;toolCardSeq=0;bufferedEventTexts=new Map;chunkSeqMap=new Map;rejectedEvents=new Set;stoppedEvents=new Set;streamCoalesce=new Map;streamCoalesceMs=y();static STREAM_COALESCE_MAX_RUNES=2048;recentToolCards=new Map;static TOOL_CARD_DEDUP_MS=100;constructor(e){this.connectorRuntimeConfig={...e}}bind(e){this.handle=e}markEventRejected(e){if(!e)return;this.rejectedEvents.add(e);const t=`${e}:`;for(const s of[...this.streamCoalesce.keys()])if(s.startsWith(t)){const i=this.streamCoalesce.get(s);i.timer&&clearTimeout(i.timer),this.streamCoalesce.delete(s)}h.info("send-ctrl",`event marked as stream-rejected, future chunks will be dropped event=${e}`)}markEventStopped(e){e&&(this.stoppedEvents.add(e),h.info("send-ctrl",`[stop-trace] event marked stopped; subsequent outbound will be flagged event=${e}`))}flagOutboundAfterStop(e,t,s,i,n,o){!e||!this.stoppedEvents.has(e)||h.warn("send-ctrl",`[stop-trace] OUTBOUND AFTER STOP ${s} event=${e} session=${t} seq=${i} is_finish=${n} len=${o}`)}getGlobalRuntimeConfig(){return this.connectorRuntimeConfig}setStreamCoalesceMs(e){this.streamCoalesceMs=Number.isFinite(e)&&e>0?Math.floor(e):0}captureEventRuntimeConfig(e){const t=T(e.connector_runtime_config);if(t||!this.eventRuntimeConfigs.has(e.event_id)){const s=t??this.connectorRuntimeConfig;this.eventRuntimeConfigs.set(e.event_id,{responseDelivery:s.responseDelivery,toolEvents:s.toolEvents,thinkingEvents:s.thinkingEvents})}e.msg_id&&!this.eventQuotedMessageIds.has(e.event_id)&&this.eventQuotedMessageIds.set(e.event_id,e.msg_id)}discardEventState(e){e&&this.clearEventTrackingState(e)}clearEventTrackingState(e){this.eventRuntimeConfigs.delete(e),this.eventQuotedMessageIds.delete(e),this.rejectedEvents.delete(e),this.bufferedEventTexts.delete(e);for(const t of[...this.streamCoalesce.keys()])if(t.startsWith(`${e}:`)){const s=this.streamCoalesce.get(t);s.timer&&clearTimeout(s.timer),this.streamCoalesce.delete(t)}this.stoppedEvents.delete(e)&&h.info("send-ctrl",`[stop-trace] event tracking cleared stop flag event=${e}`);for(const t of this.chunkSeqMap.keys())t.startsWith(`${e}:`)&&this.chunkSeqMap.delete(t);for(const t of this.recentToolCards.keys())t.startsWith(`${e}:`)&&this.recentToolCards.delete(t)}setDefaultQuoteTrigger(e){this.defaultQuoteTrigger=e}getDefaultQuotedMessageId(e){if(!(!this.defaultQuoteTrigger||!e))return this.eventQuotedMessageIds.get(e)}resolveEventRuntimeConfig(e){if(e){const t=this.eventRuntimeConfigs.get(e);if(t)return t}return this.connectorRuntimeConfig}shouldDropToolDisplayEvent(e){return S(this.resolveEventRuntimeConfig(e))}shouldDropThinkingDisplayEvent(e){return k(this.resolveEventRuntimeConfig(e))}shouldDropCodexDisplayEvent(e,t){return this.shouldDropToolDisplayEvent(e)?t==="item/completed":!1}shouldDropAcpRawDisplayEvent(e,t){return!!(this.shouldDropToolDisplayEvent(e)&&(t==="tool_use"||t==="tool_result")||this.shouldDropThinkingDisplayEvent(e)&&t==="thinking")}sendReply(e,t,s,i,n,o){if(!o?.skipToolFilter&&this.shouldDropToolDisplayEvent(e)&&D(s,n)||e&&this.rejectedEvents.has(e))return;if(this.flagOutboundAfterStop(e,t,"reply",0,!1,(s||"").length),this.resolveEventRuntimeConfig(e).responseDelivery==="stream"&&!n){const r=p(s),l=e?`reply_stream_${e}`:`reply_stream_${Date.now()}`;if(r.length===0){this.handle.sendMsg({event_id:e,session_id:t,msg_type:1,content:s||" ",quoted_message_id:i||void 0});return}for(let u=0;u<r.length;u++){const f=u===r.length-1;this.sendStreamChunk(e,t,r[u],u+1,f,l,i)}return}this.handle.sendMsg({event_id:e,session_id:t,msg_type:1,content:s,quoted_message_id:i||void 0,...n?{extra:n}:{}})}sendStreamChunk(e,t,s,i,n,o,a){if(e&&this.rejectedEvents.has(e))return;if(a=a??this.getDefaultQuotedMessageId(e),this.flagOutboundAfterStop(e,t,"stream_chunk",i,n,(s||"").length),this.finalizeThinking(e,t),this.resolveEventRuntimeConfig(e).responseDelivery==="single_message"&&e){this.bufferStreamChunk(e,t,s,n,a);return}if(this.streamCoalesceMs<=0){this.emitStreamChunkParts(e,t,s,i,n,o,a);return}this.coalesceStreamChunk(e,t,s,n,o,a)}async sendFinalStreamChunkReliable(e,t,s,i){if(e&&this.rejectedEvents.has(e))return;if(this.finalizeThinking(e,t),this.resolveEventRuntimeConfig(e).responseDelivery==="single_message"&&e){this.flushCoalesceForEvent(e),this.bufferOnly(e,t,"",!0);return}const o=`${e}:${s??"default"}`;this.flushCoalesce(o,!1),this.streamCoalesce.delete(o);const a=(this.chunkSeqMap.get(o)??0)+1;this.chunkSeqMap.set(o,a);const r=this.getDefaultQuotedMessageId(e),l={event_id:e,session_id:t,delta_content:"",chunk_seq:a,is_finish:!0,...s?{client_msg_id:s}:{},...r?{quoted_message_id:r}:{}};this.flagOutboundAfterStop(e,t,"stream_chunk_final",a,!0,0);try{await this.handle.sendStreamChunkRequest(l,i)}catch(u){C(u)?(h.warn("send-ctrl",`final chunk ACK timed out, resend same seq=${a} event=${e}`),this.handle.sendStreamChunk(l)):h.warn("send-ctrl",`final chunk send failed (no resend) event=${e}: ${u}`)}}coalesceStreamChunk(e,t,s,i,n,o){if(!s&&!i)return;const a=`${e}:${n??"default"}`;let r=this.streamCoalesce.get(a);if(r||(r={eventId:e,sessionId:t,clientMsgId:n,quotedMessageId:o,text:"",flushedOnce:!1},this.streamCoalesce.set(a,r)),s&&(r.text+=s),r.sessionId=t,n!==void 0&&(r.clientMsgId=n),!r.quotedMessageId&&o&&(r.quotedMessageId=o),i){this.flushCoalesce(a,!0);return}if(!r.flushedOnce||r.text.length>=g.STREAM_COALESCE_MAX_RUNES){this.flushCoalesce(a,!1);return}r.timer||(r.timer=setTimeout(()=>this.flushCoalesce(a,!1),this.streamCoalesceMs))}flushCoalesce(e,t){const s=this.streamCoalesce.get(e);if(!s)return;s.timer&&(clearTimeout(s.timer),s.timer=void 0);const i=s.text;s.text="",s.flushedOnce=!0,t&&this.streamCoalesce.delete(e),!(!i&&!t)&&this.emitStreamChunkParts(s.eventId,s.sessionId,i,0,t,s.clientMsgId,s.quotedMessageId)}flushCoalesceForEvent(e){const t=`${e}:`;for(const s of[...this.streamCoalesce.keys()])s.startsWith(t)&&(this.flushCoalesce(s,!1),this.streamCoalesce.delete(s))}emitStreamChunkParts(e,t,s,i,n,o,a){const r=s||(n?`
|
|
2
|
+
`:"");if(!r&&!n)return;const l=p(r,E);l.length===0&&l.push(r||`
|
|
3
|
+
`);const u=`${e}:${o??"default"}`;for(let f=0;f<l.length;f++){const _=f===l.length-1,m=this.chunkSeqMap.get(u)??0,d=f===0&&i>m?i:m+1;this.chunkSeqMap.set(u,d),this.handle.sendStreamChunk({event_id:e,session_id:t,delta_content:l[f],chunk_seq:d,is_finish:_&&n,...o?{client_msg_id:o}:{},...a?{quoted_message_id:a}:{}})}}sendEventResult(e,t,s,i){this.finalizeThinking(e,""),this.flushCoalesceForEvent(e),this.flushBufferedStreamText(e),this.handle.sendEventResult({event_id:e,status:t,...s?{msg:s}:{},...i?{code:i}:{},updated_at:Date.now()}),this.clearEventTrackingState(e)}sendThinking(e,t,s){if(this.shouldDropThinkingDisplayEvent(e)||e&&this.rejectedEvents.has(e))return;this.flagOutboundAfterStop(e,t,"thinking",this.thinkingSeqMap.get(e)??0,!1,(s||"").length);const i=(this.thinkingSeqMap.get(e)??0)+1;this.thinkingSeqMap.set(e,i),this.handle.sendStreamChunk({event_id:e,session_id:t,delta_content:s,chunk_seq:i,is_finish:!1,client_msg_id:`${e}_thinking`,is_thinking:!0})}sendToolExecutionCard(e,t,s,i){if(this.shouldDropToolDisplayEvent(e)){h.info("send-ctrl",`[tool-card] DROP event=${e} session=${t} summary=${s.summaryText}`);return}const o=Date.now(),a=`${e}:${s.summaryText}:${s.detailText??""}`,r=this.recentToolCards.get(a);if(r!==void 0&&o-r<g.TOOL_CARD_DEDUP_MS){h.info("send-ctrl",`[tool-card] DEDUP event=${e} session=${t} summary=${s.summaryText}`);return}this.recentToolCards.set(a,o);const l={summary_text:s.summaryText};s.detailText&&(l.detail_text=s.detailText),h.info("send-ctrl",`[tool-card] SEND event=${e} session=${t} summary=${s.summaryText}`),this.handle.sendMsg({event_id:e,session_id:t,client_msg_id:i??`tool_${++this.toolCardSeq}_${Date.now()}`,msg_type:1,content:"",extra:{channel_data:{grix:{toolExecution:l}},agent_api_origin:!0}})}finalizeThinking(e,t){const s=this.thinkingSeqMap.get(e);s!==void 0&&(this.thinkingSeqMap.delete(e),t&&(e&&this.rejectedEvents.has(e)||this.handle.sendStreamChunk({event_id:e,session_id:t,delta_content:"",chunk_seq:s+1,is_finish:!0,client_msg_id:`${e}_thinking`,is_thinking:!0})))}bufferOnly(e,t,s,i,n){n=n??this.getDefaultQuotedMessageId(e),this.bufferStreamChunk(e,t,s,i,n)}bufferStreamChunk(e,t,s,i,n){const o=this.bufferedEventTexts.get(e);o?(s&&(o.text+=s),!o.quotedMessageId&&n&&(o.quotedMessageId=n)):this.bufferedEventTexts.set(e,{sessionId:t,text:s??"",quotedMessageId:n||void 0}),i&&this.flushBufferedStreamText(e)}flushBufferedStreamText(e){const t=this.bufferedEventTexts.get(e);t&&(this.bufferedEventTexts.delete(e),t.text&&this.handle.sendMsg({event_id:e,session_id:t.sessionId,msg_type:1,content:t.text,...t.quotedMessageId?{quoted_message_id:t.quotedMessageId}:{}}))}}function y(){const c=process.env.GRIX_STREAM_COALESCE_MS;if(c===void 0||c==="")return 500;const e=Number(c);return Number.isFinite(e)&&e>=0?Math.floor(e):500}function D(c,e){return e&&typeof e=="object"&&e.channel_data?.grix?.toolExecution?!0:/^\[tool\]/.test(c)||/^\[tool result\]/.test(c)}export{q as DEFAULT_QUOTE_TRIGGER_BY_ADAPTER,g as SendController};
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{EventEmitter as v}from"node:events";import{randomUUID as b}from"node:crypto";import
|
|
2
|
-
`}),this.sendPacket("client_stream_chunk",e)}sendMsg(e){this.sendPacket("send_msg",e)||a.warn("aibot",`send_msg NOT sent (ws not open) event=${e.event_id??""} session=${e.session_id??""} ws=${this.ws?`state=${this.ws.readyState}`:"null"}`)}editMsg(e){this.sendPacket("edit_msg",e)}sendEventResult(e){if(!this.ws||this.ws.readyState!==m.OPEN){this.sendPacket("event_result",e);return}this.sendEventResultReliable(e)}sendLocalActionResult(e){this.sendPacket("local_action_result",e)}sendEventStopAck(e){this.sendPacket("event_stop_ack",e)}sendEventStopResult(e){this.sendPacket("event_stop_result",e)}sendSessionActivitySet(e){this.sendPacket("session_activity_set",e)}sendCodexEvent(e){this.sendPacket("codex_event",e)}sendUpdateBindingCard(e){this.sendPacket("update_binding_card",e)}sendSkillsUpdate(e){this.sendPacket("agent_skills_update",e)}sendPing(){this.sendPacket("ping",{})}sendEventState(e){this.sendPacket("event_state",e)}sendEventCancelResult(e){this.sendPacket("event_cancel_result",e)}sendQueueClearResult(e){this.sendPacket("queue_clear_result",e)}sendQueueSnapshot(e){this.sendPacket("queue_snapshot",e)}agentInvoke(e,t,s=15e3){return new Promise((i,n)=>{const h=b(),c=Math.max(1e3,Math.min(s,6e4)),o=setTimeout(()=>{this.pendingInvokes.delete(h),n(new Error(`agent_invoke timeout: ${e}`))},c);this.pendingInvokes.set(h,{resolve:i,reject:n,timer:o}),this.sendPacket("agent_invoke",{invoke_id:h,action:e,params:t,timeout_ms:c})})}sendMcpFrame(e,t){this.sendPacket("mcp_frame",{session_id:e,frame:t})}request(e,t,s){return new Promise((i,n)=>{const h=++this.seq,c=setTimeout(()=>{this.pendingRequests.delete(h),n(new Error(`request timeout: ${e} (expected ${s.expected.join("/")})`))},s.timeoutMs);this.pendingRequests.set(h,{expected:s.expected,resolve:i,reject:n,timer:c}),this.sendPacket(e,t,h)||(this.pendingRequests.delete(h),clearTimeout(c),n(new Error(`send failed: ${e}`)))})}async sendStreamChunkRequest(e,t=2e4){return this.request("client_stream_chunk",e,{expected:["send_ack","send_nack","error"],timeoutMs:t})}async sendText(e,t=2e4){return this.request("send_msg",{msg_type:1,...e},{expected:["send_ack","send_nack","error"],timeoutMs:t})}async sendMedia(e,t=2e4){return this.request("send_msg",{...e,msg_type:2},{expected:["send_ack","send_nack","error"],timeoutMs:t})}async editMessage(e,t=2e4){return this.request("edit_msg",e,{expected:["send_ack","send_nack","error"],timeoutMs:t})}async deleteMessage(e,t,s=2e4){return this.request("delete_msg",{session_id:e,msg_id:t},{expected:["send_ack","send_nack","error"],timeoutMs:s})}async sendEventResultRequest(e,t=5e3){return this.request("event_result",e,{expected:["send_ack","send_nack","error"],timeoutMs:t})}disconnect(){a.info("aibot",`disconnect() agent=${this.config.clientType}:${this.config.agentId} wasConnected=${this.connected} reconnecting=${this.reconnecting} reconnectAttempts=${this.reconnectAttempts}`),this.connected=!1,this.everConnected=!1,this.reconnecting=!1,this.reconnectAttempts=0,this.stopHeartbeat(),this.rejectAllPendingInvokes("disconnect"),this.rejectAllPendingRequests("disconnect"),this.outboundBuffer.length=0,this.ws&&(this.ws.close(1e3,"client disconnect"),this.ws=null)}static MAX_CONSECUTIVE_AUTH_FAILURES=5;async attemptReconnect(){if(this.reconnecting||this.agentDeleted)return;this.reconnecting=!0,a.info("aibot",`attemptReconnect start agent=${this.config.clientType}:${this.config.agentId} fromAttempts=${this.reconnectAttempts}`),this.emit("disconnected");let e=0;for(;this.reconnecting;){const t=Math.min(1e3*2**this.reconnectAttempts,3e4),s=Math.floor(t*.2*Math.random());if(this.reconnectAttempts++,await new Promise(i=>setTimeout(i,t+s)),!this.reconnecting)return;try{await this.connect();const i=this.reconnectAttempts;this.reconnectAttempts=0,this.reconnecting=!1,a.info("aibot",`reconnect succeeded agent=${this.config.clientType}:${this.config.agentId} attempt=${i}`);return}catch(i){if(this.ws){try{this.ws.close()}catch{}this.ws=null}const n=i instanceof Error?i.message:String(i);if(a.warn("aibot",`reconnect failed agent=${this.config.clientType}:${this.config.agentId} attempt=${this.reconnectAttempts} err=${n}`),i instanceof $){this.agentDeleted=!0,this.reconnecting=!1,a.error("aibot",`reconnect aborted: agent deleted on platform agent=${this.config.clientType}:${this.config.agentId}`),this.emit("agentDeleted",{source:"auth_ack",code:k});return}if(/Auth failed/i.test(n)){if(e++,e>=f.MAX_CONSECUTIVE_AUTH_FAILURES){this.reconnecting=!1,a.error("aibot",`reconnect giving up after ${e} consecutive auth failures agent=${this.config.clientType}:${this.config.agentId}`);return}}else e=0}}}sendPacket(e,t,s){if(this.ws&&this.ws.readyState===m.OPEN){const i=this.ws.bufferedAmount>f.BACKPRESSURE_THRESHOLD;if(!i||!f.DROPPABLE_COMMANDS.has(e)){if(i&&f.DROPPABLE_COMMANDS.has(e))return!1;const n=s??++this.seq;if(e==="client_stream_chunk"&&t&&typeof t=="object"){const c=t.event_id;if(c&&(this.seqEventMap.set(n,c),this.seqEventMap.size>200)){const o=this.seqEventMap.keys().next().value;o!==void 0&&this.seqEventMap.delete(o)}}const h={cmd:e,seq:n,payload:t};this.packetLog?.logOutboundPacket(e,n,t,"sent");try{const c=this.ws.readyState,o=this.ws.bufferedAmount;return this.ws.send(JSON.stringify(h),d=>{if(e==="event_result"){const r=t;d?a.warn("aibot",`event_result ws send callback failed event=${r.event_id??""} status=${r.status??""} seq=${n} readyState=${c} bufferedAmount=${o} err=${d.message}`):a.info("aibot",`event_result ws send callback ok event=${r.event_id??""} status=${r.status??""} seq=${n} readyState=${c} bufferedAmount=${o}`)}else if(e==="client_stream_chunk"){const r=t;d?a.warn("aibot",`stream_chunk ws send failed event=${r.event_id??""} session=${r.session_id??""} seq=${n} chunk_seq=${r.chunk_seq??""} is_finish=${r.is_finish??""} readyState=${c} bufferedAmount=${o} err=${d.message}`):a.info("aibot",`stream_chunk ws send ok event=${r.event_id??""} session=${r.session_id??""} seq=${n} chunk_seq=${r.chunk_seq??""} is_finish=${r.is_finish??""} client_msg_id=${r.client_msg_id??""} quoted_message_id=${r.quoted_message_id??""} readyState=${c} bufferedAmount=${o}`)}else if(e==="event_ack"){const r=t;d?a.warn("aibot",`event_ack ws send failed event=${r.event_id??""} seq=${n} readyState=${c} bufferedAmount=${o} err=${d.message}`):a.info("aibot",`event_ack ws send ok event=${r.event_id??""} seq=${n} readyState=${c} bufferedAmount=${o}`)}else if(e==="send_msg"){const r=t;d?a.warn("aibot",`send_msg ws send failed event=${r.event_id??""} session=${r.session_id??""} seq=${n} readyState=${c} bufferedAmount=${o} err=${d.message}`):a.info("aibot",`send_msg ws send ok event=${r.event_id??""} session=${r.session_id??""} seq=${n} readyState=${c} bufferedAmount=${o}`)}else if(d){const r=t;a.warn("aibot",`${e} ws send failed seq=${n} session=${r.session_id??""} event=${r.event_id??""} client_msg_id=${r.client_msg_id??""} readyState=${c} bufferedAmount=${o} err=${d.message}`)}}),!0}catch(c){return this.emitClientError(new Error(`sendPacket failed: ${c}`)),!1}}}if(f.DROPPABLE_COMMANDS.has(e))return this.packetLog?.logOutboundPacket(e,s??0,t,"dropped"),!1;if(s!==void 0)return this.packetLog?.logOutboundPacket(e,s,t,"dropped"),!1;if(this.outboundBuffer.length>=f.MAX_OUTBOUND_BUFFER_SIZE&&(this.outboundBuffer=this.outboundBuffer.filter(i=>f.BUFFER_OVERFLOW_RETAIN_COMMANDS.has(i.cmd)),this.outboundBuffer.length>=f.MAX_OUTBOUND_BUFFER_SIZE&&this.outboundBuffer.shift()),this.outboundBuffer.push({cmd:e,payload:t}),this.packetLog?.logOutboundPacket(e,s??0,t,"buffered"),e==="client_stream_chunk"){const i=t;a.info("aibot",`stream_chunk buffered (ws not open) event=${i.event_id??""} session=${i.session_id??""} chunk_seq=${i.chunk_seq??""} is_finish=${i.is_finish??""} ws=${this.ws?`state=${this.ws.readyState}`:"null"}`)}return!1}async sendEventResultReliable(e){const t=this.ackPolicy?.max_retries??3,s=this.ackPolicy?.push_ack_timeout_ms??5e3,i=750;for(let n=1;n<=t;n++){const h=this.ws?.readyState??-1,c=this.ws?.bufferedAmount??0;a.info("aibot",`event_result send attempt event=${e.event_id} status=${e.status} attempt=${n}/${t} readyState=${h} bufferedAmount=${c}`);try{const o=await this.sendEventResultRequest(e,s);if(o.cmd==="send_ack"){const r=o.payload;a.info("aibot",`event_result ack event=${e.event_id} status=${e.status} attempt=${n}/${t} ack_event=${r.event_id??""} ack_status=${r.status??""}`);return}const d=o.payload;if(a.warn("aibot",`event_result rejected event=${e.event_id} status=${e.status} attempt=${n}/${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){a.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(a.warn("aibot",`event_result attempt failed event=${e.event_id} status=${e.status} attempt=${n}/${t} err=${d}`),n===t){this.emitClientError(new Error(`event_result ack failed after ${t} attempts: event=${e.event_id} status=${e.status}`));return}await new Promise(r=>setTimeout(r,i*n))}}}purgeBufferedStreamChunks(e){const t=this.outboundBuffer.length;this.outboundBuffer=this.outboundBuffer.filter(s=>s.cmd!=="client_stream_chunk"?!0:s.payload?.event_id!==e),this.outboundBuffer.length<t&&a.info("aibot",`purged ${t-this.outboundBuffer.length} buffered stream chunks for event=${e}`)}emitClientError(e){if(this.listenerCount("error")===0){a.warn("aibot",`Client error (no listeners): ${e.message}`);return}this.emit("error",e)}flushOutboundBuffer(){if(this.outboundBuffer.length===0||!this.ws||this.ws.readyState!==m.OPEN)return;const e=this.outboundBuffer;this.outboundBuffer=[];for(const{cmd:t,payload:s}of e){const i=++this.seq;if(t==="client_stream_chunk"&&s&&typeof s=="object"){const h=s.event_id;h&&this.seqEventMap.set(i,h)}const n={cmd:t,seq:i,payload:s};try{this.ws.send(JSON.stringify(n))}catch{break}}if(this.seqEventMap.size>200){const t=[...this.seqEventMap.entries()].sort((s,i)=>s[0]-i[0]);this.seqEventMap.clear();for(const[s,i]of t.slice(-100))this.seqEventMap.set(s,i)}}handleInvokeResult(e){const t=this.pendingInvokes.get(e.invoke_id);t&&(this.pendingInvokes.delete(e.invoke_id),clearTimeout(t.timer),e.code===0?t.resolve(e.data??null):t.reject(new Error(`agent_invoke error code=${e.code}: ${e.msg??""}`)))}rejectAllPendingInvokes(e){for(const[,t]of this.pendingInvokes)clearTimeout(t.timer),t.reject(new Error(`agent_invoke canceled: ${e}`));this.pendingInvokes.clear()}rejectAllPendingRequests(e){for(const[,t]of this.pendingRequests)clearTimeout(t.timer),t.reject(new Error(`request canceled: ${e}`));this.pendingRequests.clear()}cleanupSocket(){if(this.ws)try{this.ws.close()}catch{}}startHeartbeat(){this.stopHeartbeat(),this.heartbeatFailures=0,this.heartbeatTimer=setInterval(()=>{this.connected&&this.request("ping",{ts:Date.now()},{expected:["pong"],timeoutMs:5e3}).then(()=>{this.heartbeatFailures=0}).catch(()=>{this.connected&&(this.heartbeatFailures++,!(this.heartbeatFailures<f.HEARTBEAT_MAX_FAILURES)&&(this.cleanupSocket(),this.attemptReconnect()))})},this.heartbeatSec*1e3)}stopHeartbeat(){this.heartbeatTimer&&(clearInterval(this.heartbeatTimer),this.heartbeatTimer=null)}}export{f as AibotClient};
|
|
1
|
+
import{EventEmitter as v}from"node:events";import{randomUUID as b}from"node:crypto";import k from"node:os";import m from"ws";import{log as a}from"../log/index.js";import{getMachineName as w}from"../util/index.js";import{detectTailnetIPv4 as E,ensureServerAndGetPort as q,getFileServerHttpsPort as A}from"../files/file-serve.js";import{AUTH_CODE_AGENT_DELETED as p,KICKED_REASON_AGENT_DELETED as S,AgentDeletedError as $,RequestTimeoutError as y}from"./errors.js";function R(g){return g.replace(/(?<=[\[:,\[]\s*)(\d{16,})(?=\s*[,}\]\n])/g,'"$1"')}function P(g){const e=[...g??["stream_chunk","local_action_v1","agent_invoke"]];return e.includes("agent_invoke")||e.push("agent_invoke"),e.includes("event_result_ack")||e.push("event_result_ack"),e}const T="aibot-agent-api-v1",I=1;class f extends v{static DROPPABLE_COMMANDS=new Set(["update_binding_card"]);static BUFFER_OVERFLOW_RETAIN_COMMANDS=new Set(["event_result","codex_event","client_stream_chunk"]);static MAX_OUTBOUND_BUFFER_SIZE=1e3;static BACKPRESSURE_THRESHOLD=64*1024;ws=null;seq=0;heartbeatTimer=null;heartbeatSec=30;heartbeatFailures=0;static HEARTBEAT_MAX_FAILURES=2;connected=!1;reconnecting=!1;reconnectAttempts=0;everConnected=!1;agentDeleted=!1;config;packetLog;pendingInvokes=new Map;seqEventMap=new Map;pendingRequests=new Map;outboundBuffer=[];ackPolicy=null;constructor(e,t){super(),this.packetLog=t?.packetLog??null,this.config={url:e.url,agentId:e.agentId,apiKey:e.apiKey,clientType:e.clientType,clientVersion:e.clientVersion??"",adapterHint:e.adapterHint??"",capabilities:P(e.capabilities),localActions:e.localActions??["exec_approve","exec_reject"],skills:e.skills}}get isConnected(){return this.connected}async connect(){let e,t,s;const i=(async()=>{try{if(e=await E(),e!==void 0)try{t=await q(e);const n=A();n>0&&(s=n)}catch(n){a.warn("aibot",`file server pre-start failed: ${n}`)}}catch(n){a.warn("aibot",`tailnet detect failed: ${n}`)}})();return new Promise((n,h)=>{const c=new m(this.config.url);this.ws=c;const o=setTimeout(()=>{h(new Error("Auth timeout: no auth_ack received within 15s")),this.cleanupSocket()},15e3),d=++this.seq,r=setTimeout(()=>{this.pendingRequests.delete(d),h(new Error("Auth request timeout")),this.cleanupSocket()},15e3);this.pendingRequests.set(d,{expected:["auth_ack"],resolve:u=>{clearTimeout(o);const l=u.payload;l.code===0?(this.connected=!0,this.everConnected=!0,this.reconnectAttempts=0,l.heartbeat_sec&&(this.heartbeatSec=l.heartbeat_sec),l.ack_policy&&(this.ackPolicy=l.ack_policy,a.info("aibot",`ack_policy received: push_ack_timeout_ms=${l.ack_policy.push_ack_timeout_ms??"default"} max_retries=${l.ack_policy.max_retries??"default"} timeout_action=${l.ack_policy.timeout_action??"default"}`)),this.startHeartbeat(),this.flushOutboundBuffer(),this.emit("auth",l),n(l)):l.code===p?(this.agentDeleted=!0,h(new $(`Agent deleted: code=${l.code} msg=${l.msg}`))):h(new Error(`Auth failed: code=${l.code} msg=${l.msg}`))},reject:u=>{clearTimeout(o),h(u)},timer:r}),c.on("open",async()=>{await i;const u={agent_id:this.config.agentId,api_key:this.config.apiKey,client_type:this.config.clientType,protocol_version:T,contract_version:I,capabilities:this.config.capabilities??[],local_actions:this.config.localActions,skills:this.config.skills};this.config.sharedOwnerId&&(u.shared_owner_id=this.config.sharedOwnerId),this.config.clientVersion&&(u.client="grix-connector",u.client_version=this.config.clientVersion,u.host_type=this.config.clientType,u.host_version=this.config.clientVersion),this.config.adapterHint&&(u.adapter_hint=this.config.adapterHint),u.host_meta={hostname:w(),platform:k.platform(),arch:k.arch(),os_release:k.release(),...e!==void 0&&{tailnet_ip:e},...t!==void 0&&t>0&&{file_server_port:t},...s!==void 0&&s>0&&{file_server_https_port:s}},this.config.concurrency&&(u.concurrency=this.config.concurrency),this.sendPacket("auth",u,d)}),c.on("message",u=>{if(this.ws!==c)return;let l;try{l=JSON.parse(R(u.toString()))}catch{return}try{this.handlePacket(l)}catch(_){this.emitClientError(new Error(`handlePacket error: ${_}`))}}),c.on("close",(u,l)=>{if(this.ws!==c)return;this.connected=!1,this.stopHeartbeat(),this.rejectAllPendingRequests("websocket closed"),this.emit("close",u,l.toString());const _=u!==1e3&&this.everConnected&&!this.agentDeleted;a.info("aibot",`ws closed agent=${this.config.clientType}:${this.config.agentId} code=${u} reason=${l.toString()||"<none>"} everConnected=${this.everConnected} reconnecting=${this.reconnecting} agentDeleted=${this.agentDeleted} willReconnect=${_}`),_&&this.attemptReconnect()}),c.on("error",u=>{this.ws===c&&(this.emitClientError(u instanceof Error?u:new Error(String(u))),this.connected||h(u))})})}handlePacket(e){if(this.packetLog?.logInboundPacket(e.cmd,e.seq,e.payload),e.seq>0&&this.pendingRequests.has(e.seq)){const t=this.pendingRequests.get(e.seq);this.pendingRequests.delete(e.seq),clearTimeout(t.timer),t.expected.includes(e.cmd)?t.resolve(e):t.reject(new Error(`unexpected response: got ${e.cmd}, expected ${t.expected.join("/")}`));return}switch(e.cmd){case"auth_ack":break;case"ping":{this.sendPacket("pong",e.payload??{});break}case"event_msg":{this.emit("event",e.payload);break}case"local_action":{this.emit("localAction",e.payload);break}case"event_stop":{this.emit("stop",e.payload);break}case"event_revoke":{this.emit("revoke",e.payload);break}case"event_edit":{this.emit("edit",e.payload);break}case"event_cancel":{this.emit("eventCancel",e.payload);break}case"queue_clear":{this.emit("queueClear",e.payload);break}case"queue_reorder":{this.emit("queueReorder",e.payload);break}case"queue_snapshot_query":{this.emit("queueSnapshotQuery",e.payload);break}case"control_share_set":{this.emit("shareSet",e.payload);break}case"agent_profile_push":{this.emit("profilePush",e.payload);break}case"skill_sync":{this.emit("skillSync",e.payload);break}case"kicked":{const t=e.payload;if(this.emit("kicked",t),this.connected=!1,this.stopHeartbeat(),this.rejectAllPendingRequests("kicked"),this.outboundBuffer.length=0,t?.reason===S){if(this.agentDeleted=!0,this.reconnecting=!1,this.ws){try{this.ws.close(4001,"kicked")}catch{}this.ws=null}a.error("aibot",`kicked: agent deleted on platform agent=${this.config.clientType}:${this.config.agentId}, reconnect disabled`),this.emit("agentDeleted",{source:"kicked",reason:t.reason});break}if(this.reconnectAttempts=Math.max(this.reconnectAttempts,3),this.ws){try{this.ws.close(4001,"kicked")}catch{}this.ws=null}break}case"error":{const t=e.payload,s=[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}${s?` ${s}`:""}`));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 s=this.seqEventMap.get(e.seq);s&&(this.seqEventMap.delete(e.seq),this.purgeBufferedStreamChunks(s),a.warn("aibot",`stream chunk rejected (4003), purging buffered chunks for event=${s}`),this.emit("streamRejected",s,t.code))}break}case"local_action_ack":break;default:break}}sendEventAck(e){this.sendPacket("event_ack",e)||a.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&&(a.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)||a.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)}sendQueueReorderResult(e){this.sendPacket("queue_reorder_result",e)}sendQueueSnapshot(e){this.sendPacket("queue_snapshot",e)}agentInvoke(e,t,s=15e3){return new Promise((i,n)=>{const h=b(),c=Math.max(1e3,Math.min(s,6e4)),o=setTimeout(()=>{this.pendingInvokes.delete(h),n(new Error(`agent_invoke timeout: ${e}`))},c);this.pendingInvokes.set(h,{resolve:i,reject:n,timer:o}),this.sendPacket("agent_invoke",{invoke_id:h,action:e,params:t,timeout_ms:c})})}sendMcpFrame(e,t){this.sendPacket("mcp_frame",{session_id:e,frame:t})}request(e,t,s){return new Promise((i,n)=>{const h=++this.seq,c=setTimeout(()=>{this.pendingRequests.delete(h),n(new y(`request timeout: ${e} (expected ${s.expected.join("/")})`))},s.timeoutMs);this.pendingRequests.set(h,{expected:s.expected,resolve:i,reject:n,timer:c}),this.sendPacket(e,t,h)||(this.pendingRequests.delete(h),clearTimeout(c),n(new Error(`send failed: ${e}`)))})}async sendStreamChunkRequest(e,t=2e4){return this.request("client_stream_chunk",e,{expected:["send_ack","send_nack","error"],timeoutMs:t})}async sendText(e,t=2e4){return this.request("send_msg",{msg_type:1,...e},{expected:["send_ack","send_nack","error"],timeoutMs:t})}async sendMedia(e,t=2e4){return this.request("send_msg",{...e,msg_type:2},{expected:["send_ack","send_nack","error"],timeoutMs:t})}async editMessage(e,t=2e4){return this.request("edit_msg",e,{expected:["send_ack","send_nack","error"],timeoutMs:t})}async deleteMessage(e,t,s=2e4){return this.request("delete_msg",{session_id:e,msg_id:t},{expected:["send_ack","send_nack","error"],timeoutMs:s})}async sendEventResultRequest(e,t=5e3){return this.request("event_result",e,{expected:["send_ack","send_nack","error"],timeoutMs:t})}disconnect(){a.info("aibot",`disconnect() agent=${this.config.clientType}:${this.config.agentId} wasConnected=${this.connected} reconnecting=${this.reconnecting} reconnectAttempts=${this.reconnectAttempts}`),this.connected=!1,this.everConnected=!1,this.reconnecting=!1,this.reconnectAttempts=0,this.stopHeartbeat(),this.rejectAllPendingInvokes("disconnect"),this.rejectAllPendingRequests("disconnect"),this.outboundBuffer.length=0,this.ws&&(this.ws.close(1e3,"client disconnect"),this.ws=null)}static MAX_CONSECUTIVE_AUTH_FAILURES=5;async attemptReconnect(){if(this.reconnecting||this.agentDeleted)return;this.reconnecting=!0,a.info("aibot",`attemptReconnect start agent=${this.config.clientType}:${this.config.agentId} fromAttempts=${this.reconnectAttempts}`),this.emit("disconnected");let e=0;for(;this.reconnecting;){const t=Math.min(1e3*2**this.reconnectAttempts,3e4),s=Math.floor(t*.2*Math.random());if(this.reconnectAttempts++,await new Promise(i=>setTimeout(i,t+s)),!this.reconnecting)return;try{await this.connect();const i=this.reconnectAttempts;this.reconnectAttempts=0,this.reconnecting=!1,a.info("aibot",`reconnect succeeded agent=${this.config.clientType}:${this.config.agentId} attempt=${i}`);return}catch(i){if(this.ws){try{this.ws.close()}catch{}this.ws=null}const n=i instanceof Error?i.message:String(i);if(a.warn("aibot",`reconnect failed agent=${this.config.clientType}:${this.config.agentId} attempt=${this.reconnectAttempts} err=${n}`),i instanceof $){this.agentDeleted=!0,this.reconnecting=!1,a.error("aibot",`reconnect aborted: agent deleted on platform agent=${this.config.clientType}:${this.config.agentId}`),this.emit("agentDeleted",{source:"auth_ack",code:p});return}if(/Auth failed/i.test(n)){if(e++,e>=f.MAX_CONSECUTIVE_AUTH_FAILURES){this.reconnecting=!1,a.error("aibot",`reconnect giving up after ${e} consecutive auth failures agent=${this.config.clientType}:${this.config.agentId}`);return}}else e=0}}}sendPacket(e,t,s){if(this.ws&&this.ws.readyState===m.OPEN){const i=this.ws.bufferedAmount>f.BACKPRESSURE_THRESHOLD;if(!i||!f.DROPPABLE_COMMANDS.has(e)){if(i&&f.DROPPABLE_COMMANDS.has(e))return!1;const n=s??++this.seq;if(e==="client_stream_chunk"&&t&&typeof t=="object"){const c=t.event_id;if(c&&(this.seqEventMap.set(n,c),this.seqEventMap.size>200)){const o=this.seqEventMap.keys().next().value;o!==void 0&&this.seqEventMap.delete(o)}}const h={cmd:e,seq:n,payload:t};this.packetLog?.logOutboundPacket(e,n,t,"sent");try{const c=this.ws.readyState,o=this.ws.bufferedAmount;return this.ws.send(JSON.stringify(h),d=>{if(e==="event_result"){const r=t;d?a.warn("aibot",`event_result ws send callback failed event=${r.event_id??""} status=${r.status??""} seq=${n} readyState=${c} bufferedAmount=${o} err=${d.message}`):a.info("aibot",`event_result ws send callback ok event=${r.event_id??""} status=${r.status??""} seq=${n} readyState=${c} bufferedAmount=${o}`)}else if(e==="client_stream_chunk"){const r=t;d?a.warn("aibot",`stream_chunk ws send failed event=${r.event_id??""} session=${r.session_id??""} seq=${n} chunk_seq=${r.chunk_seq??""} is_finish=${r.is_finish??""} readyState=${c} bufferedAmount=${o} err=${d.message}`):a.info("aibot",`stream_chunk ws send ok event=${r.event_id??""} session=${r.session_id??""} seq=${n} chunk_seq=${r.chunk_seq??""} is_finish=${r.is_finish??""} client_msg_id=${r.client_msg_id??""} quoted_message_id=${r.quoted_message_id??""} readyState=${c} bufferedAmount=${o}`)}else if(e==="event_ack"){const r=t;d?a.warn("aibot",`event_ack ws send failed event=${r.event_id??""} seq=${n} readyState=${c} bufferedAmount=${o} err=${d.message}`):a.info("aibot",`event_ack ws send ok event=${r.event_id??""} seq=${n} readyState=${c} bufferedAmount=${o}`)}else if(e==="send_msg"){const r=t;d?a.warn("aibot",`send_msg ws send failed event=${r.event_id??""} session=${r.session_id??""} seq=${n} readyState=${c} bufferedAmount=${o} err=${d.message}`):a.info("aibot",`send_msg ws send ok event=${r.event_id??""} session=${r.session_id??""} seq=${n} readyState=${c} bufferedAmount=${o}`)}else if(d){const r=t;a.warn("aibot",`${e} ws send failed seq=${n} session=${r.session_id??""} event=${r.event_id??""} client_msg_id=${r.client_msg_id??""} readyState=${c} bufferedAmount=${o} err=${d.message}`)}}),!0}catch(c){return this.emitClientError(new Error(`sendPacket failed: ${c}`)),!1}}}if(f.DROPPABLE_COMMANDS.has(e))return this.packetLog?.logOutboundPacket(e,s??0,t,"dropped"),!1;if(s!==void 0)return this.packetLog?.logOutboundPacket(e,s,t,"dropped"),!1;if(this.outboundBuffer.length>=f.MAX_OUTBOUND_BUFFER_SIZE&&(this.outboundBuffer=this.outboundBuffer.filter(i=>f.BUFFER_OVERFLOW_RETAIN_COMMANDS.has(i.cmd)),this.outboundBuffer.length>=f.MAX_OUTBOUND_BUFFER_SIZE&&this.outboundBuffer.shift()),this.outboundBuffer.push({cmd:e,payload:t}),this.packetLog?.logOutboundPacket(e,s??0,t,"buffered"),e==="client_stream_chunk"){const i=t;a.info("aibot",`stream_chunk buffered (ws not open) event=${i.event_id??""} session=${i.session_id??""} chunk_seq=${i.chunk_seq??""} is_finish=${i.is_finish??""} ws=${this.ws?`state=${this.ws.readyState}`:"null"}`)}return!1}async sendEventResultReliable(e){const t=this.ackPolicy?.max_retries??3,s=this.ackPolicy?.push_ack_timeout_ms??5e3,i=750;for(let n=1;n<=t;n++){const h=this.ws?.readyState??-1,c=this.ws?.bufferedAmount??0;a.info("aibot",`event_result send attempt event=${e.event_id} status=${e.status} attempt=${n}/${t} readyState=${h} bufferedAmount=${c}`);try{const o=await this.sendEventResultRequest(e,s);if(o.cmd==="send_ack"){const r=o.payload;a.info("aibot",`event_result ack event=${e.event_id} status=${e.status} attempt=${n}/${t} ack_event=${r.event_id??""} ack_status=${r.status??""}`);return}const d=o.payload;if(a.warn("aibot",`event_result rejected event=${e.event_id} status=${e.status} attempt=${n}/${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){a.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(a.warn("aibot",`event_result attempt failed event=${e.event_id} status=${e.status} attempt=${n}/${t} err=${d}`),n===t){this.emitClientError(new Error(`event_result ack failed after ${t} attempts: event=${e.event_id} status=${e.status}`));return}await new Promise(r=>setTimeout(r,i*n))}}}purgeBufferedStreamChunks(e){const t=this.outboundBuffer.length;this.outboundBuffer=this.outboundBuffer.filter(s=>s.cmd!=="client_stream_chunk"?!0:s.payload?.event_id!==e),this.outboundBuffer.length<t&&a.info("aibot",`purged ${t-this.outboundBuffer.length} buffered stream chunks for event=${e}`)}emitClientError(e){if(this.listenerCount("error")===0){a.warn("aibot",`Client error (no listeners): ${e.message}`);return}this.emit("error",e)}flushOutboundBuffer(){if(this.outboundBuffer.length===0||!this.ws||this.ws.readyState!==m.OPEN)return;const e=this.outboundBuffer;this.outboundBuffer=[];for(const{cmd:t,payload:s}of e){const i=++this.seq;if(t==="client_stream_chunk"&&s&&typeof s=="object"){const h=s.event_id;h&&this.seqEventMap.set(i,h)}const n={cmd:t,seq:i,payload:s};try{this.ws.send(JSON.stringify(n))}catch{break}}if(this.seqEventMap.size>200){const t=[...this.seqEventMap.entries()].sort((s,i)=>s[0]-i[0]);this.seqEventMap.clear();for(const[s,i]of t.slice(-100))this.seqEventMap.set(s,i)}}handleInvokeResult(e){const t=this.pendingInvokes.get(e.invoke_id);t&&(this.pendingInvokes.delete(e.invoke_id),clearTimeout(t.timer),e.code===0?t.resolve(e.data??null):t.reject(new Error(`agent_invoke error code=${e.code}: ${e.msg??""}`)))}rejectAllPendingInvokes(e){for(const[,t]of this.pendingInvokes)clearTimeout(t.timer),t.reject(new Error(`agent_invoke canceled: ${e}`));this.pendingInvokes.clear()}rejectAllPendingRequests(e){for(const[,t]of this.pendingRequests)clearTimeout(t.timer),t.reject(new Error(`request canceled: ${e}`));this.pendingRequests.clear()}cleanupSocket(){if(this.ws)try{this.ws.close()}catch{}}startHeartbeat(){this.stopHeartbeat(),this.heartbeatFailures=0,this.heartbeatTimer=setInterval(()=>{this.connected&&this.request("ping",{ts:Date.now()},{expected:["pong"],timeoutMs:5e3}).then(()=>{this.heartbeatFailures=0}).catch(()=>{this.connected&&(this.heartbeatFailures++,!(this.heartbeatFailures<f.HEARTBEAT_MAX_FAILURES)&&(this.cleanupSocket(),this.attemptReconnect()))})},this.heartbeatSec*1e3)}stopHeartbeat(){this.heartbeatTimer&&(clearInterval(this.heartbeatTimer),this.heartbeatTimer=null)}}export{f as AibotClient};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
class r{client;_status="ready";connectedAt;listeners=[];authAck=null;constructor(e){this.client=e,this.connectedAt=Date.now(),this.addInternalListener("close",()=>{this._status!=="closing"&&(this._status="reconnecting")}),this.addInternalListener("auth",t=>{this._status="ready",t&&(this.authAck=t)}),this.addInternalListener("disconnected",()=>{this._status!=="closing"&&this._status!=="closed"&&(this._status="reconnecting")})}get status(){return this._status}getStatusSnapshot(){return{status:this._status,connectedAt:this.connectedAt,reconnectAttempts:0}}sendEventAck(e){this.client.sendEventAck(e)}sendStreamChunk(e){this.client.sendStreamChunk(e)}sendMsg(e){this.client.sendMsg(e)}editMsg(e){this.client.editMsg(e)}sendEventResult(e){this.client.sendEventResult(e)}sendLocalActionResult(e){this.client.sendLocalActionResult(e)}sendEventStopAck(e){this.client.sendEventStopAck(e)}sendEventStopResult(e){this.client.sendEventStopResult(e)}sendSessionActivitySet(e){this.client.sendSessionActivitySet(e)}sendCodexEvent(e){this.client.sendCodexEvent(e)}sendUpdateBindingCard(e){this.client.sendUpdateBindingCard(e)}sendSkillsUpdate(e){this.client.sendSkillsUpdate(e)}sendPing(){this.client.sendPing()}sendEventState(e){this.client.sendEventState(e)}sendEventCancelResult(e){this.client.sendEventCancelResult(e)}sendQueueClearResult(e){this.client.sendQueueClearResult(e)}sendQueueSnapshot(e){this.client.sendQueueSnapshot(e)}agentInvoke(e,t,s){return this.client.agentInvoke(e,t,s)}sendStreamChunkRequest(e,t){return this.client.sendStreamChunkRequest(e,t)}sendText(e,t){return this.client.sendText(e,t)}sendMedia(e,t){return this.client.sendMedia(e,t)}editMessage(e,t){return this.client.editMessage(e,t)}deleteMessage(e,t,s){return this.client.deleteMessage(e,t,s)}sendEventResultRequest(e,t){return this.client.sendEventResultRequest(e,t)}sendCodexEventReliable(e,t){return this.client.request("codex_event",e,{expected:["send_ack","send_nack","error"],timeoutMs:t??2e4})}onEvent(e){return this.subscribe("event",e)}onLocalAction(e){return this.subscribe("localAction",e)}onStop(e){return this.subscribe("stop",e)}onRevoke(e){return this.subscribe("revoke",e)}onEdit(e){return this.subscribe("edit",e)}onEventCancel(e){return this.subscribe("eventCancel",e)}onQueueClear(e){return this.subscribe("queueClear",e)}onQueueSnapshotQuery(e){return this.subscribe("queueSnapshotQuery",e)}onKicked(e){return this.subscribe("kicked",e)}onAgentDeleted(e){return this.subscribe("agentDeleted",e)}onShareSet(e){return this.subscribe("shareSet",e)}onProfilePush(e){return this.subscribe("profilePush",e)}onError(e){return this.subscribe("error",e)}onClose(e){return this.subscribe("close",e)}onDisconnected(e){return this.subscribe("disconnected",e)}onStreamRejected(e){return this.subscribe("streamRejected",e)}onReconnected(e){return this.subscribe("auth",e)}disconnect(){this._status="closing",this.removeAllListeners(),this.client.disconnect(),this._status="closed"}sendMcpFrame(e,t){this.client.sendMcpFrame(e,t)}onMcpFrame(e){return this.subscribe("mcpFrame",e)}subscribe(e,t){return this.client.on(e,t),this.listeners.push({event:e,handler:t}),()=>{this.client.removeListener(e,t);const s=this.listeners.findIndex(n=>n.event===e&&n.handler===t);s>=0&&this.listeners.splice(s,1)}}addInternalListener(e,t){this.client.on(e,t),this.listeners.push({event:e,handler:t})}removeAllListeners(){for(const{event:e,handler:t}of this.listeners)this.client.removeListener(e,t);this.listeners.length=0}}export{r as AibotConnectionHandleImpl};
|
|
1
|
+
class r{client;_status="ready";connectedAt;listeners=[];authAck=null;constructor(e){this.client=e,this.connectedAt=Date.now(),this.addInternalListener("close",()=>{this._status!=="closing"&&(this._status="reconnecting")}),this.addInternalListener("auth",t=>{this._status="ready",t&&(this.authAck=t)}),this.addInternalListener("disconnected",()=>{this._status!=="closing"&&this._status!=="closed"&&(this._status="reconnecting")})}get status(){return this._status}getStatusSnapshot(){return{status:this._status,connectedAt:this.connectedAt,reconnectAttempts:0}}sendEventAck(e){this.client.sendEventAck(e)}sendStreamChunk(e){this.client.sendStreamChunk(e)}sendMsg(e){this.client.sendMsg(e)}editMsg(e){this.client.editMsg(e)}sendEventResult(e){this.client.sendEventResult(e)}sendLocalActionResult(e){this.client.sendLocalActionResult(e)}sendEventStopAck(e){this.client.sendEventStopAck(e)}sendEventStopResult(e){this.client.sendEventStopResult(e)}sendSessionActivitySet(e){this.client.sendSessionActivitySet(e)}sendCodexEvent(e){this.client.sendCodexEvent(e)}sendUpdateBindingCard(e){this.client.sendUpdateBindingCard(e)}sendSkillsUpdate(e){this.client.sendSkillsUpdate(e)}sendPing(){this.client.sendPing()}sendEventState(e){this.client.sendEventState(e)}sendEventCancelResult(e){this.client.sendEventCancelResult(e)}sendQueueClearResult(e){this.client.sendQueueClearResult(e)}sendQueueReorderResult(e){this.client.sendQueueReorderResult(e)}sendQueueSnapshot(e){this.client.sendQueueSnapshot(e)}agentInvoke(e,t,s){return this.client.agentInvoke(e,t,s)}sendStreamChunkRequest(e,t){return this.client.sendStreamChunkRequest(e,t)}sendText(e,t){return this.client.sendText(e,t)}sendMedia(e,t){return this.client.sendMedia(e,t)}editMessage(e,t){return this.client.editMessage(e,t)}deleteMessage(e,t,s){return this.client.deleteMessage(e,t,s)}sendEventResultRequest(e,t){return this.client.sendEventResultRequest(e,t)}sendCodexEventReliable(e,t){return this.client.request("codex_event",e,{expected:["send_ack","send_nack","error"],timeoutMs:t??2e4})}onEvent(e){return this.subscribe("event",e)}onLocalAction(e){return this.subscribe("localAction",e)}onStop(e){return this.subscribe("stop",e)}onRevoke(e){return this.subscribe("revoke",e)}onEdit(e){return this.subscribe("edit",e)}onEventCancel(e){return this.subscribe("eventCancel",e)}onQueueClear(e){return this.subscribe("queueClear",e)}onQueueReorder(e){return this.subscribe("queueReorder",e)}onQueueSnapshotQuery(e){return this.subscribe("queueSnapshotQuery",e)}onKicked(e){return this.subscribe("kicked",e)}onAgentDeleted(e){return this.subscribe("agentDeleted",e)}onShareSet(e){return this.subscribe("shareSet",e)}onProfilePush(e){return this.subscribe("profilePush",e)}onSkillSync(e){return this.subscribe("skillSync",e)}onError(e){return this.subscribe("error",e)}onClose(e){return this.subscribe("close",e)}onDisconnected(e){return this.subscribe("disconnected",e)}onStreamRejected(e){return this.subscribe("streamRejected",e)}onReconnected(e){return this.subscribe("auth",e)}disconnect(){this._status="closing",this.removeAllListeners(),this.client.disconnect(),this._status="closed"}sendMcpFrame(e,t){this.client.sendMcpFrame(e,t)}onMcpFrame(e){return this.subscribe("mcpFrame",e)}subscribe(e,t){return this.client.on(e,t),this.listeners.push({event:e,handler:t}),()=>{this.client.removeListener(e,t);const s=this.listeners.findIndex(n=>n.event===e&&n.handler===t);s>=0&&this.listeners.splice(s,1)}}addInternalListener(e,t){this.client.on(e,t),this.listeners.push({event:e,handler:t})}removeAllListeners(){for(const{event:e,handler:t}of this.listeners)this.client.removeListener(e,t);this.listeners.length=0}}export{r as AibotConnectionHandleImpl};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
const n=10008,
|
|
1
|
+
const n=10008,s="agent_deleted";class t extends Error{constructor(r){super(r),this.name="AgentDeletedError"}}function E(e){return e instanceof t||e instanceof Error&&e.name==="AgentDeletedError"}class o extends Error{constructor(r){super(r),this.name="RequestTimeoutError"}}function u(e){return e instanceof o||e instanceof Error&&e.name==="RequestTimeoutError"}export{n as AUTH_CODE_AGENT_DELETED,t as AgentDeletedError,s as KICKED_REASON_AGENT_DELETED,o as RequestTimeoutError,E as isAgentDeletedError,u as isRequestTimeoutError};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{mkdir as r}from"node:fs/promises";import{join as o}from"node:path";import{homedir as n}from"node:os";const s="GRIX_CONNECTOR_HOME",a=o(n(),".grix");function d(i){const e=i||(process.env[s]?process.env[s]:a);return{rootDir:e,configDir:o(e,"config"),logDir:o(e,"log"),dataDir:o(e,"data"),serviceDir:o(e,"service"),stateFile:o(e,"service.json"),pidFile:o(e,"grix-connector.pid"),daemonLockFile:o(e,"daemon.lock.json"),daemonStatusFile:o(e,"daemon-status.json"),stdoutLogFile:o(e,"service","daemon.out.log"),stderrLogFile:o(e,"service","daemon.err.log"),contextsDir:o(e,"data","session-contexts"),hookSignalsPath:o(e,"data","hook-signals.json"),hookSignalsLogPath:o(e,"log","hook-signals.log"),elicitationRequestsDir:o(e,"data","elicitation-requests"),eventStatesDir:o(e,"data","event-states"),questionRequestsDir:o(e,"data","question-requests"),permissionRequestsDir:o(e,"data","permission-requests"),agentGlobalConfigsFile:o(e,"data","agent-global-configs.json")}}async function m(i){const e=[i.rootDir,i.configDir,i.logDir,i.dataDir,i.serviceDir];await Promise.all(e.map(t=>r(t,{recursive:!0})))}export{a as DEFAULT_GRIX_HOME,s as GRIX_HOME_ENV,m as ensureRuntimeDirs,d as resolveRuntimePaths};
|
|
1
|
+
import{mkdir as r}from"node:fs/promises";import{join as o}from"node:path";import{homedir as n}from"node:os";const s="GRIX_CONNECTOR_HOME",a=o(n(),".grix");function d(i){const e=i||(process.env[s]?process.env[s]:a);return{rootDir:e,configDir:o(e,"config"),logDir:o(e,"log"),dataDir:o(e,"data"),serviceDir:o(e,"service"),skillsDir:o(e,"skills"),stateFile:o(e,"service.json"),pidFile:o(e,"grix-connector.pid"),daemonLockFile:o(e,"daemon.lock.json"),daemonStatusFile:o(e,"daemon-status.json"),stdoutLogFile:o(e,"service","daemon.out.log"),stderrLogFile:o(e,"service","daemon.err.log"),contextsDir:o(e,"data","session-contexts"),hookSignalsPath:o(e,"data","hook-signals.json"),hookSignalsLogPath:o(e,"log","hook-signals.log"),elicitationRequestsDir:o(e,"data","elicitation-requests"),eventStatesDir:o(e,"data","event-states"),questionRequestsDir:o(e,"data","question-requests"),permissionRequestsDir:o(e,"data","permission-requests"),agentGlobalConfigsFile:o(e,"data","agent-global-configs.json")}}async function m(i){const e=[i.rootDir,i.configDir,i.logDir,i.dataDir,i.serviceDir,i.skillsDir];await Promise.all(e.map(t=>r(t,{recursive:!0})))}export{a as DEFAULT_GRIX_HOME,s as GRIX_HOME_ENV,m as ensureRuntimeDirs,d as resolveRuntimePaths};
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import{execFile as
|
|
2
|
-
${F(i)}`);const r=q(i);if(!r.supported)return this.fail(e,"preflight",t,new c("ENVIRONMENT_UNSUPPORTED",`Current environment is not supported for automatic installation: ${r.reason}. Please install ${e} manually.`),i);if(this.setProgress(e,"preflight",t),!D(e))return this.fail(e,"preflight",t,new c("UNKNOWN_AGENT",`Unknown agent type: ${e}`),i);const l=k(e,this.os);if(!l){const s=this.getManualHint(e,this.os),a=s?`Installation of ${e} is not supported on ${this.os}. ${s}`:`Installation of ${e} is not supported on ${this.os}`;return this.fail(e,"preflight",t,new c("UNSUPPORTED_OS",a),i)}const u=A(e),
|
|
1
|
+
import{execFile as _,spawn as b}from"node:child_process";import{log as m}from"../log/logger.js";import{resolveCliPath as N,getCliVersion as T,invalidateCliPathCache as x}from"../util/cli-probe.js";import{getInstallCommand as k,getCliBinary as A,isKnownAgent as D,detectPlatformOS as O,formatInstallCommand as U}from"./registry.js";import{checkPrerequisites as P,getMissingPrerequisites as L}from"./preflight.js";import{installMissingPrerequisites as C}from"./prereq-installer.js";import{detectEnvironment as V,formatEnvironmentInfo as F,isEnvironmentSupported as q}from"./env-detect.js";import{generateManualGuide as S}from"./manual-guide.js";import{npmInstallWithMirror as G,isTransientInstallError as B}from"./npm-registry.js";import{getAllAgentInstallInfo as H}from"./registry.js";class c extends Error{code;constructor(n,t){super(t),this.name="InstallerError",this.code=n}}const w=64*1024,j=20,v=2,W=3e3;class ie{os;activeInstalls=new Map;constructor(){this.os=O()}listInstallable(){return{platform:this.os,agents:H(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 c("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 m.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 V()}catch(s){const a=s instanceof Error?s.message:String(s);return this.fail(e,"preflight",t,new c("INTERNAL",`Environment detection failed: ${a}`))}m.info("installer",`Install request: ${e}
|
|
2
|
+
${F(i)}`);const r=q(i);if(!r.supported)return this.fail(e,"preflight",t,new c("ENVIRONMENT_UNSUPPORTED",`Current environment is not supported for automatic installation: ${r.reason}. Please install ${e} manually.`),i);if(this.setProgress(e,"preflight",t),!D(e))return this.fail(e,"preflight",t,new c("UNKNOWN_AGENT",`Unknown agent type: ${e}`),i);const l=k(e,this.os);if(!l){const s=this.getManualHint(e,this.os),a=s?`Installation of ${e} is not supported on ${this.os}. ${s}`:`Installation of ${e} is not supported on ${this.os}`;return this.fail(e,"preflight",t,new c("UNSUPPORTED_OS",a),i)}const u=A(e),f=await N(u),p=f?(await T(f)).version:null;if(f&&!n.force&&!n.dryRun){this.activeInstalls.delete(e);const s=Date.now()-t;return m.info("installer",`${e} already installed at ${f}${p?` (v${p})`:""}`),{agentType:e,ok:!0,phase:"completed",installedPath:f,installedVersion:p,durationMs:s,output:"",environment:i}}const h=l.prerequisites??[];let o=[];if(h.length>0){m.info("installer",`Checking prerequisites for ${e}: ${h.join(", ")}`),o=await P(h,this.os),m.info("installer",`Prerequisites: ${o.map(a=>`${a.label}=${a.met?a.version:"missing"}`).join(", ")}`);const s=L(o);if(s.length>0){const a=s.map(d=>`${d.label}${d.minVersion?` >= ${d.minVersion}`:""}`).join(", ");if(!n.dryRun){if(n.skipPrereqInstall)return this.fail(e,"preflight",t,new c("PREREQ_MISSING",`Missing prerequisites: ${a}. Install them first or retry without skipPrereqInstall.`),i,o);const d=s.map($=>`${$.label}${$.minVersion?` >= ${$.minVersion}`:""}`);m.info("installer",`Will auto-install prerequisites: ${d.join(", ")}`),this.setProgress(e,"installing_prereq",t,s[0].label,d);const I=await C(s,this.os);if(!I.allOk){const $=I.results.find(R=>!R.ok),M=$?`Failed to install prerequisite ${$.prereq.label}: ${$.output}`:"Prerequisite installation failed";return this.fail(e,"installing_prereq",t,new c("PREREQ_INSTALL_FAILED",M),i,o)}m.info("installer",`All prerequisites installed for ${e}`),o=await P(h,this.os)}}}if(n.dryRun){this.activeInstalls.delete(e);const s=L(o),a=this.getManualHint(e,this.os),d={agentType:e,environment:i,canInstall:!0,alreadyInstalled:!!f,installedPath:f,installedVersion:p,installCommand:U(l),installMode:l.mode,prerequisites:o,missingPrerequisites:s,fallbackCommand:l.fallback?.command??null,manualHint:a},I=S({agentType:e,os:this.os,env:i,missingPrereqs:s});return{agentType:e,ok:!0,phase:"completed",durationMs:Date.now()-t,output:"dry-run: no commands executed",environment:i,dryRun:d,manualGuide:I}}this.setProgress(e,"installing",t),m.info("installer",`Installing ${e}: ${l.command}`);let g;try{g=await this.executeWithRetry(l,e,t,n.timeoutMs)}catch(s){if(l.fallback&&s instanceof c&&(s.code==="INSTALL_FAILED"||s.code==="INSTALL_TIMEOUT")){m.info("installer",`Primary install (${l.command}) failed: ${s.message}`),m.info("installer",`Trying fallback: ${l.fallback.command}`),this.setProgress(e,"installing",t);try{g=await this.executeWithRetry(l.fallback,e,t,n.timeoutMs),g=`[primary failed, fallback succeeded]
|
|
3
3
|
${g}`}catch(a){const d=s.message,I=a instanceof c?a.message:String(a);return this.fail(e,"installing",t,new c("FALLBACK_EXHAUSTED",`Both primary and fallback install methods failed.
|
|
4
4
|
Primary: ${d}
|
|
5
5
|
Fallback: ${I}`),i,o)}}else return s instanceof c?this.fail(e,"installing",t,new c(s.code,s.message),i,o):this.fail(e,"installing",t,new c("INTERNAL",s instanceof Error?s.message:String(s)),i,o)}{const s=(g??"").trim().split(`
|
|
6
6
|
`).slice(-12).join(`
|
|
7
7
|
`);m.info("installer",`${e} \u5B89\u88C5\u547D\u4EE4\u8F93\u51FA(\u5C3E\u90E8):
|
|
8
|
-
${s||"<\u7A7A>"}`)}if(x(),!n.skipVerify&&!l.skipVerification){this.setProgress(e,"verifying",t),m.info("installer",`Verifying ${e} installation...`);const s=await N(u);if(!s)return this.fail(e,"verifying",t,new c("VERIFICATION_FAILED",`${u} not found on PATH after installation. You may need to open a new terminal or run: source ~/.zshrc (or ~/.bashrc)`),i,o);let{version:a,error:d}=await T(s);a===null&&d&&(await this.sleep(1e3),{version:a,error:d}=await T(s));const I=Date.now()-t;return this.activeInstalls.delete(e),m.info("installer",`${e} installed successfully at ${s} (v${a??"unknown"}, ${I}ms)`),{agentType:e,ok:!0,phase:"completed",installedPath:s,installedVersion:a,durationMs:I,output:g,prerequisites:o.length>0?o:void 0,environment:i}}const E=Date.now()-t;return this.activeInstalls.delete(e),m.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=k(n,t)?.fallback,l={claude:"https://docs.anthropic.com/en/docs/claude-code/overview",codex:"https://github.com/openai/codex",
|
|
9
|
-
${o??""}`.trim())});this.trackOutput(
|
|
8
|
+
${s||"<\u7A7A>"}`)}if(x(),!n.skipVerify&&!l.skipVerification){this.setProgress(e,"verifying",t),m.info("installer",`Verifying ${e} installation...`);const s=await N(u);if(!s)return this.fail(e,"verifying",t,new c("VERIFICATION_FAILED",`${u} not found on PATH after installation. You may need to open a new terminal or run: source ~/.zshrc (or ~/.bashrc)`),i,o);let{version:a,error:d}=await T(s);a===null&&d&&(await this.sleep(1e3),{version:a,error:d}=await T(s));const I=Date.now()-t;return this.activeInstalls.delete(e),m.info("installer",`${e} installed successfully at ${s} (v${a??"unknown"}, ${I}ms)`),{agentType:e,ok:!0,phase:"completed",installedPath:s,installedVersion:a,durationMs:I,output:g,prerequisites:o.length>0?o:void 0,environment:i}}const E=Date.now()-t;return this.activeInstalls.delete(e),m.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=k(n,t)?.fallback,l={claude:"https://docs.anthropic.com/en/docs/claude-code/overview",codex:"https://github.com/openai/codex",qwen:"https://github.com/QwenLM/qwen-code",cursor:"https://cursor.com/docs/cli/installation",copilot:"https://github.com/github/copilot-cli",kiro:"https://kiro.dev/docs/cli/",openclaw:"https://github.com/openclaw/openclaw",reasonix:"https://github.com/esengine/DeepSeek-Reasonix"}[n],u=[];return l&&u.push(`Docs: ${l}`),i&&u.push(`Alternative: ${i.command}`),u.length>0?u.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,l,u){const f=u??this.activeInstalls.get(n)?.outputTail??"";this.activeInstalls.delete(n),m.error("installer",`${n} install failed at ${t}: ${i.message}`);const p=l?L(l):[],h=S({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:f,environment:r,prerequisites:l,manualGuide:h}}async executeWithRetry(n,t,e,i){let r=null;for(let l=0;l<=v;l++)try{return l>0&&(m.info("installer",`Retry ${l}/${v} for ${t}...`),await this.sleep(W)),await this.executeCommand(n,t,e,i)}catch(u){if(r=u instanceof c?u:new c("INTERNAL",String(u)),!(r.code==="INSTALL_TIMEOUT"||r.code==="INSTALL_FAILED"&&B(r.message))||l>=v)throw r;m.info("installer",`Attempt ${l+1} failed (retryable): ${r.message}`)}throw r??new c("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 c("INTERNAL",`Unknown install mode: ${n.mode}`))}}async executeNpm(n,t,e,i){try{const{output:r,registry:l}=await G(n,t,w);return m.info("installer",`npm install ${n} succeeded via ${l}`),r}catch(r){const l=r instanceof Error?r.message:String(r);throw l.includes("timed out")||l.includes("ETIMEDOUT")?new c("INSTALL_TIMEOUT",`npm install timed out (tried all mirrors): ${l}`):new c("INSTALL_FAILED",`npm install failed (tried all mirrors): ${l}`)}}executeShell(n,t,e,i){return new Promise((r,l)=>{const u=process.platform==="win32",f=u?"cmd.exe":"bash",p=u?["/d","/c",n]:["-c",`set -o pipefail; ${n}`];m.info("installer",`exec: ${f} ${p.join(" ")}`);const h=b(f,p,{timeout:t,stdio:["ignore","pipe","pipe"],...u?{windowsVerbatimArguments:!0}:{},env:{...process.env,NONINTERACTIVE:"1",DEBIAN_FRONTEND:"noninteractive"}});let o="",g=!1;const E=setTimeout(()=>{g=!0;try{h.kill("SIGTERM")}catch{}setTimeout(()=>{try{h.kill("SIGKILL")}catch{}},5e3).unref()},t);h.stdout?.on("data",s=>{const a=s.toString("utf-8");o+=a,o.length>w&&(o=o.slice(-w)),this.updateOutputTail(e,i,o)}),h.stderr?.on("data",s=>{const a=s.toString("utf-8");o+=a,o.length>w&&(o=o.slice(-w)),this.updateOutputTail(e,i,o)}),h.on("error",s=>{clearTimeout(E),l(new c("INSTALL_FAILED",`Spawn error: ${s.message}`))}),h.on("close",s=>{if(clearTimeout(E),g){l(new c("INSTALL_TIMEOUT",`Install timed out after ${t/1e3}s`));return}if(s!==0){const a=o.slice(-1024);l(new c("INSTALL_FAILED",`Process exited with code ${s}: ${a}`));return}r(o.trim())})})}executeExec(n,t,e,i,r){return new Promise((l,u)=>{m.info("installer",`exec: ${n} ${t.join(" ")}`);const f=_(n,t,{timeout:e,maxBuffer:w},(p,h,o)=>{if(p){if(p.killed)u(new c("INSTALL_TIMEOUT",`${n} timed out after ${e/1e3}s`));else{const g=o?.trim()||p.message;u(new c("INSTALL_FAILED",`${n} failed: ${g}`))}return}l(`${h??""}
|
|
9
|
+
${o??""}`.trim())});this.trackOutput(f,i,r)})}trackOutput(n,t,e){n.on("close",()=>{const i=this.activeInstalls.get(t);i&&(i.elapsedMs=Date.now()-e)})}updateOutputTail(n,t,e){const r=e.split(`
|
|
10
10
|
`).slice(-j).join(`
|
|
11
11
|
`),l=this.activeInstalls.get(n);l&&(l.outputTail=r,l.elapsedMs=Date.now()-t)}}export{ie as AgentInstaller,c as InstallerError};
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{getInstallCommand as f,getCliBinary as g}from"./registry.js";import{getPrereqManualInstructions as $}from"./prereq-installer.js";import{getMirrorInstallCommands as r}from"./npm-registry.js";function v(u){const{agentType:e,os:
|
|
1
|
+
import{getInstallCommand as f,getCliBinary as g}from"./registry.js";import{getPrereqManualInstructions as $}from"./prereq-installer.js";import{getMirrorInstallCommands as r}from"./npm-registry.js";function v(u){const{agentType:e,os:i,env:h,missingPrereqs:c}=u,s=[],a=g(e)??e,o=f(e,i),m=b(i);if(s.push(`=== ${e} \u4EBA\u5DE5\u5B89\u88C5\u6307\u5357 (${m}) ===`),s.push(""),s.push("\u5F53\u524D\u73AF\u5883:"),s.push(` \u7CFB\u7EDF: ${h.osVersion} (${h.arch})`),s.push(` Node.js: ${h.nodeVersion??"\u672A\u5B89\u88C5"}`),s.push(` npm: ${h.npmVersion??"\u672A\u5B89\u88C5"}`),s.push(""),u.error&&(s.push("\u81EA\u52A8\u5B89\u88C5\u5931\u8D25\u539F\u56E0:"),s.push(` ${u.error}`),s.push("")),c.length>0){s.push("--- \u6B65\u9AA4 1: \u5B89\u88C5\u524D\u7F6E\u4F9D\u8D56 ---"),s.push("");for(const n of c){const p=$(n.id,i);s.push(`\u25B8 ${n.label}${n.minVersion?` (\u9700\u8981 >= ${n.minVersion})`:""}`),p&&s.push(` \u5B89\u88C5\u65B9\u6CD5: ${p}`),s.push(` \u9A8C\u8BC1: ${n.id==="node"?"node --version":n.id==="npm"?"npm --version":`${n.id} --version`}`),s.push("")}}const l=c.length>0?"2":"1";if(s.push(`--- \u6B65\u9AA4 ${l}: \u5B89\u88C5 ${e} ---`),s.push(""),o){if(s.push(" \u63A8\u8350\u65B9\u5F0F:"),s.push(` ${o.command}`),s.push(""),o.mode==="npm"&&o.npmPackage){const n=r(o.npmPackage);s.push(" \u955C\u50CF\u5B89\u88C5\uFF08\u56FD\u5185\u7F51\u7EDC\u6162\u6216\u88AB\u5899\u65F6\u4F7F\u7528\uFF09:");for(const p of n)s.push(` # ${p.label}`),s.push(` ${p.command}`);s.push("")}if(o.fallback){if(s.push(" \u5907\u9009\u65B9\u5F0F\uFF08\u63A8\u8350\u5B89\u88C5\u5931\u8D25\u65F6\u4F7F\u7528\uFF09:"),s.push(` ${o.fallback.command}`),o.fallback.mode==="npm"&&o.fallback.npmPackage){const n=r(o.fallback.npmPackage);for(const p of n)s.push(` # ${p.label}`),s.push(` ${p.command}`)}s.push("")}}const d=c.length>0?"3":"2";s.push(`--- \u6B65\u9AA4 ${d}: \u9A8C\u8BC1\u5B89\u88C5 ---`),s.push(""),s.push(` ${a} --version`),s.push(""),s.push(" \u5982\u679C\u663E\u793A\u7248\u672C\u53F7\uFF0C\u8BF4\u660E\u5B89\u88C5\u6210\u529F\u3002"),s.push(""),s.push("--- \u5E38\u89C1\u95EE\u9898 ---"),s.push(""),s.push(" Q: \u5B89\u88C5\u540E\u547D\u4EE4\u627E\u4E0D\u5230\uFF1F"),s.push(" A: \u53EF\u80FD\u9700\u8981\u91CD\u65B0\u6253\u5F00\u7EC8\u7AEF\uFF0C\u6216\u8FD0\u884C:"),i==="macos"||i==="linux"?(s.push(" source ~/.zshrc # zsh"),s.push(" source ~/.bashrc # bash")):s.push(" \u91CD\u65B0\u6253\u5F00 PowerShell \u6216 CMD \u7A97\u53E3"),s.push(""),o?.mode==="npm"&&(s.push(" Q: npm install -g \u62A5\u6743\u9650\u9519\u8BEF\uFF1F"),i==="macos"||i==="linux"?(s.push(" A: \u4E0D\u8981\u7528 sudo npm install -g\u3002\u5EFA\u8BAE\u7528\u4EE5\u4E0B\u65B9\u5F0F\u4E4B\u4E00:"),s.push(" 1. brew install node (macOS\uFF0C\u63A8\u8350)"),s.push(" 2. \u4F7F\u7528 fnm/nvm \u7BA1\u7406 Node.js (Linux)"),s.push(" 3. \u914D\u7F6E npm \u5168\u5C40\u76EE\u5F55: npm config set prefix ~/.npm-global")):s.push(" A: \u4EE5\u7BA1\u7406\u5458\u8EAB\u4EFD\u6253\u5F00 PowerShell \u540E\u91CD\u8BD5"),s.push("")),s.push("--- \u5B98\u65B9\u6587\u6863 ---"),s.push("");const t={claude:"https://docs.anthropic.com/en/docs/claude-code/overview",codex:"https://github.com/openai/codex",qwen:"https://github.com/QwenLM/qwen-code",cursor:"https://cursor.com/docs/cli/installation",copilot:"https://github.com/github/copilot-cli",kiro:"https://kiro.dev/docs/cli/",kimi:"https://moonshotai.github.io/kimi-cli/",openclaw:"https://github.com/openclaw/openclaw",reasonix:"https://github.com/esengine/DeepSeek-Reasonix"};return t[e]&&s.push(` ${e}: ${t[e]}`),s.join(`
|
|
2
2
|
`)}function b(u){switch(u){case"macos":return"macOS";case"linux":return"Linux";case"windows":return"Windows"}}export{v as generateManualGuide};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
function i
|
|
1
|
+
function e(i,s){return{command:`npm install -g ${i}`,mode:"npm",npmPackage:i,timeoutMs:s?.timeoutMs??12e4,prerequisites:["node","npm"],minNodeVersion:s?.minNodeVersion}}function n(i,s){return{command:i,mode:"shell",timeoutMs:s?.timeoutMs??12e4,prerequisites:s?.prerequisites}}function c(i,s,o){return{command:i,mode:"exec",execArgs:s,timeoutMs:o?.timeoutMs??6e4,prerequisites:o?.prerequisites,skipVerification:o?.skipVerification}}const l={claude:{cliBinary:"claude",macos:e("@anthropic-ai/claude-code",{minNodeVersion:"18.0"}),linux:e("@anthropic-ai/claude-code",{minNodeVersion:"18.0"}),windows:e("@anthropic-ai/claude-code",{minNodeVersion:"18.0"})},codex:{cliBinary:"codex",macos:e("@openai/codex"),linux:e("@openai/codex"),windows:e("@openai/codex")},qwen:{cliBinary:"qwen",macos:{...n("curl -fsSL https://qwen-code-assets.oss-cn-hangzhou.aliyuncs.com/installation/install-qwen.sh | bash",{prerequisites:["curl"]}),fallback:e("@qwen-code/qwen-code@latest",{minNodeVersion:"22.0"})},linux:{...n("curl -fsSL https://qwen-code-assets.oss-cn-hangzhou.aliyuncs.com/installation/install-qwen.sh | bash",{prerequisites:["curl"]}),fallback:e("@qwen-code/qwen-code@latest",{minNodeVersion:"22.0"})},windows:{...n(`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:e("@qwen-code/qwen-code@latest",{minNodeVersion:"22.0"})}},cursor:{cliBinary:"agent",macos:n("curl https://cursor.com/install -fsS | bash",{prerequisites:["curl"]}),linux:n("curl https://cursor.com/install -fsS | bash",{prerequisites:["curl"]}),windows:n(`powershell -ExecutionPolicy ByPass -c "irm 'https://cursor.com/install?win32=true' | iex"`)},copilot:{cliBinary:"copilot",macos:e("@github/copilot",{minNodeVersion:"22.0"}),linux:e("@github/copilot",{minNodeVersion:"22.0"}),windows:e("@github/copilot",{minNodeVersion:"22.0"})},kiro:{cliBinary:"kiro-cli",macos:n("curl -fsSL https://cli.kiro.dev/install | bash",{prerequisites:["curl"]}),linux:n("curl -fsSL https://cli.kiro.dev/install | bash",{prerequisites:["curl"]}),windows:null},kimi:{cliBinary:"kimi",macos:n("curl -LsSf https://code.kimi.com/install.sh | bash",{prerequisites:["curl"],timeoutMs:3e5}),linux:n("curl -LsSf https://code.kimi.com/install.sh | bash",{prerequisites:["curl"],timeoutMs:3e5}),windows:n('powershell -ExecutionPolicy ByPass -c "irm https://code.kimi.com/install.ps1 | iex"',{timeoutMs:3e5})},openclaw:{cliBinary:"openclaw",macos:e("openclaw@latest",{minNodeVersion:"22.0"}),linux:e("openclaw@latest",{minNodeVersion:"22.0"}),windows:e("openclaw@latest",{minNodeVersion:"22.0"})},reasonix:{cliBinary:"reasonix",macos:e("reasonix"),linux:e("reasonix"),windows:e("reasonix")},pi:{cliBinary:"pi",macos:e("@earendil-works/pi-coding-agent",{minNodeVersion:"22.19"}),linux:e("@earendil-works/pi-coding-agent",{minNodeVersion:"22.19"}),windows:e("@earendil-works/pi-coding-agent",{minNodeVersion:"22.19"})},agy:{cliBinary:"agy",macos:n("curl -fsSL https://antigravity.google/cli/install.sh | bash",{prerequisites:["curl"]}),linux:n("curl -fsSL https://antigravity.google/cli/install.sh | bash",{prerequisites:["curl"]}),windows:n('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:e("codewhale"),linux:e("codewhale"),windows:e("codewhale")},opencode:{cliBinary:"opencode",macos:{...n("curl -fsSL https://opencode.ai/install | bash",{prerequisites:["curl"]}),fallback:e("opencode-ai")},linux:{...n("curl -fsSL https://opencode.ai/install | bash",{prerequisites:["curl"]}),fallback:e("opencode-ai")},windows:e("opencode-ai")}};function u(){switch(process.platform){case"darwin":return"macos";case"linux":return"linux";case"win32":return"windows";default:return"linux"}}function r(i){return i.mode==="exec"&&i.execArgs&&i.execArgs.length>0?[i.command,...i.execArgs].join(" "):i.command}function d(i,s){const o=l[i];return o?o[s]:null}function m(i){return l[i]?.cliBinary??null}function a(i,s){const o=l[i];if(!o)return null;const t=o[s];return{agentType:i,cliBinary:o.cliBinary,supported:t!==null,installCommand:t?r(t):null,prerequisites:t?.prerequisites}}function p(i){return Object.keys(l).sort().map(s=>a(s,i)).filter(s=>s!==null)}function w(i){return i in l}export{u as detectPlatformOS,r as formatInstallCommand,a as getAgentInstallInfo,p as getAllAgentInstallInfo,m as getCliBinary,d as getInstallCommand,w as isKnownAgent};
|
package/dist/core/log/logger.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import{createWriteStream as D,mkdirSync as T,existsSync as v,readdirSync as w,statSync as R,unlinkSync as x}from"node:fs";import{join as p}from"node:path";import{resolveRuntimePaths as I}from"../config/paths.js";const
|
|
2
|
-
`)},debug(e,n,...r){if(!
|
|
3
|
-
`)},info(e,n,...r){if(!
|
|
4
|
-
`)},warn(e,n,...r){if(!
|
|
5
|
-
`)},error(e,n,...r){if(!
|
|
6
|
-
`)}};export{l as GRIX_PATHS,j as ensureGrixDirs,
|
|
1
|
+
import{createWriteStream as D,mkdirSync as T,existsSync as v,readdirSync as w,statSync as R,unlinkSync as x}from"node:fs";import{join as p}from"node:path";import{resolveRuntimePaths as I}from"../config/paths.js";const o=I(),l={base:o.rootDir,config:o.configDir,log:o.logDir,data:o.dataDir,skills:o.skillsDir};function j(){for(const e of Object.values(l))v(e)||T(e,{recursive:!0})}const a={trace:0,debug:1,info:2,warn:3,error:4};function L(e,n){if(!e)return n;const r=e.trim().toLowerCase();return r in a?r:n}function b(e){const n=new Map;if(!e)return n;for(const r of e.split(",")){const t=r.indexOf("=");if(t<0)continue;const f=r.slice(0,t).trim(),N=L(r.slice(t+1),"info");f&&n.set(f,N)}return n}let m=L(process.env.GRIX_CONNECTOR_LOG_LEVEL,"info");const O=b(process.env.GRIX_CONNECTOR_LOG_TAG_LEVELS);function G(e){return O.get(e)??m}function i(e,n){return a[n]>=a[G(e)]}function k(e){m=e}function F(e,n){O.set(e,n)}let g=null,d="";const y="GRIX_CONNECTOR_LOG_RETENTION_DAYS",C=14;function h(){const e=process.env[y],n=e?Number.parseInt(e,10):Number.NaN;return Number.isFinite(n)&&n>0?n:C}function S(){return new Date().toISOString().slice(0,10)}function $(){const e=Date.now()-h()*24*60*60*1e3;let n;try{n=w(l.log)}catch{return}for(const r of n){if(!r.startsWith("grix-connector-")||!r.includes(".log"))continue;const t=p(l.log,r);try{R(t).mtimeMs<e&&x(t)}catch{}}}function _(e){try{g?.end()}catch{}const n=p(l.log,`grix-connector-${e}.log`);g=D(n,{flags:"a"}),d=e,$()}function P(){_(S())}function A(){return new Date().toISOString().slice(11,19)}let E=!0;function W(e){E=e}function c(e,n){E&&(e==="warn"?console.warn(n):e==="error"?console.error(n):console.log(n))}function s(e){try{const n=S();n!==d&&_(n),g?.write(e)}catch{}}function u(e,n,r,t){const f=e==="info"?"":`${e.toUpperCase()} `;return`${A()} [${n}] ${f}${r}${t.length?" "+t.map(String).join(" "):""}`}const M={trace(e,n,...r){if(!i(e,"trace"))return;const t=u("trace",e,n,r);c("trace",t),s(t+`
|
|
2
|
+
`)},debug(e,n,...r){if(!i(e,"debug"))return;const t=u("debug",e,n,r);c("debug",t),s(t+`
|
|
3
|
+
`)},info(e,n,...r){if(!i(e,"info"))return;const t=u("info",e,n,r);c("info",t),s(t+`
|
|
4
|
+
`)},warn(e,n,...r){if(!i(e,"warn"))return;const t=u("warn",e,n,r);c("warn",t),s(t+`
|
|
5
|
+
`)},error(e,n,...r){if(!i(e,"error"))return;const t=u("error",e,n,r);c("error",t),s(t+`
|
|
6
|
+
`)}};export{l as GRIX_PATHS,j as ensureGrixDirs,P as initLogger,M as log,W as setConsoleOutput,k as setLogLevel,F as setTagLogLevel};
|
package/dist/core/mcp/tools.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
const r=[{name:"grix_query",description:"Search contacts, sessions, message history, messages, or favorited sessions in the Grix/AIBot platform.",inputSchema:{type:"object",properties:{action:{type:"string",enum:["contact_search","session_search","message_history","message_search","search_favorite_sessions"],description:"Query action type. search_favorite_sessions returns the owner's favorited sessions (supports keyword filter)."},id:{type:"string",description:"Contact ID (contact_search) or Session ID (session_search)."},keyword:{type:"string",description:"Search keyword."},sessionType:{type:"integer",enum:[1,2],description:"Filter by session type (session_search only): 1=private chat, 2=group chat. Omit to return all."},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","search_favorite_sessions"]},id:{type:"string"},keyword:{type:"string",maxLength:200},sessionType:{type:"integer",enum:[1,2]},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. The link is reachable only inside the tailnet, so just send it as-is \u2014 no need to worry about or mention any link lifetime. The download link is HTTPS, served by a built-in self-signed CA. The result also returns `ca_install_url`: the first time you share a link with a user (or whenever their browser warns the cert is untrusted), also give them this CA install link so they can install and trust it once \u2014 after that all download links work without warnings. 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. Leave unset to use the long default; set only if you deliberately want a short-lived link."}},required:["file_path"]},validation:{required:["file_path"],properties:{file_path:{type:"string",maxLength:4096},ttl_ms:{type:"integer",minimum:1e4,maximum:864e5}}}},{name:"grix_file_upload",description:"Upload a local file to the Grix platform and send it as a media message in the target session. Supports images (jpg/png/webp/gif/bmp/heic/heif), videos (mp4/mov/m4v/webm/mkv/avi), documents (pdf/doc/docx/xls/xlsx/ppt/pptx/txt/md/csv/json/xml), and archives (zip/rar/7z/tar/gz). Max 50 MB. Use this instead of grix_file_link when the file should appear as a native attachment in the chat (visible inline for images/videos), rather than a tailnet download link.",inputSchema:{type:"object",properties:{file_path:{type:"string",description:"Absolute path to a local file to upload."},session_id:{type:"string",description:"Target session ID to send the file to."},caption:{type:"string",description:"Optional text caption for the media message."},reply_to_message_id:{type:"string",description:"Optional message ID to quote/reply to."}},required:["file_path","session_id"]},validation:{required:["file_path","session_id"],properties:{file_path:{type:"string",maxLength:4096},session_id:{type:"string"},caption:{type:"string",maxLength:2e3},reply_to_message_id:{type:"string"}}}},{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 creates a NEW private session between the owner and that agent for each dispatch (it does not reuse past sessions), 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. Because the task is delivered as the owner, write it in the owner's first-person voice and tone \u2014 phrase it the way the owner would speak directly to the agent (e.g. "\u5E2E\u6211\u2026", "\u4F60\u53BB\u2026"), NOT as a third-person relay or as yourself narrating on the owner's behalf. Provide a short title summarizing the core of the task \u2014 it becomes the new session's title; if omitted the backend derives one from the task text.`,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, written in the owner's first-person voice and tone \u2014 it is delivered into the session as the owner, so phrase it as the owner speaking directly to the agent, not as a third-person relay."},title:{type:"string",description:"Optional short title (a few words) summarizing the core of the task; used as the new session's title. If omitted, the backend derives a title from the task text."}},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},title:{type:"string",maxLength:255}}}},{name:"grix_session_send",description:"Send a message into a session AS THE OWNER \u2014 it appears as if the owner sent it, NOT as you (the agent). Use ONLY to relay on the owner's behalf into one of the owner's OTHER sessions that you are not part of (e.g. you were dispatched to work and need to drop a note to the owner elsewhere). NEVER use this to send your own reply in a session you are conversing in \u2014 that would make your words show up as the owner's message. To answer in your current conversation, reply normally (or use grix_message_send to send as yourself). The owner must be a member of the target session, and you (the agent) must NOT be a member of it \u2014 sending into a session you belong to is rejected.",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_chat_state_query",description:"Query the chat-level task states across all of your owner's sessions (including direct and group chats). 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). Also returns the session title (task_title) for easy identification. Supports pagination (page/page_size) and optional state filtering. Use this to see at a glance which chats are done, still running, or waiting on the owner.",inputSchema:{type:"object",properties:{session_id:{type:"string",description:"(Optional) Query a single session by its ID. Omit to return all sessions."},page:{type:"number",description:"(Optional) Page number, starting from 1. Defaults to 1 if omitted."},page_size:{type:"number",description:"(Optional) Number of items per page, max 100. Defaults to 10 if omitted."},state:{type:"string",description:"(Optional) Filter by a specific state: running, waiting_approval, waiting_question, completed, failed, or idle. Omit to return all states."}}},validation:{required:[],properties:{}}},{name:"grix_chat_state_update",description:"Manually update the task state of a specific chat session. Use this to mark a chat as completed, failed, idle, or any other state when you need to override it manually. The reason is written to stop_reason and is optional.",inputSchema:{type:"object",properties:{session_id:{type:"string",description:"The session ID whose state to update."},state:{type:"string",enum:["running","waiting_approval","waiting_question","completed","failed","idle"],description:"The new state to set. Must be one of: running, waiting_approval, waiting_question, completed, failed, idle."},reason:{type:"string",description:"(Optional) Reason for the state change, written to stop_reason."}}},validation:{required:["session_id","state"],properties:{}}},{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"]}}}}],a=[{name:"grix_reply",description:"Send your final reply \u2014 the conclusion the user is waiting for \u2014 to the specified session. This is the message the user treats as your answer; deliver your final result here, and use plain text only for brief progress notes while you work. Supports streaming in chunks; the frontend automatically aggregates them into one complete message. The connector quotes the message being answered automatically, so quoted_message_id is optional (set it only to quote a different earlier 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_status",description:"Query the Grix connection status of the current MCP session.",inputSchema:{type:"object",properties:{}},validation:{required:[],properties:{}}}],v={grix_dispatch_agent:"grix-agent-dispatch",grix_agent_update:"grix-agent-dispatch",grix_query:"grix-query",grix_group:"grix-group",grix_message_send:"message-send",grix_message_unsend:"message-unsend",grix_admin:"grix-admin",grix_call_owner:"grix-owner-relay",grix_session_send:"grix-owner-relay",grix_chat_state_query:"grix-chat-state",grix_chat_state_update:"grix-chat-state",grix_status:"grix-chat-state",grix_access_control:"grix-access-control",grix_file_link:"tailnet-file-share"};function b(e){return` Before calling this tool, follow the \`${e}\` skill's procedure first; do not invoke this tool directly without going through that skill's guidance.`}for(const e of[...r,...a]){const t=v[e.name];t&&(e.description+=b(t))}const 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)),w=new Set(r.map(e=>e.name)),x=new Set(a.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 W(e){return!!(w.has(e)||x.has(e)||c.has(e)||e.startsWith("mcp__grix"))}const S=[...r,...a],A=[...S,...p],B=new Map(A.map(e=>[e.name,e]));function Q(e,t){return e==="reply"?{name:"grix_reply",args:m("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:m("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 u(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 o=t.match(/[A-Za-z0-9._-]+/g)??[];for(const s of o)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 u(t)}function m(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 o=_(i),d=String(n.session_id??""),s=u(n.session_id),f=/\bevent_id\b|["'<>\s]/.test(d);s&&!(f&&o)?n.session_id=s:o&&(n.session_id=o)}return n}function F(e){return c.has(e)}function H(e,t){switch(e){case"grix_query":return T(t);case"grix_group":return q(t);case"grix_message_send":return M(t);case"grix_message_unsend":return O(t);case"grix_file_link":return D(t);case"grix_file_upload":return E(t);case"grix_admin":return R(t);case"grix_call_owner":return L(t);case"grix_agent_update":return j(t);case"grix_dispatch_agent":return N(t);case"grix_session_send":return z(t);case"grix_chat_state_query":return C(t);case"grix_chat_state_update":return U(t);case"grix_access_control":return P(t);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",search_favorite_sessions:"search_favorite_sessions"};function T(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.sessionType!=null&&(i.session_type=e.sessionType),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 q(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 M(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 O(e){return{action:"delete_msg",params:{session_id:e.sessionId,msg_id:e.msgId}}}function D(e){const t={file_path:e.file_path};return e.ttl_ms!=null&&(t.ttl_ms=e.ttl_ms),{action:"file_link",params:t}}function E(e){const t={file_path:e.file_path,session_id:e.session_id};return e.caption!=null&&(t.caption=e.caption),e.reply_to_message_id!=null&&(t.reply_to_message_id=e.reply_to_message_id),{action:"file_upload",params:t,timeoutMs:6e4}}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 L(e){return{action:"call_owner",params:{session_id:e.session_id}}}function j(e){return{action:"agent_introduction_update",params:{agent_id:e.agent_id,introduction:e.introduction}}}function N(e){return{action:"dispatch_agent",params:{agent_id:e.agent_id,cwd:e.cwd,task:e.task,title:e.title}}}function z(e){return{action:"session_send",params:{session_id:e.session_id,content:e.content}}}function C(e){const t={};return e.session_id!=null&&(t.session_id=e.session_id),e.page!=null&&(t.page=e.page),e.page_size!=null&&(t.page_size=e.page_size),e.state!=null&&(t.state=e.state),{action:"chat_state_query",params:t}}function P(e){const t=String(e.action??""),n=G[t];if(!n)throw new Error(`Unknown grix_access_control action: ${t}`);const i={};return e.code!=null&&(i.code=e.code),e.sender_id!=null&&(i.sender_id=e.sender_id),e.policy!=null&&(i.policy=e.policy),{action:"claude_access_control",params:{verb:n,payload:i},timeoutMs:3e4}}function U(e){const t={session_id:e.session_id,state:e.state};return e.reason!=null&&(t.reason=e.reason),{action:"chat_state_update",params:t}}function R(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 Z=new Set([...Object.values(g),...Object.values(y),...Object.values(h),"send_msg","delete_msg","file_link","file_upload","call_owner","agent_introduction_update","dispatch_agent","session_send","chat_state_query","chat_state_update","claude_access_control"]),G={pair_approve:"pair_approve",pair_deny:"pair_deny",allow_sender:"sender_allow",remove_sender:"sender_remove",set_policy:"policy_set"};export{G as ACCESS_CONTROL_ACTION_MAP,S as ALL_TOOLS,a as EVENT_TOOLS,A as EXPOSED_TOOLS,Z as PHASE1_INVOKE_ACTIONS,w as PHASE1_TOOL_NAMES,x as PHASE2_TOOL_NAMES,r as TOOLS,p as TOOL_ALIASES,B as TOOL_MAP,F as isAlias,W as isGrixInternalToolName,Q as mapToolAlias,m as normalizeEventToolArgs,H as toolCallToInvoke};
|
|
1
|
+
const r=[{name:"grix_query",description:"Search contacts, sessions, message history, messages, or favorited sessions in the Grix/AIBot platform.",inputSchema:{type:"object",properties:{action:{type:"string",enum:["contact_search","session_search","message_history","message_search","search_favorite_sessions"],description:"Query action type. search_favorite_sessions returns the owner's favorited sessions (supports keyword filter)."},id:{type:"string",description:"Contact ID (contact_search) or Session ID (session_search)."},keyword:{type:"string",description:"Search keyword."},sessionType:{type:"integer",enum:[1,2],description:"Filter by session type (session_search only): 1=private chat, 2=group chat. Omit to return all."},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","search_favorite_sessions"]},id:{type:"string"},keyword:{type:"string",maxLength:200},sessionType:{type:"integer",enum:[1,2]},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. The link is reachable only inside the tailnet, so just send it as-is \u2014 no need to worry about or mention any link lifetime. The download link is HTTPS, served by a built-in self-signed CA. The result also returns `ca_install_url`: the first time you share a link with a user (or whenever their browser warns the cert is untrusted), also give them this CA install link so they can install and trust it once \u2014 after that all download links work without warnings. 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. Leave unset to use the long default; set only if you deliberately want a short-lived link."}},required:["file_path"]},validation:{required:["file_path"],properties:{file_path:{type:"string",maxLength:4096},ttl_ms:{type:"integer",minimum:1e4,maximum:864e5}}}},{name:"grix_file_upload",description:"Upload a local file to the Grix platform and send it as a media message in the target session. Supports images (jpg/png/webp/gif/bmp/heic/heif), videos (mp4/mov/m4v/webm/mkv/avi), documents (pdf/doc/docx/xls/xlsx/ppt/pptx/txt/md/csv/json/xml), and archives (zip/rar/7z/tar/gz). Max 50 MB. Use this instead of grix_file_link when the file should appear as a native attachment in the chat (visible inline for images/videos), rather than a tailnet download link.",inputSchema:{type:"object",properties:{file_path:{type:"string",description:"Absolute path to a local file to upload."},session_id:{type:"string",description:"Target session ID to send the file to."},caption:{type:"string",description:"Optional text caption for the media message."},reply_to_message_id:{type:"string",description:"Optional message ID to quote/reply to."}},required:["file_path","session_id"]},validation:{required:["file_path","session_id"],properties:{file_path:{type:"string",maxLength:4096},session_id:{type:"string"},caption:{type:"string",maxLength:2e3},reply_to_message_id:{type:"string"}}}},{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 display name and/or text introduction of one of your owner's agents, identified by its numeric agent ID. Provide agent_name, introduction, or both \u2014 at least one is required.",inputSchema:{type:"object",properties:{agent_id:{type:"string",description:"Target agent's numeric ID, passed as a string."},agent_name:{type:"string",description:"New display name (max 100 characters, must be unique among the owner's agents)."},introduction:{type:"string",description:"New text introduction (max 300 characters)."}},required:["agent_id"]},validation:{required:["agent_id"],properties:{agent_id:{type:"string"},agent_name:{type:"string",maxLength:100},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 creates a NEW private session between the owner and that agent for each dispatch (it does not reuse past sessions), 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. Because the task is delivered as the owner, write it in the owner's first-person voice and tone \u2014 phrase it the way the owner would speak directly to the agent (e.g. "\u5E2E\u6211\u2026", "\u4F60\u53BB\u2026"), NOT as a third-person relay or as yourself narrating on the owner's behalf. Provide a short title summarizing the core of the task \u2014 it becomes the new session's title; if omitted the backend derives one from the task text.`,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, written in the owner's first-person voice and tone \u2014 it is delivered into the session as the owner, so phrase it as the owner speaking directly to the agent, not as a third-person relay."},title:{type:"string",description:"Optional short title (a few words) summarizing the core of the task; used as the new session's title. If omitted, the backend derives a title from the task text."}},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},title:{type:"string",maxLength:255}}}},{name:"grix_session_send",description:"Send a message into a session AS THE OWNER \u2014 it appears as if the owner sent it, NOT as you (the agent). Use ONLY to relay on the owner's behalf into one of the owner's OTHER sessions that you are not part of (e.g. you were dispatched to work and need to drop a note to the owner elsewhere). NEVER use this to send your own reply in a session you are conversing in \u2014 that would make your words show up as the owner's message. To answer in your current conversation, reply normally (or use grix_message_send to send as yourself). The owner must be a member of the target session, and you (the agent) must NOT be a member of it \u2014 sending into a session you belong to is rejected.",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_chat_state_query",description:"Query the chat-level task states across all of your owner's sessions (including direct and group chats). 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). Also returns the session title (task_title) for easy identification. Supports pagination (page/page_size) and optional state filtering. Use this to see at a glance which chats are done, still running, or waiting on the owner.",inputSchema:{type:"object",properties:{session_id:{type:"string",description:"(Optional) Query a single session by its ID. Omit to return all sessions."},page:{type:"number",description:"(Optional) Page number, starting from 1. Defaults to 1 if omitted."},page_size:{type:"number",description:"(Optional) Number of items per page, max 100. Defaults to 10 if omitted."},state:{type:"string",description:"(Optional) Filter by a specific state: running, waiting_approval, waiting_question, completed, failed, or idle. Omit to return all states."}}},validation:{required:[],properties:{}}},{name:"grix_chat_state_update",description:"Manually update the task state of a specific chat session. Use this to mark a chat as completed, failed, idle, or any other state when you need to override it manually. The reason is written to stop_reason and is optional.",inputSchema:{type:"object",properties:{session_id:{type:"string",description:"The session ID whose state to update."},state:{type:"string",enum:["running","waiting_approval","waiting_question","completed","failed","idle"],description:"The new state to set. Must be one of: running, waiting_approval, waiting_question, completed, failed, idle."},reason:{type:"string",description:"(Optional) Reason for the state change, written to stop_reason."}}},validation:{required:["session_id","state"],properties:{}}},{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_skill_set",description:"Create, update, or delete one of your owner's custom skills in the platform skill library. The skill library auto-syncs to every machine the owner runs an agent on (a per-machine `grix/skills` directory), so defining a skill here makes it available on all of them. A skill is a plain SKILL.md package (frontmatter + body), same standard as other Grix skills. Provide `name` and the full `content` (the SKILL.md text) to create or overwrite by name. To delete a skill, pass its `name` with an empty `content` string. How and when any agent actually loads/uses a synced skill is arranged by the owner (in a system prompt or in conversation); this tool only manages the library.",inputSchema:{type:"object",properties:{name:{type:"string",description:"Skill name (max 100 characters); unique within the owner. Used as the identifier and the synced directory name."},content:{type:"string",description:"Full SKILL.md text. Pass an empty string to delete the skill by name."}},required:["name","content"]},validation:{required:["name","content"],properties:{name:{type:"string",maxLength:100},content:{type:"string",maxLength:262144}}}},{name:"grix_skill_get",description:"Read one of your owner's custom skills by name (returns its full SKILL.md content), or list the owner's skill library when no name is given. Use this before editing to fetch current content, then call grix_skill_set with the revised text.",inputSchema:{type:"object",properties:{name:{type:"string",description:"Skill name to fetch. Omit to list all skills (names + versions) instead."}}},validation:{required:[],properties:{name:{type:"string",maxLength:100}}}}],a=[{name:"grix_reply",description:"Send your final reply \u2014 the conclusion the user is waiting for \u2014 to the specified session. This is the message the user treats as your answer; deliver your final result here, and use plain text only for brief progress notes while you work. Supports streaming in chunks; the frontend automatically aggregates them into one complete message. The connector quotes the message being answered automatically, so quoted_message_id is optional (set it only to quote a different earlier 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_status",description:"Query the Grix connection status of the current MCP session.",inputSchema:{type:"object",properties:{}},validation:{required:[],properties:{}}}],v={grix_dispatch_agent:"grix-agent-dispatch",grix_agent_update:"grix-agent-dispatch",grix_query:"grix-query",grix_group:"grix-group",grix_message_send:"message-send",grix_message_unsend:"message-unsend",grix_admin:"grix-admin",grix_call_owner:"grix-owner-relay",grix_session_send:"grix-owner-relay",grix_chat_state_query:"grix-chat-state",grix_chat_state_update:"grix-chat-state",grix_status:"grix-chat-state",grix_access_control:"grix-access-control",grix_file_link:"tailnet-file-share"};function b(e){return` Before calling this tool, follow the \`${e}\` skill's procedure first; do not invoke this tool directly without going through that skill's guidance.`}for(const e of[...r,...a]){const t=v[e.name];t&&(e.description+=b(t))}const 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)),w=new Set(r.map(e=>e.name)),x=new Set(a.map(e=>e.name)),k=/([A-Za-z0-9._-]+:[A-Za-z0-9._-]+:[A-Za-z0-9._-]+:[A-Za-z0-9._-]+)/,I=/[A-Za-z0-9._-]+/;function F(e){return!!(w.has(e)||x.has(e)||c.has(e)||e.startsWith("mcp__grix"))}const S=[...r,...a],q=[...S,...p],H=new Map(q.map(e=>[e.name,e]));function Q(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(k)?.[1]:void 0}function u(e){const t=String(e??"").trim();if(!t)return;const n=l(t);if(n)return m(n);const i=t.match(/(?:chat_id|session_id)\s*=\s*"([A-Za-z0-9._-]+)"/)?.[1];if(i)return i;const o=t.match(/[A-Za-z0-9._-]+/g)??[];for(const s of o)if(!(s==="event_id"||s==="chat_id"||s==="session_id")&&s.length>0)return s;return t.match(I)?.[0]}function m(e){if(!e)return;const t=e.split(":",1)[0]?.trim();if(t)return u(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 o=m(i),d=String(n.session_id??""),s=u(n.session_id),f=/\bevent_id\b|["'<>\s]/.test(d);s&&!(f&&o)?n.session_id=s:o&&(n.session_id=o)}return n}function Z(e){return c.has(e)}function K(e,t){switch(e){case"grix_query":return A(t);case"grix_group":return T(t);case"grix_message_send":return M(t);case"grix_message_unsend":return O(t);case"grix_file_link":return L(t);case"grix_file_upload":return D(t);case"grix_admin":return W(t);case"grix_call_owner":return E(t);case"grix_agent_update":return j(t);case"grix_dispatch_agent":return N(t);case"grix_session_send":return z(t);case"grix_chat_state_query":return C(t);case"grix_chat_state_update":return U(t);case"grix_access_control":return P(t);case"grix_skill_set":return R(t);case"grix_skill_get":return G(t);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",search_favorite_sessions:"search_favorite_sessions"};function A(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.sessionType!=null&&(i.session_type=e.sessionType),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 T(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 M(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 O(e){return{action:"delete_msg",params:{session_id:e.sessionId,msg_id:e.msgId}}}function L(e){const t={file_path:e.file_path};return e.ttl_ms!=null&&(t.ttl_ms=e.ttl_ms),{action:"file_link",params:t}}function D(e){const t={file_path:e.file_path,session_id:e.session_id};return e.caption!=null&&(t.caption=e.caption),e.reply_to_message_id!=null&&(t.reply_to_message_id=e.reply_to_message_id),{action:"file_upload",params:t,timeoutMs:6e4}}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 E(e){return{action:"call_owner",params:{session_id:e.session_id}}}function j(e){if(e.agent_name==null&&e.introduction==null)throw new Error("grix_agent_update requires agent_name or introduction");const t={agent_id:e.agent_id};return e.agent_name!=null&&(t.agent_name=e.agent_name),e.introduction!=null&&(t.introduction=e.introduction),{action:"agent_introduction_update",params:t}}function N(e){return{action:"dispatch_agent",params:{agent_id:e.agent_id,cwd:e.cwd,task:e.task,title:e.title}}}function z(e){return{action:"session_send",params:{session_id:e.session_id,content:e.content}}}function C(e){const t={};return e.session_id!=null&&(t.session_id=e.session_id),e.page!=null&&(t.page=e.page),e.page_size!=null&&(t.page_size=e.page_size),e.state!=null&&(t.state=e.state),{action:"chat_state_query",params:t}}function P(e){const t=String(e.action??""),n=B[t];if(!n)throw new Error(`Unknown grix_access_control action: ${t}`);const i={};return e.code!=null&&(i.code=e.code),e.sender_id!=null&&(i.sender_id=e.sender_id),e.policy!=null&&(i.policy=e.policy),{action:"claude_access_control",params:{verb:n,payload:i},timeoutMs:3e4}}function U(e){const t={session_id:e.session_id,state:e.state};return e.reason!=null&&(t.reason=e.reason),{action:"chat_state_update",params:t}}function R(e){return{action:"skill_set",params:{name:e.name,content:e.content},timeoutMs:3e4}}function G(e){const t={};return e.name!=null&&(t.name=e.name),{action:"skill_get",params:t}}function W(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 $=new Set([...Object.values(g),...Object.values(y),...Object.values(h),"send_msg","delete_msg","file_link","file_upload","call_owner","agent_introduction_update","dispatch_agent","session_send","chat_state_query","chat_state_update","claude_access_control","skill_set","skill_get"]),B={pair_approve:"pair_approve",pair_deny:"pair_deny",allow_sender:"sender_allow",remove_sender:"sender_remove",set_policy:"policy_set"};export{B as ACCESS_CONTROL_ACTION_MAP,S as ALL_TOOLS,a as EVENT_TOOLS,q as EXPOSED_TOOLS,$ as PHASE1_INVOKE_ACTIONS,w as PHASE1_TOOL_NAMES,x as PHASE2_TOOL_NAMES,r as TOOLS,p as TOOL_ALIASES,H as TOOL_MAP,Z as isAlias,F as isGrixInternalToolName,Q as mapToolAlias,_ as normalizeEventToolArgs,K as toolCallToInvoke};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{mkdir as d,readFile as $,writeFile as y,rm as p}from"node:fs/promises";import{join as u}from"node:path";import{createHash as v}from"node:crypto";import{log as a}from"../log/index.js";const S=6e4,m=".grix-sync.json",w=15e3;class _{configs;skillsDir;timer=null;running=!1;rerunPending=!1;stopped=!1;fetchImpl;intervalMs;constructor(t,i,n={}){this.configs=t,this.skillsDir=i,this.intervalMs=n.intervalMs??S,this.fetchImpl=n.fetchImpl??fetch}async start(){this.stopped=!1,await this.syncOnce().catch(t=>a.warn("skill-sync",`initial sync failed: ${o(t)}`)),this.timer=setInterval(()=>{this.syncOnce().catch(t=>a.warn("skill-sync",`sync failed: ${o(t)}`))},this.intervalMs),this.timer.unref?.()}stop(){this.stopped=!0,this.rerunPending=!1,this.timer&&(clearInterval(this.timer),this.timer=null)}triggerSync(){if(this.running){this.rerunPending=!0;return}this.syncOnce().catch(t=>a.warn("skill-sync",`triggered sync failed: ${o(t)}`))}async syncOnce(){if(!this.running){this.running=!0;try{const t=await this.pickReachable();if(!t)return;const{cfg:i,remote:n}=t;await d(this.skillsDir,{recursive:!0});const e=await this.readManifest(),c=new Set(n.map(s=>s.name));for(const s of n){const l=e.skills[s.name];if(l&&l.digest===s.digest)continue;const h=I(s.name);if(!h){a.warn("skill-sync",`skip skill with unsafe name: ${s.name}`);continue}const f=await this.fetchContent(i,s.id);if(f!==null)try{await d(u(this.skillsDir,h),{recursive:!0}),await y(u(this.skillsDir,h,"SKILL.md"),f,"utf8"),e.skills[s.name]={id:s.id,version:s.version,digest:s.digest,dir:h},l&&l.dir!==h&&!this.dirSharedByOthers(e,s.name,l.dir)&&await p(u(this.skillsDir,l.dir),{recursive:!0,force:!0}).catch(()=>{})}catch(g){a.warn("skill-sync",`write skill "${s.name}" failed: ${o(g)}`)}}for(const s of Object.keys(e.skills)){if(c.has(s))continue;const l=e.skills[s];this.dirSharedByOthers(e,s,l.dir)||await p(u(this.skillsDir,l.dir),{recursive:!0,force:!0}).catch(()=>{}),delete e.skills[s]}await this.writeManifest(e)}finally{this.running=!1,this.rerunPending&&(this.rerunPending=!1,(this.timer!==null||!this.stopped)&&this.syncOnce().catch(t=>a.warn("skill-sync",`rerun sync failed: ${o(t)}`)))}}}dirSharedByOthers(t,i,n){return Object.entries(t.skills).some(([e,c])=>e!==i&&c.dir===n)}async pickReachable(){for(const t of this.configs){if(!t.apiKey||!t.wsUrl)continue;const i=await this.fetchList(t);if(i!==null)return{cfg:t,remote:i}}return null}async fetchList(t){const i=`${k(t.wsUrl)}/v1/agent-api/skills`;try{const n=await this.fetchImpl(i,{headers:{Authorization:`Bearer ${t.apiKey}`},signal:AbortSignal.timeout(w)});if(!n.ok)return a.warn("skill-sync",`list returned ${n.status}`),null;const e=await n.json();return e.code!==0?null:e.data?.items??[]}catch(n){return a.warn("skill-sync",`list error: ${o(n)}`),null}}async fetchContent(t,i){const n=`${k(t.wsUrl)}/v1/agent-api/skills/${encodeURIComponent(i)}/content`;try{const e=await this.fetchImpl(n,{headers:{Authorization:`Bearer ${t.apiKey}`},signal:AbortSignal.timeout(w)});if(!e.ok)return a.warn("skill-sync",`content ${i} returned ${e.status}`),null;const c=await e.json();return c.code!==0?null:typeof c.data?.content=="string"?c.data.content:null}catch(e){return a.warn("skill-sync",`content ${i} error: ${o(e)}`),null}}async readManifest(){try{const t=await $(u(this.skillsDir,m),"utf8"),i=JSON.parse(t);if(i&&typeof i=="object"&&i.skills)return i}catch{}return{skills:{}}}async writeManifest(t){await y(u(this.skillsDir,m),JSON.stringify(t,null,2),"utf8")}}function k(r){return new URL(r.replace(/^wss:/,"https:").replace(/^ws:/,"http:")).origin}function I(r){const t=r.trim().replace(/[/\\]/g,"_").replace(/[<>:"|?*\u0000-\u001f]/g,"_").replace(/^\.+/,"").replace(/[. ]+$/,"");return!t||t.includes("..")||/^(con|prn|aux|nul|com[1-9]|lpt[1-9])$/i.test(t)?null:t!==r?`${t}-${v("sha1").update(r).digest("hex").slice(0,8)}`:t}function o(r){return r instanceof Error?r.message:String(r)}export{_ as SkillSyncer};
|