grix-connector 2.0.2 → 2.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- import{log as f}from"../core/log/index.js";const v=2,o=3e3;class h{name;deferredClaudeEvents=new Map;deferredClaudeReplayRetries=new Map;deferredCodexEvents=new Map;deferredCursorEvents=new Map;deferredAcpEvents=new Map;deferredPiEvents=new Map;deferredOpenHumanEvents=new Map;deferredCodeWhaleEvents=new Map;deferredOpenCodeEvents=new Map;deferredAgyEvents=new Map;constructor(e){this.name=e}deferClaudeEvent(e,t){if(!e)return;const r=this.deferredClaudeEvents.get(e)??[];r.some(n=>n.event_id===t.event_id)||(f.info(this.name,`deferClaudeEvent session_id=${e} event_id=${t.event_id} queue_len=${r.length}`),r.push(t),this.deferredClaudeEvents.set(e,r))}deferCodexEvent(e,t){if(!e)return;const r=this.deferredCodexEvents.get(e)??[];r.some(n=>n.event_id===t.event_id)||(r.push(t),this.deferredCodexEvents.set(e,r))}deferCursorEvent(e,t){if(!e)return;const r=this.deferredCursorEvents.get(e)??[];r.some(n=>n.event_id===t.event_id)||(r.push(t),this.deferredCursorEvents.set(e,r))}deferAcpEvent(e,t){if(!e)return;const r=this.deferredAcpEvents.get(e)??[];r.some(n=>n.event_id===t.event_id)||(r.push(t),this.deferredAcpEvents.set(e,r))}deferPiEvent(e,t){if(!e)return;const r=this.deferredPiEvents.get(e)??[];r.some(n=>n.event_id===t.event_id)||(r.push(t),this.deferredPiEvents.set(e,r))}deferOpenHumanEvent(e,t){if(!e)return;const r=this.deferredOpenHumanEvents.get(e)??[];r.some(n=>n.event_id===t.event_id)||(r.push(t),this.deferredOpenHumanEvents.set(e,r))}deferCodeWhaleEvent(e,t){if(!e)return;const r=this.deferredCodeWhaleEvents.get(e)??[];r.some(n=>n.event_id===t.event_id)||(r.push(t),this.deferredCodeWhaleEvents.set(e,r))}deferOpenCodeEvent(e,t){if(!e)return;const r=this.deferredOpenCodeEvents.get(e)??[];r.some(n=>n.event_id===t.event_id)||(r.push(t),this.deferredOpenCodeEvents.set(e,r))}deferAgyEvent(e,t){if(!e)return;const r=this.deferredAgyEvents.get(e)??[];r.some(n=>n.event_id===t.event_id)||(r.push(t),this.deferredAgyEvents.set(e,r))}async replayDeferredClaudeEvents(e,t){const r=this.deferredClaudeEvents.get(e);if(!(!r||r.length===0)){this.deferredClaudeEvents.delete(e);for(const n of r)try{t.captureEventRuntimeConfig(n),await t.deliverInboundEvent(n),this.deferredClaudeReplayRetries.delete(n.event_id)}catch(d){const s=d instanceof Error?d.message:String(d),a=this.deferredClaudeReplayRetries.get(n.event_id)??0;if(this.isClaudeDeferredReplayRetriable(s)&&a<v){this.deferredClaudeReplayRetries.set(n.event_id,a+1);const u=this.deferredClaudeEvents.get(e)??[];u.some(i=>i.event_id===n.event_id)||(u.push(n),this.deferredClaudeEvents.set(e,u)),f.warn(this.name,`replayDeferredClaudeEvents retry ${a+1}/${v} session_id=${e} event_id=${n.event_id} reason=${s}`),setTimeout(()=>{this.replayDeferredClaudeEvents(e,t).catch(i=>{f.warn(this.name,`replayDeferredClaudeEvents async retry failed session_id=${e}: ${i instanceof Error?i.message:String(i)}`)})},o).unref();continue}this.deferredClaudeReplayRetries.delete(n.event_id),t.sendEventResult(n.event_id,"failed",s)}}}async replayDeferredCodexEvents(e,t,r={}){const n=this.deferredCodexEvents.get(e);if(!(!n||n.length===0)){this.deferredCodexEvents.delete(e),r.announceComposing!==!1&&t.sendSessionComposing?.(e,!0);for(const d of n)try{t.captureEventRuntimeConfig(d),await t.deliverInboundEvent(d)}catch(s){t.sendEventResult(d.event_id,"failed",s instanceof Error?s.message:String(s))}}}async replayDeferredCursorEvents(e,t,r={}){const n=this.deferredCursorEvents.get(e);if(!(!n||n.length===0)){this.deferredCursorEvents.delete(e);for(const d of n)try{t.captureEventRuntimeConfig(d),await t.deliverInboundEvent(d)}catch(s){t.sendEventResult(d.event_id,"failed",s instanceof Error?s.message:String(s))}}}async replayDeferredPiEvents(e,t){const r=this.deferredPiEvents.get(e);if(!(!r||r.length===0)){this.deferredPiEvents.delete(e),t.sendSessionComposing?.(e,!0,{ttlMs:3e4});for(const n of r)try{t.captureEventRuntimeConfig(n),await t.deliverInboundEvent(n)}catch(d){t.sendSessionComposing?.(e,!1),t.sendEventResult(n.event_id,"failed",d instanceof Error?d.message:String(d))}}}async replayDeferredOpenHumanEvents(e,t){const r=this.deferredOpenHumanEvents.get(e);if(!(!r||r.length===0)){this.deferredOpenHumanEvents.delete(e),t.sendSessionComposing?.(e,!0);for(const n of r)try{t.captureEventRuntimeConfig(n),await t.deliverInboundEvent(n)}catch(d){t.sendEventResult(n.event_id,"failed",d instanceof Error?d.message:String(d))}}}async replayDeferredOpenCodeEvents(e,t){const r=this.deferredOpenCodeEvents.get(e);if(!(!r||r.length===0)){this.deferredOpenCodeEvents.delete(e),t.sendSessionComposing?.(e,!0);for(const n of r)try{t.captureEventRuntimeConfig(n),await t.deliverInboundEvent(n)}catch(d){t.sendEventResult(n.event_id,"failed",d instanceof Error?d.message:String(d))}}}async replayDeferredAgyEvents(e,t){const r=this.deferredAgyEvents.get(e);if(!(!r||r.length===0)){this.deferredAgyEvents.delete(e),t.sendSessionComposing?.(e,!0);for(const n of r)try{t.captureEventRuntimeConfig(n),await t.deliverInboundEvent(n)}catch(d){t.sendEventResult(n.event_id,"failed",d instanceof Error?d.message:String(d))}}}async replayDeferredAcpEvents(e,t){const r=this.deferredAcpEvents.get(e);if(!(!r||r.length===0)){this.deferredAcpEvents.delete(e),t.sendSessionComposing?.(e,!0);for(const n of r)try{t.captureEventRuntimeConfig(n),await t.deliverInboundEvent(n)}catch(d){t.sendEventResult(n.event_id,"failed",d instanceof Error?d.message:String(d))}}}async replayDeferredCodeWhaleEvents(e,t,r={}){const n=this.deferredCodeWhaleEvents.get(e);if(!(!n||n.length===0)){this.deferredCodeWhaleEvents.delete(e),r.announceComposing!==!1&&t.sendSessionComposing?.(e,!0);for(const d of n)try{t.captureEventRuntimeConfig(d),await t.deliverInboundEvent(d)}catch(s){t.sendEventResult(d.event_id,"failed",s instanceof Error?s.message:String(s))}}}sendCodexDeferredReplayComposing(e,t){const r=this.deferredCodexEvents.get(e);!r||r.length===0||t.sendSessionComposing?.(e,!0,{ttlMs:12e4,activity:"preparing"})}sendCursorDeferredReplayComposing(e,t){const r=this.deferredCursorEvents.get(e);!r||r.length===0||t.sendSessionComposing?.(e,!0)}hasDeferred(e,t){return this.getMap(e).has(t)}getAllDeferredEvents(){return[...this.deferredClaudeEvents.values(),...this.deferredCodexEvents.values(),...this.deferredCursorEvents.values(),...this.deferredPiEvents.values(),...this.deferredAcpEvents.values(),...this.deferredOpenHumanEvents.values(),...this.deferredCodeWhaleEvents.values(),...this.deferredOpenCodeEvents.values(),...this.deferredAgyEvents.values()].flat()}clearAll(){this.deferredClaudeEvents.clear(),this.deferredClaudeReplayRetries.clear(),this.deferredCodexEvents.clear(),this.deferredCursorEvents.clear(),this.deferredPiEvents.clear(),this.deferredOpenHumanEvents.clear(),this.deferredCodeWhaleEvents.clear(),this.deferredOpenCodeEvents.clear(),this.deferredAgyEvents.clear(),this.deferredAcpEvents.clear()}removeEvent(e){for(const t of[this.deferredClaudeEvents,this.deferredCodexEvents,this.deferredCursorEvents,this.deferredAcpEvents,this.deferredPiEvents,this.deferredOpenHumanEvents,this.deferredCodeWhaleEvents,this.deferredOpenCodeEvents,this.deferredAgyEvents])for(const[r,n]of t.entries()){const d=n.findIndex(s=>s.event_id===e);if(d>=0)return n.splice(d,1),n.length===0&&t.delete(r),this.deferredClaudeReplayRetries.delete(e),f.info(this.name,`removeEvent event_id=${e} from deferred queue`),!0}return!1}clearSession(e){const t=this.deferredClaudeEvents.get(e)??[];for(const r of t)this.deferredClaudeReplayRetries.delete(r.event_id);this.deferredClaudeEvents.delete(e),this.deferredCodexEvents.delete(e),this.deferredCursorEvents.delete(e),this.deferredAcpEvents.delete(e),this.deferredPiEvents.delete(e),this.deferredOpenHumanEvents.delete(e),this.deferredCodeWhaleEvents.delete(e),this.deferredOpenCodeEvents.delete(e),this.deferredAgyEvents.delete(e)}isClaudeDeferredReplayRetriable(e){const t=e.toLowerCase();return t.includes("claude channel listener not ready")||t.includes("notify port")||t.includes("mcp stdio server not available")||t.includes("mcp server startup failed")}getMap(e){switch(e){case"claude":return this.deferredClaudeEvents;case"codex":return this.deferredCodexEvents;case"cursor":return this.deferredCursorEvents;case"acp":return this.deferredAcpEvents;case"pi":return this.deferredPiEvents;case"openhuman":return this.deferredOpenHumanEvents;case"codewhale":return this.deferredCodeWhaleEvents;case"opencode":return this.deferredOpenCodeEvents;case"agy":return this.deferredAgyEvents}}}export{h as DeferredEventManager};
1
+ import{log as l}from"../core/log/index.js";const v=2,E=3e3,C=h=>{const e=h.toLowerCase();return e.includes("claude channel listener not ready")||e.includes("notify port")||e.includes("mcp stdio server not available")||e.includes("mcp server startup failed")},y={claude:{announceComposing:!1,retriable:C,retryMax:v,retryDelayMs:E},codex:{announceComposing:!0,composingOptsOverridable:!0},codewhale:{announceComposing:!0,composingOptsOverridable:!0},cursor:{announceComposing:!1},pi:{announceComposing:!0,composingTtlMs:3e4,stopComposingOnError:!0},openhuman:{announceComposing:!0},opencode:{announceComposing:!0},agy:{announceComposing:!0},acp:{announceComposing:!0}},g=["claude","codex","cursor","acp","pi","openhuman","codewhale","opencode","agy"];class _{name;deferredClaudeEvents=new Map;deferredClaudeReplayRetries=new Map;deferredCodexEvents=new Map;deferredCursorEvents=new Map;deferredAcpEvents=new Map;deferredPiEvents=new Map;deferredOpenHumanEvents=new Map;deferredCodeWhaleEvents=new Map;deferredOpenCodeEvents=new Map;deferredAgyEvents=new Map;constructor(e){this.name=e}defer(e,r,t){if(!r)return;const n=this.getMap(e),d=n.get(r)??[];d.some(i=>i.event_id===t.event_id)||(l.info(this.name,`defer channel=${e} session_id=${r} event_id=${t.event_id} queue_len=${d.length}`),d.push(t),n.set(r,d))}deferClaudeEvent(e,r){this.defer("claude",e,r)}deferCodexEvent(e,r){this.defer("codex",e,r)}deferCursorEvent(e,r){this.defer("cursor",e,r)}deferAcpEvent(e,r){this.defer("acp",e,r)}deferPiEvent(e,r){this.defer("pi",e,r)}deferOpenHumanEvent(e,r){this.defer("openhuman",e,r)}deferCodeWhaleEvent(e,r){this.defer("codewhale",e,r)}deferOpenCodeEvent(e,r){this.defer("opencode",e,r)}deferAgyEvent(e,r){this.defer("agy",e,r)}async replayChannel(e,r,t,n={}){const d=this.getMap(e),i=d.get(r);if(!i||i.length===0)return;d.delete(r);const a=y[e];((a.composingOptsOverridable?n.announceComposing:void 0)??a.announceComposing)&&t.sendSessionComposing?.(r,!0,a.composingTtlMs?{ttlMs:a.composingTtlMs}:void 0);for(const s of i)try{t.captureEventRuntimeConfig(s),await t.deliverInboundEvent(s),this.deferredClaudeReplayRetries.delete(s.event_id)}catch(f){const p=f instanceof Error?f.message:String(f),u=this.deferredClaudeReplayRetries.get(s.event_id)??0;if(a.retriable?.(p)&&u<(a.retryMax??0)){this.deferredClaudeReplayRetries.set(s.event_id,u+1);const c=d.get(r)??[];c.some(o=>o.event_id===s.event_id)||(c.push(s),d.set(r,c)),l.warn(this.name,`replay retry ${u+1}/${a.retryMax} channel=${e} session_id=${r} event_id=${s.event_id} reason=${p}`),setTimeout(()=>{this.replayChannel(e,r,t,n).catch(o=>{l.warn(this.name,`replay async retry failed channel=${e} session_id=${r}: ${o instanceof Error?o.message:String(o)}`)})},a.retryDelayMs??0).unref();continue}this.deferredClaudeReplayRetries.delete(s.event_id),a.stopComposingOnError&&t.sendSessionComposing?.(r,!1),t.sendEventResult(s.event_id,"failed",p)}}async replayDeferredClaudeEvents(e,r){await this.replayChannel("claude",e,r)}async replayDeferredCodexEvents(e,r,t={}){await this.replayChannel("codex",e,r,t)}async replayDeferredCursorEvents(e,r,t={}){await this.replayChannel("cursor",e,r,t)}async replayDeferredPiEvents(e,r){await this.replayChannel("pi",e,r)}async replayDeferredOpenHumanEvents(e,r){await this.replayChannel("openhuman",e,r)}async replayDeferredOpenCodeEvents(e,r){await this.replayChannel("opencode",e,r)}async replayDeferredAgyEvents(e,r){await this.replayChannel("agy",e,r)}async replayDeferredAcpEvents(e,r){await this.replayChannel("acp",e,r)}async replayDeferredCodeWhaleEvents(e,r,t={}){await this.replayChannel("codewhale",e,r,t)}async release(e,r,t={}){for(const n of g)this.getMap(n).has(e)&&await this.replayChannel(n,e,r,t)}sendCodexDeferredReplayComposing(e,r){const t=this.deferredCodexEvents.get(e);!t||t.length===0||r.sendSessionComposing?.(e,!0,{ttlMs:12e4,activity:"preparing"})}sendCursorDeferredReplayComposing(e,r){const t=this.deferredCursorEvents.get(e);!t||t.length===0||r.sendSessionComposing?.(e,!0)}hasDeferred(e,r){return this.getMap(e).has(r)}getAllDeferredEvents(){return[...this.deferredClaudeEvents.values(),...this.deferredCodexEvents.values(),...this.deferredCursorEvents.values(),...this.deferredPiEvents.values(),...this.deferredAcpEvents.values(),...this.deferredOpenHumanEvents.values(),...this.deferredCodeWhaleEvents.values(),...this.deferredOpenCodeEvents.values(),...this.deferredAgyEvents.values()].flat()}clearAll(){this.deferredClaudeEvents.clear(),this.deferredClaudeReplayRetries.clear(),this.deferredCodexEvents.clear(),this.deferredCursorEvents.clear(),this.deferredPiEvents.clear(),this.deferredOpenHumanEvents.clear(),this.deferredCodeWhaleEvents.clear(),this.deferredOpenCodeEvents.clear(),this.deferredAgyEvents.clear(),this.deferredAcpEvents.clear()}removeEvent(e){for(const r of[this.deferredClaudeEvents,this.deferredCodexEvents,this.deferredCursorEvents,this.deferredAcpEvents,this.deferredPiEvents,this.deferredOpenHumanEvents,this.deferredCodeWhaleEvents,this.deferredOpenCodeEvents,this.deferredAgyEvents])for(const[t,n]of r.entries()){const d=n.findIndex(i=>i.event_id===e);if(d>=0)return n.splice(d,1),n.length===0&&r.delete(t),this.deferredClaudeReplayRetries.delete(e),l.info(this.name,`removeEvent event_id=${e} from deferred queue`),!0}return!1}clearSession(e){const r=this.deferredClaudeEvents.get(e)??[];for(const t of r)this.deferredClaudeReplayRetries.delete(t.event_id);this.deferredClaudeEvents.delete(e),this.deferredCodexEvents.delete(e),this.deferredCursorEvents.delete(e),this.deferredAcpEvents.delete(e),this.deferredPiEvents.delete(e),this.deferredOpenHumanEvents.delete(e),this.deferredCodeWhaleEvents.delete(e),this.deferredOpenCodeEvents.delete(e),this.deferredAgyEvents.delete(e)}getMap(e){switch(e){case"claude":return this.deferredClaudeEvents;case"codex":return this.deferredCodexEvents;case"cursor":return this.deferredCursorEvents;case"acp":return this.deferredAcpEvents;case"pi":return this.deferredPiEvents;case"openhuman":return this.deferredOpenHumanEvents;case"codewhale":return this.deferredCodeWhaleEvents;case"opencode":return this.deferredOpenCodeEvents;case"agy":return this.deferredAgyEvents}}}export{_ as DeferredEventManager};
@@ -1 +1 @@
1
- const r=25e3;class a{config;callbacks;running=new Map;queued=[];timers=new Map;composingTimers=new Map;constructor(e,t){this.config=e,this.callbacks=t}submit(e){return this.running.has(e.event_id)||this.queued.some(t=>t.event_id===e.event_id)?"accepted":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}complete(e){const i=this.running.get(e)?.session_id;this.running.delete(e),this.clearTimer(e),this.drainNext(),i&&this.checkStopComposing(i)}clear(e){const t=[],i=[];for(const s of this.queued)s.session_id===e?(this.clearTimer(s.event_id),this.callbacks.onStateChange(s.event_id,e,"canceled",{reason:"queue cleared"}),t.push(s.event_id)):i.push(s);return this.queued=i,t.length>0&&this.broadcastQueuePositions(),this.checkStopComposing(e),t}drainQueuedForSession(e){const t=[],i=[];for(const s of this.queued)s.session_id===e?(this.clearTimer(s.event_id),t.push(s)):i.push(s);return this.queued=i,t}snapshot(e){const t=[...this.running.values()].filter(n=>n.session_id===e),i=t.map(n=>n.event_id),s=t.map(n=>({event_id:n.event_id,content_preview:this.buildQueueItemTitle(n.content),title:this.buildQueueItemTitle(n.content),summary:this.buildQueueItemTitle(n.content)})),u=this.queued.flatMap((n,o)=>n.session_id===e?[{event_id:n.event_id,position:o+1,content_preview:this.buildQueueItemTitle(n.content),title:this.buildQueueItemTitle(n.content),summary:this.buildQueueItemTitle(n.content)}]:[]);return{running:i,running_items:s,queued:u}}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()}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(s=>s.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.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)}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)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)return t.event_id}}export{a as EventQueue};
1
+ const c=25e3;class a{config;callbacks;running=new Map;queued=[];timers=new Map;composingTimers=new Map;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(s=>s.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.clearTimer(e),queueMicrotask(()=>this.drainNext()),i&&this.checkStopComposing(i)}clear(e){const t=[],i=[];for(const s of this.queued)s.session_id===e?(this.clearTimer(s.event_id),this.callbacks.onStateChange(s.event_id,e,"canceled",{reason:"queue cleared"}),t.push(s.event_id)):i.push(s);return this.queued=i,t.length>0&&this.broadcastQueuePositions(),this.checkStopComposing(e),t}drainQueuedForSession(e){const t=[],i=[];for(const s of this.queued)s.session_id===e?(this.clearTimer(s.event_id),t.push(s)):i.push(s);return this.queued=i,t}snapshot(e){const t=[...this.running.values()].filter(n=>n.session_id===e),i=t.map(n=>n.event_id),s=t.map(n=>({event_id:n.event_id,content_preview:this.buildQueueItemTitle(n.content),title:this.buildQueueItemTitle(n.content),summary:this.buildQueueItemTitle(n.content)})),u=this.queued.flatMap((n,o)=>n.session_id===e?[{event_id:n.event_id,position:o+1,content_preview:this.buildQueueItemTitle(n.content),title:this.buildQueueItemTitle(n.content),summary:this.buildQueueItemTitle(n.content)}]:[]);return{running:i,running_items:s,queued:u}}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(s=>s.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)}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)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)return t.event_id}}export{a as EventQueue};
@@ -1,2 +1,2 @@
1
- import{randomUUID as y}from"node:crypto";import{execFile as $}from"node:child_process";import A from"node:http";import{createReadStream as T,createWriteStream as U}from"node:fs";import{stat as v,rename as k,unlink as C,access as M}from"node:fs/promises";import{basename as w,extname as S,isAbsolute as P,join as b,normalize as E,resolve as F}from"node:path";import*as N from"node:os";const L=600*1e3,D={".jpg":"image/jpeg",".jpeg":"image/jpeg",".png":"image/png",".gif":"image/gif",".webp":"image/webp",".svg":"image/svg+xml",".bmp":"image/bmp",".tiff":"image/tiff",".tif":"image/tiff",".ico":"image/x-icon",".avif":"image/avif"};function x(t){return D[S(t).toLowerCase()]}function g(t){const e=t.split(".");if(e.length!==4)return!1;const i=Number(e[0]),o=Number(e[1]);return!Number.isInteger(i)||!Number.isInteger(o)?!1:i===100&&o>=64&&o<=127}function z(){return new Promise(t=>{$("tailscale",["ip","-4"],{timeout:3e3},(e,i)=>{if(e){t(void 0);return}const o=i.trim().split(`
2
- `)[0].trim();t(o&&g(o)?o:void 0)})})}function H(){const t=N.networkInterfaces();for(const e of Object.values(t))if(e){for(const i of e)if(!(i.family!=="IPv4"||i.internal)&&g(i.address))return i.address}}async function O(){const t=await z();return t!==void 0?t:H()}const l=new Map;let d=null,u="",p=0,m=null;function R(t){for(const[e,i]of l)i.expiresAt<=t&&l.delete(e)}const j=2*1024*1024*1024;function G(t){const e=t.socket.remoteAddress??"",i=e.startsWith("::ffff:")?e.slice(7):e;return g(i)}async function V(t,e){const i=S(e),o=w(e,i);let n=b(t,e),r=0;for(;;)try{await M(n),r++,n=b(t,`${o}(${r})${i}`)}catch{return n}}function W(t,e,i){return new Promise((o,n)=>{const r=U(e);let a=0,f=!1;const s=()=>{f||(f=!0,r.destroy(),C(e).catch(()=>{}))};t.on("data",c=>{a+=c.length,a>j&&(s(),n(Object.assign(new Error("file too large"),{code:413})))}),r.on("error",c=>{s(),n(c)}),t.on("error",c=>{s(),n(c)}),r.on("finish",async()=>{if(!f){f=!0;try{await k(e,i),o()}catch(c){C(e).catch(()=>{}),n(c)}}}),t.pipe(r)})}async function B(t,e){if(!G(t)){e.statusCode=403,e.end("forbidden");return}const o=new URL(t.url??"/",`http://${t.headers.host}`).searchParams.get("dir")??"";if(!P(o)||E(o)!==F(o)){e.statusCode=400,e.end("invalid dir");return}const n=t.headers["x-filename"]??"",r=Array.isArray(n)?n[0]:n;let a;try{a=decodeURIComponent(r)}catch{a=r}if(!a||a.includes("/")||a.includes("\\")||a==="."||a===".."){e.statusCode=400,e.end("invalid filename");return}try{if(!(await v(o)).isDirectory()){e.statusCode=400,e.end("dir not found");return}}catch{e.statusCode=400,e.end("dir not found");return}const f=await V(o,a),s=`${f}.${y()}.tmp`;try{await W(t,s,f),e.statusCode=200,e.setHeader("Content-Type","application/json"),e.end(JSON.stringify({ok:!0,path:f,name:w(f)}))}catch(c){c.code===413?(e.statusCode=413,e.end("file too large")):(e.statusCode=500,e.end("upload failed"))}}async function J(t){const e=A.createServer((o,n)=>{n.setHeader("Access-Control-Allow-Origin","*");const r=String(o.url??"").split("?")[0];if(r==="/ping"){n.statusCode=200,n.end("ok");return}if(r==="/upload"&&o.method==="POST"){B(o,n).catch(()=>{n.headersSent||(n.statusCode=500,n.end("internal error"))});return}const f=/^\/d\/([A-Za-z0-9-]+)$/.exec(r)?.[1],s=f?l.get(f):void 0;if(!s||s.expiresAt<=Date.now()){n.statusCode=404,n.end("not found");return}n.statusCode=200;const c=x(s.fileName);n.setHeader("Content-Type",c??"application/octet-stream"),n.setHeader("Content-Length",String(s.size)),c?n.setHeader("Content-Disposition",`inline; filename*=UTF-8''${encodeURIComponent(s.fileName)}`):n.setHeader("Content-Disposition",`attachment; filename*=UTF-8''${encodeURIComponent(s.fileName)}`);const h=T(s.filePath);h.on("error",()=>{n.headersSent||(n.statusCode=500),n.end()}),h.pipe(n)});await new Promise((o,n)=>{e.once("error",n),e.listen(0,t,()=>{e.removeListener("error",n),o()})});const i=e.address();d=e,u=t,p=typeof i=="object"&&i?i.port:0}async function I(){const t=d;d=null,u="",p=0,t&&await new Promise(e=>t.close(()=>e()))}async function _(t){d&&u===t||(d&&u!==t&&await I(),m||(m=J(t).catch(e=>{throw m=null,e})),await m,m=null)}async function X(t){const e=await v(t.filePath);if(!e.isFile())throw new Error(`path is not a file: ${t.filePath}`);const i=Date.now();R(i),await _(t.host);const o=y(),n=w(t.filePath),r=t.ttlMs&&t.ttlMs>0?t.ttlMs:L,a=i+r;return l.set(o,{filePath:t.filePath,fileName:n,size:e.size,expiresAt:a}),{url:`http://${u}:${p}/d/${o}`,file_name:n,size:e.size,expires_at:a}}async function te(){l.clear(),await I()}async function ne(t){return await _(t),p}async function ie(t){const e=String(t.file_path??"").trim();if(!e)throw new Error("missing file_path");if(!P(e))throw new Error("file_path must be an absolute path");const i=await O();if(!i)throw new Error("tailnet_unavailable: no tailnet IPv4 detected; ensure Tailscale is up on this host");const o=typeof t.ttl_ms=="number"?t.ttl_ms:void 0,n=await X({filePath:e,host:i,ttlMs:o}),r=x(n.file_name)!==void 0;return{ok:!0,markdown:r?`![${n.file_name}](${n.url})`:`[${n.file_name}](${n.url})`,is_image:r,...n}}export{O as detectTailnetIPv4,ne as ensureServerAndGetPort,X as registerFileForServe,ie as serveLocalFile,te as stopFileServer};
1
+ import{randomUUID as I}from"node:crypto";import{execFile as H}from"node:child_process";import N from"node:http";import{createReadStream as U,createWriteStream as D}from"node:fs";import{stat as P,rename as L,unlink as _,access as F}from"node:fs/promises";import{basename as y,extname as b,isAbsolute as A,join as M,normalize as R,resolve as O}from"node:path";import*as z from"node:os";const j=600*1e3,k={".jpg":"image/jpeg",".jpeg":"image/jpeg",".png":"image/png",".gif":"image/gif",".webp":"image/webp",".svg":"image/svg+xml",".bmp":"image/bmp",".tiff":"image/tiff",".tif":"image/tiff",".ico":"image/x-icon",".avif":"image/avif"};function V(t){return k[b(t).toLowerCase()]}const G={".mp4":"video/mp4",".m4v":"video/mp4",".mov":"video/quicktime",".webm":"video/webm",".ogv":"video/ogg",".mkv":"video/x-matroska",".avi":"video/x-msvideo",".3gp":"video/3gpp",".ts":"video/mp2t"},W={".mp3":"audio/mpeg",".m4a":"audio/mp4",".aac":"audio/aac",".wav":"audio/wav",".ogg":"audio/ogg",".oga":"audio/ogg",".opus":"audio/opus",".flac":"audio/flac",".weba":"audio/webm"};function B(t){const e=b(t).toLowerCase();return k[e]??G[e]??W[e]}function C(t){const e=t.split(".");if(e.length!==4)return!1;const o=Number(e[0]),i=Number(e[1]);return!Number.isInteger(o)||!Number.isInteger(i)?!1:o===100&&i>=64&&i<=127}function J(){return new Promise(t=>{H("tailscale",["ip","-4"],{timeout:3e3},(e,o)=>{if(e){t(void 0);return}const i=o.trim().split(`
2
+ `)[0].trim();t(i&&C(i)?i:void 0)})})}function X(){const t=z.networkInterfaces();for(const e of Object.values(t))if(e){for(const o of e)if(!(o.family!=="IPv4"||o.internal)&&C(o.address))return o.address}}async function Y(){const t=await J();return t!==void 0?t:X()}const p=new Map;let g=null,w="",v=0,h=null;function Z(t){for(const[e,o]of p)o.expiresAt<=t&&p.delete(e)}const K=2*1024*1024*1024;function Q(t){const e=t.socket.remoteAddress??"",o=e.startsWith("::ffff:")?e.slice(7):e;return C(o)}async function q(t,e){const o=b(e),i=y(e,o);let n=M(t,e),a=0;for(;;)try{await F(n),a++,n=M(t,`${i}(${a})${o}`)}catch{return n}}function ee(t,e,o){return new Promise((i,n)=>{const a=D(e);let r=0,c=!1;const d=()=>{c||(c=!0,a.destroy(),_(e).catch(()=>{}))};t.on("data",s=>{r+=s.length,r>K&&(d(),n(Object.assign(new Error("file too large"),{code:413})))}),a.on("error",s=>{d(),n(s)}),t.on("error",s=>{d(),n(s)}),a.on("finish",async()=>{if(!c){c=!0;try{await L(e,o),i()}catch(s){_(e).catch(()=>{}),n(s)}}}),t.pipe(a)})}async function te(t,e){if(!Q(t)){e.statusCode=403,e.end("forbidden");return}const i=new URL(t.url??"/",`http://${t.headers.host}`).searchParams.get("dir")??"";if(!A(i)||R(i)!==O(i)){e.statusCode=400,e.end("invalid dir");return}const n=t.headers["x-filename"]??"",a=Array.isArray(n)?n[0]:n;let r;try{r=decodeURIComponent(a)}catch{r=a}if(!r||r.includes("/")||r.includes("\\")||r==="."||r===".."){e.statusCode=400,e.end("invalid filename");return}try{if(!(await P(i)).isDirectory()){e.statusCode=400,e.end("dir not found");return}}catch{e.statusCode=400,e.end("dir not found");return}const c=await q(i,r),d=`${c}.${I()}.tmp`;try{await ee(t,d,c),e.statusCode=200,e.setHeader("Content-Type","application/json"),e.end(JSON.stringify({ok:!0,path:c,name:y(c)}))}catch(s){s.code===413?(e.statusCode=413,e.end("file too large")):(e.statusCode=500,e.end("upload failed"))}}async function ne(t){const e=N.createServer((i,n)=>{n.setHeader("Access-Control-Allow-Origin","*");const a=String(i.url??"").split("?")[0];if(a==="/ping"){n.statusCode=200,n.end("ok");return}if(a==="/upload"&&i.method==="POST"){te(i,n).catch(()=>{n.headersSent||(n.statusCode=500,n.end("internal error"))});return}const c=/^\/d\/([A-Za-z0-9-]+)$/.exec(a)?.[1],d=c?p.get(c):void 0;if(!d||d.expiresAt<=Date.now()){n.statusCode=404,n.end("not found");return}const s=B(d.fileName);n.setHeader("Content-Type",s??"application/octet-stream"),n.setHeader("Content-Disposition",`${s?"inline":"attachment"}; filename*=UTF-8''${encodeURIComponent(d.fileName)}`),n.setHeader("Accept-Ranges","bytes");const u=d.size;let l=0,m=u-1;const $=i.headers.range;if($){const f=/^bytes=(\d*)-(\d*)$/.exec($.trim());if(!f||f[1]===""&&f[2]===""){n.statusCode=416,n.setHeader("Content-Range",`bytes */${u}`),n.end();return}if(f[1]===""){const S=Number(f[2]);l=S>=u?0:u-S}else l=Number(f[1]),m=f[2]===""?u-1:Math.min(Number(f[2]),u-1);if(l>m||l>=u){n.statusCode=416,n.setHeader("Content-Range",`bytes */${u}`),n.end();return}n.statusCode=206,n.setHeader("Content-Range",`bytes ${l}-${m}/${u}`)}else n.statusCode=200;if(n.setHeader("Content-Length",String(m-l+1)),i.method==="HEAD"){n.end();return}const x=U(d.filePath,{start:l,end:m});x.on("error",()=>{n.headersSent||(n.statusCode=500),n.end()}),x.pipe(n)});await new Promise((i,n)=>{e.once("error",n),e.listen(0,t,()=>{e.removeListener("error",n),i()})});const o=e.address();g=e,w=t,v=typeof o=="object"&&o?o.port:0}async function E(){const t=g;g=null,w="",v=0,t&&await new Promise(e=>t.close(()=>e()))}async function T(t){g&&w===t||(g&&w!==t&&await E(),h||(h=ne(t).catch(e=>{throw h=null,e})),await h,h=null)}async function ie(t){const e=await P(t.filePath);if(!e.isFile())throw new Error(`path is not a file: ${t.filePath}`);const o=Date.now();Z(o),await T(t.host);const i=I(),n=y(t.filePath),a=t.ttlMs&&t.ttlMs>0?t.ttlMs:j,r=o+a;return p.set(i,{filePath:t.filePath,fileName:n,size:e.size,expiresAt:r}),{url:`http://${w}:${v}/d/${i}`,file_name:n,size:e.size,expires_at:r}}async function ue(){p.clear(),await E()}async function fe(t){return await T(t),v}async function le(t){const e=String(t.file_path??"").trim();if(!e)throw new Error("missing file_path");if(!A(e))throw new Error("file_path must be an absolute path");const o=await Y();if(!o)throw new Error("tailnet_unavailable: no tailnet IPv4 detected; ensure Tailscale is up on this host");const i=typeof t.ttl_ms=="number"?t.ttl_ms:void 0,n=await ie({filePath:e,host:o,ttlMs:i}),a=V(n.file_name)!==void 0;return{ok:!0,markdown:a?`![${n.file_name}](${n.url})`:`[${n.file_name}](${n.url})`,is_image:a,...n}}export{Y as detectTailnetIPv4,fe as ensureServerAndGetPort,ie as registerFileForServe,le as serveLocalFile,ue as stopFileServer};
@@ -1 +1 @@
1
- const a=[{name:"grix_query",description:"Search contacts, sessions, message history, or messages by keyword in the Grix/AIBot platform.",inputSchema:{type:"object",properties:{action:{type:"string",enum:["contact_search","session_search","message_history","message_search"],description:"Query action type."},id:{type:"string",description:"Contact ID (contact_search) or Session ID (session_search)."},keyword:{type:"string",description:"Search keyword."},limit:{type:"integer",description:"Max results."},offset:{type:"integer",description:"Result offset."},sessionId:{type:"string",description:"Session ID (message_history, message_search)."},beforeId:{type:"string",description:"Pagination cursor (message_history, message_search)."}},required:["action"]},validation:{required:["action"],properties:{action:{type:"string",enum:["contact_search","session_search","message_history","message_search"]},id:{type:"string"},keyword:{type:"string",maxLength:200},limit:{type:"integer",minimum:1,maximum:100},offset:{type:"integer",minimum:0},sessionId:{type:"string"},beforeId:{type:"string"}}}},{name:"grix_group",description:"Manage groups in the Grix/AIBot platform: create, get details, leave, dissolve, manage members and permissions.",inputSchema:{type:"object",properties:{action:{type:"string",enum:["create","detail","leave","add_members","remove_members","update_member_role","update_all_members_muted","update_member_speaking","dissolve"],description:"Group action type."},sessionId:{type:"string",description:"Group session ID."},name:{type:"string",description:"Group name (create)."},memberIds:{type:"array",items:{type:"string"},description:"Member IDs to add/remove."},memberTypes:{type:"array",items:{type:"integer",enum:[1,2]},description:"Member types (1=user, 2=agent)."},memberId:{type:"string",description:"Target member ID."},role:{type:"integer",enum:[1,2],description:"New role (1=admin, 2=member)."},memberType:{type:"integer",description:"Member type."},allMembersMuted:{type:"boolean",description:"Whether to mute all members."},isSpeakMuted:{type:"boolean",description:"Whether member is muted."},canSpeakWhenAllMuted:{type:"boolean",description:"Allow speaking when all muted."}},required:["action"]},validation:{required:["action"],properties:{action:{type:"string",enum:["create","detail","leave","add_members","remove_members","update_member_role","update_all_members_muted","update_member_speaking","dissolve"]},sessionId:{type:"string"},name:{type:"string",maxLength:128},memberIds:{type:"array",items:{type:"string"},maxItems:100},memberTypes:{type:"array",items:{type:"integer",enum:[1,2]}},memberId:{type:"string"},role:{type:"integer",enum:[1,2]},memberType:{type:"integer"},allMembersMuted:{type:"boolean"},isSpeakMuted:{type:"boolean"},canSpeakWhenAllMuted:{type:"boolean"}}}},{name:"grix_message_send",description:"Send a message to a session in the Grix/AIBot platform.",inputSchema:{type:"object",properties:{sessionId:{type:"string",description:"Target session ID"},content:{type:"string",description:"Message content"},msgType:{type:"integer",description:"Message type (1=text, default 1)"},quotedMessageId:{type:"string",description:"Message ID to reply to"},threadId:{type:"string",description:"Thread ID for threaded reply"}},required:["sessionId","content"]},validation:{required:["sessionId","content"],properties:{sessionId:{type:"string"},content:{type:"string",maxLength:1e4},msgType:{type:"integer"},quotedMessageId:{type:"string"},threadId:{type:"string"}}}},{name:"grix_message_unsend",description:"Recall/unsend a message in the Grix/AIBot platform.",inputSchema:{type:"object",properties:{sessionId:{type:"string",description:"Session ID"},msgId:{type:"string",description:"Message ID to unsend"}},required:["sessionId","msgId"]},validation:{required:["sessionId","msgId"],properties:{sessionId:{type:"string"},msgId:{type:"string"}}}},{name:"grix_file_link",description:"Create a direct, tailnet-only download link for a local file on this host. Use this whenever the user asks you to send, share, give, or deliver a file that exists on the machine where you run (a report, log, build artifact, export, or any local path). It returns a ready-to-use Markdown link in the `markdown` field \u2014 include that exact Markdown link in your reply so the user can click and download the file directly over the shared Tailscale network. Each link is one-time and expires, so call this again to produce a fresh link every time you deliver a file. Requires this host to be on a tailnet (Tailscale running).",inputSchema:{type:"object",properties:{file_path:{type:"string",description:"Absolute path to a local file on this host to share with the user."},ttl_ms:{type:"integer",description:"Optional link lifetime in milliseconds (default 10 minutes)."}},required:["file_path"]},validation:{required:["file_path"],properties:{file_path:{type:"string",maxLength:4096},ttl_ms:{type:"integer",minimum:1e4,maximum:864e5}}}},{name:"grix_admin",description:"Agent and category management in the Grix/AIBot platform: create agents, manage categories, rotate API keys.",inputSchema:{type:"object",properties:{action:{type:"string",enum:["create_agent","list_categories","create_category","update_category","assign_category","rotate_api_key"],description:"Admin action type."},agentName:{type:"string",description:"Agent name (create_agent)."},introduction:{type:"string",description:"Agent introduction (create_agent)."},isMain:{type:"boolean",description:"Set as main agent (create_agent)."},agentId:{type:"string",description:"Agent ID (assign_category, rotate_api_key)."},categoryId:{type:"string",description:"Category ID (create_agent, update_category, assign_category)."},name:{type:"string",description:"Category name (create_category, update_category)."},parentId:{type:"string",description:"Parent category ID (create_category, update_category)."},sortOrder:{type:"integer",description:"Sort order (create_category, update_category)."}},required:["action"]},validation:{required:["action"],properties:{action:{type:"string",enum:["create_agent","list_categories","create_category","update_category","assign_category","rotate_api_key"]},agentName:{type:"string"},introduction:{type:"string"},isMain:{type:"boolean"},agentId:{type:"string"},categoryId:{type:"string"},name:{type:"string"},parentId:{type:"string"},sortOrder:{type:"integer"}}}},{name:"grix_call_owner",description:"Call your owner into this session to talk by voice. Use this when, during your work, you need to reach your owner \u2014 to discuss something or to get an approval/review. It sends the owner an offline notification; when they tap it they land directly in this conversation and a voice-brain call is started automatically. Requires the owner to have configured a voice brain. Rate-limited per session.",inputSchema:{type:"object",properties:{session_id:{type:"string",description:"The session ID to call the owner into."}},required:["session_id"]},validation:{required:["session_id"],properties:{session_id:{type:"string"}}}},{name:"grix_agent_update",description:"Update the text introduction of one of your owner's agents, identified by its numeric agent ID.",inputSchema:{type:"object",properties:{agent_id:{type:"string",description:"Target agent's numeric ID, passed as a string."},introduction:{type:"string",description:"New text introduction (max 300 characters)."}},required:["agent_id","introduction"]},validation:{required:["agent_id","introduction"],properties:{agent_id:{type:"string"},introduction:{type:"string",maxLength:300}}}},{name:"grix_dispatch_agent",description:"Dispatch one of your owner's agents to do work in a given working directory. Provide the target agent numeric ID, the working directory, and a text description of the task. The backend opens (or reuses) a private session between the owner and that agent, binds the working directory when the agent type requires it (claude/codex/etc.), and sends the task into the session as the owner so the agent starts working.",inputSchema:{type:"object",properties:{agent_id:{type:"string",description:"Target agent's numeric ID, passed as a string."},cwd:{type:"string",description:"Absolute working directory where the agent should do the work."},task:{type:"string",description:"Text description of the task to perform."}},required:["agent_id","cwd","task"]},validation:{required:["agent_id","cwd","task"],properties:{agent_id:{type:"string"},cwd:{type:"string",maxLength:4096},task:{type:"string",maxLength:1e4}}}},{name:"grix_session_send",description:"Send a message into a session as the owner (not as yourself). Use this to speak on the owner's behalf in a session the owner is a member of. The owner must be a member of the target session.",inputSchema:{type:"object",properties:{session_id:{type:"string",description:"Target session ID."},content:{type:"string",description:"Message content to send as the owner."}},required:["session_id","content"]},validation:{required:["session_id","content"],properties:{session_id:{type:"string"},content:{type:"string",maxLength:1e4}}}},{name:"grix_task_query",description:"Query the session-level task states of all your owner's sessions. Takes no parameters \u2014 owner and agent are resolved from your authenticated connection. Returns one entry per session with a single mutually-exclusive state: running (working), waiting_approval (blocked on your owner to approve/deny), waiting_question (asked the owner a question, awaiting their reply), completed, failed, or idle (no task / stopped). Use this to see at a glance which tasks are done, still running, or waiting on the owner.",inputSchema:{type:"object",properties:{}},validation:{required:[],properties:{}}}],d=[{name:"grix_reply",description:"Send a reply message to the specified session. Supports streaming in chunks; the frontend will automatically aggregate them into one complete message.",inputSchema:{type:"object",properties:{event_id:{type:"string",description:"Associated event ID from the inbound event."},session_id:{type:"string",description:"Target session ID."},text:{type:"string",description:"Reply text content."},quoted_message_id:{type:"string",description:"Quoted message ID (optional)."},is_final:{type:"boolean",description:"Whether this is a stage-final reply. Advisory only \u2014 does not trigger event completion; completion is handled by the complete tool or Stop hook."}},required:["session_id","text"]},validation:{required:["session_id","text"],properties:{event_id:{type:"string"},session_id:{type:"string"},text:{type:"string",maxLength:5e4},quoted_message_id:{type:"string"},is_final:{type:"boolean"}}}},{name:"grix_complete",description:"Mark event processing as complete, notifying the backend that no more replies are expected.",inputSchema:{type:"object",properties:{event_id:{type:"string",description:"The event ID to complete."},status:{type:"string",enum:["responded","canceled","failed"],description:"Completion status."},msg:{type:"string",description:"Additional note (optional)."}},required:["event_id","status"]},validation:{required:["event_id","status"],properties:{event_id:{type:"string"},status:{type:"string",enum:["responded","canceled","failed"]},msg:{type:"string",maxLength:500}}}},{name:"grix_event_ack",description:"Acknowledge event receipt (usually done automatically by the Dispatcher; agents typically do not need to call this manually).",inputSchema:{type:"object",properties:{event_id:{type:"string",description:"The event ID to acknowledge."},session_id:{type:"string",description:"Session ID."}},required:["event_id"]},validation:{required:["event_id"],properties:{event_id:{type:"string"},session_id:{type:"string"}}}},{name:"grix_composing",description:'Set the "typing" indicator status for a session.',inputSchema:{type:"object",properties:{session_id:{type:"string",description:"Session ID."},active:{type:"boolean",description:"true = typing, false = stopped."},event_id:{type:"string",description:"Associated event ID (optional)."}},required:["session_id","active"]},validation:{required:["session_id","active"],properties:{session_id:{type:"string"},active:{type:"boolean"},event_id:{type:"string"}}}},{name:"grix_access_control",description:"Manage sender access control: pair approval, allow/remove senders, set policy.",inputSchema:{type:"object",properties:{action:{type:"string",enum:["pair_approve","pair_deny","allow_sender","remove_sender","set_policy"],description:"Access control action type."},code:{type:"string",description:"Pairing code (required for pair_approve/pair_deny)."},sender_id:{type:"string",description:"Sender ID (required for allow_sender/remove_sender)."},policy:{type:"string",enum:["allowlist","open","disabled"],description:"Access policy (required for set_policy)."}},required:["action"]},validation:{required:["action"],properties:{action:{type:"string",enum:["pair_approve","pair_deny","allow_sender","remove_sender","set_policy"]},code:{type:"string"},sender_id:{type:"string"},policy:{type:"string",enum:["allowlist","open","disabled"]}}}},{name:"grix_status",description:"Query the Grix connection status of the current MCP session.",inputSchema:{type:"object",properties:{}},validation:{required:[],properties:{}}}],p=[{name:"reply",description:"Send a visible message back to the chat for this grix-claude event.",inputSchema:{type:"object",properties:{text:{type:"string",description:"The visible reply text to send."},chat_id:{type:"string",description:"The target chat/session id from the <channel> tag."},event_id:{type:"string",description:"The Aibot event_id from the <channel> tag."},reply_to:{type:"string",description:"Optional message_id to quote instead of the inbound trigger message."},final:{type:"boolean",description:"Advisory flag only. It does not complete the event; completion is handled by complete tool or Stop hook."}},required:["chat_id","event_id","text"]}},{name:"complete",description:"Finish an event without sending a visible reply so the backend does not time out.",inputSchema:{type:"object",properties:{event_id:{type:"string",description:"The Aibot event_id from the <channel> tag."},status:{type:"string",enum:["responded","canceled","failed"]},msg:{type:"string"},code:{type:"string"}},required:["event_id","status"]}}],c=new Set(p.map(e=>e.name)),b=new Set(a.map(e=>e.name)),v=new Set(d.map(e=>e.name)),I=/([A-Za-z0-9._-]+:[A-Za-z0-9._-]+:[A-Za-z0-9._-]+:[A-Za-z0-9._-]+)/,k=/[A-Za-z0-9._-]+/;function C(e){return!!(b.has(e)||v.has(e)||c.has(e)||e.startsWith("mcp__grix"))}const w=[...a,...d],x=[...w,...p],P=new Map(x.map(e=>[e.name,e]));function z(e,t){return e==="reply"?{name:"grix_reply",args:u("grix_reply",{event_id:t.event_id,session_id:t.chat_id,text:t.text,quoted_message_id:t.reply_to,is_final:t.final})}:e==="complete"?{name:"grix_complete",args:u("grix_complete",{event_id:t.event_id,status:t.status,msg:t.msg,code:t.code})}:{name:e,args:t}}function l(e){const t=String(e??"").trim();return t?t.match(I)?.[1]:void 0}function m(e){const t=String(e??"").trim();if(!t)return;const n=l(t);if(n)return _(n);const i=t.match(/(?:chat_id|session_id)\s*=\s*"([A-Za-z0-9._-]+)"/)?.[1];if(i)return i;const r=t.match(/[A-Za-z0-9._-]+/g)??[];for(const s of r)if(!(s==="event_id"||s==="chat_id"||s==="session_id")&&s.length>0)return s;return t.match(k)?.[0]}function _(e){if(!e)return;const t=e.split(":",1)[0]?.trim();if(t)return m(t)}function u(e,t){if(e!=="grix_reply"&&e!=="grix_complete")return t;const n={...t},i=l(n.event_id);if(i&&(n.event_id=i),e==="grix_reply"){const r=_(i),o=String(n.session_id??""),s=m(n.session_id),f=/\bevent_id\b|["'<>\s]/.test(o);s&&!(f&&r)?n.session_id=s:r&&(n.session_id=r)}return n}function U(e){return c.has(e)}function G(e,t){switch(e){case"grix_query":return S(t);case"grix_group":return A(t);case"grix_message_send":return q(t);case"grix_message_unsend":return T(t);case"grix_file_link":return M(t);case"grix_admin":return N(t);case"grix_call_owner":return O(t);case"grix_agent_update":return D(t);case"grix_dispatch_agent":return E(t);case"grix_session_send":return L(t);case"grix_task_query":return j();default:throw new Error(`Unknown tool: ${e}`)}}const g={contact_search:"contact_search",session_search:"session_search",message_history:"message_history",message_search:"message_search"};function S(e){const t=String(e.action??""),n=g[t];if(!n)throw new Error(`Unknown grix_query action: ${t}`);const i={};return e.id!=null&&(i.id=e.id),e.keyword!=null&&(i.keyword=e.keyword),e.limit!=null&&(i.limit=e.limit),e.offset!=null&&(i.offset=e.offset),e.sessionId!=null&&(i.session_id=e.sessionId),e.beforeId!=null&&(i.before_id=e.beforeId),{action:n,params:i}}const y={create:"group_create",detail:"group_detail_read",leave:"group_leave_self",add_members:"group_member_add",remove_members:"group_member_remove",update_member_role:"group_member_role_update",update_all_members_muted:"group_all_members_muted_update",update_member_speaking:"group_member_speaking_update",dissolve:"group_dissolve"};function A(e){const t=String(e.action??""),n=y[t];if(!n)throw new Error(`Unknown grix_group action: ${t}`);const i={};return e.sessionId!=null&&(i.session_id=e.sessionId),e.name!=null&&(i.name=e.name),e.memberIds!=null&&(i.member_ids=e.memberIds),e.memberTypes!=null&&(i.member_types=e.memberTypes),e.memberId!=null&&(i.member_id=e.memberId),e.role!=null&&(i.role=e.role),e.memberType!=null&&(i.member_type=e.memberType),e.allMembersMuted!=null&&(i.all_members_muted=e.allMembersMuted),e.isSpeakMuted!=null&&(i.is_speak_muted=e.isSpeakMuted),e.canSpeakWhenAllMuted!=null&&(i.can_speak_when_all_muted=e.canSpeakWhenAllMuted),{action:n,params:i}}function q(e){const t={session_id:e.sessionId,msg_type:e.msgType??1,content:e.content};return e.quotedMessageId!=null&&(t.quoted_message_id=e.quotedMessageId),e.threadId!=null&&(t.thread_id=e.threadId),{action:"send_msg",params:t}}function T(e){return{action:"delete_msg",params:{session_id:e.sessionId,msg_id:e.msgId}}}function M(e){const t={file_path:e.file_path};return e.ttl_ms!=null&&(t.ttl_ms=e.ttl_ms),{action:"file_link",params:t}}const h={create_agent:"agent_api_create",list_categories:"agent_category_list",create_category:"agent_category_create",update_category:"agent_category_update",assign_category:"agent_category_assign",rotate_api_key:"agent_api_key_rotate"};function O(e){return{action:"call_owner",params:{session_id:e.session_id}}}function D(e){return{action:"agent_introduction_update",params:{agent_id:e.agent_id,introduction:e.introduction}}}function E(e){return{action:"dispatch_agent",params:{agent_id:e.agent_id,cwd:e.cwd,task:e.task}}}function L(e){return{action:"session_send",params:{session_id:e.session_id,content:e.content}}}function j(){return{action:"agent_task_query",params:{}}}function N(e){const t=String(e.action??""),n=h[t];if(!n)throw new Error(`Unknown grix_admin action: ${t}`);const i={};return e.agentName!=null&&(i.agent_name=e.agentName),e.introduction!=null&&(i.introduction=e.introduction),e.isMain!=null&&(i.is_main=e.isMain),e.agentId!=null&&(i.agent_id=e.agentId),e.categoryId!=null&&(i.category_id=e.categoryId),e.name!=null&&(i.name=e.name),e.parentId!=null&&(i.parent_id=e.parentId),e.sortOrder!=null&&(i.sort_order=e.sortOrder),{action:n,params:i}}const R=new Set([...Object.values(g),...Object.values(y),...Object.values(h),"send_msg","delete_msg","file_link","call_owner","agent_introduction_update","dispatch_agent","session_send","agent_task_query"]),W={pair_approve:"pair_approve",pair_deny:"pair_deny",allow_sender:"sender_allow",remove_sender:"sender_remove",set_policy:"policy_set"};export{W as ACCESS_CONTROL_ACTION_MAP,w as ALL_TOOLS,d as EVENT_TOOLS,x as EXPOSED_TOOLS,R as PHASE1_INVOKE_ACTIONS,b as PHASE1_TOOL_NAMES,v as PHASE2_TOOL_NAMES,a as TOOLS,p as TOOL_ALIASES,P as TOOL_MAP,U as isAlias,C as isGrixInternalToolName,z as mapToolAlias,u as normalizeEventToolArgs,G as toolCallToInvoke};
1
+ const a=[{name:"grix_query",description:"Search contacts, sessions, message history, or messages by keyword in the Grix/AIBot platform.",inputSchema:{type:"object",properties:{action:{type:"string",enum:["contact_search","session_search","message_history","message_search"],description:"Query action type."},id:{type:"string",description:"Contact ID (contact_search) or Session ID (session_search)."},keyword:{type:"string",description:"Search keyword."},limit:{type:"integer",description:"Max results."},offset:{type:"integer",description:"Result offset."},sessionId:{type:"string",description:"Session ID (message_history, message_search)."},beforeId:{type:"string",description:"Pagination cursor (message_history, message_search)."}},required:["action"]},validation:{required:["action"],properties:{action:{type:"string",enum:["contact_search","session_search","message_history","message_search"]},id:{type:"string"},keyword:{type:"string",maxLength:200},limit:{type:"integer",minimum:1,maximum:100},offset:{type:"integer",minimum:0},sessionId:{type:"string"},beforeId:{type:"string"}}}},{name:"grix_group",description:"Manage groups in the Grix/AIBot platform: create, get details, leave, dissolve, manage members and permissions.",inputSchema:{type:"object",properties:{action:{type:"string",enum:["create","detail","leave","add_members","remove_members","update_member_role","update_all_members_muted","update_member_speaking","dissolve"],description:"Group action type."},sessionId:{type:"string",description:"Group session ID."},name:{type:"string",description:"Group name (create)."},memberIds:{type:"array",items:{type:"string"},description:"Member IDs to add/remove."},memberTypes:{type:"array",items:{type:"integer",enum:[1,2]},description:"Member types (1=user, 2=agent)."},memberId:{type:"string",description:"Target member ID."},role:{type:"integer",enum:[1,2],description:"New role (1=admin, 2=member)."},memberType:{type:"integer",description:"Member type."},allMembersMuted:{type:"boolean",description:"Whether to mute all members."},isSpeakMuted:{type:"boolean",description:"Whether member is muted."},canSpeakWhenAllMuted:{type:"boolean",description:"Allow speaking when all muted."}},required:["action"]},validation:{required:["action"],properties:{action:{type:"string",enum:["create","detail","leave","add_members","remove_members","update_member_role","update_all_members_muted","update_member_speaking","dissolve"]},sessionId:{type:"string"},name:{type:"string",maxLength:128},memberIds:{type:"array",items:{type:"string"},maxItems:100},memberTypes:{type:"array",items:{type:"integer",enum:[1,2]}},memberId:{type:"string"},role:{type:"integer",enum:[1,2]},memberType:{type:"integer"},allMembersMuted:{type:"boolean"},isSpeakMuted:{type:"boolean"},canSpeakWhenAllMuted:{type:"boolean"}}}},{name:"grix_message_send",description:"Send a message to a session in the Grix/AIBot platform.",inputSchema:{type:"object",properties:{sessionId:{type:"string",description:"Target session ID"},content:{type:"string",description:"Message content"},msgType:{type:"integer",description:"Message type (1=text, default 1)"},quotedMessageId:{type:"string",description:"Message ID to reply to"},threadId:{type:"string",description:"Thread ID for threaded reply"}},required:["sessionId","content"]},validation:{required:["sessionId","content"],properties:{sessionId:{type:"string"},content:{type:"string",maxLength:1e4},msgType:{type:"integer"},quotedMessageId:{type:"string"},threadId:{type:"string"}}}},{name:"grix_message_unsend",description:"Recall/unsend a message in the Grix/AIBot platform.",inputSchema:{type:"object",properties:{sessionId:{type:"string",description:"Session ID"},msgId:{type:"string",description:"Message ID to unsend"}},required:["sessionId","msgId"]},validation:{required:["sessionId","msgId"],properties:{sessionId:{type:"string"},msgId:{type:"string"}}}},{name:"grix_file_link",description:"Create a direct, tailnet-only download link for a local file on this host. Use this whenever the user asks you to send, share, give, or deliver a file that exists on the machine where you run (a report, log, build artifact, export, or any local path). It returns a ready-to-use Markdown link in the `markdown` field \u2014 include that exact Markdown link in your reply so the user can click and download the file directly over the shared Tailscale network. Each link is one-time and expires, so call this again to produce a fresh link every time you deliver a file. Requires this host to be on a tailnet (Tailscale running).",inputSchema:{type:"object",properties:{file_path:{type:"string",description:"Absolute path to a local file on this host to share with the user."},ttl_ms:{type:"integer",description:"Optional link lifetime in milliseconds (default 10 minutes)."}},required:["file_path"]},validation:{required:["file_path"],properties:{file_path:{type:"string",maxLength:4096},ttl_ms:{type:"integer",minimum:1e4,maximum:864e5}}}},{name:"grix_admin",description:"Agent and category management in the Grix/AIBot platform: create agents, manage categories, rotate API keys.",inputSchema:{type:"object",properties:{action:{type:"string",enum:["create_agent","list_categories","create_category","update_category","assign_category","rotate_api_key"],description:"Admin action type."},agentName:{type:"string",description:"Agent name (create_agent)."},introduction:{type:"string",description:"Agent introduction (create_agent)."},isMain:{type:"boolean",description:"Set as main agent (create_agent)."},agentId:{type:"string",description:"Agent ID (assign_category, rotate_api_key)."},categoryId:{type:"string",description:"Category ID (create_agent, update_category, assign_category)."},name:{type:"string",description:"Category name (create_category, update_category)."},parentId:{type:"string",description:"Parent category ID (create_category, update_category)."},sortOrder:{type:"integer",description:"Sort order (create_category, update_category)."}},required:["action"]},validation:{required:["action"],properties:{action:{type:"string",enum:["create_agent","list_categories","create_category","update_category","assign_category","rotate_api_key"]},agentName:{type:"string"},introduction:{type:"string"},isMain:{type:"boolean"},agentId:{type:"string"},categoryId:{type:"string"},name:{type:"string"},parentId:{type:"string"},sortOrder:{type:"integer"}}}},{name:"grix_call_owner",description:"Call your owner into this session to talk by voice. Use this when, during your work, you need to reach your owner \u2014 to discuss something or to get an approval/review. It sends the owner an offline notification; when they tap it they land directly in this conversation and a voice-brain call is started automatically. Requires the owner to have configured a voice brain. Rate-limited per session.",inputSchema:{type:"object",properties:{session_id:{type:"string",description:"The session ID to call the owner into."}},required:["session_id"]},validation:{required:["session_id"],properties:{session_id:{type:"string"}}}},{name:"grix_agent_update",description:"Update the text introduction of one of your owner's agents, identified by its numeric agent ID.",inputSchema:{type:"object",properties:{agent_id:{type:"string",description:"Target agent's numeric ID, passed as a string."},introduction:{type:"string",description:"New text introduction (max 300 characters)."}},required:["agent_id","introduction"]},validation:{required:["agent_id","introduction"],properties:{agent_id:{type:"string"},introduction:{type:"string",maxLength:300}}}},{name:"grix_dispatch_agent",description:"Dispatch one of your owner's agents to do work in a given working directory. Provide the target agent numeric ID, the working directory, and a text description of the task. The backend opens (or reuses) a private session between the owner and that agent, binds the working directory when the agent type requires it (claude/codex/etc.), and sends the task into the session as the owner so the agent starts working.",inputSchema:{type:"object",properties:{agent_id:{type:"string",description:"Target agent's numeric ID, passed as a string."},cwd:{type:"string",description:"Absolute working directory where the agent should do the work."},task:{type:"string",description:"Text description of the task to perform."}},required:["agent_id","cwd","task"]},validation:{required:["agent_id","cwd","task"],properties:{agent_id:{type:"string"},cwd:{type:"string",maxLength:4096},task:{type:"string",maxLength:1e4}}}},{name:"grix_session_send",description:"Send a message into a session AS THE OWNER \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_task_query",description:"Query the session-level task states of all your owner's sessions. Takes no parameters \u2014 owner and agent are resolved from your authenticated connection. Returns one entry per session with a single mutually-exclusive state: running (working), waiting_approval (blocked on your owner to approve/deny), waiting_question (asked the owner a question, awaiting their reply), completed, failed, or idle (no task / stopped). Use this to see at a glance which tasks are done, still running, or waiting on the owner.",inputSchema:{type:"object",properties:{}},validation:{required:[],properties:{}}}],d=[{name:"grix_reply",description:"Send a reply message to the specified session. Supports streaming in chunks; the frontend will automatically aggregate them into one complete message. Any content meant for the user \u2014 including your final conclusion at the end of a turn \u2014 MUST be sent through this tool; text written outside this tool is not delivered to the user.",inputSchema:{type:"object",properties:{event_id:{type:"string",description:"Associated event ID from the inbound event."},session_id:{type:"string",description:"Target session ID."},text:{type:"string",description:"Reply text content."},quoted_message_id:{type:"string",description:"Quoted message ID (optional)."},is_final:{type:"boolean",description:"Whether this is a stage-final reply. Advisory only \u2014 does not trigger event completion; completion is handled by the complete tool or Stop hook."}},required:["session_id","text"]},validation:{required:["session_id","text"],properties:{event_id:{type:"string"},session_id:{type:"string"},text:{type:"string",maxLength:5e4},quoted_message_id:{type:"string"},is_final:{type:"boolean"}}}},{name:"grix_complete",description:"Mark event processing as complete, notifying the backend that no more replies are expected.",inputSchema:{type:"object",properties:{event_id:{type:"string",description:"The event ID to complete."},status:{type:"string",enum:["responded","canceled","failed"],description:"Completion status."},msg:{type:"string",description:"Additional note (optional)."}},required:["event_id","status"]},validation:{required:["event_id","status"],properties:{event_id:{type:"string"},status:{type:"string",enum:["responded","canceled","failed"]},msg:{type:"string",maxLength:500}}}},{name:"grix_event_ack",description:"Acknowledge event receipt (usually done automatically by the Dispatcher; agents typically do not need to call this manually).",inputSchema:{type:"object",properties:{event_id:{type:"string",description:"The event ID to acknowledge."},session_id:{type:"string",description:"Session ID."}},required:["event_id"]},validation:{required:["event_id"],properties:{event_id:{type:"string"},session_id:{type:"string"}}}},{name:"grix_composing",description:'Set the "typing" indicator status for a session.',inputSchema:{type:"object",properties:{session_id:{type:"string",description:"Session ID."},active:{type:"boolean",description:"true = typing, false = stopped."},event_id:{type:"string",description:"Associated event ID (optional)."}},required:["session_id","active"]},validation:{required:["session_id","active"],properties:{session_id:{type:"string"},active:{type:"boolean"},event_id:{type:"string"}}}},{name:"grix_access_control",description:"Manage sender access control: pair approval, allow/remove senders, set policy.",inputSchema:{type:"object",properties:{action:{type:"string",enum:["pair_approve","pair_deny","allow_sender","remove_sender","set_policy"],description:"Access control action type."},code:{type:"string",description:"Pairing code (required for pair_approve/pair_deny)."},sender_id:{type:"string",description:"Sender ID (required for allow_sender/remove_sender)."},policy:{type:"string",enum:["allowlist","open","disabled"],description:"Access policy (required for set_policy)."}},required:["action"]},validation:{required:["action"],properties:{action:{type:"string",enum:["pair_approve","pair_deny","allow_sender","remove_sender","set_policy"]},code:{type:"string"},sender_id:{type:"string"},policy:{type:"string",enum:["allowlist","open","disabled"]}}}},{name:"grix_status",description:"Query the Grix connection status of the current MCP session.",inputSchema:{type:"object",properties:{}},validation:{required:[],properties:{}}}],p=[{name:"reply",description:"Send a visible message back to the chat for this grix-claude event.",inputSchema:{type:"object",properties:{text:{type:"string",description:"The visible reply text to send."},chat_id:{type:"string",description:"The target chat/session id from the <channel> tag."},event_id:{type:"string",description:"The Aibot event_id from the <channel> tag."},reply_to:{type:"string",description:"Optional message_id to quote instead of the inbound trigger message."},final:{type:"boolean",description:"Advisory flag only. It does not complete the event; completion is handled by complete tool or Stop hook."}},required:["chat_id","event_id","text"]}},{name:"complete",description:"Finish an event without sending a visible reply so the backend does not time out.",inputSchema:{type:"object",properties:{event_id:{type:"string",description:"The Aibot event_id from the <channel> tag."},status:{type:"string",enum:["responded","canceled","failed"]},msg:{type:"string"},code:{type:"string"}},required:["event_id","status"]}}],c=new Set(p.map(e=>e.name)),b=new Set(a.map(e=>e.name)),v=new Set(d.map(e=>e.name)),I=/([A-Za-z0-9._-]+:[A-Za-z0-9._-]+:[A-Za-z0-9._-]+:[A-Za-z0-9._-]+)/,w=/[A-Za-z0-9._-]+/;function C(e){return!!(b.has(e)||v.has(e)||c.has(e)||e.startsWith("mcp__grix"))}const k=[...a,...d],x=[...k,...p],P=new Map(x.map(e=>[e.name,e]));function R(e,t){return e==="reply"?{name:"grix_reply",args:_("grix_reply",{event_id:t.event_id,session_id:t.chat_id,text:t.text,quoted_message_id:t.reply_to,is_final:t.final})}:e==="complete"?{name:"grix_complete",args:_("grix_complete",{event_id:t.event_id,status:t.status,msg:t.msg,code:t.code})}:{name:e,args:t}}function l(e){const t=String(e??"").trim();return t?t.match(I)?.[1]:void 0}function 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 r=t.match(/[A-Za-z0-9._-]+/g)??[];for(const s of r)if(!(s==="event_id"||s==="chat_id"||s==="session_id")&&s.length>0)return s;return t.match(w)?.[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 r=m(i),o=String(n.session_id??""),s=u(n.session_id),f=/\bevent_id\b|["'<>\s]/.test(o);s&&!(f&&r)?n.session_id=s:r&&(n.session_id=r)}return n}function U(e){return c.has(e)}function z(e,t){switch(e){case"grix_query":return S(t);case"grix_group":return A(t);case"grix_message_send":return q(t);case"grix_message_unsend":return T(t);case"grix_file_link":return M(t);case"grix_admin":return j(t);case"grix_call_owner":return O(t);case"grix_agent_update":return D(t);case"grix_dispatch_agent":return E(t);case"grix_session_send":return L(t);case"grix_task_query":return N();default:throw new Error(`Unknown tool: ${e}`)}}const g={contact_search:"contact_search",session_search:"session_search",message_history:"message_history",message_search:"message_search"};function S(e){const t=String(e.action??""),n=g[t];if(!n)throw new Error(`Unknown grix_query action: ${t}`);const i={};return e.id!=null&&(i.id=e.id),e.keyword!=null&&(i.keyword=e.keyword),e.limit!=null&&(i.limit=e.limit),e.offset!=null&&(i.offset=e.offset),e.sessionId!=null&&(i.session_id=e.sessionId),e.beforeId!=null&&(i.before_id=e.beforeId),{action:n,params:i}}const y={create:"group_create",detail:"group_detail_read",leave:"group_leave_self",add_members:"group_member_add",remove_members:"group_member_remove",update_member_role:"group_member_role_update",update_all_members_muted:"group_all_members_muted_update",update_member_speaking:"group_member_speaking_update",dissolve:"group_dissolve"};function A(e){const t=String(e.action??""),n=y[t];if(!n)throw new Error(`Unknown grix_group action: ${t}`);const i={};return e.sessionId!=null&&(i.session_id=e.sessionId),e.name!=null&&(i.name=e.name),e.memberIds!=null&&(i.member_ids=e.memberIds),e.memberTypes!=null&&(i.member_types=e.memberTypes),e.memberId!=null&&(i.member_id=e.memberId),e.role!=null&&(i.role=e.role),e.memberType!=null&&(i.member_type=e.memberType),e.allMembersMuted!=null&&(i.all_members_muted=e.allMembersMuted),e.isSpeakMuted!=null&&(i.is_speak_muted=e.isSpeakMuted),e.canSpeakWhenAllMuted!=null&&(i.can_speak_when_all_muted=e.canSpeakWhenAllMuted),{action:n,params:i}}function q(e){const t={session_id:e.sessionId,msg_type:e.msgType??1,content:e.content};return e.quotedMessageId!=null&&(t.quoted_message_id=e.quotedMessageId),e.threadId!=null&&(t.thread_id=e.threadId),{action:"send_msg",params:t}}function T(e){return{action:"delete_msg",params:{session_id:e.sessionId,msg_id:e.msgId}}}function M(e){const t={file_path:e.file_path};return e.ttl_ms!=null&&(t.ttl_ms=e.ttl_ms),{action:"file_link",params:t}}const h={create_agent:"agent_api_create",list_categories:"agent_category_list",create_category:"agent_category_create",update_category:"agent_category_update",assign_category:"agent_category_assign",rotate_api_key:"agent_api_key_rotate"};function O(e){return{action:"call_owner",params:{session_id:e.session_id}}}function D(e){return{action:"agent_introduction_update",params:{agent_id:e.agent_id,introduction:e.introduction}}}function E(e){return{action:"dispatch_agent",params:{agent_id:e.agent_id,cwd:e.cwd,task:e.task}}}function L(e){return{action:"session_send",params:{session_id:e.session_id,content:e.content}}}function N(){return{action:"agent_task_query",params:{}}}function j(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 G=new Set([...Object.values(g),...Object.values(y),...Object.values(h),"send_msg","delete_msg","file_link","call_owner","agent_introduction_update","dispatch_agent","session_send","agent_task_query"]),W={pair_approve:"pair_approve",pair_deny:"pair_deny",allow_sender:"sender_allow",remove_sender:"sender_remove",set_policy:"policy_set"};export{W as ACCESS_CONTROL_ACTION_MAP,k as ALL_TOOLS,d as EVENT_TOOLS,x as EXPOSED_TOOLS,G as PHASE1_INVOKE_ACTIONS,b as PHASE1_TOOL_NAMES,v as PHASE2_TOOL_NAMES,a as TOOLS,p as TOOL_ALIASES,P as TOOL_MAP,U as isAlias,C as isGrixInternalToolName,R as mapToolAlias,_ as normalizeEventToolArgs,z as toolCallToInvoke};
@@ -0,0 +1,31 @@
1
+ ---
2
+ name: grix-access-control
3
+ description: Manage sender access control with the typed `grix_access_control` tool — approve/deny a pairing code, allow/remove a sender, or set the access policy. Trigger when the user asks to approve a pairing request, allowlist or block a sender, or change who is allowed to message the agent.
4
+ trigger: 当用户要批准/拒绝配对码、允许或移除某个发送者、或调整谁可以给 Agent 发消息的访问策略时
5
+ ---
6
+
7
+ # Grix Access Control
8
+
9
+ Use the `grix_access_control` tool to manage who may message this agent.
10
+
11
+ ## Tool contract
12
+
13
+ Call `grix_access_control` with one `action`:
14
+
15
+ - `pair_approve` / `pair_deny` — approve or deny a pairing request. Requires the
16
+ `code` from the pairing request.
17
+ - `allow_sender` — add a sender to the allowlist. Requires `sender_id`.
18
+ - `remove_sender` — remove a sender. Requires `sender_id`.
19
+ - `set_policy` — set the access policy. Requires `policy`, one of:
20
+ - `allowlist` — only allowlisted senders may message
21
+ - `open` — anyone may message
22
+ - `disabled` — access control off
23
+
24
+ ## Rules
25
+
26
+ 1. Pick exactly one `action` and supply only the field it needs (`code` for
27
+ pairing, `sender_id` for allow/remove, `policy` for set_policy).
28
+ 2. These actions change who can reach the agent — confirm with the user before
29
+ approving an unknown pairing code or switching the policy to `open`.
30
+ 3. On failure, report the exact reason (e.g. expired/invalid code) instead of
31
+ retrying with a guessed value.
@@ -0,0 +1,35 @@
1
+ ---
2
+ name: grix-admin
3
+ description: Use the typed `grix_admin` tool to manage agents and categories on the Grix platform — create agents, list/create/update categories, assign an agent to a category, and rotate an agent's API key. Trigger when the user asks to create a new agent, organize agents into categories, or rotate an agent key.
4
+ trigger: 当用户要在 Grix 平台创建 Agent、管理分类、给 Agent 分配分类、或轮换 Agent 的 API key 时
5
+ ---
6
+
7
+ # Grix Admin
8
+
9
+ Use the `grix_admin` tool for agent and category management on the Grix
10
+ platform. This skill covers only platform-side management — it does not touch
11
+ any local connector configuration or binding.
12
+
13
+ ## Tool contract
14
+
15
+ Call `grix_admin` with one `action`:
16
+
17
+ - `create_agent` — create an agent. Requires `agentName`; optional
18
+ `introduction`, `isMain`, `categoryId`.
19
+ - `list_categories` — list all categories. No extra fields.
20
+ - `create_category` — requires `name`; optional `parentId`, `sortOrder`.
21
+ - `update_category` — requires `categoryId`; optional `name`, `parentId`,
22
+ `sortOrder`.
23
+ - `assign_category` — assign an agent to a category. Requires `agentId` and
24
+ `categoryId`.
25
+ - `rotate_api_key` — rotate an agent's API key. Requires `agentId`.
26
+
27
+ ## Rules
28
+
29
+ 1. Pick exactly one `action` per call and supply only the fields that action
30
+ needs.
31
+ 2. For `assign_category` / `rotate_api_key` you need the target `agentId`; for
32
+ category edits you need the `categoryId`. Resolve unknown IDs first
33
+ (`list_categories`, or `grix_query` for agents) rather than guessing.
34
+ 3. `rotate_api_key` invalidates the old key. Confirm the target agent with the
35
+ user before rotating, and report the new key handling per platform policy.
@@ -0,0 +1,35 @@
1
+ ---
2
+ name: grix-agent-dispatch
3
+ description: Dispatch one of the owner's other agents to do work in a given directory (`grix_dispatch_agent`), and update an agent's text introduction (`grix_agent_update`). Trigger when the user asks to hand a task to another agent, run work in a specific directory via a sibling agent, or change an agent's introduction.
4
+ trigger: 当用户要把任务派发给 owner 名下的另一个 Agent、让某个 Agent 在指定目录干活、或修改某个 Agent 的简介时
5
+ ---
6
+
7
+ # Grix Agent Dispatch
8
+
9
+ Manage and delegate to the owner's other agents.
10
+
11
+ ## Dispatch a task — `grix_dispatch_agent`
12
+
13
+ Hand work to another of the owner's agents. The backend opens (or reuses) a
14
+ private session between the owner and that agent, binds the working directory
15
+ when the agent type requires it (claude/codex/etc.), and sends the task in as
16
+ the owner so the agent starts working.
17
+
18
+ - `agent_id` (required) — target agent's numeric ID, as a string.
19
+ - `cwd` (required) — absolute working directory for the task.
20
+ - `task` (required) — text description of what to do.
21
+
22
+ ## Update an introduction — `grix_agent_update`
23
+
24
+ Change the text introduction of one of the owner's agents.
25
+
26
+ - `agent_id` (required) — target agent's numeric ID, as a string.
27
+ - `introduction` (required) — new introduction text (max 300 chars).
28
+
29
+ ## Rules
30
+
31
+ 1. You need the exact numeric `agent_id`. Resolve it with `grix_query` first if
32
+ you only have a name; never guess an ID.
33
+ 2. `cwd` must be an absolute path the target agent can access.
34
+ 3. Dispatch runs the task as the owner in a separate session — confirm the
35
+ target agent and directory with the user when the task is consequential.
@@ -0,0 +1,35 @@
1
+ ---
2
+ name: grix-group
3
+ description: Use the typed `grix_group` tool for Grix group lifecycle and membership operations. Trigger when users ask to create, inspect, leave, update, or dissolve groups, or when these operations fail with scope or permission errors.
4
+ trigger: 当用户要创建、查看、退出、更新或解散群组,或群成员/禁言权限相关操作时
5
+ ---
6
+
7
+ # Grix Group
8
+
9
+ Use the `grix_group` tool for Grix group lifecycle and membership management.
10
+
11
+ ## Tool contract
12
+
13
+ Call `grix_group` with one `action`:
14
+
15
+ - `create` — create a group. Requires `name`; optionally seed members with
16
+ `memberIds` + matching `memberTypes` (1=user, 2=agent).
17
+ - `detail` — get group details. Requires `sessionId`.
18
+ - `leave` — leave the group. Requires `sessionId`.
19
+ - `dissolve` — dissolve the group (owner/admin only). Requires `sessionId`.
20
+ - `add_members` / `remove_members` — requires `sessionId`, `memberIds`, and
21
+ `memberTypes` aligned by index.
22
+ - `update_member_role` — requires `sessionId`, `memberId`, `role` (1=admin,
23
+ 2=member).
24
+ - `update_all_members_muted` — requires `sessionId`, `allMembersMuted`.
25
+ - `update_member_speaking` — requires `sessionId`, `memberId`, `isSpeakMuted`,
26
+ and optionally `canSpeakWhenAllMuted`.
27
+
28
+ ## Rules
29
+
30
+ 1. `memberIds` and `memberTypes` are parallel arrays — keep them the same length
31
+ and order.
32
+ 2. Any action except `create` needs the target group's `sessionId`; resolve it
33
+ with `grix_query` (`session_search`) first if unknown.
34
+ 3. Scope or permission errors usually mean the current agent is not an admin of
35
+ that group — surface the exact error and required role, don't retry blindly.
@@ -0,0 +1,66 @@
1
+ ---
2
+ name: grix-owner-relay
3
+ description: Act on the owner's behalf in a session — send a message as the owner (`grix_session_send`), or call the owner into the current session for a voice talk/approval (`grix_call_owner`). Trigger when the user asks to speak as the owner in a session, or when you need to reach the owner to discuss or get approval.
4
+ trigger: 当需要以 owner 身份在某会话发言、或把 owner 叫进当前会话语音沟通/审批时
5
+ ---
6
+
7
+ # Grix Owner Relay
8
+
9
+ Interact with sessions on the owner's behalf, or pull the owner in.
10
+
11
+ ## Speak as the owner — `grix_session_send`
12
+
13
+ Send a message into a session **as the owner** — it shows up as if the owner
14
+ themselves sent it, **not** as you (the agent).
15
+
16
+ - `session_id` (required) — target session ID.
17
+ - `content` (required) — message text to send as the owner (max 10000 chars).
18
+
19
+ ### When to use it
20
+
21
+ Only to relay on the owner's behalf into one of the owner's **other** sessions
22
+ that you are **not** a participant in. Typical case: you were dispatched to work
23
+ somewhere and need to drop a note to the owner (or to others) in a *different*
24
+ session of theirs.
25
+
26
+ ### Before you call it, make sure
27
+
28
+ 1. You genuinely want to **impersonate the owner**, not speak as yourself.
29
+ 2. The owner is a member of the target session (otherwise it fails on scope —
30
+ surface the error, don't retry blindly).
31
+ 3. The target session is **not** one you are conversing in / a member of.
32
+
33
+ ### Never use it for
34
+
35
+ - ❌ **Sending your own reply in the conversation you are currently in.** Reply
36
+ normally instead (or use `grix_message_send` to send as yourself). Using
37
+ `grix_session_send` here makes *your* answer appear as the *owner's* words —
38
+ i.e. the agent's text shows up as the user's message. This is wrong and
39
+ confusing.
40
+ - ❌ **Any session you (the agent) are a member of.** The backend rejects this,
41
+ precisely to stop the agent from impersonating the owner in its own
42
+ conversation.
43
+ - ❌ As a generic substitute for sending a message as yourself — use
44
+ `grix_message_send` for that.
45
+
46
+ To send as yourself (the agent), use the `message-send` skill
47
+ (`grix_message_send`).
48
+
49
+ ## Call the owner in — `grix_call_owner`
50
+
51
+ Bring the owner into a session for a voice conversation — use this when you need
52
+ to discuss something or get an approval/review during your work. It sends the
53
+ owner an offline notification; tapping it lands them in the conversation and
54
+ auto-starts a voice-brain call.
55
+
56
+ - `session_id` (required) — the session to call the owner into.
57
+
58
+ ## Rules
59
+
60
+ 1. `grix_session_send` only works when the owner is a member of the target
61
+ session **and you (the agent) are not** — sending into a session you belong
62
+ to is rejected (it would impersonate the owner in your own conversation).
63
+ On failure, surface the error; don't retry blindly.
64
+ 2. `grix_call_owner` requires the owner to have configured a voice brain and is
65
+ rate-limited per session. Use it only when you genuinely need the owner, not
66
+ as a routine notification.
@@ -0,0 +1,38 @@
1
+ ---
2
+ name: grix-query
3
+ description: Use the typed `grix_query` tool for Grix contact lookup, keyword search, session search, and session message history lookup. Trigger when users ask to find contacts, search conversations, list visible sessions, or inspect recent messages in a known session.
4
+ trigger: 当用户要查找联系人、搜索会话、列出可见会话、或查看某个已知会话的历史消息时
5
+ ---
6
+
7
+ # Grix Query
8
+
9
+ Use the `grix_query` tool for read-only Grix lookup. This skill only queries
10
+ existing contacts, sessions, and raw session messages — it never sends or
11
+ changes anything.
12
+
13
+ ## Tool contract
14
+
15
+ Always call the `grix_query` tool with one `action`:
16
+
17
+ - `contact_search` — find contacts. Use exactly one mode: exact lookup with
18
+ `id`, keyword search with `keyword`, or list-all with neither.
19
+ - `session_search` — find sessions. Same three modes as `contact_search`.
20
+ - `message_history` — read recent messages in a session. Requires `sessionId`;
21
+ page backwards with `beforeId`.
22
+ - `message_search` — keyword search inside a session. Requires `sessionId` and
23
+ `keyword`.
24
+
25
+ Other parameters: `limit` (1–100), `offset`.
26
+
27
+ ## Rules
28
+
29
+ 1. Parse the request into exactly one action before calling.
30
+ 2. If both `id` and `keyword` are given, the backend prioritizes `id`; do not
31
+ send both unless you explicitly want exact-match behavior.
32
+ 3. For message history or in-session search when no `sessionId` is known, first
33
+ locate the session via `session_search`, or ask the user for a precise target.
34
+ 4. When a result is paginated and `has_more` is true, keep paging only when the
35
+ user asked for everything, the target is still unresolved, or one page is
36
+ clearly insufficient.
37
+ 5. On scope/auth/parameter errors, report the exact failure and the fix; do not
38
+ silently retry with guessed parameters.
@@ -0,0 +1,36 @@
1
+ ---
2
+ name: grix-task-status
3
+ description: Observe state — query the task state of all the owner's sessions (`grix_task_query`), and check the current MCP connection status (`grix_status`). Trigger when the user asks which tasks are running/done/waiting, or to verify the Grix connection is healthy.
4
+ trigger: 当用户问哪些任务在跑/已完成/在等待审批,或要确认 Grix 连接是否正常时
5
+ ---
6
+
7
+ # Grix Task & Status
8
+
9
+ Read-only observation of task and connection state.
10
+
11
+ ## Owner task states — `grix_task_query`
12
+
13
+ Query the session-level task state across all the owner's sessions. Takes no
14
+ parameters — owner and agent are resolved from the authenticated connection.
15
+ Returns one entry per session with a single mutually-exclusive state:
16
+
17
+ - `running` — working
18
+ - `waiting_approval` — blocked on the owner to approve/deny
19
+ - `waiting_question` — asked the owner a question, awaiting reply
20
+ - `completed` / `failed` — finished
21
+ - `idle` — no task / stopped
22
+
23
+ Use it to see at a glance which tasks are done, still running, or waiting on the
24
+ owner.
25
+
26
+ ## Connection status — `grix_status`
27
+
28
+ Query the Grix connection status of the current MCP session. Takes no
29
+ parameters. Use it to confirm the agent is connected before relying on other
30
+ grix tools.
31
+
32
+ ## Rules
33
+
34
+ 1. Both tools are read-only — safe to call any time to orient yourself.
35
+ 2. `grix_task_query` reports per-session state, not per-message; pair it with
36
+ `grix_query` (`message_history`) when you need the actual content.
@@ -0,0 +1,36 @@
1
+ ---
2
+ name: message-send
3
+ description: Send a message into a specific Grix session by session ID, including cross-session and proactive sends. For replying to the current event use the reply/complete tools instead. Trigger words: send DM, DM, send message, notify, message another session.
4
+ trigger: 当用户要主动给某个指定会话发消息、跨会话发送、或通知另一个会话时
5
+ ---
6
+
7
+ # Message Send
8
+
9
+ Use the `grix_message_send` tool to deliver a message into a specific Grix
10
+ session identified by its `sessionId`.
11
+
12
+ ## When to use which tool
13
+
14
+ - **Replying to the current event** (the one you are handling right now): use
15
+ the `reply` / `grix_reply` tool and finish with `complete`. Do NOT use
16
+ `grix_message_send` for this — it is for other sessions.
17
+ - **Sending to another session, or proactively starting one**: use
18
+ `grix_message_send` with that session's `sessionId`.
19
+
20
+ ## Tool contract
21
+
22
+ Call `grix_message_send`:
23
+
24
+ - `sessionId` (required) — the exact target session ID.
25
+ - `content` (required) — the message text (max 10000 chars).
26
+ - `msgType` (optional) — message type, default 1 (text).
27
+ - `quotedMessageId` (optional) — message ID to quote/reply to.
28
+ - `threadId` (optional) — thread ID for a threaded reply.
29
+
30
+ ## Rules
31
+
32
+ 1. You must have an exact `sessionId`. If you only have a name or keyword,
33
+ resolve it first with `grix_query` (`session_search` / `contact_search`).
34
+ 2. Never guess a `sessionId`. If it cannot be resolved, ask the user.
35
+ 3. Sending as yourself (the agent). To speak on the owner's behalf in a session
36
+ the owner belongs to, use the `grix-owner-relay` skill (`grix_session_send`).
@@ -0,0 +1,27 @@
1
+ ---
2
+ name: message-unsend
3
+ description: Silently recall/unsend an already-sent message in a Grix session. After execution, end immediately without replying any confirmation text. Trigger words: recall, unsend, delete message, withdraw message.
4
+ trigger: 当用户要撤回、收回、删除一条已经发出的消息时
5
+ ---
6
+
7
+ # Message Unsend
8
+
9
+ Use the `grix_message_unsend` tool to recall a message that was already sent.
10
+
11
+ ## Tool contract
12
+
13
+ Call `grix_message_unsend`:
14
+
15
+ - `sessionId` (required) — the session the message lives in.
16
+ - `msgId` (required) — the ID of the message to recall.
17
+
18
+ ## Rules
19
+
20
+ 1. You need both the `sessionId` and the exact `msgId`. If the `msgId` is
21
+ unknown, find it first with `grix_query` (`message_history` /
22
+ `message_search`).
23
+ 2. This is a silent operation: after a successful recall, end immediately — do
24
+ not send any confirmation message back to the chat.
25
+ 3. Only recall messages that were actually sent; recalling someone else's
26
+ message will fail on scope/permission — surface that error rather than
27
+ retrying.
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import y from"node:http";import i from"node:process";import{TOOLS as h,toolCallToInvoke as N}from"../core/mcp/tools.js";function O(){const n=i.argv.slice(2);for(let r=0;r<n.length;r++)if(n[r]==="--api-url"&&n[r+1])return n[r+1];const t=i.env.GRIX_CONNECTOR_INTERNAL_API;if(t)return t;i.stderr.write(`FATAL: --api-url <url> required (or set GRIX_CONNECTOR_INTERNAL_API)
3
- `),i.exit(1)}const v=O();function u(n,t){return{jsonrpc:"2.0",id:n,result:t}}function f(n,t,r){return{jsonrpc:"2.0",id:n,error:{code:t,message:r}}}async function S(n,t){const r=new URL("/api/invoke",v),o=JSON.stringify({action:n,params:t,timeout_ms:15e3});return new Promise((m,e)=>{const s=y.request(r,{method:"POST",headers:{"content-type":"application/json","content-length":Buffer.byteLength(o)}},l=>{const g=[];l.on("data",c=>g.push(c)),l.on("end",()=>{const c=Buffer.concat(g).toString("utf8");try{const d=JSON.parse(c);d.ok?m(d.data??null):e(new Error(d.error??"invoke failed"))}catch{e(new Error(`invalid response: ${c.slice(0,200)}`))}})});s.on("error",l=>e(l)),s.write(o),s.end()})}function w(n){return u(n.id??null,{protocolVersion:"2024-11-05",capabilities:{tools:{}},serverInfo:{name:"grix-connector-tools",version:"1.0.0"}})}function E(n){return u(n.id??null,{tools:h})}async function I(n){const t=n.params??{},r=String(t.name??""),o=t.arguments??{};if(!h.find(e=>e.name===r))return f(n.id??null,-32602,`Unknown tool: ${r}`);try{const e=N(r,o),s=await S(e.action,e.params);return u(n.id??null,{content:[{type:"text",text:JSON.stringify(s,null,2)}]})}catch(e){const s=e instanceof Error?e.message:String(e);return u(n.id??null,{content:[{type:"text",text:JSON.stringify({error:s})}],isError:!0})}}let p="";i.stdin.setEncoding("utf8"),i.stdin.on("data",n=>{p+=n;const t=p.split(`
4
- `);p=t.pop()??"";for(const r of t){const o=r.trim();o&&T(o)}}),i.stdin.on("end",()=>{i.exit(0)});async function T(n){let t;try{t=JSON.parse(n)}catch{a(f(null,-32700,"Parse error"));return}try{switch(t.method){case"initialize":a(w(t));return;case"notifications/initialized":return;case"tools/list":a(E(t));return;case"tools/call":a(await I(t));return;default:a(f(t.id??null,-32601,`Method not found: ${t.method}`))}}catch(r){const o=r instanceof Error?r.message:String(r);a(f(t.id??null,-32603,`Internal error: ${o}`))}}function a(n){n&&i.stdout.write(`${JSON.stringify(n)}
2
+ import N from"node:http";import i from"node:process";import{TOOLS as y,toolCallToInvoke as O}from"../core/mcp/tools.js";function v(){const n=i.argv.slice(2);for(let r=0;r<n.length;r++)if(n[r]==="--api-url"&&n[r+1])return n[r+1];const t=i.env.GRIX_CONNECTOR_INTERNAL_API;if(t)return t;i.stderr.write(`FATAL: --api-url <url> required (or set GRIX_CONNECTOR_INTERNAL_API)
3
+ `),i.exit(1)}const w=v();function u(n,t){return{jsonrpc:"2.0",id:n,result:t}}function f(n,t,r){return{jsonrpc:"2.0",id:n,error:{code:t,message:r}}}async function S(n,t){const r=new URL(w),o=new URL("/api/invoke",r);o.search=r.search;const d=JSON.stringify({action:n,params:t,timeout_ms:15e3});return new Promise((e,s)=>{const p=N.request(o,{method:"POST",headers:{"content-type":"application/json","content-length":Buffer.byteLength(d)}},c=>{const g=[];c.on("data",l=>g.push(l)),c.on("end",()=>{const l=Buffer.concat(g).toString("utf8");try{const m=JSON.parse(l);m.ok?e(m.data??null):s(new Error(m.error??"invoke failed"))}catch{s(new Error(`invalid response: ${l.slice(0,200)}`))}})});p.on("error",c=>s(c)),p.write(d),p.end()})}function E(n){return u(n.id??null,{protocolVersion:"2024-11-05",capabilities:{tools:{}},serverInfo:{name:"grix-connector-tools",version:"1.0.0"}})}function I(n){return u(n.id??null,{tools:y})}async function L(n){const t=n.params??{},r=String(t.name??""),o=t.arguments??{};if(!y.find(e=>e.name===r))return f(n.id??null,-32602,`Unknown tool: ${r}`);try{const e=O(r,o),s=await S(e.action,e.params);return u(n.id??null,{content:[{type:"text",text:JSON.stringify(s,null,2)}]})}catch(e){const s=e instanceof Error?e.message:String(e);return u(n.id??null,{content:[{type:"text",text:JSON.stringify({error:s})}],isError:!0})}}let h="";i.stdin.setEncoding("utf8"),i.stdin.on("data",n=>{h+=n;const t=h.split(`
4
+ `);h=t.pop()??"";for(const r of t){const o=r.trim();o&&R(o)}}),i.stdin.on("end",()=>{i.exit(0)});async function R(n){let t;try{t=JSON.parse(n)}catch{a(f(null,-32700,"Parse error"));return}try{switch(t.method){case"initialize":a(E(t));return;case"notifications/initialized":return;case"tools/list":a(I(t));return;case"tools/call":a(await L(t));return;default:a(f(t.id??null,-32601,`Method not found: ${t.method}`))}}catch(r){const o=r instanceof Error?r.message:String(r);a(f(t.id??null,-32603,`Internal error: ${o}`))}}function a(n){n&&i.stdout.write(`${JSON.stringify(n)}
5
5
  `)}