grix-connector 2.0.3 → 2.0.5

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. Any content meant for the user \u2014 including your final conclusion at the end of a turn \u2014 MUST be sent through this tool; text written outside this tool is not delivered to the user.",inputSchema:{type:"object",properties:{event_id:{type:"string",description:"Associated event ID from the inbound event."},session_id:{type:"string",description:"Target session ID."},text:{type:"string",description:"Reply text content."},quoted_message_id:{type:"string",description:"Quoted message ID (optional)."},is_final:{type:"boolean",description:"Whether this is a stage-final reply. Advisory only \u2014 does not trigger event completion; completion is handled by the complete tool or Stop hook."}},required:["session_id","text"]},validation:{required:["session_id","text"],properties:{event_id:{type:"string"},session_id:{type:"string"},text:{type:"string",maxLength:5e4},quoted_message_id:{type:"string"},is_final:{type:"boolean"}}}},{name:"grix_complete",description:"Mark event processing as complete, notifying the backend that no more replies are expected.",inputSchema:{type:"object",properties:{event_id:{type:"string",description:"The event ID to complete."},status:{type:"string",enum:["responded","canceled","failed"],description:"Completion status."},msg:{type:"string",description:"Additional note (optional)."}},required:["event_id","status"]},validation:{required:["event_id","status"],properties:{event_id:{type:"string"},status:{type:"string",enum:["responded","canceled","failed"]},msg:{type:"string",maxLength:500}}}},{name:"grix_event_ack",description:"Acknowledge event receipt (usually done automatically by the Dispatcher; agents typically do not need to call this manually).",inputSchema:{type:"object",properties:{event_id:{type:"string",description:"The event ID to acknowledge."},session_id:{type:"string",description:"Session ID."}},required:["event_id"]},validation:{required:["event_id"],properties:{event_id:{type:"string"},session_id:{type:"string"}}}},{name:"grix_composing",description:'Set the "typing" indicator status for a session.',inputSchema:{type:"object",properties:{session_id:{type:"string",description:"Session ID."},active:{type:"boolean",description:"true = typing, false = stopped."},event_id:{type:"string",description:"Associated event ID (optional)."}},required:["session_id","active"]},validation:{required:["session_id","active"],properties:{session_id:{type:"string"},active:{type:"boolean"},event_id:{type:"string"}}}},{name:"grix_access_control",description:"Manage sender access control: pair approval, allow/remove senders, set policy.",inputSchema:{type:"object",properties:{action:{type:"string",enum:["pair_approve","pair_deny","allow_sender","remove_sender","set_policy"],description:"Access control action type."},code:{type:"string",description:"Pairing code (required for pair_approve/pair_deny)."},sender_id:{type:"string",description:"Sender ID (required for allow_sender/remove_sender)."},policy:{type:"string",enum:["allowlist","open","disabled"],description:"Access policy (required for set_policy)."}},required:["action"]},validation:{required:["action"],properties:{action:{type:"string",enum:["pair_approve","pair_deny","allow_sender","remove_sender","set_policy"]},code:{type:"string"},sender_id:{type:"string"},policy:{type:"string",enum:["allowlist","open","disabled"]}}}},{name:"grix_status",description:"Query the Grix connection status of the current MCP session.",inputSchema:{type:"object",properties:{}},validation:{required:[],properties:{}}}],p=[{name:"reply",description:"Send a visible message back to the chat for this grix-claude event.",inputSchema:{type:"object",properties:{text:{type:"string",description:"The visible reply text to send."},chat_id:{type:"string",description:"The target chat/session id from the <channel> tag."},event_id:{type:"string",description:"The Aibot event_id from the <channel> tag."},reply_to:{type:"string",description:"Optional message_id to quote instead of the inbound trigger message."},final:{type:"boolean",description:"Advisory flag only. It does not complete the event; completion is handled by complete tool or Stop hook."}},required:["chat_id","event_id","text"]}},{name:"complete",description:"Finish an event without sending a visible reply so the backend does not time out.",inputSchema:{type:"object",properties:{event_id:{type:"string",description:"The Aibot event_id from the <channel> tag."},status:{type:"string",enum:["responded","canceled","failed"]},msg:{type:"string"},code:{type:"string"}},required:["event_id","status"]}}],c=new Set(p.map(e=>e.name)),b=new Set(a.map(e=>e.name)),v=new Set(d.map(e=>e.name)),I=/([A-Za-z0-9._-]+:[A-Za-z0-9._-]+:[A-Za-z0-9._-]+:[A-Za-z0-9._-]+)/,k=/[A-Za-z0-9._-]+/;function C(e){return!!(b.has(e)||v.has(e)||c.has(e)||e.startsWith("mcp__grix"))}const w=[...a,...d],x=[...w,...p],P=new Map(x.map(e=>[e.name,e]));function U(e,t){return e==="reply"?{name:"grix_reply",args:_("grix_reply",{event_id:t.event_id,session_id:t.chat_id,text:t.text,quoted_message_id:t.reply_to,is_final:t.final})}:e==="complete"?{name:"grix_complete",args:_("grix_complete",{event_id:t.event_id,status:t.status,msg:t.msg,code:t.code})}:{name:e,args:t}}function l(e){const t=String(e??"").trim();return t?t.match(I)?.[1]:void 0}function m(e){const t=String(e??"").trim();if(!t)return;const n=l(t);if(n)return u(n);const i=t.match(/(?:chat_id|session_id)\s*=\s*"([A-Za-z0-9._-]+)"/)?.[1];if(i)return i;const r=t.match(/[A-Za-z0-9._-]+/g)??[];for(const s of r)if(!(s==="event_id"||s==="chat_id"||s==="session_id")&&s.length>0)return s;return t.match(k)?.[0]}function u(e){if(!e)return;const t=e.split(":",1)[0]?.trim();if(t)return m(t)}function _(e,t){if(e!=="grix_reply"&&e!=="grix_complete")return t;const n={...t},i=l(n.event_id);if(i&&(n.event_id=i),e==="grix_reply"){const r=u(i),o=String(n.session_id??""),s=m(n.session_id),f=/\bevent_id\b|["'<>\s]/.test(o);s&&!(f&&r)?n.session_id=s:r&&(n.session_id=r)}return n}function z(e){return c.has(e)}function G(e,t){switch(e){case"grix_query":return S(t);case"grix_group":return A(t);case"grix_message_send":return q(t);case"grix_message_unsend":return T(t);case"grix_file_link":return M(t);case"grix_admin":return N(t);case"grix_call_owner":return O(t);case"grix_agent_update":return D(t);case"grix_dispatch_agent":return E(t);case"grix_session_send":return L(t);case"grix_task_query":return j();default:throw new Error(`Unknown tool: ${e}`)}}const g={contact_search:"contact_search",session_search:"session_search",message_history:"message_history",message_search:"message_search"};function S(e){const t=String(e.action??""),n=g[t];if(!n)throw new Error(`Unknown grix_query action: ${t}`);const i={};return e.id!=null&&(i.id=e.id),e.keyword!=null&&(i.keyword=e.keyword),e.limit!=null&&(i.limit=e.limit),e.offset!=null&&(i.offset=e.offset),e.sessionId!=null&&(i.session_id=e.sessionId),e.beforeId!=null&&(i.before_id=e.beforeId),{action:n,params:i}}const y={create:"group_create",detail:"group_detail_read",leave:"group_leave_self",add_members:"group_member_add",remove_members:"group_member_remove",update_member_role:"group_member_role_update",update_all_members_muted:"group_all_members_muted_update",update_member_speaking:"group_member_speaking_update",dissolve:"group_dissolve"};function A(e){const t=String(e.action??""),n=y[t];if(!n)throw new Error(`Unknown grix_group action: ${t}`);const i={};return e.sessionId!=null&&(i.session_id=e.sessionId),e.name!=null&&(i.name=e.name),e.memberIds!=null&&(i.member_ids=e.memberIds),e.memberTypes!=null&&(i.member_types=e.memberTypes),e.memberId!=null&&(i.member_id=e.memberId),e.role!=null&&(i.role=e.role),e.memberType!=null&&(i.member_type=e.memberType),e.allMembersMuted!=null&&(i.all_members_muted=e.allMembersMuted),e.isSpeakMuted!=null&&(i.is_speak_muted=e.isSpeakMuted),e.canSpeakWhenAllMuted!=null&&(i.can_speak_when_all_muted=e.canSpeakWhenAllMuted),{action:n,params:i}}function q(e){const t={session_id:e.sessionId,msg_type:e.msgType??1,content:e.content};return e.quotedMessageId!=null&&(t.quoted_message_id=e.quotedMessageId),e.threadId!=null&&(t.thread_id=e.threadId),{action:"send_msg",params:t}}function T(e){return{action:"delete_msg",params:{session_id:e.sessionId,msg_id:e.msgId}}}function M(e){const t={file_path:e.file_path};return e.ttl_ms!=null&&(t.ttl_ms=e.ttl_ms),{action:"file_link",params:t}}const h={create_agent:"agent_api_create",list_categories:"agent_category_list",create_category:"agent_category_create",update_category:"agent_category_update",assign_category:"agent_category_assign",rotate_api_key:"agent_api_key_rotate"};function O(e){return{action:"call_owner",params:{session_id:e.session_id}}}function D(e){return{action:"agent_introduction_update",params:{agent_id:e.agent_id,introduction:e.introduction}}}function E(e){return{action:"dispatch_agent",params:{agent_id:e.agent_id,cwd:e.cwd,task:e.task}}}function L(e){return{action:"session_send",params:{session_id:e.session_id,content:e.content}}}function j(){return{action:"agent_task_query",params:{}}}function N(e){const t=String(e.action??""),n=h[t];if(!n)throw new Error(`Unknown grix_admin action: ${t}`);const i={};return e.agentName!=null&&(i.agent_name=e.agentName),e.introduction!=null&&(i.introduction=e.introduction),e.isMain!=null&&(i.is_main=e.isMain),e.agentId!=null&&(i.agent_id=e.agentId),e.categoryId!=null&&(i.category_id=e.categoryId),e.name!=null&&(i.name=e.name),e.parentId!=null&&(i.parent_id=e.parentId),e.sortOrder!=null&&(i.sort_order=e.sortOrder),{action:n,params:i}}const R=new Set([...Object.values(g),...Object.values(y),...Object.values(h),"send_msg","delete_msg","file_link","call_owner","agent_introduction_update","dispatch_agent","session_send","agent_task_query"]),W={pair_approve:"pair_approve",pair_deny:"pair_deny",allow_sender:"sender_allow",remove_sender:"sender_remove",set_policy:"policy_set"};export{W as ACCESS_CONTROL_ACTION_MAP,w as ALL_TOOLS,d as EVENT_TOOLS,x as EXPOSED_TOOLS,R as PHASE1_INVOKE_ACTIONS,b as PHASE1_TOOL_NAMES,v as PHASE2_TOOL_NAMES,a as TOOLS,p as TOOL_ALIASES,P as TOOL_MAP,z as isAlias,C as isGrixInternalToolName,U as mapToolAlias,_ as normalizeEventToolArgs,G as toolCallToInvoke};
1
+ 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};
@@ -10,13 +10,40 @@ Interact with sessions on the owner's behalf, or pull the owner in.
10
10
 
11
11
  ## Speak as the owner — `grix_session_send`
12
12
 
13
- Send a message into a session **as the owner** (not as yourself). The owner must
14
- be a member of the target session.
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
15
 
16
16
  - `session_id` (required) — target session ID.
17
17
  - `content` (required) — message text to send as the owner (max 10000 chars).
18
18
 
19
- To send as yourself (the agent) instead, use the `message-send` skill
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
20
47
  (`grix_message_send`).
21
48
 
22
49
  ## Call the owner in — `grix_call_owner`
@@ -30,8 +57,10 @@ auto-starts a voice-brain call.
30
57
 
31
58
  ## Rules
32
59
 
33
- 1. `grix_session_send` only works when the owner is a member of that session;
34
- otherwise it fails on scopesurface the error, don't retry blindly.
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.
35
64
  2. `grix_call_owner` requires the owner to have configured a voice brain and is
36
65
  rate-limited per session. Use it only when you genuinely need the owner, not
37
66
  as a routine notification.
@@ -1 +1 @@
1
- function a(e){const t=new Set([`http://127.0.0.1:${e.serverPort}`,`http://localhost:${e.serverPort}`,...e.allowedOrigins]),o=new Set([`127.0.0.1:${e.serverPort}`,`localhost:${e.serverPort}`,...e.allowedHosts]);return{validateRequest(s){const r=i(s,t);if(!r.ok)return r;const n=l(s,o);return n.ok?{ok:!0}:n}}}function i(e,t){const o=e.headers.origin;return o?t.has(o)?{ok:!0}:{ok:!1,statusCode:403,message:`Origin not allowed: ${o}`}:{ok:!0}}function l(e,t){const o=e.headers.host;return o?t.has(o)?{ok:!0}:{ok:!1,statusCode:403,message:`Host not allowed: ${o}`}:{ok:!1,statusCode:403,message:"Missing Host header"}}export{a as createSecurityPolicy};
1
+ function a(t){const o=new Set([`http://127.0.0.1:${t.serverPort}`,`http://localhost:${t.serverPort}`,...t.allowedOrigins]),e=new Set([`127.0.0.1:${t.serverPort}`,`localhost:${t.serverPort}`,...t.allowedHosts]);return{validateRequest(s){const r=i(s,o);if(!r.ok)return r;const n=l(s,e);return n.ok?{ok:!0}:n}}}function i(t,o){const e=t.headers.origin;return e?o.has(e)?{ok:!0}:{ok:!1,statusCode:403,message:`Origin not allowed: ${e}`}:{ok:!0}}function l(t,o){const e=t.headers.host;return e?o.has(e)?{ok:!0}:{ok:!1,statusCode:403,message:`Host not allowed: ${e}`}:{ok:!1,statusCode:403,message:"Missing Host header"}}export{a as createSecurityPolicy};
@@ -9081,7 +9081,7 @@ var TOOLS = [
9081
9081
  },
9082
9082
  {
9083
9083
  name: "grix_session_send",
9084
- 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.",
9084
+ 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.",
9085
9085
  inputSchema: {
9086
9086
  type: "object",
9087
9087
  properties: {
@@ -11699,6 +11699,32 @@ var IMAGE_MIME = {
11699
11699
  function imageMimeType(fileName) {
11700
11700
  return IMAGE_MIME[path5.extname(fileName).toLowerCase()];
11701
11701
  }
11702
+ var VIDEO_MIME = {
11703
+ ".mp4": "video/mp4",
11704
+ ".m4v": "video/mp4",
11705
+ ".mov": "video/quicktime",
11706
+ ".webm": "video/webm",
11707
+ ".ogv": "video/ogg",
11708
+ ".mkv": "video/x-matroska",
11709
+ ".avi": "video/x-msvideo",
11710
+ ".3gp": "video/3gpp",
11711
+ ".ts": "video/mp2t"
11712
+ };
11713
+ var AUDIO_MIME = {
11714
+ ".mp3": "audio/mpeg",
11715
+ ".m4a": "audio/mp4",
11716
+ ".aac": "audio/aac",
11717
+ ".wav": "audio/wav",
11718
+ ".ogg": "audio/ogg",
11719
+ ".oga": "audio/ogg",
11720
+ ".opus": "audio/opus",
11721
+ ".flac": "audio/flac",
11722
+ ".weba": "audio/webm"
11723
+ };
11724
+ function inlineMimeType(fileName) {
11725
+ const ext = path5.extname(fileName).toLowerCase();
11726
+ return IMAGE_MIME[ext] ?? VIDEO_MIME[ext] ?? AUDIO_MIME[ext];
11727
+ }
11702
11728
  var DEFAULT_TTL_MS = 10 * 60 * 1e3;
11703
11729
  var entries = /* @__PURE__ */ new Map();
11704
11730
  var server = null;
@@ -11723,15 +11749,49 @@ async function startServer(host) {
11723
11749
  res.end("not found");
11724
11750
  return;
11725
11751
  }
11726
- res.statusCode = 200;
11727
- const mime = imageMimeType(entry.fileName);
11752
+ const mime = inlineMimeType(entry.fileName);
11728
11753
  res.setHeader("Content-Type", mime ?? "application/octet-stream");
11729
- res.setHeader("Content-Length", String(entry.size));
11730
11754
  res.setHeader(
11731
11755
  "Content-Disposition",
11732
- mime ? `inline; filename*=UTF-8''${encodeURIComponent(entry.fileName)}` : `attachment; filename*=UTF-8''${encodeURIComponent(entry.fileName)}`
11756
+ `${mime ? "inline" : "attachment"}; filename*=UTF-8''${encodeURIComponent(entry.fileName)}`
11733
11757
  );
11734
- const stream = fs.createReadStream(entry.filePath);
11758
+ res.setHeader("Accept-Ranges", "bytes");
11759
+ const total = entry.size;
11760
+ let start = 0;
11761
+ let end = total - 1;
11762
+ const rangeHeader = req.headers.range;
11763
+ if (rangeHeader) {
11764
+ const m = /^bytes=(\d*)-(\d*)$/.exec(String(rangeHeader).trim());
11765
+ if (!m || m[1] === "" && m[2] === "") {
11766
+ res.statusCode = 416;
11767
+ res.setHeader("Content-Range", `bytes */${total}`);
11768
+ res.end();
11769
+ return;
11770
+ }
11771
+ if (m[1] === "") {
11772
+ const suffix = Number(m[2]);
11773
+ start = suffix >= total ? 0 : total - suffix;
11774
+ } else {
11775
+ start = Number(m[1]);
11776
+ end = m[2] === "" ? total - 1 : Math.min(Number(m[2]), total - 1);
11777
+ }
11778
+ if (start > end || start >= total) {
11779
+ res.statusCode = 416;
11780
+ res.setHeader("Content-Range", `bytes */${total}`);
11781
+ res.end();
11782
+ return;
11783
+ }
11784
+ res.statusCode = 206;
11785
+ res.setHeader("Content-Range", `bytes ${start}-${end}/${total}`);
11786
+ } else {
11787
+ res.statusCode = 200;
11788
+ }
11789
+ res.setHeader("Content-Length", String(end - start + 1));
11790
+ if (req.method === "HEAD") {
11791
+ res.end();
11792
+ return;
11793
+ }
11794
+ const stream = fs.createReadStream(entry.filePath, { start, end });
11735
11795
  stream.on("error", () => {
11736
11796
  if (!res.headersSent) res.statusCode = 500;
11737
11797
  res.end();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "grix-connector",
3
- "version": "2.0.3",
3
+ "version": "2.0.5",
4
4
  "description": "Connect local AI coding agents (Claude, Codex, Gemini, Qwen, DeepSeek, Cursor, OpenCode, Pi, OpenHuman, Reasonix) to the Grix scheduling platform. Also serves as an OpenClaw plugin for Grix channel transport.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",