grix-connector 3.8.2 → 3.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
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(n=>n.event_id===e);if(t<0)return!1;const[i]=this.queued.splice(t,1);return this.clearTimer(e),this.broadcastQueuePositions(),this.checkStopComposing(i.session_id),!0}complete(e){const i=this.running.get(e)?.session_id;this.running.delete(e),this.clearTimer(e),queueMicrotask(()=>this.drainNext()),i&&this.checkStopComposing(i)}clear(e,t="queue cleared"){const i=[],n=[];for(const u of this.queued)u.session_id===e?(this.clearTimer(u.event_id),this.callbacks.onStateChange(u.event_id,e,"canceled",{reason:t}),i.push(u.event_id)):n.push(u);return this.queued=n,i.length>0&&this.broadcastQueuePositions(),this.checkStopComposing(e),i}drainQueuedForSession(e){const t=[],i=[];for(const n of this.queued)n.session_id===e?(this.clearTimer(n.event_id),t.push(n)):i.push(n);return this.queued=i,t}snapshot(e){const t=[...this.running.values()].filter(s=>s.session_id===e),i=t.map(s=>s.event_id),n=t.map(s=>({event_id:s.event_id,content_preview:this.buildQueueItemTitle(s.content),title:this.buildQueueItemTitle(s.content),summary:this.buildQueueItemTitle(s.content)})),u=this.queued.flatMap((s,o)=>s.session_id===e?[{event_id:s.event_id,position:o+1,content_preview:this.buildQueueItemTitle(s.content),title:this.buildQueueItemTitle(s.content),summary:this.buildQueueItemTitle(s.content)}]:[]);return{running:i,running_items:n,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(n=>n.event_id===e);if(t<0)return;const[i]=this.queued.splice(t,1);this.timers.delete(e),this.callbacks.onStateChange(e,i.session_id,"failed",{reason:"queue timeout"}),this.broadcastQueuePositions(),this.drainNext(),this.checkStopComposing(i.session_id)}startRunning(e){this.running.set(e.event_id,e),this.callbacks.onStateChange(e.event_id,e.session_id,"running",{actions:this.config.cancelableRunning?[{type:"stop"}]:[],content_preview:this.buildQueueItemTitle(e.content)}),this.ensureComposing(e.session_id),this.callbacks.onDeliver(e)}drainNext(){for(;this.ready&&this.running.size<this.config.maxConcurrent&&this.queued.length>0;){const e=this.queued.shift();this.clearTimer(e.event_id),this.startRunning(e)}this.queued.length>0&&this.broadcastQueuePositions()}broadcastQueuePositions(){const e=this.queued.length;for(let t=0;t<e;t++){const i=this.queued[t];this.callbacks.onStateChange(i.event_id,i.session_id,"queued",{queue_position:t+1,queue_total:e,actions:this.config.cancelableQueued?[{type:"cancel"}]:[],content_preview:this.buildQueueItemTitle(i.content)})}}clearTimer(e){const t=this.timers.get(e);t&&(clearTimeout(t),this.timers.delete(e))}buildQueueItemTitle(e){const t=String(e??"").replace(/\s+/g," ").trim();return t?t.length>64?`${t.slice(0,64)}...`:t:"Message"}ensureComposing(e){if(!this.callbacks.onComposing||this.composingTimers.has(e))return;this.callbacks.onComposing(e,!0,this.getFirstRunningEventId(e));const t=setInterval(()=>{this.sessionHasEvents(e)?this.callbacks.onComposing(e,!0,this.getFirstRunningEventId(e)):this.stopComposing(e)},25e3);t.unref(),this.composingTimers.set(e,t)}checkStopComposing(e){this.sessionHasEvents(e)||this.stopComposing(e)}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 a=25e3;class c{config;callbacks;running=new Map;queued=[];timers=new Map;composingTimers=new Map;adapterDoneEventIds=new Set;pauseReasons=new Set;get ready(){return this.pauseReasons.size===0}constructor(e,t){this.config=e,this.callbacks=t}pause(e){this.pauseReasons.add(e)}resume(e){this.pauseReasons.delete(e)&&this.ready&&this.drainNext()}submit(e){return this.running.has(e.event_id)||this.queued.some(t=>t.event_id===e.event_id)?"accepted":this.ready&&this.running.size<this.config.maxConcurrent?(this.startRunning(e),"accepted"):this.config.maxQueued<=0||this.queued.length>=this.config.maxQueued?(this.callbacks.onRejected(e,"queue full"),"rejected"):(this.enqueue(e),"accepted")}cancel(e){const t=this.queued.findIndex(i=>i.event_id===e);if(t>=0){if(!this.config.cancelableQueued)return!1;const[i]=this.queued.splice(t,1);return this.clearTimer(e),this.callbacks.onStateChange(e,i.session_id,"canceled",{reason:"canceled by user"}),this.broadcastQueuePositions(),this.drainNext(),this.checkStopComposing(i.session_id),!0}return this.running.has(e)&&this.config.cancelableRunning?(this.callbacks.onCancelRunning(e),!0):!1}removeQueued(e){const t=this.queued.findIndex(n=>n.event_id===e);if(t<0)return!1;const[i]=this.queued.splice(t,1);return this.clearTimer(e),this.broadcastQueuePositions(),this.checkStopComposing(i.session_id),!0}complete(e){const i=this.running.get(e)?.session_id;this.running.delete(e),this.adapterDoneEventIds.delete(e),this.clearTimer(e),queueMicrotask(()=>this.drainNext()),i&&this.checkStopComposing(i)}clear(e,t="queue cleared"){const i=[],n=[];for(const u of this.queued)u.session_id===e?(this.clearTimer(u.event_id),this.callbacks.onStateChange(u.event_id,e,"canceled",{reason:t}),i.push(u.event_id)):n.push(u);return this.queued=n,i.length>0&&this.broadcastQueuePositions(),this.checkStopComposing(e),i}drainQueuedForSession(e){const t=[],i=[];for(const n of this.queued)n.session_id===e?(this.clearTimer(n.event_id),t.push(n)):i.push(n);return this.queued=i,t}snapshot(e){const t=[...this.running.values()].filter(s=>s.session_id===e),i=t.map(s=>s.event_id),n=t.map(s=>({event_id:s.event_id,content_preview:this.buildQueueItemTitle(s.content),title:this.buildQueueItemTitle(s.content),summary:this.buildQueueItemTitle(s.content)})),u=this.queued.flatMap((s,o)=>s.session_id===e?[{event_id:s.event_id,position:o+1,content_preview:this.buildQueueItemTitle(s.content),title:this.buildQueueItemTitle(s.content),summary:this.buildQueueItemTitle(s.content)}]:[]);return{running:i,running_items:n,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(n=>n.event_id===e);if(t<0)return;const[i]=this.queued.splice(t,1);this.timers.delete(e),this.callbacks.onStateChange(e,i.session_id,"failed",{reason:"queue timeout"}),this.broadcastQueuePositions(),this.drainNext(),this.checkStopComposing(i.session_id)}startRunning(e){this.running.set(e.event_id,e),this.callbacks.onStateChange(e.event_id,e.session_id,"running",{actions:this.config.cancelableRunning?[{type:"stop"}]:[],content_preview:this.buildQueueItemTitle(e.content)}),this.ensureComposing(e.session_id),this.callbacks.onDeliver(e)}drainNext(){for(;this.ready&&this.running.size<this.config.maxConcurrent&&this.queued.length>0;){const e=this.queued.shift();this.clearTimer(e.event_id),this.startRunning(e)}this.queued.length>0&&this.broadcastQueuePositions()}broadcastQueuePositions(){const e=this.queued.length;for(let t=0;t<e;t++){const i=this.queued[t];this.callbacks.onStateChange(i.event_id,i.session_id,"queued",{queue_position:t+1,queue_total:e,actions:this.config.cancelableQueued?[{type:"cancel"}]:[],content_preview:this.buildQueueItemTitle(i.content)})}}clearTimer(e){const t=this.timers.get(e);t&&(clearTimeout(t),this.timers.delete(e))}buildQueueItemTitle(e){const t=String(e??"").replace(/\s+/g," ").trim();return t?t.length>64?`${t.slice(0,64)}...`:t:"Message"}ensureComposing(e){if(!this.callbacks.onComposing||this.composingTimers.has(e))return;this.callbacks.onComposing(e,!0,this.getFirstRunningEventId(e));const t=setInterval(()=>{this.sessionHasEvents(e)?this.callbacks.onComposing(e,!0,this.getFirstRunningEventId(e)):this.stopComposing(e)},25e3);t.unref(),this.composingTimers.set(e,t)}checkStopComposing(e){this.sessionHasEvents(e)||this.stopComposing(e)}markAdapterDone(e){const t=this.running.get(e);t&&(this.adapterDoneEventIds.add(e),this.sessionHasEvents(t.session_id)||this.stopComposing(t.session_id))}stopComposing(e){const t=this.composingTimers.get(e);t&&(clearInterval(t),this.composingTimers.delete(e)),this.callbacks.onComposing?.(e,!1)}sessionHasEvents(e){for(const t of this.running.values())if(t.session_id===e&&!this.adapterDoneEventIds.has(t.event_id))return!0;return this.queued.some(t=>t.session_id===e)}getFirstRunningEventId(e){for(const t of this.running.values())if(t.session_id===e&&!this.adapterDoneEventIds.has(t.event_id))return t.event_id}}export{c as EventQueue};
@@ -1,3 +1,3 @@
1
- import{log as c}from"../core/log/index.js";import{splitTextForAibotProtocol as g,AIBOT_PROTOCOL_MAX_RUNES as C}from"../core/protocol/protocol-text.js";import{isThinkingEventDropped as E,isToolEventDropped as k,normalizeRuntimeConfigSnapshot as S}from"./runtime-config.js";class d{handle;connectorRuntimeConfig;eventRuntimeConfigs=new Map;thinkingSeqMap=new Map;toolCardSeq=0;bufferedEventTexts=new Map;chunkSeqMap=new Map;rejectedEvents=new Set;stoppedEvents=new Set;streamCoalesce=new Map;streamCoalesceMs=T();static STREAM_COALESCE_MAX_RUNES=2048;constructor(e){this.connectorRuntimeConfig={...e}}bind(e){this.handle=e}markEventRejected(e){if(!e)return;this.rejectedEvents.add(e);const t=`${e}:`;for(const s of[...this.streamCoalesce.keys()])if(s.startsWith(t)){const n=this.streamCoalesce.get(s);n.timer&&clearTimeout(n.timer),this.streamCoalesce.delete(s)}c.info("send-ctrl",`event marked as stream-rejected, future chunks will be dropped event=${e}`)}markEventStopped(e){e&&(this.stoppedEvents.add(e),c.info("send-ctrl",`[stop-trace] event marked stopped; subsequent outbound will be flagged event=${e}`))}flagOutboundAfterStop(e,t,s,n,i,r){!e||!this.stoppedEvents.has(e)||c.warn("send-ctrl",`[stop-trace] OUTBOUND AFTER STOP ${s} event=${e} session=${t} seq=${n} is_finish=${i} len=${r}`)}getGlobalRuntimeConfig(){return this.connectorRuntimeConfig}setStreamCoalesceMs(e){this.streamCoalesceMs=Number.isFinite(e)&&e>0?Math.floor(e):0}captureEventRuntimeConfig(e){const t=S(e.connector_runtime_config)??this.connectorRuntimeConfig;this.eventRuntimeConfigs.set(e.event_id,{responseDelivery:t.responseDelivery,toolEvents:t.toolEvents,thinkingEvents:t.thinkingEvents})}resolveEventRuntimeConfig(e){if(e){const t=this.eventRuntimeConfigs.get(e);if(t)return t}return this.connectorRuntimeConfig}shouldDropToolDisplayEvent(e){return k(this.resolveEventRuntimeConfig(e))}shouldDropThinkingDisplayEvent(e){return E(this.resolveEventRuntimeConfig(e))}shouldDropCodexDisplayEvent(e,t){return this.shouldDropToolDisplayEvent(e)?t==="item/completed":!1}shouldDropAcpRawDisplayEvent(e,t){return!!(this.shouldDropToolDisplayEvent(e)&&(t==="tool_use"||t==="tool_result")||this.shouldDropThinkingDisplayEvent(e)&&t==="thinking")}sendReply(e,t,s,n,i,r){if(!r?.skipToolFilter&&this.shouldDropToolDisplayEvent(e)&&y(s,i)||e&&this.rejectedEvents.has(e))return;if(this.flagOutboundAfterStop(e,t,"reply",0,!1,(s||"").length),this.resolveEventRuntimeConfig(e).responseDelivery==="stream"&&!i){const o=g(s),u=e?`reply_stream_${e}`:`reply_stream_${Date.now()}`;if(o.length===0){this.handle.sendMsg({event_id:e,session_id:t,msg_type:1,content:s||" ",quoted_message_id:n||void 0});return}for(let a=0;a<o.length;a++){const h=a===o.length-1;this.sendStreamChunk(e,t,o[a],a+1,h,u,n)}return}this.handle.sendMsg({event_id:e,session_id:t,msg_type:1,content:s,quoted_message_id:n||void 0,...i?{extra:i}:{}})}sendStreamChunk(e,t,s,n,i,r,l){if(e&&this.rejectedEvents.has(e))return;if(this.flagOutboundAfterStop(e,t,"stream_chunk",n,i,(s||"").length),this.finalizeThinking(e,t),this.resolveEventRuntimeConfig(e).responseDelivery==="single_message"&&e){this.bufferStreamChunk(e,t,s,i,l);return}if(this.streamCoalesceMs<=0){this.emitStreamChunkParts(e,t,s,n,i,r,l);return}this.coalesceStreamChunk(e,t,s,i,r,l)}coalesceStreamChunk(e,t,s,n,i,r){if(!s&&!n)return;const l=`${e}:${i??"default"}`;let o=this.streamCoalesce.get(l);if(o||(o={eventId:e,sessionId:t,clientMsgId:i,quotedMessageId:r,text:"",flushedOnce:!1},this.streamCoalesce.set(l,o)),s&&(o.text+=s),o.sessionId=t,i!==void 0&&(o.clientMsgId=i),!o.quotedMessageId&&r&&(o.quotedMessageId=r),n){this.flushCoalesce(l,!0);return}if(!o.flushedOnce||o.text.length>=d.STREAM_COALESCE_MAX_RUNES){this.flushCoalesce(l,!1);return}o.timer||(o.timer=setTimeout(()=>this.flushCoalesce(l,!1),this.streamCoalesceMs))}flushCoalesce(e,t){const s=this.streamCoalesce.get(e);if(!s)return;s.timer&&(clearTimeout(s.timer),s.timer=void 0);const n=s.text;s.text="",s.flushedOnce=!0,t&&this.streamCoalesce.delete(e),!(!n&&!t)&&this.emitStreamChunkParts(s.eventId,s.sessionId,n,0,t,s.clientMsgId,s.quotedMessageId)}flushCoalesceForEvent(e){const t=`${e}:`;for(const s of[...this.streamCoalesce.keys()])s.startsWith(t)&&(this.flushCoalesce(s,!1),this.streamCoalesce.delete(s))}emitStreamChunkParts(e,t,s,n,i,r,l){const o=s||(i?`
2
- `:"");if(!o&&!i)return;const u=g(o,C);u.length===0&&u.push(o||`
3
- `);const a=`${e}:${r??"default"}`;for(let h=0;h<u.length;h++){const _=h===u.length-1,m=this.chunkSeqMap.get(a)??0,p=h===0&&n>m?n:m+1;this.chunkSeqMap.set(a,p),this.handle.sendStreamChunk({event_id:e,session_id:t,delta_content:u[h],chunk_seq:p,is_finish:_&&i,...r?{client_msg_id:r}:{},...l?{quoted_message_id:l}:{}})}}sendEventResult(e,t,s,n){this.finalizeThinking(e,""),this.flushCoalesceForEvent(e),this.flushBufferedStreamText(e),this.handle.sendEventResult({event_id:e,status:t,...s?{msg:s}:{},...n?{code:n}:{},updated_at:Date.now()}),this.eventRuntimeConfigs.delete(e),this.rejectedEvents.delete(e),this.stoppedEvents.delete(e)&&c.info("send-ctrl",`[stop-trace] event_result(${t}) cleared stop flag event=${e}`);for(const i of this.chunkSeqMap.keys())i.startsWith(`${e}:`)&&this.chunkSeqMap.delete(i)}sendThinking(e,t,s){if(this.shouldDropThinkingDisplayEvent(e)||e&&this.rejectedEvents.has(e))return;this.flagOutboundAfterStop(e,t,"thinking",this.thinkingSeqMap.get(e)??0,!1,(s||"").length);const n=(this.thinkingSeqMap.get(e)??0)+1;this.thinkingSeqMap.set(e,n),this.handle.sendStreamChunk({event_id:e,session_id:t,delta_content:s,chunk_seq:n,is_finish:!1,client_msg_id:`${e}_thinking`,is_thinking:!0})}sendToolExecutionCard(e,t,s,n){if(this.shouldDropToolDisplayEvent(e)){c.info("send-ctrl",`[tool-card] DROP event=${e} session=${t} summary=${s.summaryText}`);return}const r={summary_text:s.summaryText};s.detailText&&(r.detail_text=s.detailText),c.info("send-ctrl",`[tool-card] SEND event=${e} session=${t} summary=${s.summaryText}`),this.handle.sendMsg({event_id:e,session_id:t,client_msg_id:n??`tool_${++this.toolCardSeq}_${Date.now()}`,msg_type:1,content:"",extra:{channel_data:{grix:{toolExecution:r}},agent_api_origin:!0}})}finalizeThinking(e,t){const s=this.thinkingSeqMap.get(e);s!==void 0&&(this.thinkingSeqMap.delete(e),t&&(e&&this.rejectedEvents.has(e)||this.handle.sendStreamChunk({event_id:e,session_id:t,delta_content:"",chunk_seq:s+1,is_finish:!0,client_msg_id:`${e}_thinking`,is_thinking:!0})))}bufferOnly(e,t,s,n,i){this.bufferStreamChunk(e,t,s,n,i)}bufferStreamChunk(e,t,s,n,i){const r=this.bufferedEventTexts.get(e);r?(s&&(r.text+=s),!r.quotedMessageId&&i&&(r.quotedMessageId=i)):this.bufferedEventTexts.set(e,{sessionId:t,text:s??"",quotedMessageId:i||void 0}),n&&this.flushBufferedStreamText(e)}flushBufferedStreamText(e){const t=this.bufferedEventTexts.get(e);t&&(this.bufferedEventTexts.delete(e),t.text&&this.handle.sendMsg({event_id:e,session_id:t.sessionId,msg_type:1,content:t.text,...t.quotedMessageId?{quoted_message_id:t.quotedMessageId}:{}}))}}function T(){const f=process.env.GRIX_STREAM_COALESCE_MS;if(f===void 0||f==="")return 500;const e=Number(f);return Number.isFinite(e)&&e>=0?Math.floor(e):500}function y(f,e){return e&&typeof e=="object"&&e.channel_data?.grix?.toolExecution?!0:/^\[tool\]/.test(f)||/^\[tool result\]/.test(f)}export{d as SendController};
1
+ import{log as f}from"../core/log/index.js";import{splitTextForAibotProtocol as g,AIBOT_PROTOCOL_MAX_RUNES as C}from"../core/protocol/protocol-text.js";import{isThinkingEventDropped as E,isToolEventDropped as k,normalizeRuntimeConfigSnapshot as S}from"./runtime-config.js";class p{handle;connectorRuntimeConfig;eventRuntimeConfigs=new Map;thinkingSeqMap=new Map;toolCardSeq=0;bufferedEventTexts=new Map;chunkSeqMap=new Map;rejectedEvents=new Set;stoppedEvents=new Set;streamCoalesce=new Map;streamCoalesceMs=T();static STREAM_COALESCE_MAX_RUNES=2048;recentToolCards=new Map;static TOOL_CARD_DEDUP_MS=100;constructor(e){this.connectorRuntimeConfig={...e}}bind(e){this.handle=e}markEventRejected(e){if(!e)return;this.rejectedEvents.add(e);const t=`${e}:`;for(const s of[...this.streamCoalesce.keys()])if(s.startsWith(t)){const i=this.streamCoalesce.get(s);i.timer&&clearTimeout(i.timer),this.streamCoalesce.delete(s)}f.info("send-ctrl",`event marked as stream-rejected, future chunks will be dropped event=${e}`)}markEventStopped(e){e&&(this.stoppedEvents.add(e),f.info("send-ctrl",`[stop-trace] event marked stopped; subsequent outbound will be flagged event=${e}`))}flagOutboundAfterStop(e,t,s,i,n,o){!e||!this.stoppedEvents.has(e)||f.warn("send-ctrl",`[stop-trace] OUTBOUND AFTER STOP ${s} event=${e} session=${t} seq=${i} is_finish=${n} len=${o}`)}getGlobalRuntimeConfig(){return this.connectorRuntimeConfig}setStreamCoalesceMs(e){this.streamCoalesceMs=Number.isFinite(e)&&e>0?Math.floor(e):0}captureEventRuntimeConfig(e){const t=S(e.connector_runtime_config)??this.connectorRuntimeConfig;this.eventRuntimeConfigs.set(e.event_id,{responseDelivery:t.responseDelivery,toolEvents:t.toolEvents,thinkingEvents:t.thinkingEvents})}resolveEventRuntimeConfig(e){if(e){const t=this.eventRuntimeConfigs.get(e);if(t)return t}return this.connectorRuntimeConfig}shouldDropToolDisplayEvent(e){return k(this.resolveEventRuntimeConfig(e))}shouldDropThinkingDisplayEvent(e){return E(this.resolveEventRuntimeConfig(e))}shouldDropCodexDisplayEvent(e,t){return this.shouldDropToolDisplayEvent(e)?t==="item/completed":!1}shouldDropAcpRawDisplayEvent(e,t){return!!(this.shouldDropToolDisplayEvent(e)&&(t==="tool_use"||t==="tool_result")||this.shouldDropThinkingDisplayEvent(e)&&t==="thinking")}sendReply(e,t,s,i,n,o){if(!o?.skipToolFilter&&this.shouldDropToolDisplayEvent(e)&&y(s,n)||e&&this.rejectedEvents.has(e))return;if(this.flagOutboundAfterStop(e,t,"reply",0,!1,(s||"").length),this.resolveEventRuntimeConfig(e).responseDelivery==="stream"&&!n){const r=g(s),a=e?`reply_stream_${e}`:`reply_stream_${Date.now()}`;if(r.length===0){this.handle.sendMsg({event_id:e,session_id:t,msg_type:1,content:s||" ",quoted_message_id:i||void 0});return}for(let u=0;u<r.length;u++){const h=u===r.length-1;this.sendStreamChunk(e,t,r[u],u+1,h,a,i)}return}this.handle.sendMsg({event_id:e,session_id:t,msg_type:1,content:s,quoted_message_id:i||void 0,...n?{extra:n}:{}})}sendStreamChunk(e,t,s,i,n,o,l){if(e&&this.rejectedEvents.has(e))return;if(this.flagOutboundAfterStop(e,t,"stream_chunk",i,n,(s||"").length),this.finalizeThinking(e,t),this.resolveEventRuntimeConfig(e).responseDelivery==="single_message"&&e){this.bufferStreamChunk(e,t,s,n,l);return}if(this.streamCoalesceMs<=0){this.emitStreamChunkParts(e,t,s,i,n,o,l);return}this.coalesceStreamChunk(e,t,s,n,o,l)}coalesceStreamChunk(e,t,s,i,n,o){if(!s&&!i)return;const l=`${e}:${n??"default"}`;let r=this.streamCoalesce.get(l);if(r||(r={eventId:e,sessionId:t,clientMsgId:n,quotedMessageId:o,text:"",flushedOnce:!1},this.streamCoalesce.set(l,r)),s&&(r.text+=s),r.sessionId=t,n!==void 0&&(r.clientMsgId=n),!r.quotedMessageId&&o&&(r.quotedMessageId=o),i){this.flushCoalesce(l,!0);return}if(!r.flushedOnce||r.text.length>=p.STREAM_COALESCE_MAX_RUNES){this.flushCoalesce(l,!1);return}r.timer||(r.timer=setTimeout(()=>this.flushCoalesce(l,!1),this.streamCoalesceMs))}flushCoalesce(e,t){const s=this.streamCoalesce.get(e);if(!s)return;s.timer&&(clearTimeout(s.timer),s.timer=void 0);const i=s.text;s.text="",s.flushedOnce=!0,t&&this.streamCoalesce.delete(e),!(!i&&!t)&&this.emitStreamChunkParts(s.eventId,s.sessionId,i,0,t,s.clientMsgId,s.quotedMessageId)}flushCoalesceForEvent(e){const t=`${e}:`;for(const s of[...this.streamCoalesce.keys()])s.startsWith(t)&&(this.flushCoalesce(s,!1),this.streamCoalesce.delete(s))}emitStreamChunkParts(e,t,s,i,n,o,l){const r=s||(n?`
2
+ `:"");if(!r&&!n)return;const a=g(r,C);a.length===0&&a.push(r||`
3
+ `);const u=`${e}:${o??"default"}`;for(let h=0;h<a.length;h++){const _=h===a.length-1,m=this.chunkSeqMap.get(u)??0,d=h===0&&i>m?i:m+1;this.chunkSeqMap.set(u,d),this.handle.sendStreamChunk({event_id:e,session_id:t,delta_content:a[h],chunk_seq:d,is_finish:_&&n,...o?{client_msg_id:o}:{},...l?{quoted_message_id:l}:{}})}}sendEventResult(e,t,s,i){this.finalizeThinking(e,""),this.flushCoalesceForEvent(e),this.flushBufferedStreamText(e),this.handle.sendEventResult({event_id:e,status:t,...s?{msg:s}:{},...i?{code:i}:{},updated_at:Date.now()}),this.eventRuntimeConfigs.delete(e),this.rejectedEvents.delete(e),this.stoppedEvents.delete(e)&&f.info("send-ctrl",`[stop-trace] event_result(${t}) cleared stop flag event=${e}`);for(const n of this.chunkSeqMap.keys())n.startsWith(`${e}:`)&&this.chunkSeqMap.delete(n);for(const n of this.recentToolCards.keys())n.startsWith(`${e}:`)&&this.recentToolCards.delete(n)}sendThinking(e,t,s){if(this.shouldDropThinkingDisplayEvent(e)||e&&this.rejectedEvents.has(e))return;this.flagOutboundAfterStop(e,t,"thinking",this.thinkingSeqMap.get(e)??0,!1,(s||"").length);const i=(this.thinkingSeqMap.get(e)??0)+1;this.thinkingSeqMap.set(e,i),this.handle.sendStreamChunk({event_id:e,session_id:t,delta_content:s,chunk_seq:i,is_finish:!1,client_msg_id:`${e}_thinking`,is_thinking:!0})}sendToolExecutionCard(e,t,s,i){if(this.shouldDropToolDisplayEvent(e)){f.info("send-ctrl",`[tool-card] DROP event=${e} session=${t} summary=${s.summaryText}`);return}const o=Date.now(),l=`${e}:${s.summaryText}:${s.detailText??""}`,r=this.recentToolCards.get(l);if(r!==void 0&&o-r<p.TOOL_CARD_DEDUP_MS){f.info("send-ctrl",`[tool-card] DEDUP event=${e} session=${t} summary=${s.summaryText}`);return}this.recentToolCards.set(l,o);const a={summary_text:s.summaryText};s.detailText&&(a.detail_text=s.detailText),f.info("send-ctrl",`[tool-card] SEND event=${e} session=${t} summary=${s.summaryText}`),this.handle.sendMsg({event_id:e,session_id:t,client_msg_id:i??`tool_${++this.toolCardSeq}_${Date.now()}`,msg_type:1,content:"",extra:{channel_data:{grix:{toolExecution:a}},agent_api_origin:!0}})}finalizeThinking(e,t){const s=this.thinkingSeqMap.get(e);s!==void 0&&(this.thinkingSeqMap.delete(e),t&&(e&&this.rejectedEvents.has(e)||this.handle.sendStreamChunk({event_id:e,session_id:t,delta_content:"",chunk_seq:s+1,is_finish:!0,client_msg_id:`${e}_thinking`,is_thinking:!0})))}bufferOnly(e,t,s,i,n){this.bufferStreamChunk(e,t,s,i,n)}bufferStreamChunk(e,t,s,i,n){const o=this.bufferedEventTexts.get(e);o?(s&&(o.text+=s),!o.quotedMessageId&&n&&(o.quotedMessageId=n)):this.bufferedEventTexts.set(e,{sessionId:t,text:s??"",quotedMessageId:n||void 0}),i&&this.flushBufferedStreamText(e)}flushBufferedStreamText(e){const t=this.bufferedEventTexts.get(e);t&&(this.bufferedEventTexts.delete(e),t.text&&this.handle.sendMsg({event_id:e,session_id:t.sessionId,msg_type:1,content:t.text,...t.quotedMessageId?{quoted_message_id:t.quotedMessageId}:{}}))}}function T(){const c=process.env.GRIX_STREAM_COALESCE_MS;if(c===void 0||c==="")return 500;const e=Number(c);return Number.isFinite(e)&&e>=0?Math.floor(e):500}function y(c,e){return e&&typeof e=="object"&&e.channel_data?.grix?.toolExecution?!0:/^\[tool\]/.test(c)||/^\[tool result\]/.test(c)}export{p as SendController};
@@ -1,4 +1,4 @@
1
- import{SESSION_CONTROL_VERBS as l,SESSION_CONTROL_ERROR_CODES as g}from"../adapter/claude/protocol-contract.js";import{parseInteractionMessage as _,readCardSubmissionParam as b}from"../core/protocol/interaction-parser.js";import{log as S}from"../core/log/index.js";function w(t){const o=String(t.content??"").trim();if(!o.startsWith("/grix ")){const s=_(o);for(const i of s.cardSubmissions){if(i.cardId!=="agent_open_session")continue;const e=b(i,"cwd")??"";return e?{verb:l.open,args:e}:null}return null}const d=o.slice(6).trim().split(/\s+/),c=d[0]?.toLowerCase(),m=d.slice(1).join(" ");return Object.values(l).includes(c)?{verb:c,args:m}:null}function k(t,o,d,c){const{verb:m,args:r}=t,s=o.session_id,i=()=>c.sendEventAck(o.event_id,s),e=(n,a)=>c.sendEventResult(o.event_id,n,a);switch(m){case l.open:{const n=r.trim();if(!n){i(),e("failed",{msg:"Usage: /grix open <working-directory>",code:g.cwdRequired});return}d.getSessionBindings().set(s,n),d.onSessionBound?.(s,n),i(),e("responded",{msg:`Session bound to ${n}`});break}case l.where:{const n=d.getSessionBindings().get(s)||d.getCwd()||"not set";i(),e("responded",{msg:`Working directory: ${n}`});break}case l.status:{const n=d.getStatus(),a=d.getAcpSessionOptions(),f=a?`
1
+ import{SESSION_CONTROL_VERBS as l,SESSION_CONTROL_ERROR_CODES as g}from"../adapter/claude/protocol-contract.js";import{parseInteractionMessage as _,readCardSubmissionParam as b}from"../core/protocol/interaction-parser.js";import{log as S}from"../core/log/index.js";function w(t){const o=String(t.content??"").trim();if(!o.startsWith("/grix ")){const s=_(o);for(const i of s.cardSubmissions){if(i.cardId!=="agent_open_session")continue;const e=b(i,"cwd")??"";return{verb:l.open,args:e}}return null}const d=o.slice(6).trim().split(/\s+/),c=d[0]?.toLowerCase(),m=d.slice(1).join(" ");return Object.values(l).includes(c)?{verb:c,args:m}:null}function k(t,o,d,c){const{verb:m,args:r}=t,s=o.session_id,i=()=>c.sendEventAck(o.event_id,s),e=(n,a)=>c.sendEventResult(o.event_id,n,a);switch(m){case l.open:{const n=r.trim();if(!n){i(),e("failed",{msg:"Usage: /grix open <working-directory>",code:g.cwdRequired});return}d.getSessionBindings().set(s,n),d.onSessionBound?.(s,n),i(),e("responded",{msg:`Session bound to ${n}`});break}case l.where:{const n=d.getSessionBindings().get(s)||d.getCwd()||"not set";i(),e("responded",{msg:`Working directory: ${n}`});break}case l.status:{const n=d.getStatus(),a=d.getAcpSessionOptions(),f=a?`
2
2
  Mode: ${a.currentModeId} (${a.modes.map(p=>p.id).join(", ")})`:"",u=a?`
3
3
  Model: ${a.currentModelId} (${a.models.map(p=>p.modelId).join(", ")})`:"";i(),e("responded",{msg:`Status: alive=${n.alive} busy=${n.busy} adapter=${n.adapterType}${f}${u}`});break}case l.stop:{if(!d.getSessionBindings().get(s)){i(),e("failed",{msg:"No session binding found for this session",code:g.bindingMissing});return}d.cancelActiveRun?.().catch(()=>{}),i(),e("responded",{msg:`Session ${s} stopped`});break}case l.restart:{const n=d.getSessionBindings().get(s);if(!n){i(),e("failed",{msg:"No session binding found for this session",code:g.bindingMissing});return}d.cancelActiveRun?.().catch(()=>{}),i(),e("responded",{msg:`Session ${s} restarted for ${n}`});break}case"set_mode":{const n=r.trim();if(!n){i(),e("failed",{msg:"Usage: /grix set_mode <mode-id>"});return}d.setMode(n).then(a=>{a&&d.onModeSet?.(n),i(),e(a?"responded":"failed",{msg:a?`Mode set to ${n}`:`Failed to set mode ${n}`})});break}case"set_model":{const n=r.trim();if(!n){i(),e("failed",{msg:"Usage: /grix set_model <model-id>"});return}d.setModel(n).then(a=>{a&&d.onModelSet?.(n),i(),e(a?"responded":"failed",{msg:a?`Model set to ${n}`:`Failed to set model ${n}`})});break}case"list_options":{const n=d.getAcpSessionOptions();if(!n){i(),e("failed",{msg:"No ACP session active"});return}const a=n.modes.map(u=>`${u.id}: ${u.name}`).join(`
4
4
  `),f=n.models.map(u=>`${u.modelId}: ${u.name}`).join(`
@@ -1 +1 @@
1
- import{readdirSync as h,statSync as l}from"node:fs";import i from"node:path";import{homedir as f}from"node:os";class g{cachedEntries=null;builtAt=0;dirSignatures=new Map;inflight=null;minCheckIntervalMs;maxTTLMs;scanner;resolveLeafDirs;constructor(t,s,e){this.scanner=t,this.resolveLeafDirs=s,this.minCheckIntervalMs=e?.minCheckIntervalMs??2e3,this.maxTTLMs=e?.maxTTLMs??3e4}invalidate(){this.cachedEntries=null,this.builtAt=0,this.dirSignatures.clear()}get(){const t=Date.now();return this.cachedEntries&&t-this.builtAt<this.minCheckIntervalMs?this.cachedEntries:!this.cachedEntries||t-this.builtAt>=this.maxTTLMs?this.rebuild():this.hasSignatureChanged()?this.rebuild():this.cachedEntries}rebuild(){const t=this.scanner();return this.cachedEntries=t,this.builtAt=Date.now(),this.refreshSignatures(),t}hasSignatureChanged(){const t=this.resolveLeafDirs();if(t.length!==this.dirSignatures.size)return!0;for(const s of t){const e=this.dirSignatures.get(s);if(!e)return!0;try{if(l(s).mtimeMs!==e.mtimeMs||h(s).length!==e.childCount)return!0}catch{return!0}}return!1}refreshSignatures(){this.dirSignatures.clear();const t=this.resolveLeafDirs();for(const s of t)try{const e=l(s),r=h(s);this.dirSignatures.set(s,{mtimeMs:e.mtimeMs,childCount:r.length})}catch{}}}function y(n){const t=n||process.env.CODEX_HOME||i.join(f(),".codex"),s=[i.join(t,"sessions"),i.join(t,"archived_sessions")],e=[];for(const r of s)e.push(r),d(r,3,7,e);return e}function j(n){const t=n||i.join(f(),".claude","projects"),s=[t];try{const e=h(t,{withFileTypes:!0});for(const r of e)r.isDirectory()&&s.push(i.join(t,r.name))}catch{}return s}function v(n){const t=n||f(),s=[],e=i.join(t,".gemini","tmp");s.push(e);try{const o=h(e,{withFileTypes:!0});for(const c of o)if(c.isDirectory()){const u=i.join(e,c.name,"chats");s.push(u)}}catch{}const r=i.join(t,".qwen","projects");s.push(r);try{const o=h(r,{withFileTypes:!0});for(const c of o)if(c.isDirectory()){const u=i.join(r,c.name,"chats");s.push(u)}}catch{}const a=i.join(t,".kiro","sessions","cli");return s.push(a),s}function d(n,t,s,e){if(t<=0){e.push(n);return}let r;try{r=h(n)}catch{return}r.sort((o,c)=>c.localeCompare(o));const a=r.slice(0,s);for(const o of a){const c=i.join(n,o);try{l(c).isDirectory()&&d(c,t-1,s,e)}catch{}}}export{g as SessionScanCache,v as resolveAcpLeafDirs,j as resolveClaudeLeafDirs,y as resolveCodexLeafDirs};
1
+ import{readdirSync as h,statSync as l}from"node:fs";import o from"node:path";import{homedir as u}from"node:os";import{resolveKimiCodeHome as p,resolveCopilotHome as d,resolveReasonixSessionsDir as D}from"../adapter/acp/session-scanner.js";import{resolvePiAgentDir as v}from"../adapter/pi/session-scanner.js";import{resolveCodeWhaleHome as y}from"../adapter/codewhale/session-scanner.js";import{resolveCursorChatsDir as j}from"../adapter/cursor/session-scanner.js";import{resolveOpenCodeDataDir as g}from"../adapter/opencode/session-scanner.js";class E{cachedEntries=null;builtAt=0;dirSignatures=new Map;inflight=null;minCheckIntervalMs;maxTTLMs;scanner;resolveLeafDirs;constructor(s,t,e){this.scanner=s,this.resolveLeafDirs=t,this.minCheckIntervalMs=e?.minCheckIntervalMs??2e3,this.maxTTLMs=e?.maxTTLMs??3e4}invalidate(){this.cachedEntries=null,this.builtAt=0,this.dirSignatures.clear()}get(){const s=Date.now();return this.cachedEntries&&s-this.builtAt<this.minCheckIntervalMs?this.cachedEntries:!this.cachedEntries||s-this.builtAt>=this.maxTTLMs?this.rebuild():this.hasSignatureChanged()?this.rebuild():this.cachedEntries}rebuild(){const s=this.scanner();return this.cachedEntries=s,this.builtAt=Date.now(),this.refreshSignatures(),s}hasSignatureChanged(){const s=this.resolveLeafDirs();if(s.length!==this.dirSignatures.size)return!0;for(const t of s){const e=this.dirSignatures.get(t);if(!e)return!0;try{if(l(t).mtimeMs!==e.mtimeMs||h(t).length!==e.childCount)return!0}catch{return!0}}return!1}refreshSignatures(){this.dirSignatures.clear();const s=this.resolveLeafDirs();for(const t of s)try{const e=l(t),r=h(t);this.dirSignatures.set(t,{mtimeMs:e.mtimeMs,childCount:r.length})}catch{}}}function b(i){const s=i||process.env.CODEX_HOME||o.join(u(),".codex"),t=[o.join(s,"sessions"),o.join(s,"archived_sessions")],e=[];for(const r of t)e.push(r),m(r,3,7,e);return e}function A(i){const s=i||o.join(u(),".claude","projects"),t=[s];try{const e=h(s,{withFileTypes:!0});for(const r of e)r.isDirectory()&&t.push(o.join(s,r.name))}catch{}return t}function F(i,s){const t=i||u(),e=[],r=n=>!s||s.includes(n);if(r("gemini")){const n=o.join(t,".gemini","tmp");e.push(n);try{const a=h(n,{withFileTypes:!0});for(const c of a)if(c.isDirectory()){const f=o.join(n,c.name,"chats");e.push(f)}}catch{}}if(r("qwen")){const n=o.join(t,".qwen","projects");e.push(n);try{const a=h(n,{withFileTypes:!0});for(const c of a)if(c.isDirectory()){const f=o.join(n,c.name,"chats");e.push(f)}}catch{}}if(r("kiro")&&e.push(o.join(t,".kiro","sessions","cli")),r("kimi")){const n=o.join(p(t),"sessions");e.push(n);try{const a=h(n,{withFileTypes:!0});for(const c of a)c.isDirectory()&&e.push(o.join(n,c.name))}catch{}}return r("copilot")&&e.push(o.join(d(t),"session-state")),e}function H(i){return[D(i||u())]}function I(i,s){const t=o.join(v(i||u(),s),"sessions"),e=[t];try{for(const r of h(t,{withFileTypes:!0}))r.isDirectory()&&e.push(o.join(t,r.name))}catch{}return e}function O(i){return[o.join(y(i||u()),"sessions")]}function R(i){const s=j(i||u()),t=[s];try{for(const e of h(s,{withFileTypes:!0}))e.isDirectory()&&t.push(o.join(s,e.name))}catch{}return t}function q(i){return[g(i||u())]}function m(i,s,t,e){if(s<=0){e.push(i);return}let r;try{r=h(i)}catch{return}r.sort((a,c)=>c.localeCompare(a));const n=r.slice(0,t);for(const a of n){const c=o.join(i,a);try{l(c).isDirectory()&&m(c,s-1,t,e)}catch{}}}export{E as SessionScanCache,F as resolveAcpLeafDirs,A as resolveClaudeLeafDirs,O as resolveCodeWhaleLeafDirs,b as resolveCodexLeafDirs,R as resolveCursorLeafDirs,q as resolveOpenCodeLeafDirs,I as resolvePiLeafDirs,H as resolveReasonixLeafDirs};
@@ -1,2 +1,2 @@
1
- import{EventEmitter as v}from"node:events";import{randomUUID as b}from"node:crypto";import p from"node:os";import m from"ws";import{log as c}from"../log/index.js";import{getMachineName as w}from"../util/index.js";import{detectTailnetIPv4 as E,ensureServerAndGetPort as q,getFileServerHttpsPort as S}from"../files/file-serve.js";import{AUTH_CODE_AGENT_DELETED as k,KICKED_REASON_AGENT_DELETED as A,AgentDeletedError as $}from"./errors.js";function y(g){return g.replace(/(?<=[\[:,\[]\s*)(\d{16,})(?=\s*[,}\]\n])/g,'"$1"')}function P(g){const e=[...g??["stream_chunk","local_action_v1","agent_invoke"]];return e.includes("agent_invoke")||e.push("agent_invoke"),e.includes("event_result_ack")||e.push("event_result_ack"),e}const R="aibot-agent-api-v1",T=1;class f extends v{static DROPPABLE_COMMANDS=new Set(["update_binding_card"]);static BUFFER_OVERFLOW_RETAIN_COMMANDS=new Set(["event_result","codex_event","client_stream_chunk"]);static MAX_OUTBOUND_BUFFER_SIZE=1e3;static BACKPRESSURE_THRESHOLD=64*1024;ws=null;seq=0;heartbeatTimer=null;heartbeatSec=30;connected=!1;reconnecting=!1;reconnectAttempts=0;everConnected=!1;agentDeleted=!1;config;packetLog;pendingInvokes=new Map;seqEventMap=new Map;pendingRequests=new Map;outboundBuffer=[];ackPolicy=null;constructor(e,t){super(),this.packetLog=t?.packetLog??null,this.config={url:e.url,agentId:e.agentId,apiKey:e.apiKey,clientType:e.clientType,clientVersion:e.clientVersion??"",adapterHint:e.adapterHint??"",capabilities:P(e.capabilities),localActions:e.localActions??["exec_approve","exec_reject"],skills:e.skills}}get isConnected(){return this.connected}async connect(){let e,t,n;const i=(async()=>{try{if(e=await E(),e!==void 0)try{t=await q(e);const s=S();s>0&&(n=s)}catch(s){c.warn("aibot",`file server pre-start failed: ${s}`)}}catch(s){c.warn("aibot",`tailnet detect failed: ${s}`)}})();return new Promise((s,h)=>{const o=new m(this.config.url);this.ws=o;const a=setTimeout(()=>{h(new Error("Auth timeout: no auth_ack received within 15s")),this.cleanupSocket()},15e3),d=++this.seq,r=setTimeout(()=>{this.pendingRequests.delete(d),h(new Error("Auth request timeout")),this.cleanupSocket()},15e3);this.pendingRequests.set(d,{expected:["auth_ack"],resolve:u=>{clearTimeout(a);const l=u.payload;l.code===0?(this.connected=!0,this.everConnected=!0,this.reconnectAttempts=0,l.heartbeat_sec&&(this.heartbeatSec=l.heartbeat_sec),l.ack_policy&&(this.ackPolicy=l.ack_policy,c.info("aibot",`ack_policy received: push_ack_timeout_ms=${l.ack_policy.push_ack_timeout_ms??"default"} max_retries=${l.ack_policy.max_retries??"default"} timeout_action=${l.ack_policy.timeout_action??"default"}`)),this.startHeartbeat(),this.flushOutboundBuffer(),this.emit("auth",l),s(l)):l.code===k?(this.agentDeleted=!0,h(new $(`Agent deleted: code=${l.code} msg=${l.msg}`))):h(new Error(`Auth failed: code=${l.code} msg=${l.msg}`))},reject:u=>{clearTimeout(a),h(u)},timer:r}),o.on("open",async()=>{await i;const u={agent_id:this.config.agentId,api_key:this.config.apiKey,client_type:this.config.clientType,protocol_version:R,contract_version:T,capabilities:this.config.capabilities??[],local_actions:this.config.localActions,skills:this.config.skills};this.config.sharedOwnerId&&(u.shared_owner_id=this.config.sharedOwnerId),this.config.clientVersion&&(u.client="grix-connector",u.client_version=this.config.clientVersion,u.host_type=this.config.clientType,u.host_version=this.config.clientVersion),this.config.adapterHint&&(u.adapter_hint=this.config.adapterHint),u.host_meta={hostname:w(),platform:p.platform(),arch:p.arch(),os_release:p.release(),...e!==void 0&&{tailnet_ip:e},...t!==void 0&&t>0&&{file_server_port:t},...n!==void 0&&n>0&&{file_server_https_port:n}},this.config.concurrency&&(u.concurrency=this.config.concurrency),this.sendPacket("auth",u,d)}),o.on("message",u=>{if(this.ws!==o)return;let l;try{l=JSON.parse(y(u.toString()))}catch{return}try{this.handlePacket(l)}catch(_){this.emitClientError(new Error(`handlePacket error: ${_}`))}}),o.on("close",(u,l)=>{if(this.ws!==o)return;this.connected=!1,this.stopHeartbeat(),this.rejectAllPendingRequests("websocket closed"),this.emit("close",u,l.toString());const _=u!==1e3&&this.everConnected&&!this.agentDeleted;c.info("aibot",`ws closed agent=${this.config.clientType}:${this.config.agentId} code=${u} reason=${l.toString()||"<none>"} everConnected=${this.everConnected} reconnecting=${this.reconnecting} agentDeleted=${this.agentDeleted} willReconnect=${_}`),_&&this.attemptReconnect()}),o.on("error",u=>{this.ws===o&&(this.emitClientError(u instanceof Error?u:new Error(String(u))),this.connected||h(u))})})}handlePacket(e){if(this.packetLog?.logInboundPacket(e.cmd,e.seq,e.payload),e.seq>0&&this.pendingRequests.has(e.seq)){const t=this.pendingRequests.get(e.seq);this.pendingRequests.delete(e.seq),clearTimeout(t.timer),t.expected.includes(e.cmd)?t.resolve(e):t.reject(new Error(`unexpected response: got ${e.cmd}, expected ${t.expected.join("/")}`));return}switch(e.cmd){case"auth_ack":break;case"ping":{this.sendPacket("pong",e.payload??{});break}case"event_msg":{this.emit("event",e.payload);break}case"local_action":{this.emit("localAction",e.payload);break}case"event_stop":{this.emit("stop",e.payload);break}case"event_revoke":{this.emit("revoke",e.payload);break}case"event_edit":{this.emit("edit",e.payload);break}case"event_cancel":{this.emit("eventCancel",e.payload);break}case"queue_clear":{this.emit("queueClear",e.payload);break}case"queue_snapshot_query":{this.emit("queueSnapshotQuery",e.payload);break}case"control_share_set":{this.emit("shareSet",e.payload);break}case"agent_profile_push":{this.emit("profilePush",e.payload);break}case"kicked":{const t=e.payload;if(this.emit("kicked",t),this.connected=!1,this.stopHeartbeat(),this.rejectAllPendingRequests("kicked"),this.outboundBuffer.length=0,t?.reason===A){if(this.agentDeleted=!0,this.reconnecting=!1,this.ws){try{this.ws.close(4001,"kicked")}catch{}this.ws=null}c.error("aibot",`kicked: agent deleted on platform agent=${this.config.clientType}:${this.config.agentId}, reconnect disabled`),this.emit("agentDeleted",{source:"kicked",reason:t.reason});break}if(this.reconnectAttempts=Math.max(this.reconnectAttempts,3),this.ws){try{this.ws.close(4001,"kicked")}catch{}this.ws=null}break}case"error":{const t=e.payload,n=[t.ref_cmd?`ref_cmd=${t.ref_cmd}`:"",t.ref_id?`ref_id=${t.ref_id}`:""].filter(Boolean).join(" ");this.emitClientError(new Error(`Server error: code=${t.code} msg=${t.msg}${n?` ${n}`:""}`));break}case"agent_invoke_result":{this.handleInvokeResult(e.payload);break}case"mcp_frame":{const t=e.payload;this.emit("mcpFrame",t.session_id??"",t.frame??null);break}case"send_ack":break;case"send_nack":{const t=e.payload;if(t.code===4003&&e.seq>0){const n=this.seqEventMap.get(e.seq);n&&(this.seqEventMap.delete(e.seq),this.purgeBufferedStreamChunks(n),c.warn("aibot",`stream chunk rejected (4003), purging buffered chunks for event=${n}`),this.emit("streamRejected",n,t.code))}break}case"local_action_ack":break;default:break}}sendEventAck(e){this.sendPacket("event_ack",e)||c.warn("aibot",`event_ack NOT sent (ws not open) event=${e.event_id} ws=${this.ws?`state=${this.ws.readyState}`:"null"}`)}sendStreamChunk(e){!e.delta_content&&!e.is_finish&&(c.warn("aibot",`stream_chunk delta_content empty, patched to newline event=${e.event_id??""} session=${e.session_id} chunk_seq=${e.chunk_seq} is_finish=${e.is_finish}`),e={...e,delta_content:`
2
- `}),this.sendPacket("client_stream_chunk",e)}sendMsg(e){this.sendPacket("send_msg",e)||c.warn("aibot",`send_msg NOT sent (ws not open) event=${e.event_id??""} session=${e.session_id??""} ws=${this.ws?`state=${this.ws.readyState}`:"null"}`)}editMsg(e){this.sendPacket("edit_msg",e)}sendEventResult(e){if(!this.ws||this.ws.readyState!==m.OPEN){this.sendPacket("event_result",e);return}this.sendEventResultReliable(e)}sendLocalActionResult(e){this.sendPacket("local_action_result",e)}sendEventStopAck(e){this.sendPacket("event_stop_ack",e)}sendEventStopResult(e){this.sendPacket("event_stop_result",e)}sendSessionActivitySet(e){this.sendPacket("session_activity_set",e)}sendCodexEvent(e){this.sendPacket("codex_event",e)}sendUpdateBindingCard(e){this.sendPacket("update_binding_card",e)}sendSkillsUpdate(e){this.sendPacket("agent_skills_update",e)}sendPing(){this.sendPacket("ping",{})}sendEventState(e){this.sendPacket("event_state",e)}sendEventCancelResult(e){this.sendPacket("event_cancel_result",e)}sendQueueClearResult(e){this.sendPacket("queue_clear_result",e)}sendQueueSnapshot(e){this.sendPacket("queue_snapshot",e)}agentInvoke(e,t,n=15e3){return new Promise((i,s)=>{const h=b(),o=Math.max(1e3,Math.min(n,6e4)),a=setTimeout(()=>{this.pendingInvokes.delete(h),s(new Error(`agent_invoke timeout: ${e}`))},o);this.pendingInvokes.set(h,{resolve:i,reject:s,timer:a}),this.sendPacket("agent_invoke",{invoke_id:h,action:e,params:t,timeout_ms:o})})}sendMcpFrame(e,t){this.sendPacket("mcp_frame",{session_id:e,frame:t})}request(e,t,n){return new Promise((i,s)=>{const h=++this.seq,o=setTimeout(()=>{this.pendingRequests.delete(h),s(new Error(`request timeout: ${e} (expected ${n.expected.join("/")})`))},n.timeoutMs);this.pendingRequests.set(h,{expected:n.expected,resolve:i,reject:s,timer:o}),this.sendPacket(e,t,h)||(this.pendingRequests.delete(h),clearTimeout(o),s(new Error(`send failed: ${e}`)))})}async sendStreamChunkRequest(e,t=2e4){return this.request("client_stream_chunk",e,{expected:["send_ack","send_nack","error"],timeoutMs:t})}async sendText(e,t=2e4){return this.request("send_msg",{msg_type:1,...e},{expected:["send_ack","send_nack","error"],timeoutMs:t})}async sendMedia(e,t=2e4){return this.request("send_msg",{...e,msg_type:2},{expected:["send_ack","send_nack","error"],timeoutMs:t})}async editMessage(e,t=2e4){return this.request("edit_msg",e,{expected:["send_ack","send_nack","error"],timeoutMs:t})}async deleteMessage(e,t,n=2e4){return this.request("delete_msg",{session_id:e,msg_id:t},{expected:["send_ack","send_nack","error"],timeoutMs:n})}async sendEventResultRequest(e,t=5e3){return this.request("event_result",e,{expected:["send_ack","send_nack","error"],timeoutMs:t})}disconnect(){c.info("aibot",`disconnect() agent=${this.config.clientType}:${this.config.agentId} wasConnected=${this.connected} reconnecting=${this.reconnecting} reconnectAttempts=${this.reconnectAttempts}`),this.connected=!1,this.everConnected=!1,this.reconnecting=!1,this.reconnectAttempts=0,this.stopHeartbeat(),this.rejectAllPendingInvokes("disconnect"),this.rejectAllPendingRequests("disconnect"),this.outboundBuffer.length=0,this.ws&&(this.ws.close(1e3,"client disconnect"),this.ws=null)}static MAX_CONSECUTIVE_AUTH_FAILURES=5;async attemptReconnect(){if(this.reconnecting||this.agentDeleted)return;this.reconnecting=!0,c.info("aibot",`attemptReconnect start agent=${this.config.clientType}:${this.config.agentId} fromAttempts=${this.reconnectAttempts}`),this.emit("disconnected");let e=0;for(;this.reconnecting;){const t=Math.min(1e3*2**this.reconnectAttempts,3e4),n=Math.floor(t*.2*Math.random());if(this.reconnectAttempts++,await new Promise(i=>setTimeout(i,t+n)),!this.reconnecting)return;try{const i=await this.connect(),s=this.reconnectAttempts;this.reconnectAttempts=0,this.reconnecting=!1,c.info("aibot",`reconnect succeeded agent=${this.config.clientType}:${this.config.agentId} attempt=${s}`),this.emit("auth",i);return}catch(i){if(this.ws){try{this.ws.close()}catch{}this.ws=null}const s=i instanceof Error?i.message:String(i);if(c.warn("aibot",`reconnect failed agent=${this.config.clientType}:${this.config.agentId} attempt=${this.reconnectAttempts} err=${s}`),i instanceof $){this.agentDeleted=!0,this.reconnecting=!1,c.error("aibot",`reconnect aborted: agent deleted on platform agent=${this.config.clientType}:${this.config.agentId}`),this.emit("agentDeleted",{source:"auth_ack",code:k});return}if(/Auth failed/i.test(s)){if(e++,e>=f.MAX_CONSECUTIVE_AUTH_FAILURES){this.reconnecting=!1,c.error("aibot",`reconnect giving up after ${e} consecutive auth failures agent=${this.config.clientType}:${this.config.agentId}`);return}}else e=0}}}sendPacket(e,t,n){if(this.ws&&this.ws.readyState===m.OPEN){const i=this.ws.bufferedAmount>f.BACKPRESSURE_THRESHOLD;if(!i||!f.DROPPABLE_COMMANDS.has(e)){if(i&&f.DROPPABLE_COMMANDS.has(e))return!1;const s=n??++this.seq;if(e==="client_stream_chunk"&&t&&typeof t=="object"){const o=t.event_id;if(o&&(this.seqEventMap.set(s,o),this.seqEventMap.size>200)){const a=this.seqEventMap.keys().next().value;a!==void 0&&this.seqEventMap.delete(a)}}const h={cmd:e,seq:s,payload:t};this.packetLog?.logOutboundPacket(e,s,t,"sent");try{const o=this.ws.readyState,a=this.ws.bufferedAmount;return this.ws.send(JSON.stringify(h),d=>{if(e==="event_result"){const r=t;d?c.warn("aibot",`event_result ws send callback failed event=${r.event_id??""} status=${r.status??""} seq=${s} readyState=${o} bufferedAmount=${a} err=${d.message}`):c.info("aibot",`event_result ws send callback ok event=${r.event_id??""} status=${r.status??""} seq=${s} readyState=${o} bufferedAmount=${a}`)}else if(e==="client_stream_chunk"){const r=t;d?c.warn("aibot",`stream_chunk ws send failed event=${r.event_id??""} session=${r.session_id??""} seq=${s} chunk_seq=${r.chunk_seq??""} is_finish=${r.is_finish??""} readyState=${o} bufferedAmount=${a} err=${d.message}`):c.info("aibot",`stream_chunk ws send ok event=${r.event_id??""} session=${r.session_id??""} seq=${s} chunk_seq=${r.chunk_seq??""} is_finish=${r.is_finish??""} client_msg_id=${r.client_msg_id??""} quoted_message_id=${r.quoted_message_id??""} readyState=${o} bufferedAmount=${a}`)}else if(e==="event_ack"){const r=t;d?c.warn("aibot",`event_ack ws send failed event=${r.event_id??""} seq=${s} readyState=${o} bufferedAmount=${a} err=${d.message}`):c.info("aibot",`event_ack ws send ok event=${r.event_id??""} seq=${s} readyState=${o} bufferedAmount=${a}`)}else if(e==="send_msg"){const r=t;d?c.warn("aibot",`send_msg ws send failed event=${r.event_id??""} session=${r.session_id??""} seq=${s} readyState=${o} bufferedAmount=${a} err=${d.message}`):c.info("aibot",`send_msg ws send ok event=${r.event_id??""} session=${r.session_id??""} seq=${s} readyState=${o} bufferedAmount=${a}`)}else if(d){const r=t;c.warn("aibot",`${e} ws send failed seq=${s} session=${r.session_id??""} event=${r.event_id??""} client_msg_id=${r.client_msg_id??""} readyState=${o} bufferedAmount=${a} err=${d.message}`)}}),!0}catch(o){return this.emitClientError(new Error(`sendPacket failed: ${o}`)),!1}}}if(f.DROPPABLE_COMMANDS.has(e))return this.packetLog?.logOutboundPacket(e,n??0,t,"dropped"),!1;if(n!==void 0)return this.packetLog?.logOutboundPacket(e,n,t,"dropped"),!1;if(this.outboundBuffer.length>=f.MAX_OUTBOUND_BUFFER_SIZE&&(this.outboundBuffer=this.outboundBuffer.filter(i=>f.BUFFER_OVERFLOW_RETAIN_COMMANDS.has(i.cmd)),this.outboundBuffer.length>=f.MAX_OUTBOUND_BUFFER_SIZE&&this.outboundBuffer.shift()),this.outboundBuffer.push({cmd:e,payload:t}),this.packetLog?.logOutboundPacket(e,n??0,t,"buffered"),e==="client_stream_chunk"){const i=t;c.info("aibot",`stream_chunk buffered (ws not open) event=${i.event_id??""} session=${i.session_id??""} chunk_seq=${i.chunk_seq??""} is_finish=${i.is_finish??""} ws=${this.ws?`state=${this.ws.readyState}`:"null"}`)}return!1}async sendEventResultReliable(e){const t=this.ackPolicy?.max_retries??3,n=this.ackPolicy?.push_ack_timeout_ms??5e3,i=750;for(let s=1;s<=t;s++){const h=this.ws?.readyState??-1,o=this.ws?.bufferedAmount??0;c.info("aibot",`event_result send attempt event=${e.event_id} status=${e.status} attempt=${s}/${t} readyState=${h} bufferedAmount=${o}`);try{const a=await this.sendEventResultRequest(e,n);if(a.cmd==="send_ack"){const r=a.payload;c.info("aibot",`event_result ack event=${e.event_id} status=${e.status} attempt=${s}/${t} ack_event=${r.event_id??""} ack_status=${r.status??""}`);return}const d=a.payload;if(c.warn("aibot",`event_result rejected event=${e.event_id} status=${e.status} attempt=${s}/${t} cmd=${a.cmd} code=${d.code??""} msg=${d.msg??""}${d.ref_cmd?` ref_cmd=${d.ref_cmd}`:""}${d.ref_id?` ref_id=${d.ref_id}`:""}`),d.code===4003){c.warn("aibot",`event_result stopping retries: 4003 ownership denied event=${e.event_id}`);return}return}catch(a){const d=a instanceof Error?a.message:String(a);if(c.warn("aibot",`event_result attempt failed event=${e.event_id} status=${e.status} attempt=${s}/${t} err=${d}`),s===t){this.emitClientError(new Error(`event_result ack failed after ${t} attempts: event=${e.event_id} status=${e.status}`));return}await new Promise(r=>setTimeout(r,i*s))}}}purgeBufferedStreamChunks(e){const t=this.outboundBuffer.length;this.outboundBuffer=this.outboundBuffer.filter(n=>n.cmd!=="client_stream_chunk"?!0:n.payload?.event_id!==e),this.outboundBuffer.length<t&&c.info("aibot",`purged ${t-this.outboundBuffer.length} buffered stream chunks for event=${e}`)}emitClientError(e){if(this.listenerCount("error")===0){c.warn("aibot",`Client error (no listeners): ${e.message}`);return}this.emit("error",e)}flushOutboundBuffer(){if(this.outboundBuffer.length===0||!this.ws||this.ws.readyState!==m.OPEN)return;const e=this.outboundBuffer;this.outboundBuffer=[];for(const{cmd:t,payload:n}of e){const i=++this.seq;if(t==="client_stream_chunk"&&n&&typeof n=="object"){const h=n.event_id;h&&this.seqEventMap.set(i,h)}const s={cmd:t,seq:i,payload:n};try{this.ws.send(JSON.stringify(s))}catch{break}}if(this.seqEventMap.size>200){const t=[...this.seqEventMap.entries()].sort((n,i)=>n[0]-i[0]);this.seqEventMap.clear();for(const[n,i]of t.slice(-100))this.seqEventMap.set(n,i)}}handleInvokeResult(e){const t=this.pendingInvokes.get(e.invoke_id);t&&(this.pendingInvokes.delete(e.invoke_id),clearTimeout(t.timer),e.code===0?t.resolve(e.data??null):t.reject(new Error(`agent_invoke error code=${e.code}: ${e.msg??""}`)))}rejectAllPendingInvokes(e){for(const[,t]of this.pendingInvokes)clearTimeout(t.timer),t.reject(new Error(`agent_invoke canceled: ${e}`));this.pendingInvokes.clear()}rejectAllPendingRequests(e){for(const[,t]of this.pendingRequests)clearTimeout(t.timer),t.reject(new Error(`request canceled: ${e}`));this.pendingRequests.clear()}cleanupSocket(){if(this.ws)try{this.ws.close()}catch{}}startHeartbeat(){this.stopHeartbeat(),this.heartbeatTimer=setInterval(()=>{this.connected&&this.request("ping",{ts:Date.now()},{expected:["pong"],timeoutMs:5e3}).catch(()=>{this.connected&&(this.cleanupSocket(),this.attemptReconnect())})},this.heartbeatSec*1e3)}stopHeartbeat(){this.heartbeatTimer&&(clearInterval(this.heartbeatTimer),this.heartbeatTimer=null)}}export{f as AibotClient};
1
+ import{EventEmitter as v}from"node:events";import{randomUUID as b}from"node:crypto";import p from"node:os";import m from"ws";import{log as a}from"../log/index.js";import{getMachineName as w}from"../util/index.js";import{detectTailnetIPv4 as E,ensureServerAndGetPort as q,getFileServerHttpsPort as A}from"../files/file-serve.js";import{AUTH_CODE_AGENT_DELETED as k,KICKED_REASON_AGENT_DELETED as S,AgentDeletedError as $}from"./errors.js";function y(g){return g.replace(/(?<=[\[:,\[]\s*)(\d{16,})(?=\s*[,}\]\n])/g,'"$1"')}function P(g){const e=[...g??["stream_chunk","local_action_v1","agent_invoke"]];return e.includes("agent_invoke")||e.push("agent_invoke"),e.includes("event_result_ack")||e.push("event_result_ack"),e}const R="aibot-agent-api-v1",T=1;class f extends v{static DROPPABLE_COMMANDS=new Set(["update_binding_card"]);static BUFFER_OVERFLOW_RETAIN_COMMANDS=new Set(["event_result","codex_event","client_stream_chunk"]);static MAX_OUTBOUND_BUFFER_SIZE=1e3;static BACKPRESSURE_THRESHOLD=64*1024;ws=null;seq=0;heartbeatTimer=null;heartbeatSec=30;heartbeatFailures=0;static HEARTBEAT_MAX_FAILURES=2;connected=!1;reconnecting=!1;reconnectAttempts=0;everConnected=!1;agentDeleted=!1;config;packetLog;pendingInvokes=new Map;seqEventMap=new Map;pendingRequests=new Map;outboundBuffer=[];ackPolicy=null;constructor(e,t){super(),this.packetLog=t?.packetLog??null,this.config={url:e.url,agentId:e.agentId,apiKey:e.apiKey,clientType:e.clientType,clientVersion:e.clientVersion??"",adapterHint:e.adapterHint??"",capabilities:P(e.capabilities),localActions:e.localActions??["exec_approve","exec_reject"],skills:e.skills}}get isConnected(){return this.connected}async connect(){let e,t,s;const i=(async()=>{try{if(e=await E(),e!==void 0)try{t=await q(e);const n=A();n>0&&(s=n)}catch(n){a.warn("aibot",`file server pre-start failed: ${n}`)}}catch(n){a.warn("aibot",`tailnet detect failed: ${n}`)}})();return new Promise((n,h)=>{const c=new m(this.config.url);this.ws=c;const o=setTimeout(()=>{h(new Error("Auth timeout: no auth_ack received within 15s")),this.cleanupSocket()},15e3),d=++this.seq,r=setTimeout(()=>{this.pendingRequests.delete(d),h(new Error("Auth request timeout")),this.cleanupSocket()},15e3);this.pendingRequests.set(d,{expected:["auth_ack"],resolve:u=>{clearTimeout(o);const l=u.payload;l.code===0?(this.connected=!0,this.everConnected=!0,this.reconnectAttempts=0,l.heartbeat_sec&&(this.heartbeatSec=l.heartbeat_sec),l.ack_policy&&(this.ackPolicy=l.ack_policy,a.info("aibot",`ack_policy received: push_ack_timeout_ms=${l.ack_policy.push_ack_timeout_ms??"default"} max_retries=${l.ack_policy.max_retries??"default"} timeout_action=${l.ack_policy.timeout_action??"default"}`)),this.startHeartbeat(),this.flushOutboundBuffer(),this.emit("auth",l),n(l)):l.code===k?(this.agentDeleted=!0,h(new $(`Agent deleted: code=${l.code} msg=${l.msg}`))):h(new Error(`Auth failed: code=${l.code} msg=${l.msg}`))},reject:u=>{clearTimeout(o),h(u)},timer:r}),c.on("open",async()=>{await i;const u={agent_id:this.config.agentId,api_key:this.config.apiKey,client_type:this.config.clientType,protocol_version:R,contract_version:T,capabilities:this.config.capabilities??[],local_actions:this.config.localActions,skills:this.config.skills};this.config.sharedOwnerId&&(u.shared_owner_id=this.config.sharedOwnerId),this.config.clientVersion&&(u.client="grix-connector",u.client_version=this.config.clientVersion,u.host_type=this.config.clientType,u.host_version=this.config.clientVersion),this.config.adapterHint&&(u.adapter_hint=this.config.adapterHint),u.host_meta={hostname:w(),platform:p.platform(),arch:p.arch(),os_release:p.release(),...e!==void 0&&{tailnet_ip:e},...t!==void 0&&t>0&&{file_server_port:t},...s!==void 0&&s>0&&{file_server_https_port:s}},this.config.concurrency&&(u.concurrency=this.config.concurrency),this.sendPacket("auth",u,d)}),c.on("message",u=>{if(this.ws!==c)return;let l;try{l=JSON.parse(y(u.toString()))}catch{return}try{this.handlePacket(l)}catch(_){this.emitClientError(new Error(`handlePacket error: ${_}`))}}),c.on("close",(u,l)=>{if(this.ws!==c)return;this.connected=!1,this.stopHeartbeat(),this.rejectAllPendingRequests("websocket closed"),this.emit("close",u,l.toString());const _=u!==1e3&&this.everConnected&&!this.agentDeleted;a.info("aibot",`ws closed agent=${this.config.clientType}:${this.config.agentId} code=${u} reason=${l.toString()||"<none>"} everConnected=${this.everConnected} reconnecting=${this.reconnecting} agentDeleted=${this.agentDeleted} willReconnect=${_}`),_&&this.attemptReconnect()}),c.on("error",u=>{this.ws===c&&(this.emitClientError(u instanceof Error?u:new Error(String(u))),this.connected||h(u))})})}handlePacket(e){if(this.packetLog?.logInboundPacket(e.cmd,e.seq,e.payload),e.seq>0&&this.pendingRequests.has(e.seq)){const t=this.pendingRequests.get(e.seq);this.pendingRequests.delete(e.seq),clearTimeout(t.timer),t.expected.includes(e.cmd)?t.resolve(e):t.reject(new Error(`unexpected response: got ${e.cmd}, expected ${t.expected.join("/")}`));return}switch(e.cmd){case"auth_ack":break;case"ping":{this.sendPacket("pong",e.payload??{});break}case"event_msg":{this.emit("event",e.payload);break}case"local_action":{this.emit("localAction",e.payload);break}case"event_stop":{this.emit("stop",e.payload);break}case"event_revoke":{this.emit("revoke",e.payload);break}case"event_edit":{this.emit("edit",e.payload);break}case"event_cancel":{this.emit("eventCancel",e.payload);break}case"queue_clear":{this.emit("queueClear",e.payload);break}case"queue_snapshot_query":{this.emit("queueSnapshotQuery",e.payload);break}case"control_share_set":{this.emit("shareSet",e.payload);break}case"agent_profile_push":{this.emit("profilePush",e.payload);break}case"kicked":{const t=e.payload;if(this.emit("kicked",t),this.connected=!1,this.stopHeartbeat(),this.rejectAllPendingRequests("kicked"),this.outboundBuffer.length=0,t?.reason===S){if(this.agentDeleted=!0,this.reconnecting=!1,this.ws){try{this.ws.close(4001,"kicked")}catch{}this.ws=null}a.error("aibot",`kicked: agent deleted on platform agent=${this.config.clientType}:${this.config.agentId}, reconnect disabled`),this.emit("agentDeleted",{source:"kicked",reason:t.reason});break}if(this.reconnectAttempts=Math.max(this.reconnectAttempts,3),this.ws){try{this.ws.close(4001,"kicked")}catch{}this.ws=null}break}case"error":{const t=e.payload,s=[t.ref_cmd?`ref_cmd=${t.ref_cmd}`:"",t.ref_id?`ref_id=${t.ref_id}`:""].filter(Boolean).join(" ");this.emitClientError(new Error(`Server error: code=${t.code} msg=${t.msg}${s?` ${s}`:""}`));break}case"agent_invoke_result":{this.handleInvokeResult(e.payload);break}case"mcp_frame":{const t=e.payload;this.emit("mcpFrame",t.session_id??"",t.frame??null);break}case"send_ack":break;case"send_nack":{const t=e.payload;if(t.code===4003&&e.seq>0){const s=this.seqEventMap.get(e.seq);s&&(this.seqEventMap.delete(e.seq),this.purgeBufferedStreamChunks(s),a.warn("aibot",`stream chunk rejected (4003), purging buffered chunks for event=${s}`),this.emit("streamRejected",s,t.code))}break}case"local_action_ack":break;default:break}}sendEventAck(e){this.sendPacket("event_ack",e)||a.warn("aibot",`event_ack NOT sent (ws not open) event=${e.event_id} ws=${this.ws?`state=${this.ws.readyState}`:"null"}`)}sendStreamChunk(e){!e.delta_content&&!e.is_finish&&(a.warn("aibot",`stream_chunk delta_content empty, patched to newline event=${e.event_id??""} session=${e.session_id} chunk_seq=${e.chunk_seq} is_finish=${e.is_finish}`),e={...e,delta_content:`
2
+ `}),this.sendPacket("client_stream_chunk",e)}sendMsg(e){this.sendPacket("send_msg",e)||a.warn("aibot",`send_msg NOT sent (ws not open) event=${e.event_id??""} session=${e.session_id??""} ws=${this.ws?`state=${this.ws.readyState}`:"null"}`)}editMsg(e){this.sendPacket("edit_msg",e)}sendEventResult(e){if(!this.ws||this.ws.readyState!==m.OPEN){this.sendPacket("event_result",e);return}this.sendEventResultReliable(e)}sendLocalActionResult(e){this.sendPacket("local_action_result",e)}sendEventStopAck(e){this.sendPacket("event_stop_ack",e)}sendEventStopResult(e){this.sendPacket("event_stop_result",e)}sendSessionActivitySet(e){this.sendPacket("session_activity_set",e)}sendCodexEvent(e){this.sendPacket("codex_event",e)}sendUpdateBindingCard(e){this.sendPacket("update_binding_card",e)}sendSkillsUpdate(e){this.sendPacket("agent_skills_update",e)}sendPing(){this.sendPacket("ping",{})}sendEventState(e){this.sendPacket("event_state",e)}sendEventCancelResult(e){this.sendPacket("event_cancel_result",e)}sendQueueClearResult(e){this.sendPacket("queue_clear_result",e)}sendQueueSnapshot(e){this.sendPacket("queue_snapshot",e)}agentInvoke(e,t,s=15e3){return new Promise((i,n)=>{const h=b(),c=Math.max(1e3,Math.min(s,6e4)),o=setTimeout(()=>{this.pendingInvokes.delete(h),n(new Error(`agent_invoke timeout: ${e}`))},c);this.pendingInvokes.set(h,{resolve:i,reject:n,timer:o}),this.sendPacket("agent_invoke",{invoke_id:h,action:e,params:t,timeout_ms:c})})}sendMcpFrame(e,t){this.sendPacket("mcp_frame",{session_id:e,frame:t})}request(e,t,s){return new Promise((i,n)=>{const h=++this.seq,c=setTimeout(()=>{this.pendingRequests.delete(h),n(new Error(`request timeout: ${e} (expected ${s.expected.join("/")})`))},s.timeoutMs);this.pendingRequests.set(h,{expected:s.expected,resolve:i,reject:n,timer:c}),this.sendPacket(e,t,h)||(this.pendingRequests.delete(h),clearTimeout(c),n(new Error(`send failed: ${e}`)))})}async sendStreamChunkRequest(e,t=2e4){return this.request("client_stream_chunk",e,{expected:["send_ack","send_nack","error"],timeoutMs:t})}async sendText(e,t=2e4){return this.request("send_msg",{msg_type:1,...e},{expected:["send_ack","send_nack","error"],timeoutMs:t})}async sendMedia(e,t=2e4){return this.request("send_msg",{...e,msg_type:2},{expected:["send_ack","send_nack","error"],timeoutMs:t})}async editMessage(e,t=2e4){return this.request("edit_msg",e,{expected:["send_ack","send_nack","error"],timeoutMs:t})}async deleteMessage(e,t,s=2e4){return this.request("delete_msg",{session_id:e,msg_id:t},{expected:["send_ack","send_nack","error"],timeoutMs:s})}async sendEventResultRequest(e,t=5e3){return this.request("event_result",e,{expected:["send_ack","send_nack","error"],timeoutMs:t})}disconnect(){a.info("aibot",`disconnect() agent=${this.config.clientType}:${this.config.agentId} wasConnected=${this.connected} reconnecting=${this.reconnecting} reconnectAttempts=${this.reconnectAttempts}`),this.connected=!1,this.everConnected=!1,this.reconnecting=!1,this.reconnectAttempts=0,this.stopHeartbeat(),this.rejectAllPendingInvokes("disconnect"),this.rejectAllPendingRequests("disconnect"),this.outboundBuffer.length=0,this.ws&&(this.ws.close(1e3,"client disconnect"),this.ws=null)}static MAX_CONSECUTIVE_AUTH_FAILURES=5;async attemptReconnect(){if(this.reconnecting||this.agentDeleted)return;this.reconnecting=!0,a.info("aibot",`attemptReconnect start agent=${this.config.clientType}:${this.config.agentId} fromAttempts=${this.reconnectAttempts}`),this.emit("disconnected");let e=0;for(;this.reconnecting;){const t=Math.min(1e3*2**this.reconnectAttempts,3e4),s=Math.floor(t*.2*Math.random());if(this.reconnectAttempts++,await new Promise(i=>setTimeout(i,t+s)),!this.reconnecting)return;try{await this.connect();const i=this.reconnectAttempts;this.reconnectAttempts=0,this.reconnecting=!1,a.info("aibot",`reconnect succeeded agent=${this.config.clientType}:${this.config.agentId} attempt=${i}`);return}catch(i){if(this.ws){try{this.ws.close()}catch{}this.ws=null}const n=i instanceof Error?i.message:String(i);if(a.warn("aibot",`reconnect failed agent=${this.config.clientType}:${this.config.agentId} attempt=${this.reconnectAttempts} err=${n}`),i instanceof $){this.agentDeleted=!0,this.reconnecting=!1,a.error("aibot",`reconnect aborted: agent deleted on platform agent=${this.config.clientType}:${this.config.agentId}`),this.emit("agentDeleted",{source:"auth_ack",code:k});return}if(/Auth failed/i.test(n)){if(e++,e>=f.MAX_CONSECUTIVE_AUTH_FAILURES){this.reconnecting=!1,a.error("aibot",`reconnect giving up after ${e} consecutive auth failures agent=${this.config.clientType}:${this.config.agentId}`);return}}else e=0}}}sendPacket(e,t,s){if(this.ws&&this.ws.readyState===m.OPEN){const i=this.ws.bufferedAmount>f.BACKPRESSURE_THRESHOLD;if(!i||!f.DROPPABLE_COMMANDS.has(e)){if(i&&f.DROPPABLE_COMMANDS.has(e))return!1;const n=s??++this.seq;if(e==="client_stream_chunk"&&t&&typeof t=="object"){const c=t.event_id;if(c&&(this.seqEventMap.set(n,c),this.seqEventMap.size>200)){const o=this.seqEventMap.keys().next().value;o!==void 0&&this.seqEventMap.delete(o)}}const h={cmd:e,seq:n,payload:t};this.packetLog?.logOutboundPacket(e,n,t,"sent");try{const c=this.ws.readyState,o=this.ws.bufferedAmount;return this.ws.send(JSON.stringify(h),d=>{if(e==="event_result"){const r=t;d?a.warn("aibot",`event_result ws send callback failed event=${r.event_id??""} status=${r.status??""} seq=${n} readyState=${c} bufferedAmount=${o} err=${d.message}`):a.info("aibot",`event_result ws send callback ok event=${r.event_id??""} status=${r.status??""} seq=${n} readyState=${c} bufferedAmount=${o}`)}else if(e==="client_stream_chunk"){const r=t;d?a.warn("aibot",`stream_chunk ws send failed event=${r.event_id??""} session=${r.session_id??""} seq=${n} chunk_seq=${r.chunk_seq??""} is_finish=${r.is_finish??""} readyState=${c} bufferedAmount=${o} err=${d.message}`):a.info("aibot",`stream_chunk ws send ok event=${r.event_id??""} session=${r.session_id??""} seq=${n} chunk_seq=${r.chunk_seq??""} is_finish=${r.is_finish??""} client_msg_id=${r.client_msg_id??""} quoted_message_id=${r.quoted_message_id??""} readyState=${c} bufferedAmount=${o}`)}else if(e==="event_ack"){const r=t;d?a.warn("aibot",`event_ack ws send failed event=${r.event_id??""} seq=${n} readyState=${c} bufferedAmount=${o} err=${d.message}`):a.info("aibot",`event_ack ws send ok event=${r.event_id??""} seq=${n} readyState=${c} bufferedAmount=${o}`)}else if(e==="send_msg"){const r=t;d?a.warn("aibot",`send_msg ws send failed event=${r.event_id??""} session=${r.session_id??""} seq=${n} readyState=${c} bufferedAmount=${o} err=${d.message}`):a.info("aibot",`send_msg ws send ok event=${r.event_id??""} session=${r.session_id??""} seq=${n} readyState=${c} bufferedAmount=${o}`)}else if(d){const r=t;a.warn("aibot",`${e} ws send failed seq=${n} session=${r.session_id??""} event=${r.event_id??""} client_msg_id=${r.client_msg_id??""} readyState=${c} bufferedAmount=${o} err=${d.message}`)}}),!0}catch(c){return this.emitClientError(new Error(`sendPacket failed: ${c}`)),!1}}}if(f.DROPPABLE_COMMANDS.has(e))return this.packetLog?.logOutboundPacket(e,s??0,t,"dropped"),!1;if(s!==void 0)return this.packetLog?.logOutboundPacket(e,s,t,"dropped"),!1;if(this.outboundBuffer.length>=f.MAX_OUTBOUND_BUFFER_SIZE&&(this.outboundBuffer=this.outboundBuffer.filter(i=>f.BUFFER_OVERFLOW_RETAIN_COMMANDS.has(i.cmd)),this.outboundBuffer.length>=f.MAX_OUTBOUND_BUFFER_SIZE&&this.outboundBuffer.shift()),this.outboundBuffer.push({cmd:e,payload:t}),this.packetLog?.logOutboundPacket(e,s??0,t,"buffered"),e==="client_stream_chunk"){const i=t;a.info("aibot",`stream_chunk buffered (ws not open) event=${i.event_id??""} session=${i.session_id??""} chunk_seq=${i.chunk_seq??""} is_finish=${i.is_finish??""} ws=${this.ws?`state=${this.ws.readyState}`:"null"}`)}return!1}async sendEventResultReliable(e){const t=this.ackPolicy?.max_retries??3,s=this.ackPolicy?.push_ack_timeout_ms??5e3,i=750;for(let n=1;n<=t;n++){const h=this.ws?.readyState??-1,c=this.ws?.bufferedAmount??0;a.info("aibot",`event_result send attempt event=${e.event_id} status=${e.status} attempt=${n}/${t} readyState=${h} bufferedAmount=${c}`);try{const o=await this.sendEventResultRequest(e,s);if(o.cmd==="send_ack"){const r=o.payload;a.info("aibot",`event_result ack event=${e.event_id} status=${e.status} attempt=${n}/${t} ack_event=${r.event_id??""} ack_status=${r.status??""}`);return}const d=o.payload;if(a.warn("aibot",`event_result rejected event=${e.event_id} status=${e.status} attempt=${n}/${t} cmd=${o.cmd} code=${d.code??""} msg=${d.msg??""}${d.ref_cmd?` ref_cmd=${d.ref_cmd}`:""}${d.ref_id?` ref_id=${d.ref_id}`:""}`),d.code===4003){a.warn("aibot",`event_result stopping retries: 4003 ownership denied event=${e.event_id}`);return}return}catch(o){const d=o instanceof Error?o.message:String(o);if(a.warn("aibot",`event_result attempt failed event=${e.event_id} status=${e.status} attempt=${n}/${t} err=${d}`),n===t){this.emitClientError(new Error(`event_result ack failed after ${t} attempts: event=${e.event_id} status=${e.status}`));return}await new Promise(r=>setTimeout(r,i*n))}}}purgeBufferedStreamChunks(e){const t=this.outboundBuffer.length;this.outboundBuffer=this.outboundBuffer.filter(s=>s.cmd!=="client_stream_chunk"?!0:s.payload?.event_id!==e),this.outboundBuffer.length<t&&a.info("aibot",`purged ${t-this.outboundBuffer.length} buffered stream chunks for event=${e}`)}emitClientError(e){if(this.listenerCount("error")===0){a.warn("aibot",`Client error (no listeners): ${e.message}`);return}this.emit("error",e)}flushOutboundBuffer(){if(this.outboundBuffer.length===0||!this.ws||this.ws.readyState!==m.OPEN)return;const e=this.outboundBuffer;this.outboundBuffer=[];for(const{cmd:t,payload:s}of e){const i=++this.seq;if(t==="client_stream_chunk"&&s&&typeof s=="object"){const h=s.event_id;h&&this.seqEventMap.set(i,h)}const n={cmd:t,seq:i,payload:s};try{this.ws.send(JSON.stringify(n))}catch{break}}if(this.seqEventMap.size>200){const t=[...this.seqEventMap.entries()].sort((s,i)=>s[0]-i[0]);this.seqEventMap.clear();for(const[s,i]of t.slice(-100))this.seqEventMap.set(s,i)}}handleInvokeResult(e){const t=this.pendingInvokes.get(e.invoke_id);t&&(this.pendingInvokes.delete(e.invoke_id),clearTimeout(t.timer),e.code===0?t.resolve(e.data??null):t.reject(new Error(`agent_invoke error code=${e.code}: ${e.msg??""}`)))}rejectAllPendingInvokes(e){for(const[,t]of this.pendingInvokes)clearTimeout(t.timer),t.reject(new Error(`agent_invoke canceled: ${e}`));this.pendingInvokes.clear()}rejectAllPendingRequests(e){for(const[,t]of this.pendingRequests)clearTimeout(t.timer),t.reject(new Error(`request canceled: ${e}`));this.pendingRequests.clear()}cleanupSocket(){if(this.ws)try{this.ws.close()}catch{}}startHeartbeat(){this.stopHeartbeat(),this.heartbeatFailures=0,this.heartbeatTimer=setInterval(()=>{this.connected&&this.request("ping",{ts:Date.now()},{expected:["pong"],timeoutMs:5e3}).then(()=>{this.heartbeatFailures=0}).catch(()=>{this.connected&&(this.heartbeatFailures++,!(this.heartbeatFailures<f.HEARTBEAT_MAX_FAILURES)&&(this.cleanupSocket(),this.attemptReconnect()))})},this.heartbeatSec*1e3)}stopHeartbeat(){this.heartbeatTimer&&(clearInterval(this.heartbeatTimer),this.heartbeatTimer=null)}}export{f as AibotClient};
@@ -1 +1 @@
1
- import{readJSONFile as c,writeJSONFileAtomic as g}from"../util/json-file.js";import{log as l}from"../log/index.js";import{SESSION_MODE_IDS as s}from"../../adapter/claude/protocol-contract.js";const a=s.fullAuto;function n(r){const e=String(r??"").trim().toLowerCase();return e===s.approval||e===s.fullAuto?e:a}class x{bindings=new Map;filePath;writePromise=Promise.resolve();constructor(e){this.filePath=e??null}load(){if(!this.filePath)return;const e=c(this.filePath);if(Array.isArray(e)){this.bindings.clear();for(const d of e)d.aibotSessionId&&this.bindings.set(d.aibotSessionId,{...d,modeId:n(d.modeId)})}}set(e,d,t){const i=this.bindings.get(e),o=n(t?.modeId??i?.modeId);this.bindings.set(e,{aibotSessionId:e,cwd:d,acpSessionId:i?.acpSessionId,claudeSessionId:i?.claudeSessionId,codexThreadId:i?.codexThreadId,codexModelId:i?.codexModelId,codexModeId:i?.codexModeId,codewhaleThreadId:i?.codewhaleThreadId,agyConversationId:i?.agyConversationId,codexReasoningEffort:i?.codexReasoningEffort,codexServiceTier:i?.codexServiceTier,codexSandboxMode:i?.codexSandboxMode,piSessionPath:i?.piSessionPath,modeId:o,modelId:i?.modelId,acpModelId:i?.acpModelId,acpModeId:i?.acpModeId,updatedAt:Date.now()}),this.scheduleWrite()}setAcpSessionId(e,d){this.updateBinding(e,{acpSessionId:d})}setClaudeSessionId(e,d){this.updateBinding(e,{claudeSessionId:d})}getClaudeSessionId(e){return this.bindings.get(e)?.claudeSessionId}setCodexThreadId(e,d){this.updateBinding(e,{codexThreadId:d})}getCodexThreadId(e){return this.bindings.get(e)?.codexThreadId}setAgyConversationId(e,d){this.updateBinding(e,{agyConversationId:d})}getAgyConversationId(e){return this.bindings.get(e)?.agyConversationId}setCodexContext(e,d){this.updateBinding(e,{codexModelId:d.modelId,codexModeId:d.modeId,codexReasoningEffort:d.reasoningEffort,codexServiceTier:d.serviceTier,codexSandboxMode:d.sandboxMode})}getCodexModelId(e){return this.bindings.get(e)?.codexModelId}getCodexModeId(e){return this.bindings.get(e)?.codexModeId}getCodexReasoningEffort(e){return this.bindings.get(e)?.codexReasoningEffort}getCodexServiceTier(e){return this.bindings.get(e)?.codexServiceTier}getCodexSandboxMode(e){return this.bindings.get(e)?.codexSandboxMode}setPiSessionPath(e,d){this.updateBinding(e,{piSessionPath:d})}getPiSessionPath(e){return this.bindings.get(e)?.piSessionPath}setCodeWhaleThreadId(e,d){this.updateBinding(e,{codewhaleThreadId:d})}getCodeWhaleThreadId(e){return this.bindings.get(e)?.codewhaleThreadId}setModeId(e,d){const t=this.bindings.get(e);if(!t)return;const i=n(d);t.modeId!==i&&(this.bindings.set(e,{...t,modeId:i,updatedAt:Date.now()}),this.scheduleWrite())}ensureModeId(e,d=a){const t=this.bindings.get(e);t&&(t.modeId||(this.bindings.set(e,{...t,modeId:n(d),updatedAt:Date.now()}),this.scheduleWrite()))}getAcpSessionId(e){return this.bindings.get(e)?.acpSessionId}getModeId(e){const d=this.bindings.get(e)?.modeId;return d?n(d):void 0}setModelId(e,d){const t=this.bindings.get(e);if(!t)return;const i=d.trim();t.modelId!==i&&(this.bindings.set(e,{...t,modelId:i,updatedAt:Date.now()}),this.scheduleWrite())}getModelId(e){return this.bindings.get(e)?.modelId}setAcpModelId(e,d){const t=this.bindings.get(e);if(!t)return;const i=String(d??"").trim(),o=i===""?void 0:i;t.acpModelId!==o&&(this.bindings.set(e,{...t,acpModelId:o,updatedAt:Date.now()}),this.scheduleWrite())}getAcpModelId(e){return this.bindings.get(e)?.acpModelId}setAcpModeId(e,d){const t=this.bindings.get(e);if(!t)return;const i=String(d??"").trim(),o=i===""?void 0:i;t.acpModeId!==o&&(this.bindings.set(e,{...t,acpModeId:o,updatedAt:Date.now()}),this.scheduleWrite())}getAcpModeId(e){return this.bindings.get(e)?.acpModeId}get(e){return this.bindings.get(e)}getMostRecentlyUpdatedSessionId(e){const d=e?.requireCwd!==!1;let t,i=-1;for(const o of this.bindings.values())d&&!String(o.cwd??"").trim()||o.updatedAt>i&&(i=o.updatedAt,t=o.aibotSessionId);return t}delete(e){this.bindings.delete(e),this.scheduleWrite()}entries(){return this.bindings.entries()}async flush(){await this.writePromise}scheduleWrite(){this.filePath&&(this.writePromise=this.writePromise.then(()=>this.write()).catch(e=>{l.error("session-binding-store",`Persist failed: ${e instanceof Error?e.message:e}`)}))}async write(){if(!this.filePath)return;const e=Array.from(this.bindings.values());await g(this.filePath,e)}updateBinding(e,d){const t=this.bindings.get(e),i={aibotSessionId:e,acpSessionId:Object.prototype.hasOwnProperty.call(d,"acpSessionId")?d.acpSessionId:t?.acpSessionId,claudeSessionId:Object.prototype.hasOwnProperty.call(d,"claudeSessionId")?d.claudeSessionId:t?.claudeSessionId,codexThreadId:Object.prototype.hasOwnProperty.call(d,"codexThreadId")?d.codexThreadId:t?.codexThreadId,codexModelId:Object.prototype.hasOwnProperty.call(d,"codexModelId")?d.codexModelId:t?.codexModelId,codexModeId:Object.prototype.hasOwnProperty.call(d,"codexModeId")?d.codexModeId:t?.codexModeId,codewhaleThreadId:Object.prototype.hasOwnProperty.call(d,"codewhaleThreadId")?d.codewhaleThreadId:t?.codewhaleThreadId,agyConversationId:Object.prototype.hasOwnProperty.call(d,"agyConversationId")?d.agyConversationId:t?.agyConversationId,codexReasoningEffort:Object.prototype.hasOwnProperty.call(d,"codexReasoningEffort")?d.codexReasoningEffort:t?.codexReasoningEffort,codexServiceTier:Object.prototype.hasOwnProperty.call(d,"codexServiceTier")?d.codexServiceTier:t?.codexServiceTier,codexSandboxMode:Object.prototype.hasOwnProperty.call(d,"codexSandboxMode")?d.codexSandboxMode:t?.codexSandboxMode,piSessionPath:Object.prototype.hasOwnProperty.call(d,"piSessionPath")?d.piSessionPath:t?.piSessionPath,cwd:t?.cwd,modeId:n(t?.modeId),modelId:t?.modelId,acpModelId:t?.acpModelId,acpModeId:t?.acpModeId,updatedAt:Date.now()};this.bindings.set(e,i),this.scheduleWrite()}}export{x as SessionBindingStore};
1
+ import{readJSONFile as c,writeJSONFileAtomic as g}from"../util/json-file.js";import{log as l}from"../log/index.js";import{SESSION_MODE_IDS as s}from"../../adapter/claude/protocol-contract.js";const a=s.fullAuto;function n(r){const e=String(r??"").trim().toLowerCase();return e===s.approval||e===s.fullAuto?e:a}class x{bindings=new Map;filePath;writePromise=Promise.resolve();constructor(e){this.filePath=e??null}load(){if(!this.filePath)return;const e=c(this.filePath);if(Array.isArray(e)){this.bindings.clear();for(const d of e)d.aibotSessionId&&this.bindings.set(d.aibotSessionId,{...d,modeId:n(d.modeId)})}}set(e,d,t){const i=this.bindings.get(e),o=n(t?.modeId??i?.modeId);this.bindings.set(e,{aibotSessionId:e,cwd:d,acpSessionId:i?.acpSessionId,claudeSessionId:i?.claudeSessionId,codexThreadId:i?.codexThreadId,codexModelId:i?.codexModelId,codexModeId:i?.codexModeId,codewhaleThreadId:i?.codewhaleThreadId,agyConversationId:i?.agyConversationId,codexReasoningEffort:i?.codexReasoningEffort,codexServiceTier:i?.codexServiceTier,codexSandboxMode:i?.codexSandboxMode,piSessionPath:i?.piSessionPath,modeId:o,modelId:i?.modelId,acpModelId:i?.acpModelId,acpModeId:i?.acpModeId,updatedAt:Date.now()}),this.scheduleWrite()}setAcpSessionId(e,d){this.updateBinding(e,{acpSessionId:d})}setClaudeSessionId(e,d){this.updateBinding(e,{claudeSessionId:d})}getClaudeSessionId(e){return this.bindings.get(e)?.claudeSessionId}setCodexThreadId(e,d){this.updateBinding(e,{codexThreadId:d})}getCodexThreadId(e){return this.bindings.get(e)?.codexThreadId}setAgyConversationId(e,d){this.updateBinding(e,{agyConversationId:d})}getAgyConversationId(e){return this.bindings.get(e)?.agyConversationId}setCodexContext(e,d){this.updateBinding(e,{codexModelId:d.modelId,codexModeId:d.modeId,codexReasoningEffort:d.reasoningEffort,codexServiceTier:d.serviceTier,codexSandboxMode:d.sandboxMode})}getCodexModelId(e){return this.bindings.get(e)?.codexModelId}setCodexModelId(e,d){const t=this.bindings.get(e);if(!t)return;const i=String(d??"").trim();!i||t.codexModelId===i||(this.bindings.set(e,{...t,codexModelId:i,updatedAt:Date.now()}),this.scheduleWrite())}getCodexModeId(e){return this.bindings.get(e)?.codexModeId}getCodexReasoningEffort(e){return this.bindings.get(e)?.codexReasoningEffort}getCodexServiceTier(e){return this.bindings.get(e)?.codexServiceTier}getCodexSandboxMode(e){return this.bindings.get(e)?.codexSandboxMode}setPiSessionPath(e,d){this.updateBinding(e,{piSessionPath:d})}getPiSessionPath(e){return this.bindings.get(e)?.piSessionPath}setCodeWhaleThreadId(e,d){this.updateBinding(e,{codewhaleThreadId:d})}getCodeWhaleThreadId(e){return this.bindings.get(e)?.codewhaleThreadId}setModeId(e,d){const t=this.bindings.get(e);if(!t)return;const i=n(d);t.modeId!==i&&(this.bindings.set(e,{...t,modeId:i,updatedAt:Date.now()}),this.scheduleWrite())}ensureModeId(e,d=a){const t=this.bindings.get(e);t&&(t.modeId||(this.bindings.set(e,{...t,modeId:n(d),updatedAt:Date.now()}),this.scheduleWrite()))}getAcpSessionId(e){return this.bindings.get(e)?.acpSessionId}getModeId(e){const d=this.bindings.get(e)?.modeId;return d?n(d):void 0}setModelId(e,d){const t=this.bindings.get(e);if(!t)return;const i=d.trim();t.modelId!==i&&(this.bindings.set(e,{...t,modelId:i,updatedAt:Date.now()}),this.scheduleWrite())}getModelId(e){return this.bindings.get(e)?.modelId}setAcpModelId(e,d){const t=this.bindings.get(e);if(!t)return;const i=String(d??"").trim(),o=i===""?void 0:i;t.acpModelId!==o&&(this.bindings.set(e,{...t,acpModelId:o,updatedAt:Date.now()}),this.scheduleWrite())}getAcpModelId(e){return this.bindings.get(e)?.acpModelId}setAcpModeId(e,d){const t=this.bindings.get(e);if(!t)return;const i=String(d??"").trim(),o=i===""?void 0:i;t.acpModeId!==o&&(this.bindings.set(e,{...t,acpModeId:o,updatedAt:Date.now()}),this.scheduleWrite())}getAcpModeId(e){return this.bindings.get(e)?.acpModeId}get(e){return this.bindings.get(e)}getMostRecentlyUpdatedSessionId(e){const d=e?.requireCwd!==!1;let t,i=-1;for(const o of this.bindings.values())d&&!String(o.cwd??"").trim()||o.updatedAt>i&&(i=o.updatedAt,t=o.aibotSessionId);return t}delete(e){this.bindings.delete(e),this.scheduleWrite()}entries(){return this.bindings.entries()}async flush(){await this.writePromise}scheduleWrite(){this.filePath&&(this.writePromise=this.writePromise.then(()=>this.write()).catch(e=>{l.error("session-binding-store",`Persist failed: ${e instanceof Error?e.message:e}`)}))}async write(){if(!this.filePath)return;const e=Array.from(this.bindings.values());await g(this.filePath,e)}updateBinding(e,d){const t=this.bindings.get(e),i={aibotSessionId:e,acpSessionId:Object.prototype.hasOwnProperty.call(d,"acpSessionId")?d.acpSessionId:t?.acpSessionId,claudeSessionId:Object.prototype.hasOwnProperty.call(d,"claudeSessionId")?d.claudeSessionId:t?.claudeSessionId,codexThreadId:Object.prototype.hasOwnProperty.call(d,"codexThreadId")?d.codexThreadId:t?.codexThreadId,codexModelId:Object.prototype.hasOwnProperty.call(d,"codexModelId")?d.codexModelId:t?.codexModelId,codexModeId:Object.prototype.hasOwnProperty.call(d,"codexModeId")?d.codexModeId:t?.codexModeId,codewhaleThreadId:Object.prototype.hasOwnProperty.call(d,"codewhaleThreadId")?d.codewhaleThreadId:t?.codewhaleThreadId,agyConversationId:Object.prototype.hasOwnProperty.call(d,"agyConversationId")?d.agyConversationId:t?.agyConversationId,codexReasoningEffort:Object.prototype.hasOwnProperty.call(d,"codexReasoningEffort")?d.codexReasoningEffort:t?.codexReasoningEffort,codexServiceTier:Object.prototype.hasOwnProperty.call(d,"codexServiceTier")?d.codexServiceTier:t?.codexServiceTier,codexSandboxMode:Object.prototype.hasOwnProperty.call(d,"codexSandboxMode")?d.codexSandboxMode:t?.codexSandboxMode,piSessionPath:Object.prototype.hasOwnProperty.call(d,"piSessionPath")?d.piSessionPath:t?.piSessionPath,cwd:t?.cwd,modeId:n(t?.modeId),modelId:t?.modelId,acpModelId:t?.acpModelId,acpModeId:t?.acpModeId,updatedAt:Date.now()};this.bindings.set(e,i),this.scheduleWrite()}}export{x as SessionBindingStore};
@@ -1 +1 @@
1
- const c="grix://card/",l="grix://open/",u="_submit",d=/\(\s*(grix:\/\/card\/[\s\S]+?)\s*\)/gi,m=/grix:\/\/card\/[^\s)\]]+/gi,f=/^grix:\/\/open\/\S+$/i;function g(t){const n=[],r=new Set;for(const e of p(t)){const s=h(e);!s||r.has(s.normalizedUri)||(r.add(s.normalizedUri),n.push(s))}return{cardSubmissions:n}}function C(t,n){return U(t.params.get(n))}function p(t){const n=S(t),r=[];for(const o of n.matchAll(d)){const i=a(o[1]);i&&r.push(i)}for(const o of n.matchAll(m)){const i=a(o[0]);i&&r.push(i)}const e=n.trim();f.test(e)&&r.push(e);const s=a(e);return s.startsWith(c)&&r.push(s),R(r)}function h(t){if(t.startsWith(l))return _(t);if(!t.startsWith(c))return null;let n;try{n=new URL(t)}catch{return null}if(n.hostname!=="card")return null;const r=n.pathname.replace(/^\/+/,"").trim();if(!r.endsWith(u))return null;const e=r.slice(0,-u.length).trim();return!e||e==="agent_open_session"?null:{cardId:e,rawUri:t,normalizedUri:n.toString(),params:n.searchParams}}function _(t){let n;try{n=new URL(t)}catch{return null}if(n.hostname!=="open")return null;const r=n.pathname.split("/").map(e=>e.trim()).filter(Boolean);return r.length!==1||r[0]!=="session"?null:{cardId:"agent_open_session",rawUri:t,normalizedUri:n.toString(),params:n.searchParams}}function S(t){return t.replace(/&amp;/gi,"&")}function a(t){return t.replace(/\s+/g,"").trim()}function R(t){const n=[...new Set(t)];return n.filter(r=>!n.some(e=>e!==r&&e.startsWith(r)))}function U(t){if(!t)return;let n=t.trim();for(let r=0;r<3;r++)try{const e=decodeURIComponent(n);if(e===n)break;n=e}catch{break}return n.trim()||void 0}export{g as parseInteractionMessage,C as readCardSubmissionParam};
1
+ const c="grix://card/",m="grix://open/",u="_submit",f=/\(\s*(grix:\/\/card\/[\s\S]+?)\s*\)/gi,p=/grix:\/\/card\/[^\s)\]]+/gi,l=/^grix:\/\/open\/\S+$/i;function U(t){const n=[],r=new Set;for(const e of h(t)){const s=S(e);!s||r.has(s.normalizedUri)||(r.add(s.normalizedUri),n.push(s))}return{cardSubmissions:n}}function C(t,n){return g(t.params.get(n))}function I(t){return l.test(d(t).trim())}function h(t){const n=d(t),r=[];for(const o of n.matchAll(f)){const i=a(o[1]);i&&r.push(i)}for(const o of n.matchAll(p)){const i=a(o[0]);i&&r.push(i)}const e=n.trim();l.test(e)&&r.push(e);const s=a(e);return s.startsWith(c)&&r.push(s),R(r)}function S(t){if(t.startsWith(m))return _(t);if(!t.startsWith(c))return null;let n;try{n=new URL(t)}catch{return null}if(n.hostname!=="card")return null;const r=n.pathname.replace(/^\/+/,"").trim();if(!r.endsWith(u))return null;const e=r.slice(0,-u.length).trim();return!e||e==="agent_open_session"?null:{cardId:e,rawUri:t,normalizedUri:n.toString(),params:n.searchParams}}function _(t){let n;try{n=new URL(t)}catch{return null}if(n.hostname!=="open")return null;const r=n.pathname.split("/").map(e=>e.trim()).filter(Boolean);return r.length!==1||r[0]!=="session"?null:{cardId:"agent_open_session",rawUri:t,normalizedUri:n.toString(),params:n.searchParams}}function d(t){return t.replace(/&amp;/gi,"&")}function a(t){return t.replace(/\s+/g,"").trim()}function R(t){const n=[...new Set(t)];return n.filter(r=>!n.some(e=>e!==r&&e.startsWith(r)))}function g(t){if(!t)return;let n=t.trim();for(let r=0;r<3;r++)try{const e=decodeURIComponent(n);if(e===n)break;n=e}catch{break}return n.trim()||void 0}export{I as isOpenSessionDirectiveMessage,U as parseInteractionMessage,C as readCardSubmissionParam};
@@ -1 +1 @@
1
- import{detectProvider as o,queryProviderQuota as t}from"./providers.js";export{o as detectProvider,t as queryProviderQuota};
1
+ import{detectProvider as i,queryProviderQuota as o,readKimiProviderSettings as t,resolveKimiProviderSettings as d}from"./providers.js";export{i as detectProvider,o as queryProviderQuota,t as readKimiProviderSettings,d as resolveKimiProviderSettings};
@@ -1 +1,2 @@
1
- function R(s){const e=s.toLowerCase();return e.includes("open.bigmodel.cn")||e.includes("bigmodel.cn")||e.includes("api.z.ai")?{id:"zhipu",label:"Zhipu GLM"}:e.includes("api.kimi.com")?{id:"kimi",label:"Kimi"}:e.includes("api.minimaxi.com")?{id:"minimax_cn",label:"MiniMax"}:e.includes("api.minimax.io")?{id:"minimax_en",label:"MiniMax"}:e.includes("api.deepseek.com")?{id:"deepseek",label:"DeepSeek"}:e.includes("api.stepfun.ai")||e.includes("api.stepfun.com")?{id:"stepfun",label:"StepFun"}:e.includes("api.siliconflow.cn")?{id:"siliconflow_cn",label:"SiliconFlow"}:e.includes("api.siliconflow.com")?{id:"siliconflow_en",label:"SiliconFlow"}:e.includes("openrouter.ai")?{id:"openrouter",label:"OpenRouter"}:e.includes("api.novita.ai")?{id:"novita",label:"Novita AI"}:null}const f=1e4;function u(s,e,i){return{provider:s,providerLabel:e,planName:null,tiers:[],balance:null,success:!1,error:i}}function m(s){if(typeof s=="number")return s;if(typeof s=="string"){const e=Number(s);return Number.isFinite(e)?e:null}return null}function w(s){if(!Number.isFinite(s)||s<=0)return null;try{return new Date(s).toISOString()}catch{return null}}function T(s){if(typeof s=="string")return s;if(typeof s=="number"){const e=s<1e12?s*1e3:s;return w(e)}return null}async function S(s){const e="zhipu",i="Zhipu GLM";try{const t=await fetch("https://api.z.ai/api/monitor/usage/quota/limit",{method:"GET",headers:{Authorization:s,"Content-Type":"application/json","Accept-Language":"en-US,en"},signal:AbortSignal.timeout(f)});if(t.status===401||t.status===403)return u(e,i,`Authentication failed (HTTP ${t.status})`);if(!t.ok){const l=await t.text().catch(()=>"");return u(e,i,`API error (HTTP ${t.status}): ${l.slice(0,200)}`)}const o=await t.json();if(o.success===!1)return u(e,i,`API error: ${o.msg??"Unknown error"}`);const n=o.data;if(!n)return u(e,i,"Missing data field");const r=typeof n.level=="string"?n.level:null,a=$(n);return{provider:e,providerLabel:i,planName:r,tiers:a,balance:null,success:!0,error:null}}catch(t){return u(e,i,`Network error: ${t instanceof Error?t.message:String(t)}`)}}function $(s){const e=Array.isArray(s.limits)?s.limits:[],i=[];for(const o of e){if(String(o.type??"").toUpperCase()!=="TOKENS_LIMIT")continue;const r=m(o.percentage)??0,a=typeof o.nextResetTime=="number"?o.nextResetTime:Number.MAX_SAFE_INTEGER,l=a===Number.MAX_SAFE_INTEGER?null:w(a);i.push({percentage:r,resetMs:a,resetIso:l})}i.sort((o,n)=>o.resetMs-n.resetMs);const t=[];if(i.length>0){const o=i[0];t.push({name:"five_hour",label:"5h limit",usedPercent:Math.round(o.percentage*100)/100,resetsAt:o.resetIso})}return t}async function P(s){const e="kimi",i="Kimi";try{const t=await fetch("https://api.kimi.com/coding/v1/usages",{method:"GET",headers:{Authorization:`Bearer ${s}`,Accept:"application/json"},signal:AbortSignal.timeout(f)});if(t.status===401||t.status===403)return u(e,i,`Authentication failed (HTTP ${t.status})`);if(!t.ok){const l=await t.text().catch(()=>"");return u(e,i,`API error (HTTP ${t.status}): ${l.slice(0,200)}`)}const o=await t.json(),n=[],r=Array.isArray(o.limits)?o.limits:[];for(const l of r){const d=l.detail;if(!d)continue;const c=m(d.limit)??1,p=m(d.remaining)??0,b=T(d.resetTime),h=Math.max(0,c-p);n.push({name:"five_hour",label:"5h limit",usedPercent:c>0?Math.round(h/c*1e4)/100:0,resetsAt:b})}const a=o.usage;if(a){const l=m(a.limit)??1,d=m(a.remaining)??0,c=T(a.resetTime),p=Math.max(0,l-d);n.push({name:"weekly_limit",label:"Weekly limit",usedPercent:l>0?Math.round(p/l*1e4)/100:0,resetsAt:c})}return{provider:e,providerLabel:i,planName:null,tiers:n,balance:null,success:!0,error:null}}catch(t){return u(e,i,`Network error: ${t instanceof Error?t.message:String(t)}`)}}async function v(s,e){const i=e?"minimax_cn":"minimax_en",t="MiniMax",o=e?"api.minimaxi.com":"api.minimax.io";try{const n=await fetch(`https://${o}/v1/api/openplatform/coding_plan/remains`,{method:"GET",headers:{Authorization:`Bearer ${s}`,"Content-Type":"application/json"},signal:AbortSignal.timeout(f)});if(n.status===401||n.status===403)return u(i,t,`Authentication failed (HTTP ${n.status})`);if(!n.ok){const p=await n.text().catch(()=>"");return u(i,t,`API error (HTTP ${n.status}): ${p.slice(0,200)}`)}const r=await n.json(),a=r.base_resp;if(a&&typeof a.status_code=="number"&&a.status_code!==0)return u(i,t,`API error (code ${a.status_code}): ${a.status_msg??"Unknown"}`);const l=[],c=(Array.isArray(r.model_remains)?r.model_remains:[])[0];if(c){const p=m(c.current_interval_total_count)??0,b=m(c.current_interval_usage_count)??0,h=typeof c.end_time=="number"?c.end_time:null;p>0&&l.push({name:"five_hour",label:"5h limit",usedPercent:Math.round(b/p*1e4)/100,resetsAt:h!==null?w(h):null});const g=m(c.current_weekly_total_count)??0,j=m(c.current_weekly_usage_count)??0,k=typeof c.weekly_end_time=="number"?c.weekly_end_time:null;g>0&&l.push({name:"weekly_limit",label:"Weekly limit",usedPercent:Math.round(j/g*1e4)/100,resetsAt:k!==null?w(k):null})}return{provider:i,providerLabel:t,planName:null,tiers:l,balance:null,success:!0,error:null}}catch(n){return u(i,t,`Network error: ${n instanceof Error?n.message:String(n)}`)}}async function N(s){const e="deepseek",i="DeepSeek";try{const t=await fetch("https://api.deepseek.com/user/balance",{method:"GET",headers:{Authorization:`Bearer ${s}`,Accept:"application/json"},signal:AbortSignal.timeout(f)});if(t.status===401||t.status===403)return u(e,i,`Authentication failed (HTTP ${t.status})`);if(!t.ok){const c=await t.text().catch(()=>"");return u(e,i,`API error (HTTP ${t.status}): ${c.slice(0,200)}`)}const o=await t.json(),n=o.is_available===!0,a=(Array.isArray(o.balance_infos)?o.balance_infos:[])[0];if(!a)return u(e,i,"No balance info returned");const l=String(a.currency??"CNY"),d=m(a.total_balance);return{provider:e,providerLabel:i,planName:null,tiers:[],balance:{remaining:d??0,total:null,used:null,unit:l},success:!0,error:n?null:"Insufficient balance"}}catch(t){return u(e,i,`Network error: ${t instanceof Error?t.message:String(t)}`)}}async function E(s){const e="stepfun",i="StepFun";try{const t=await fetch("https://api.stepfun.com/v1/accounts",{method:"GET",headers:{Authorization:`Bearer ${s}`,Accept:"application/json"},signal:AbortSignal.timeout(f)});if(t.status===401||t.status===403)return u(e,i,`Authentication failed (HTTP ${t.status})`);if(!t.ok){const r=await t.text().catch(()=>"");return u(e,i,`API error (HTTP ${t.status}): ${r.slice(0,200)}`)}const o=await t.json(),n=m(o.balance)??0;return{provider:e,providerLabel:i,planName:null,tiers:[],balance:{remaining:n,total:null,used:null,unit:"CNY"},success:!0,error:null}}catch(t){return u(e,i,`Network error: ${t instanceof Error?t.message:String(t)}`)}}async function _(s,e){const i=e?"siliconflow_cn":"siliconflow_en",t=e?"SiliconFlow":"SiliconFlow (EN)",o=e?"api.siliconflow.cn":"api.siliconflow.com",n=e?"CNY":"USD";try{const r=await fetch(`https://${o}/v1/user/info`,{method:"GET",headers:{Authorization:`Bearer ${s}`,Accept:"application/json"},signal:AbortSignal.timeout(f)});if(r.status===401||r.status===403)return u(i,t,`Authentication failed (HTTP ${r.status})`);if(!r.ok){const c=await r.text().catch(()=>"");return u(i,t,`API error (HTTP ${r.status}): ${c.slice(0,200)}`)}const l=(await r.json()).data;if(!l)return u(i,t,"Missing data field");const d=m(l.totalBalance)??0;return{provider:i,providerLabel:t,planName:null,tiers:[],balance:{remaining:d,total:null,used:null,unit:n},success:!0,error:null}}catch(r){return u(i,t,`Network error: ${r instanceof Error?r.message:String(r)}`)}}async function x(s){const e="openrouter",i="OpenRouter";try{const t=await fetch("https://openrouter.ai/api/v1/credits",{method:"GET",headers:{Authorization:`Bearer ${s}`,Accept:"application/json"},signal:AbortSignal.timeout(f)});if(t.status===401||t.status===403)return u(e,i,`Authentication failed (HTTP ${t.status})`);if(!t.ok){const d=await t.text().catch(()=>"");return u(e,i,`API error (HTTP ${t.status}): ${d.slice(0,200)}`)}const o=await t.json(),n=o.data??o,r=m(n.total_credits)??0,a=m(n.total_usage)??0,l=r-a;return{provider:e,providerLabel:i,planName:null,tiers:[],balance:{remaining:l,total:r,used:a,unit:"USD"},success:!0,error:null}}catch(t){return u(e,i,`Network error: ${t instanceof Error?t.message:String(t)}`)}}async function M(s){const e="novita",i="Novita AI";try{const t=await fetch("https://api.novita.ai/v3/user/balance",{method:"GET",headers:{Authorization:`Bearer ${s}`,Accept:"application/json"},signal:AbortSignal.timeout(f)});if(t.status===401||t.status===403)return u(e,i,`Authentication failed (HTTP ${t.status})`);if(!t.ok){const r=await t.text().catch(()=>"");return u(e,i,`API error (HTTP ${t.status}): ${r.slice(0,200)}`)}const o=await t.json(),n=(m(o.availableBalance)??0)/1e4;return{provider:e,providerLabel:i,planName:null,tiers:[],balance:{remaining:n,total:null,used:null,unit:"USD"},success:!0,error:null}}catch(t){return u(e,i,`Network error: ${t instanceof Error?t.message:String(t)}`)}}const y=new Map,G=300*1e3;async function z(s,e){const i=`${e.slice(0,8)}@${s}`,t=y.get(i);if(t&&Date.now()-t.timestamp<=G){const r=await L(t.providerId,s,e);if(r)return r;y.delete(i)}else t&&y.delete(i);const o=["zhipu","deepseek","kimi","openrouter","stepfun","minimax_cn","siliconflow_cn","novita"],n=await Promise.allSettled(o.map(r=>L(r,s,e)));for(let r=0;r<n.length;r++){const a=n[r];if(a.status==="fulfilled"&&a.value?.success)return y.set(i,{providerId:o[r],timestamp:Date.now()}),a.value}return null}const A=new Map,B=300*1e3;async function H(s){const e=s.slice(0,8),i=A.get(e);if(i&&Date.now()-i.timestamp<=B){const n=[i.providerId],r=await Promise.allSettled(n.map(a=>I(a,s)));for(const a of r)if(a.status==="fulfilled"&&a.value?.success)return a.value;A.delete(e)}else i&&A.delete(e);const t=["zhipu","deepseek","kimi","openrouter","stepfun","minimax_cn","siliconflow_cn","novita"],o=await Promise.allSettled(t.map(n=>I(n,s)));for(let n=0;n<o.length;n++){const r=o[n];if(r.status==="fulfilled"&&r.value?.success)return A.set(e,{providerId:t[n],timestamp:Date.now()}),r.value}return null}async function I(s,e){try{switch(s){case"zhipu":return S(e);case"kimi":return P(e);case"minimax_cn":return v(e,!0);case"minimax_en":return v(e,!1);case"deepseek":return N(e);case"stepfun":return E(e);case"siliconflow_cn":return _(e,!0);case"siliconflow_en":return _(e,!1);case"openrouter":return x(e);case"novita":return M(e)}}catch{return null}}async function L(s,e,i){const t=e.replace(/\/+$/,""),o={Authorization:"Bearer ${apiKey}",Accept:"application/json"};try{switch(s){case"zhipu":{const n=await fetch("${origin}/api/monitor/usage/quota/limit",{method:"GET",headers:{...o,Authorization:i,"Content-Type":"application/json","Accept-Language":"en-US,en"},signal:AbortSignal.timeout(f)});if(!n.ok)return null;const r=await n.json();if(r.success===!1)return null;const a=r.data;if(!a)return null;const l=typeof a.level=="string"?a.level:null,d=$(a);return{provider:"zhipu",providerLabel:"Zhipu GLM",planName:l,tiers:d,balance:null,success:!0,error:null}}case"deepseek":{const n=await fetch("${origin}/user/balance",{method:"GET",headers:o,signal:AbortSignal.timeout(f)});if(!n.ok)return null;const r=await n.json(),a=r.is_available===!0,d=(Array.isArray(r.balance_infos)?r.balance_infos:[])[0];if(!d)return null;const c=String(d.currency??"CNY"),p=m(d.total_balance);return{provider:"deepseek",providerLabel:"DeepSeek",planName:null,tiers:[],balance:{remaining:p??0,total:null,used:null,unit:c},success:!0,error:a?null:"Insufficient balance"}}case"kimi":{const n=await fetch("${origin}/coding/v1/usages",{method:"GET",headers:o,signal:AbortSignal.timeout(f)});if(!n.ok)return null;const r=await n.json(),a=[],l=Array.isArray(r.limits)?r.limits:[];for(const d of l){const c=d.detail;if(!c)continue;const p=m(c.limit)??1,b=m(c.remaining)??0,h=T(c.resetTime),g=Math.max(0,p-b);a.push({name:"five_hour",label:"5h limit",usedPercent:p>0?Math.round(g/p*1e4)/100:0,resetsAt:h})}return{provider:"kimi",providerLabel:"Kimi",planName:null,tiers:a,balance:null,success:!0,error:null}}case"openrouter":{const n=await fetch("${origin}/api/v1/credits",{method:"GET",headers:o,signal:AbortSignal.timeout(f)});if(!n.ok)return null;const r=await n.json(),a=r.data??r,l=m(a.total_credits)??0,d=m(a.total_usage)??0,c=l-d;return{provider:"openrouter",providerLabel:"OpenRouter",planName:null,tiers:[],balance:{remaining:c,total:l,used:d,unit:"USD"},success:!0,error:null}}case"stepfun":{const n=await fetch("${origin}/v1/accounts",{method:"GET",headers:o,signal:AbortSignal.timeout(f)});if(!n.ok)return null;const r=await n.json(),a=m(r.balance)??0;return{provider:"stepfun",providerLabel:"StepFun",planName:null,tiers:[],balance:{remaining:a,total:null,used:null,unit:"CNY"},success:!0,error:null}}case"minimax_cn":{const n=await fetch("${origin}/v1/api/openplatform/coding_plan/remains",{method:"GET",headers:o,signal:AbortSignal.timeout(f)});if(!n.ok)return null;const r=await n.json(),a=r.base_resp;if(a&&typeof a.status_code=="number"&&a.status_code!==0)return null;const d=(Array.isArray(r.model_remains)?r.model_remains:[])[0];if(!d)return null;const c=[],p=m(d.current_interval_total_count)??0,b=m(d.current_interval_usage_count)??0;return p>0&&c.push({name:"five_hour",label:"5h limit",usedPercent:Math.round(b/p*1e4)/100,resetsAt:null}),{provider:"minimax_cn",providerLabel:"MiniMax",planName:null,tiers:c,balance:null,success:!0,error:null}}case"siliconflow_cn":{const n=await fetch("${origin}/v1/user/info",{method:"GET",headers:o,signal:AbortSignal.timeout(f)});if(!n.ok)return null;const a=(await n.json()).data;if(!a)return null;const l=m(a.totalBalance)??0;return{provider:"siliconflow_cn",providerLabel:"SiliconFlow",planName:null,tiers:[],balance:{remaining:l,total:null,used:null,unit:"CNY"},success:!0,error:null}}case"novita":{const n=await fetch("${origin}/v3/user/balance",{method:"GET",headers:o,signal:AbortSignal.timeout(f)});if(!n.ok)return null;const r=await n.json(),a=(m(r.availableBalance)??0)/1e4;return{provider:"novita",providerLabel:"Novita AI",planName:null,tiers:[],balance:{remaining:a,total:null,used:null,unit:"USD"},success:!0,error:null}}default:return null}}catch{return null}}async function U(s,e){if(!e.trim())return{provider:"unknown",providerLabel:"Unknown",planName:null,tiers:[],balance:null,success:!1,error:"API key is empty"};const i=R(s);if(i)switch(i.id){case"zhipu":return S(e);case"kimi":return P(e);case"minimax_cn":return v(e,!0);case"minimax_en":return v(e,!1);case"deepseek":return N(e);case"stepfun":return E(e);case"siliconflow_cn":return _(e,!0);case"siliconflow_en":return _(e,!1);case"openrouter":return x(e);case"novita":return M(e)}const t=await z(s,e);if(t)return t;const o=await H(e);return o||{provider:"unknown",providerLabel:"Unknown",planName:null,tiers:[],balance:null,success:!1,error:`Could not identify provider for base URL: ${s}`}}export{R as detectProvider,U as queryProviderQuota};
1
+ import{readFileSync as y,writeFileSync as z,mkdirSync as N,rmdirSync as x,statSync as C}from"node:fs";import{homedir as I}from"node:os";import{join as b}from"node:path";function D(o){const e=o.toLowerCase();return e.includes("open.bigmodel.cn")||e.includes("bigmodel.cn")||e.includes("api.z.ai")?{id:"zhipu",label:"Zhipu GLM"}:e.includes("api.kimi.com")?{id:"kimi",label:"Kimi"}:e.includes("api.minimaxi.com")?{id:"minimax_cn",label:"MiniMax"}:e.includes("api.minimax.io")?{id:"minimax_en",label:"MiniMax"}:e.includes("api.deepseek.com")?{id:"deepseek",label:"DeepSeek"}:e.includes("api.stepfun.ai")||e.includes("api.stepfun.com")?{id:"stepfun",label:"StepFun"}:e.includes("api.siliconflow.cn")?{id:"siliconflow_cn",label:"SiliconFlow"}:e.includes("api.siliconflow.com")?{id:"siliconflow_en",label:"SiliconFlow"}:e.includes("openrouter.ai")?{id:"openrouter",label:"OpenRouter"}:e.includes("api.novita.ai")?{id:"novita",label:"Novita AI"}:null}const f=1e4;function d(o,e,i){return{provider:o,providerLabel:e,planName:null,tiers:[],balance:null,success:!1,error:i}}function p(o){if(typeof o=="number")return o;if(typeof o=="string"){const e=Number(o);return Number.isFinite(e)?e:null}return null}function w(o){if(!Number.isFinite(o)||o<=0)return null;try{return new Date(o).toISOString()}catch{return null}}function S(o){if(typeof o=="string")return o;if(typeof o=="number"){const e=o<1e12?o*1e3:o;return w(e)}return null}function $(o){const e=o||b(I(),".kimi"),i=300*1e3;let t;try{const r=b(e,"config.toml"),n=y(r,"utf8"),s=n.indexOf('[providers."managed:kimi-code"]');if(s>=0){const c=n.indexOf(`
2
+ [`,s+1),u=(c>=0?n.slice(s,c):n.slice(s)).match(/^\s*base_url\s*=\s*"([^"]+)"/m);u&&(t=u[1].trim())}}catch{}let a;try{const r=b(e,"credentials","kimi-code.json"),n=JSON.parse(y(r,"utf8")),s=typeof n.access_token=="string"?n.access_token.trim():"",c=typeof n.expires_at=="number"?n.expires_at:null;s&&(!c||Date.now()<c*1e3-i)&&(a=s)}catch{}return{baseUrl:t,apiKey:a}}const F="17e5f671-d194-4dfb-9706-5516cb48c098";function q(){return(process.env.KIMI_CODE_OAUTH_HOST||process.env.KIMI_OAUTH_HOST||"https://auth.kimi.com").replace(/\/+$/,"")}const Y=30*1e3;async function ne(o){const e=$(o);if(e.apiKey)return e;const i=await Z(o);return{baseUrl:e.baseUrl,apiKey:i}}async function Z(o){const e=o||b(I(),".kimi"),i=b(e,"credentials","kimi-code.json");let t;try{t=JSON.parse(y(i,"utf8"))}catch{return}const a=typeof t.refresh_token=="string"?t.refresh_token.trim():"";if(!a)return;const r=b(e,"credentials","kimi-code.lock.lock");if(V(r))try{const n=$(o);if(n.apiKey)return n.apiKey;let s="";try{s=y(b(e,"device_id"),"utf8").trim()}catch{}const c=await fetch(`${q()}/api/oauth/token`,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded",...s?{"X-Msh-Device-Id":s}:{}},body:new URLSearchParams({client_id:F,grant_type:"refresh_token",refresh_token:a}),signal:AbortSignal.timeout(8e3)}),l=await c.json().catch(()=>({})),u=typeof l.access_token=="string"?l.access_token:"";if(c.status!==200||!u)return $(o).apiKey;const m=Number(l.expires_in),h={access_token:u,refresh_token:typeof l.refresh_token=="string"&&l.refresh_token?l.refresh_token:a,expires_at:Math.floor(Date.now()/1e3)+(Number.isFinite(m)&&m>0?m:900),scope:typeof l.scope=="string"&&l.scope?l.scope:t.scope??"kimi-code",token_type:typeof l.token_type=="string"&&l.token_type?l.token_type:"Bearer",expires_in:Number.isFinite(m)&&m>0?m:900};return z(i,JSON.stringify(h),{mode:384}),u}catch{return}finally{try{x(r)}catch{}}}function V(o){try{return N(o),!0}catch{}try{const e=C(o);return Date.now()-e.mtimeMs<Y?!1:(x(o),N(o),!0)}catch{return!1}}async function E(o){const e="zhipu",i="Zhipu GLM";try{const t=await fetch("https://api.z.ai/api/monitor/usage/quota/limit",{method:"GET",headers:{Authorization:o,"Content-Type":"application/json","Accept-Language":"en-US,en"},signal:AbortSignal.timeout(f)});if(t.status===401||t.status===403)return d(e,i,`Authentication failed (HTTP ${t.status})`);if(!t.ok){const c=await t.text().catch(()=>"");return d(e,i,`API error (HTTP ${t.status}): ${c.slice(0,200)}`)}const a=await t.json();if(a.success===!1)return d(e,i,`API error: ${a.msg??"Unknown error"}`);const r=a.data;if(!r)return d(e,i,"Missing data field");const n=typeof r.level=="string"?r.level:null,s=M(r);return{provider:e,providerLabel:i,planName:n,tiers:s,balance:null,success:!0,error:null}}catch(t){return d(e,i,`Network error: ${t instanceof Error?t.message:String(t)}`)}}function M(o){const e=Array.isArray(o.limits)?o.limits:[],i=[];for(const a of e){if(String(a.type??"").toUpperCase()!=="TOKENS_LIMIT")continue;const n=p(a.percentage)??0,s=typeof a.nextResetTime=="number"?a.nextResetTime:Number.MAX_SAFE_INTEGER,c=s===Number.MAX_SAFE_INTEGER?null:w(s);i.push({percentage:n,resetMs:s,resetIso:c})}i.sort((a,r)=>a.resetMs-r.resetMs);const t=[];if(i.length>0){const a=i[0];t.push({name:"five_hour",label:"5h limit",usedPercent:Math.round(a.percentage*100)/100,resetsAt:a.resetIso})}return t}async function L(o){const e="kimi",i="Kimi";try{const t=await fetch("https://api.kimi.com/coding/v1/usages",{method:"GET",headers:{Authorization:`Bearer ${o}`,Accept:"application/json"},signal:AbortSignal.timeout(f)});if(t.status===401||t.status===403)return d(e,i,`Authentication failed (HTTP ${t.status})`);if(!t.ok){const c=await t.text().catch(()=>"");return d(e,i,`API error (HTTP ${t.status}): ${c.slice(0,200)}`)}const a=await t.json(),r=[],n=Array.isArray(a.limits)?a.limits:[];for(const c of n){const l=c.detail;if(!l)continue;const u=p(l.limit)??1,m=p(l.remaining)??0,h=S(l.resetTime),_=Math.max(0,u-m);r.push({name:"five_hour",label:"5h limit",usedPercent:u>0?Math.round(_/u*1e4)/100:0,resetsAt:h})}const s=a.usage;if(s){const c=p(s.limit)??1,l=p(s.remaining)??0,u=S(s.resetTime),m=Math.max(0,c-l);r.push({name:"weekly_limit",label:"Weekly limit",usedPercent:c>0?Math.round(m/c*1e4)/100:0,resetsAt:u})}return{provider:e,providerLabel:i,planName:null,tiers:r,balance:null,success:!0,error:null}}catch(t){return d(e,i,`Network error: ${t instanceof Error?t.message:String(t)}`)}}async function v(o,e){const i=e?"minimax_cn":"minimax_en",t="MiniMax",a=e?"api.minimaxi.com":"api.minimax.io";try{const r=await fetch(`https://${a}/v1/api/openplatform/coding_plan/remains`,{method:"GET",headers:{Authorization:`Bearer ${o}`,"Content-Type":"application/json"},signal:AbortSignal.timeout(f)});if(r.status===401||r.status===403)return d(i,t,`Authentication failed (HTTP ${r.status})`);if(!r.ok){const m=await r.text().catch(()=>"");return d(i,t,`API error (HTTP ${r.status}): ${m.slice(0,200)}`)}const n=await r.json(),s=n.base_resp;if(s&&typeof s.status_code=="number"&&s.status_code!==0)return d(i,t,`API error (code ${s.status_code}): ${s.status_msg??"Unknown"}`);const c=[],u=(Array.isArray(n.model_remains)?n.model_remains:[])[0];if(u){const m=p(u.current_interval_total_count)??0,h=p(u.current_interval_usage_count)??0,_=typeof u.end_time=="number"?u.end_time:null;m>0&&c.push({name:"five_hour",label:"5h limit",usedPercent:Math.round(h/m*1e4)/100,resetsAt:_!==null?w(_):null});const g=p(u.current_weekly_total_count)??0,H=p(u.current_weekly_usage_count)??0,P=typeof u.weekly_end_time=="number"?u.weekly_end_time:null;g>0&&c.push({name:"weekly_limit",label:"Weekly limit",usedPercent:Math.round(H/g*1e4)/100,resetsAt:P!==null?w(P):null})}return{provider:i,providerLabel:t,planName:null,tiers:c,balance:null,success:!0,error:null}}catch(r){return d(i,t,`Network error: ${r instanceof Error?r.message:String(r)}`)}}async function j(o){const e="deepseek",i="DeepSeek";try{const t=await fetch("https://api.deepseek.com/user/balance",{method:"GET",headers:{Authorization:`Bearer ${o}`,Accept:"application/json"},signal:AbortSignal.timeout(f)});if(t.status===401||t.status===403)return d(e,i,`Authentication failed (HTTP ${t.status})`);if(!t.ok){const u=await t.text().catch(()=>"");return d(e,i,`API error (HTTP ${t.status}): ${u.slice(0,200)}`)}const a=await t.json(),r=a.is_available===!0,s=(Array.isArray(a.balance_infos)?a.balance_infos:[])[0];if(!s)return d(e,i,"No balance info returned");const c=String(s.currency??"CNY"),l=p(s.total_balance);return{provider:e,providerLabel:i,planName:null,tiers:[],balance:{remaining:l??0,total:null,used:null,unit:c},success:!0,error:r?null:"Insufficient balance"}}catch(t){return d(e,i,`Network error: ${t instanceof Error?t.message:String(t)}`)}}async function O(o){const e="stepfun",i="StepFun";try{const t=await fetch("https://api.stepfun.com/v1/accounts",{method:"GET",headers:{Authorization:`Bearer ${o}`,Accept:"application/json"},signal:AbortSignal.timeout(f)});if(t.status===401||t.status===403)return d(e,i,`Authentication failed (HTTP ${t.status})`);if(!t.ok){const n=await t.text().catch(()=>"");return d(e,i,`API error (HTTP ${t.status}): ${n.slice(0,200)}`)}const a=await t.json(),r=p(a.balance)??0;return{provider:e,providerLabel:i,planName:null,tiers:[],balance:{remaining:r,total:null,used:null,unit:"CNY"},success:!0,error:null}}catch(t){return d(e,i,`Network error: ${t instanceof Error?t.message:String(t)}`)}}async function k(o,e){const i=e?"siliconflow_cn":"siliconflow_en",t=e?"SiliconFlow":"SiliconFlow (EN)",a=e?"api.siliconflow.cn":"api.siliconflow.com",r=e?"CNY":"USD";try{const n=await fetch(`https://${a}/v1/user/info`,{method:"GET",headers:{Authorization:`Bearer ${o}`,Accept:"application/json"},signal:AbortSignal.timeout(f)});if(n.status===401||n.status===403)return d(i,t,`Authentication failed (HTTP ${n.status})`);if(!n.ok){const u=await n.text().catch(()=>"");return d(i,t,`API error (HTTP ${n.status}): ${u.slice(0,200)}`)}const c=(await n.json()).data;if(!c)return d(i,t,"Missing data field");const l=p(c.totalBalance)??0;return{provider:i,providerLabel:t,planName:null,tiers:[],balance:{remaining:l,total:null,used:null,unit:r},success:!0,error:null}}catch(n){return d(i,t,`Network error: ${n instanceof Error?n.message:String(n)}`)}}async function R(o){const e="openrouter",i="OpenRouter";try{const t=await fetch("https://openrouter.ai/api/v1/credits",{method:"GET",headers:{Authorization:`Bearer ${o}`,Accept:"application/json"},signal:AbortSignal.timeout(f)});if(t.status===401||t.status===403)return d(e,i,`Authentication failed (HTTP ${t.status})`);if(!t.ok){const l=await t.text().catch(()=>"");return d(e,i,`API error (HTTP ${t.status}): ${l.slice(0,200)}`)}const a=await t.json(),r=a.data??a,n=p(r.total_credits)??0,s=p(r.total_usage)??0,c=n-s;return{provider:e,providerLabel:i,planName:null,tiers:[],balance:{remaining:c,total:n,used:s,unit:"USD"},success:!0,error:null}}catch(t){return d(e,i,`Network error: ${t instanceof Error?t.message:String(t)}`)}}async function U(o){const e="novita",i="Novita AI";try{const t=await fetch("https://api.novita.ai/v3/user/balance",{method:"GET",headers:{Authorization:`Bearer ${o}`,Accept:"application/json"},signal:AbortSignal.timeout(f)});if(t.status===401||t.status===403)return d(e,i,`Authentication failed (HTTP ${t.status})`);if(!t.ok){const n=await t.text().catch(()=>"");return d(e,i,`API error (HTTP ${t.status}): ${n.slice(0,200)}`)}const a=await t.json(),r=(p(a.availableBalance)??0)/1e4;return{provider:e,providerLabel:i,planName:null,tiers:[],balance:{remaining:r,total:null,used:null,unit:"USD"},success:!0,error:null}}catch(t){return d(e,i,`Network error: ${t instanceof Error?t.message:String(t)}`)}}const A=new Map,X=300*1e3;async function J(o,e){const i=`${e.slice(0,8)}@${o}`,t=A.get(i);if(t&&Date.now()-t.timestamp<=X){const n=await G(t.providerId,o,e);if(n)return n;A.delete(i)}else t&&A.delete(i);const a=["zhipu","deepseek","kimi","openrouter","stepfun","minimax_cn","siliconflow_cn","novita"],r=await Promise.allSettled(a.map(n=>G(n,o,e)));for(let n=0;n<r.length;n++){const s=r[n];if(s.status==="fulfilled"&&s.value?.success)return A.set(i,{providerId:a[n],timestamp:Date.now()}),s.value}return null}const T=new Map,K=300*1e3;async function W(o){const e=o.slice(0,8),i=T.get(e);if(i&&Date.now()-i.timestamp<=K){const r=[i.providerId],n=await Promise.allSettled(r.map(s=>B(s,o)));for(const s of n)if(s.status==="fulfilled"&&s.value?.success)return s.value;T.delete(e)}else i&&T.delete(e);const t=["zhipu","deepseek","kimi","openrouter","stepfun","minimax_cn","siliconflow_cn","novita"],a=await Promise.allSettled(t.map(r=>B(r,o)));for(let r=0;r<a.length;r++){const n=a[r];if(n.status==="fulfilled"&&n.value?.success)return T.set(e,{providerId:t[r],timestamp:Date.now()}),n.value}return null}async function B(o,e){try{switch(o){case"zhipu":return E(e);case"kimi":return L(e);case"minimax_cn":return v(e,!0);case"minimax_en":return v(e,!1);case"deepseek":return j(e);case"stepfun":return O(e);case"siliconflow_cn":return k(e,!0);case"siliconflow_en":return k(e,!1);case"openrouter":return R(e);case"novita":return U(e)}}catch{return null}}async function G(o,e,i){const t=e.replace(/\/+$/,""),a={Authorization:"Bearer ${apiKey}",Accept:"application/json"};try{switch(o){case"zhipu":{const r=await fetch("${origin}/api/monitor/usage/quota/limit",{method:"GET",headers:{...a,Authorization:i,"Content-Type":"application/json","Accept-Language":"en-US,en"},signal:AbortSignal.timeout(f)});if(!r.ok)return null;const n=await r.json();if(n.success===!1)return null;const s=n.data;if(!s)return null;const c=typeof s.level=="string"?s.level:null,l=M(s);return{provider:"zhipu",providerLabel:"Zhipu GLM",planName:c,tiers:l,balance:null,success:!0,error:null}}case"deepseek":{const r=await fetch("${origin}/user/balance",{method:"GET",headers:a,signal:AbortSignal.timeout(f)});if(!r.ok)return null;const n=await r.json(),s=n.is_available===!0,l=(Array.isArray(n.balance_infos)?n.balance_infos:[])[0];if(!l)return null;const u=String(l.currency??"CNY"),m=p(l.total_balance);return{provider:"deepseek",providerLabel:"DeepSeek",planName:null,tiers:[],balance:{remaining:m??0,total:null,used:null,unit:u},success:!0,error:s?null:"Insufficient balance"}}case"kimi":{const r=await fetch("${origin}/coding/v1/usages",{method:"GET",headers:a,signal:AbortSignal.timeout(f)});if(!r.ok)return null;const n=await r.json(),s=[],c=Array.isArray(n.limits)?n.limits:[];for(const l of c){const u=l.detail;if(!u)continue;const m=p(u.limit)??1,h=p(u.remaining)??0,_=S(u.resetTime),g=Math.max(0,m-h);s.push({name:"five_hour",label:"5h limit",usedPercent:m>0?Math.round(g/m*1e4)/100:0,resetsAt:_})}return{provider:"kimi",providerLabel:"Kimi",planName:null,tiers:s,balance:null,success:!0,error:null}}case"openrouter":{const r=await fetch("${origin}/api/v1/credits",{method:"GET",headers:a,signal:AbortSignal.timeout(f)});if(!r.ok)return null;const n=await r.json(),s=n.data??n,c=p(s.total_credits)??0,l=p(s.total_usage)??0,u=c-l;return{provider:"openrouter",providerLabel:"OpenRouter",planName:null,tiers:[],balance:{remaining:u,total:c,used:l,unit:"USD"},success:!0,error:null}}case"stepfun":{const r=await fetch("${origin}/v1/accounts",{method:"GET",headers:a,signal:AbortSignal.timeout(f)});if(!r.ok)return null;const n=await r.json(),s=p(n.balance)??0;return{provider:"stepfun",providerLabel:"StepFun",planName:null,tiers:[],balance:{remaining:s,total:null,used:null,unit:"CNY"},success:!0,error:null}}case"minimax_cn":{const r=await fetch("${origin}/v1/api/openplatform/coding_plan/remains",{method:"GET",headers:a,signal:AbortSignal.timeout(f)});if(!r.ok)return null;const n=await r.json(),s=n.base_resp;if(s&&typeof s.status_code=="number"&&s.status_code!==0)return null;const l=(Array.isArray(n.model_remains)?n.model_remains:[])[0];if(!l)return null;const u=[],m=p(l.current_interval_total_count)??0,h=p(l.current_interval_usage_count)??0;return m>0&&u.push({name:"five_hour",label:"5h limit",usedPercent:Math.round(h/m*1e4)/100,resetsAt:null}),{provider:"minimax_cn",providerLabel:"MiniMax",planName:null,tiers:u,balance:null,success:!0,error:null}}case"siliconflow_cn":{const r=await fetch("${origin}/v1/user/info",{method:"GET",headers:a,signal:AbortSignal.timeout(f)});if(!r.ok)return null;const s=(await r.json()).data;if(!s)return null;const c=p(s.totalBalance)??0;return{provider:"siliconflow_cn",providerLabel:"SiliconFlow",planName:null,tiers:[],balance:{remaining:c,total:null,used:null,unit:"CNY"},success:!0,error:null}}case"novita":{const r=await fetch("${origin}/v3/user/balance",{method:"GET",headers:a,signal:AbortSignal.timeout(f)});if(!r.ok)return null;const n=await r.json(),s=(p(n.availableBalance)??0)/1e4;return{provider:"novita",providerLabel:"Novita AI",planName:null,tiers:[],balance:{remaining:s,total:null,used:null,unit:"USD"},success:!0,error:null}}default:return null}}catch{return null}}async function re(o,e){if(!e.trim())return{provider:"unknown",providerLabel:"Unknown",planName:null,tiers:[],balance:null,success:!1,error:"API key is empty"};const i=D(o);if(i)switch(i.id){case"zhipu":return E(e);case"kimi":return L(e);case"minimax_cn":return v(e,!0);case"minimax_en":return v(e,!1);case"deepseek":return j(e);case"stepfun":return O(e);case"siliconflow_cn":return k(e,!0);case"siliconflow_en":return k(e,!1);case"openrouter":return R(e);case"novita":return U(e)}const t=await J(o,e);if(t)return t;const a=await W(e);return a||{provider:"unknown",providerLabel:"Unknown",planName:null,tiers:[],balance:null,success:!1,error:`Could not identify provider for base URL: ${o}`}}export{D as detectProvider,re as queryProviderQuota,$ as readKimiProviderSettings,ne as resolveKimiProviderSettings};
@@ -0,0 +1 @@
1
+ import{resolveCommandPath as n}from"./spawn.js";const e=["cursor-agent","agent"];function s(){for(const o of e){const r=n(o);if(r!==o)return r}return e[0]}export{s as resolveCursorCommand};
@@ -0,0 +1 @@
1
+ const r=100;function c(t){const e=t.replace(/\[\[message_id:[^\]]+\]\]/g,"").replace(/\s+/g," ").trim();if(e)return e.length>100?e.slice(0,100)+"\u2026":e}function l(t){const e=t.replace(/\[system-identity\][\s\S]*?\[\/system-identity\]/g,""),n=[...e.matchAll(/\[\[message_id:[^\]]+\]\]/g)];if(n.length===0)return e;const s=n[n.length-1],i=e.slice((s.index??0)+s[0].length);return i.trim()?i:e}export{c as makeTitle,l as stripInjectedPreamble};
@@ -0,0 +1 @@
1
+ import{createRequire as l}from"node:module";import{log as s}from"../log/index.js";let e;function c(){if(e!==void 0)return e;try{e=l(import.meta.url)("node:sqlite").DatabaseSync}catch{e=null,s.warn("sqlite-reader",`node:sqlite unavailable on Node ${process.version}; sessions stored in sqlite (cursor/opencode) will not be listed`)}return e}function d(r){const t=c();if(!t)return null;try{const n=new t(r,{readOnly:!0});return{all(a,...o){return n.prepare(a).all(...o)},close(){try{n.close()}catch{}}}}catch{return null}}export{d as openReadOnlyDatabase};
package/dist/manager.js CHANGED
@@ -1,2 +1,2 @@
1
- import{readFileSync as L,readdirSync as Q,writeFileSync as W}from"node:fs";import{join as S}from"node:path";import{AgentInstance as T}from"./bridge/bridge.js";import{buildSharedInstanceConfig as K,diffSharedOwners as z,sharedInstanceKey as G,SharedOwnersCache as J}from"./manager-share-config.js";import{isAgentDeletedError as M}from"./core/aibot/errors.js";import{GRIX_PATHS as A,log as l}from"./core/log/index.js";import{resolveClientVersion as V}from"./core/util/client-version.js";import{UpgradeChecker as X}from"./core/upgrade/upgrade-checker.js";import{AgentGlobalConfigStore as Y}from"./core/persistence/agent-global-config-store.js";import{resolveSkillScanMode as Z}from"./adapter/claude/skill-scanner.js";import{buildReportedSkills as ee,logDefaultSkillsCheck as te,cleanupProjectedSkills as j}from"./default-skills/index.js";import{supportsDirectProviderConfig as ne}from"./core/config/provider-env.js";import{resolveCopilotCommand as D}from"./core/runtime/copilot-resolve.js";import{getCliVersion as ae,resolveCliPath as re}from"./core/util/cli-probe.js";import{AgentInstaller as se}from"./core/installer/installer.js";import{reportInstallFailure as oe}from"./core/observability/sentry.js";const ie=8e3;function ce(){const n=D();return[{clientType:"openclaw",command:"openclaw"},{clientType:"claude",command:"claude"},{clientType:"codex",command:"codex"},{clientType:"gemini",command:"gemini"},{clientType:"qwen",command:"qwen"},{clientType:"hermes",command:"hermes"},{clientType:"reasonix",command:"reasonix"},{clientType:"codewhale",command:"codewhale"},{clientType:"opencode",command:"opencode"},{clientType:"cursor",command:"agent"},{clientType:"pi",command:"pi"},{clientType:"openhuman",command:"openhuman-core"},{clientType:"kiro",command:"kiro-cli"},{clientType:"kimi",command:"kimi"},{clientType:"copilot",command:n.command},{clientType:"agy",command:"agy"}]}async function le(){return Promise.all(ce().map(async n=>{const e=await re(n.command);if(!e)return{client_type:n.clientType,command:n.command,installed:!1,path:null,version:null};const t=await ae(e,n.versionArgs??["--version"]);return{client_type:n.clientType,command:n.command,installed:!0,path:e,version:t.version,error:t.error}}))}function de(n){switch(n){case"claude":return{adapterType:"claude",command:"claude"};case"codex":return{adapterType:"codex",command:"codex",options:{sandboxMode:"danger-full-access"}};case"gemini":return{adapterType:"acp",command:"gemini",autoInjectArgs:{acp:!0},enableSessionBinding:!0};case"qwen":return{adapterType:"acp",command:"qwen",adapterHint:"qwen/base",autoInjectArgs:{acp:!0},enableSessionBinding:!0};case"pi":return{adapterType:"pi",command:"pi"};case"cursor":return{adapterType:"cursor",command:"agent"};case"reasonix":return{adapterType:"acp",command:"reasonix",args:["acp"],enableSessionBinding:!0};case"codewhale":return{adapterType:"codewhale",command:"codewhale",enableSessionBinding:!0};case"openhuman":return{adapterType:"openhuman",command:"openhuman-core",enableSessionBinding:!0};case"kiro":return{adapterType:"acp",command:"kiro-cli",args:["acp"],enableSessionBinding:!0};case"kimi":return{adapterType:"acp",command:"kimi",args:["acp"],enableSessionBinding:!0};case"opencode":return{adapterType:"opencode",command:"opencode",args:["serve"],enableSessionBinding:!0};case"copilot":{const e=D();return{adapterType:"acp",command:e.command,args:[...e.prefixArgs,"--acp"],enableSessionBinding:!0}}case"agy":return{adapterType:"agy",command:"agy",enableSessionBinding:!0};case"hermes":return{adapterType:"acp",command:"hermes",args:["acp"],enableSessionBinding:!0};default:throw new Error(`Unsupported client_type: ${n}`)}}function P(n,e){return String(n??"").trim().toLowerCase().replace(/[^a-z0-9._-]+/g,"-").replace(/-+/g,"-").replace(/^-|-$/g,"")||String(e??"").trim().toLowerCase().replace(/[^a-z0-9._-]+/g,"-").replace(/-+/g,"-").replace(/^-|-$/g,"")||"default"}function pe(n,e){return S(A.data,`session-bindings-${P(n,e)}.json`)}function me(n,e){return S(A.data,`active-events-${P(n,e)}.json`)}function ue(...n){const e=[],t=new Set;for(const r of n)for(const a of r??[]){const s=String(a??"").trim(),o=s.toLowerCase();!s||t.has(o)||(t.add(o),e.push(s))}return e.length>0?e:void 0}function ge(n,e){const t={claude:{maxConcurrent:1,maxQueued:5,queueTimeoutMs:0},codex:{maxConcurrent:1,maxQueued:5,queueTimeoutMs:0},cursor:{maxConcurrent:1,maxQueued:3,queueTimeoutMs:0},acp:{maxConcurrent:1,maxQueued:3,queueTimeoutMs:0},pi:{maxConcurrent:1,maxQueued:5,queueTimeoutMs:0},codewhale:{maxConcurrent:1,maxQueued:3,queueTimeoutMs:0},openhuman:{maxConcurrent:1,maxQueued:3,queueTimeoutMs:0},opencode:{maxConcurrent:1,maxQueued:3,queueTimeoutMs:0},agy:{maxConcurrent:1,maxQueued:3,queueTimeoutMs:0}},r=t[n]??t.acp;return{maxConcurrent:e?.max_concurrent??r.maxConcurrent??1,maxQueued:e?.max_queued??r.maxQueued??3,queueTimeoutMs:e?.queue_timeout_ms??r.queueTimeoutMs??0,cancelableQueued:!0,cancelableRunning:!0}}function x(n){const e=V(),t=String(n.client_type??"").trim().toLowerCase(),r=de(t),a=String(n.ws_url??"").trim(),s="get_session_usage",o="get_rate_limits",c="get_agent_global_config";if(!n.name?.trim())throw new Error("agent name is required");if(!a)throw new Error(`agent ${n.name}: ws_url is required`);if(!n.agent_id?.trim())throw new Error(`agent ${n.name}: agent_id is required`);if(!n.api_key?.trim())throw new Error(`agent ${n.name}: api_key is required`);const i=r.adapterType,p=i==="acp",g=t==="qwen",f={...r.options??{}},w=i==="codex"?{capabilities:["local_action_v1","agent_invoke"],localActions:["session_control","get_context","set_model","set_mode","set_reasoning_effort","set_service_tier","set_sandbox_mode","exec_approve","exec_reject","file_list","create_folder","turn_interrupt","permission_approve","permission_reject","thread_compact",s,o]}:null,d=i==="claude"?{localActions:["session_control","set_mode","set_model","claude_interaction_reply","exec_approve","exec_reject","file_list","create_folder","thread_compact",s,o]}:null,u=g?{capabilities:["stream_chunk","local_action_v1"],localActions:["exec_approve","exec_reject","permission_approve","permission_reject","session_control","set_model","set_mode","file_list","create_folder",s],adapterHint:"qwen/base"}:null,h=i==="pi"?{adapterHint:"pi/base",capabilities:["local_action_v1"],localActions:["session_control","set_model","get_context","file_list","create_folder",s]}:null,m=i==="openhuman"?{adapterHint:"openhuman/base",capabilities:["local_action_v1"],localActions:["session_control","set_model","file_list","create_folder",s]}:null,_=i==="cursor"?{adapterHint:"cursor/base",capabilities:["stream_chunk","local_action_v1"],localActions:["session_control","set_model","set_mode","get_context","file_list","create_folder",s,o]}:null,y=i==="codewhale"?{capabilities:["stream_chunk","local_action_v1"],localActions:["session_control","set_model","file_list","create_folder",s]}:null,b=i==="opencode"?{adapterHint:"opencode/base",capabilities:["stream_chunk","local_action_v1"],localActions:["exec_approve","exec_reject","permission_approve","permission_reject","session_control","set_model","set_mode","file_list","create_folder",s]}:null,C=i==="agy"?{adapterHint:"agy/base",capabilities:["stream_chunk","local_action_v1"],localActions:["session_control","set_model","file_list","create_folder",s]}:null,H=p&&!g?{localActions:["exec_approve","exec_reject","permission_approve","permission_reject","session_control","set_model","set_mode","file_list","create_folder",s]}:null,q=t==="kiro"?{localActions:["exec_approve","exec_reject","permission_approve","permission_reject","session_control","set_model","set_mode","file_list","create_folder","thread_compact",s,o]}:null,R=i==="codex"||i==="claude"||t==="gemini"?["session_control","set_model","set_mode"]:void 0,N=[s,o];p&&f.raw_transport===void 0&&(f.raw_transport=t==="gemini");const F=`${t}/base`,I=Z(i,t),B=I==="kiro"?void 0:process.cwd(),k=ee({mode:I,projectDir:B}),E=k.filter($=>$.source==="connector").map($=>$.name);E.length>0&&l.info("manager",`[${n.name}] injecting connector skills: [${E.join(", ")}]`);let U=k.length>0?k:void 0;return{name:n.name,adapterType:i,aibot:{url:a,agentId:n.agent_id,apiKey:n.api_key,clientType:t,clientVersion:e,adapterHint:r.adapterHint??u?.adapterHint??h?.adapterHint??m?.adapterHint??_?.adapterHint??b?.adapterHint??C?.adapterHint??F,capabilities:w?.capabilities??y?.capabilities??h?.capabilities??m?.capabilities??_?.capabilities??b?.capabilities??C?.capabilities??u?.capabilities??["stream_chunk","local_action_v1","connector_upgrade"],localActions:ue(w?.localActions??y?.localActions??d?.localActions??h?.localActions??m?.localActions??_?.localActions??b?.localActions??C?.localActions??u?.localActions??q?.localActions??H?.localActions??["exec_approve","exec_reject"],R,N,["connector_rollback","connector_upgrade_push",c,"configure_gateway_provider"]),skills:U},agent:{command:r.command,args:[...r.args??[],...n.args??[]],env:n.env,clientType:t,...n.provider?{provider:{...n.provider.base_url?.trim()?{baseUrl:n.provider.base_url.trim()}:{},...n.provider.api_key?.trim()?{apiKey:n.provider.api_key.trim()}:{},...n.provider.model?.trim()?{model:n.provider.model.trim()}:{}}}:{}},adapterOptions:f,acpAuthMethod:f.auth_method,acpInitialMode:f.initial_mode,acpMcpTools:f.acp_mcp_tools,promptTimeoutMs:n.prompt_timeout_ms,bindingsPath:pe(n.name,n.agent_id),activeEventStorePath:me(n.name,n.agent_id),...r.enableSessionBinding||p?{enableSessionBinding:!0}:{},...r.autoInjectArgs||t==="reasonix"&&n.provider?.model?.trim()?{autoInjectArgs:{...r.autoInjectArgs??{},...t==="reasonix"&&n.provider?.model?.trim()?{model:n.provider.model.trim()}:{}}}:{},poolMaxSize:n.pool?.maxSize,poolIdleTimeoutMs:n.pool?.idleTimeoutMs,eventQueue:ge(i,n.event_queue),logDir:A.log,providerBaseUrl:n.provider_base_url?.trim()||void 0,providerApiKey:n.provider_api_key?.trim()||void 0}}function he(){const n=process.env.GRIX_AGENT_STARTUP_WAIT_MS,e=Number(n);return Number.isFinite(e)&&e>=500?Math.floor(e):ie}function O(n){const e=[],t=[],r=Q(n).filter(a=>a.endsWith(".json")).sort();for(const a of r)try{const s=JSON.parse(L(S(n,a),"utf-8"));if(Array.isArray(s.agents)){if(s.agents.length===0){t.push({file:a,error:"agents array is empty"});continue}for(const o of s.agents)e.push({entry:o,file:a})}else t.push({file:a,error:"unrecognized config format"})}catch(s){t.push({file:a,error:s instanceof Error?s.message:String(s)})}return{entries:e,fileErrors:t}}function v(n){if(n===null||typeof n!="object")return JSON.stringify(n)??"null";if(Array.isArray(n))return`[${n.map(v).join(",")}]`;const e=n;return`{${Object.keys(e).sort().map(r=>`${JSON.stringify(r)}:${v(e[r])}`).join(",")}}`}function fe(n,e){return v(n)===v(e)}class Oe{instances=[];configMap=new Map;rawConfigMap=new Map;reloadChain=Promise.resolve();sharedInstances=new Map;shareSyncChains=new Map;stopping=!1;deletedAgentCleanups=new Set;upgradeChecker=null;globalConfigStore;configDir=A.config;sharedOwnersCache=new J(A.data);installer=new se;async start(e){const t=e??A.config;this.configDir=t,l.info("manager",`Loading configs from ${t}`),te(),j(),this.globalConfigStore=new Y(S(A.data,"agent-global-configs.json")),this.globalConfigStore.load();const{entries:r,fileErrors:a}=O(t);for(const i of a)l.error("manager",`Failed to load ${i.file}: ${i.error}`);if(r.length===0&&a.length===0)throw new Error(`No config files found in ${t}`);const s=[];let o=a.length;for(const{entry:i,file:p}of r)try{const g=x(i);s.push({config:g,entry:i,file:p}),l.info("manager",`Loaded ${g.name} (${g.adapterType??"acp"}) from ${p}`)}catch(g){const f=typeof i?.name=="string"?i.name:"<unknown>";l.error("manager",`Invalid agent config in ${p} (name=${f}): ${g}`),o++}let c=0;if(s.length>0){const i=he();l.info("manager",`Starting ${s.length} agent(s), startup wait=${i}ms`);const p=()=>this.upgradeChecker?.triggerCheck(),g=d=>{this.instances=this.instances.filter(u=>u!==d)},f=s.map(({config:d,entry:u,file:h})=>{const m=new T(d,this.globalConfigStore);return m.setUpgradeTrigger(p),m.setShareSetHandler(_=>this.onShareSet(d,_)),m.setAgentDeletedHandler(()=>this.onAgentDeletedByPlatform(d.name)),this.instances.push(m),this.configMap.set(d.name,d),this.rawConfigMap.set(d.name,{entry:u,file:h}),{config:d,instance:m,startPromise:m.start()}}),w=await Promise.all(f.map(async d=>{const u=await new Promise(h=>{let m=!1;const _=setTimeout(()=>{m||(m=!0,h({kind:"timeout"}))},i);d.startPromise.then(()=>{m||(m=!0,clearTimeout(_),h({kind:"started"}))}).catch(y=>{m||(m=!0,clearTimeout(_),h({kind:"failed",error:y}))})});return{task:d,outcome:u}}));for(const{task:d,outcome:u}of w){if(u.kind==="started"){this.restoreCachedSharedInstances(d.config);continue}if(u.kind==="failed"){if(M(u.error)){this.onAgentDeletedByPlatform(d.config.name);continue}g(d.instance),l.error("manager",`Failed to start ${d.config.name}: ${u.error}`);continue}c++,l.warn("manager",`Startup pending for ${d.config.name}, continue retrying in background`),d.startPromise.then(()=>{l.info("manager",`Delayed start succeeded: ${d.config.name}`),this.restoreCachedSharedInstances(d.config)}).catch(h=>{if(M(h)){this.onAgentDeletedByPlatform(d.config.name);return}g(d.instance),l.error("manager",`Delayed start failed: ${d.config.name}: ${h}`)})}if(this.instances.length>0){const d=Math.max(0,this.instances.length-c);l.info("manager",`${d}/${s.length} agent(s) running now`)}c>0&&l.warn("manager",`${c} agent(s) still connecting in background`)}if(this.instances.length===0&&s.length>0)throw new Error("All agent configurations failed to start");s.length>0&&(this.upgradeChecker=new X(s.map(({config:i})=>({apiKey:i.aibot.apiKey,wsUrl:i.aibot.url})),()=>this.instances.some(i=>i.hasPendingWork())||[...this.sharedInstances.values()].some(i=>i.hasPendingWork())),await this.upgradeChecker.start())}async stop(){l.info("manager","Stopping all agents..."),this.stopping=!0,this.upgradeChecker?.stop(),await Promise.allSettled([...this.shareSyncChains.values()]),this.shareSyncChains.clear();const e=[...this.sharedInstances.values()];this.sharedInstances.clear(),await Promise.allSettled([...this.instances.map(t=>t.stop()),...e.map(t=>t.stop())]),await this.globalConfigStore?.flush(),this.instances=[],j(),l.info("manager","All stopped")}onShareSet(e,t){this.sharedOwnersCache.save(e.name,t);const a=(this.shareSyncChains.get(e.name)??Promise.resolve()).catch(()=>{}).then(()=>this.syncSharedInstances(e,t));this.shareSyncChains.set(e.name,a)}async syncSharedInstances(e,t){if(this.stopping)return;const{toAdd:r,toRemove:a}=z(e.name,this.sharedInstances.keys(),t);for(const s of r){if(this.stopping)break;const o=G(e.name,s);try{const c=K(e,s),i=new T(c,this.globalConfigStore);this.sharedInstances.set(o,i),await i.start(),l.info("manager",`shared instance started: ${o}`)}catch(c){this.sharedInstances.delete(o);const i=c instanceof Error?c.message:String(c);/Auth failed/i.test(i)?(this.sharedOwnersCache.remove(e.name,s),l.warn("manager",`shared instance auth rejected, removed from cache: ${o}`)):l.error("manager",`start shared instance failed ${o}: ${c}`)}}for(const{key:s}of a){const o=this.sharedInstances.get(s);o&&(this.sharedInstances.delete(s),o.stop().catch(c=>l.error("manager",`stop shared instance failed ${s}: ${c}`)),l.info("manager",`shared instance stopped: ${s}`))}}restoreCachedSharedInstances(e){const t=this.sharedOwnersCache.load(e.name);t.length!==0&&(l.info("manager",`restoring ${t.length} cached shared instance(s) for ${e.name}`),this.onShareSet(e,t))}getAgentsStatus(){return this.instances.map(e=>e.getStatus())}async addAgent(e){await this.addAgentInternal(e,"agents.json"),this.persistAgentsConfig(),l.info("manager",`Added agent: ${e.name}`)}async addAgentInternal(e,t){const r=x(e);if(this.instances.some(a=>a.name===r.name))throw new Error(`Agent "${r.name}" already exists`);await this.startInstanceFromConfig(r,e,t)}async startInstanceFromConfig(e,t,r){const a=new T(e,this.globalConfigStore);a.setUpgradeTrigger(()=>this.upgradeChecker?.triggerCheck()),a.setShareSetHandler(s=>this.onShareSet(e,s)),a.setAgentDeletedHandler(()=>this.onAgentDeletedByPlatform(e.name)),a.setProviderConfigHandler(s=>this.setAgentProvider(e.name,s)),a.setRelayEnvSettledHandler(()=>this.relayEnvSettledHandler?.()),await a.start(),this.instances.push(a),this.configMap.set(e.name,e),this.rawConfigMap.set(e.name,{entry:t,file:r}),this.relayEnvSettledHandler?.()}async removeAgent(e){if(!this.instances.some(t=>t.name===e))throw Object.assign(new Error(`Agent "${e}" not found`),{code:"NOT_FOUND"});await this.removeAgentInternal(e),this.persistAgentsConfig(),l.info("manager",`Removed agent: ${e}`),this.relayEnvSettledHandler?.()}async removeAgentInternal(e,t){const r=this.instances.findIndex(c=>c.name===e),a=r===-1?void 0:this.instances[r];r!==-1&&this.instances.splice(r,1),this.configMap.delete(e),this.rawConfigMap.delete(e);const s=this.shareSyncChains.get(e);this.shareSyncChains.delete(e),s&&await s.catch(()=>{});const o=`${e}#shared:`;for(const[c,i]of[...this.sharedInstances.entries()])c.startsWith(o)&&(this.sharedInstances.delete(c),i.stop().catch(p=>l.error("manager",`stop shared instance failed ${c}: ${p}`)));t?.keepShareCache||this.sharedOwnersCache.delete(e),a&&await a.stop()}onAgentDeletedByPlatform(e){this.stopping||this.deletedAgentCleanups.has(e)||(this.deletedAgentCleanups.add(e),(async()=>{try{if(!(this.rawConfigMap.has(e)||this.instances.some(r=>r.name===e)))return;l.warn("manager",`Agent "${e}" \u5DF2\u5728\u5E73\u53F0\u5220\u9664\uFF0C\u505C\u6B62\u5B9E\u4F8B\u5E76\u6E05\u7406\u672C\u5730\u914D\u7F6E`),await this.removeAgentInternal(e),this.persistAgentsConfig(),l.info("manager",`Agent "${e}" \u5DF2\u5728\u5E73\u53F0\u5220\u9664\uFF0C\u672C\u5730\u914D\u7F6E\u5DF2\u6E05\u7406\uFF08agents.json \u5DF2\u79FB\u9664\u8BE5\u6761\u76EE\uFF09`)}catch(t){l.error("manager",`cleanup for deleted agent "${e}" failed: ${t}`)}finally{this.deletedAgentCleanups.delete(e)}})())}persistAgentsConfig(){const e=S(this.configDir,"agents.json");try{const t=[];for(const[,a]of this.configMap){const s=this.rawConfigMap.get(a.name)?.entry.provider;t.push({name:a.name,ws_url:a.aibot.url,agent_id:a.aibot.agentId,api_key:a.aibot.apiKey,client_type:a.aibot.clientType,...s?{provider:s}:{}})}W(e,JSON.stringify({agents:t},null,4)+`
2
- `,"utf-8")}catch(t){l.error("manager",`Failed to persist agents config: ${t}`)}}isAgentBusy(e){return this.instances.find(r=>r.name===e)?.hasPendingWork()??!1}getAgentsWithStaleRelayEnv(){return this.instances.filter(e=>e.hasStaleRelayEnv()).map(e=>e.name)}markAgentRelayEnvStale(e){const t=this.instances.find(r=>r.name===e);return t?(t.markRelayEnvStale(),!0):!1}relayEnvSettledHandler;setRelayEnvSettledHandler(e){this.relayEnvSettledHandler=e}async restartAgent(e){const t=this.rawConfigMap.get(e);if(!t)throw Object.assign(new Error(`Agent "${e}" not found`),{code:"NOT_FOUND"});await this.replaceInstance(e,t.entry,t.file),l.info("manager",`Restarted agent: ${e}`)}async setAgentProvider(e,t){const r=this.rawConfigMap.get(e);if(!r)throw Object.assign(new Error(`Agent "${e}" not found`),{code:"NOT_FOUND"});const a={...r.entry,provider:t};await this.replaceInstance(e,a,r.file),this.persistAgentsConfig(),l.info("manager",`Updated provider config for agent: ${e}`)}async clearDirectProviderConfigs(){const e=[...this.rawConfigMap.entries()].filter(([,r])=>!!r.entry.provider&&ne(r.entry.client_type)).map(([r])=>r),t=[];for(const r of e)try{await this.setAgentProvider(r,void 0),t.push(r)}catch(a){l.warn("manager",`Failed to clear provider config for "${r}": ${a instanceof Error?a.message:String(a)}`)}return t}async replaceInstance(e,t,r){const a=x(t);await this.removeAgentInternal(e,{keepShareCache:!0}),await this.startInstanceFromConfig(a,t,r),this.restoreCachedSharedInstances(a)}async reload(){const e=this.reloadChain.catch(()=>{}).then(()=>this.doReload());return this.reloadChain=e.catch(()=>{}),e}async doReload(){if(this.stopping)throw Object.assign(new Error("manager is stopping"),{code:"RELOAD_UNSAFE"});const{entries:e,fileErrors:t}=O(this.configDir);if(t.length>0){const o=t.map(c=>`${c.file}: ${c.error}`).join("; ");throw Object.assign(new Error(`reload aborted: ${t.length} config file(s) failed to parse [${o}]`),{code:"RELOAD_UNSAFE"})}if(e.length===0)throw Object.assign(new Error("reload aborted: no valid agent config found"),{code:"RELOAD_UNSAFE"});const r=new Map;for(const o of e){const c=String(o.entry?.name??"").trim();c&&(r.has(c)&&l.warn("manager",`reload: duplicate agent name "${c}", last one wins`),r.set(c,o))}const a={added:[],removed:[],restarted:[],unchanged:[],failed:[]},s=new Set(this.rawConfigMap.keys());for(const o of s)if(!r.has(o))try{await this.removeAgentInternal(o),a.removed.push(o)}catch(c){a.failed.push({name:o,error:c instanceof Error?c.message:String(c)})}for(const[o,c]of r){const i=this.rawConfigMap.get(o);if(i)if(fe(i.entry,c.entry))this.rawConfigMap.set(o,c),a.unchanged.push(o);else try{await this.replaceInstance(o,c.entry,c.file),a.restarted.push(o)}catch(p){a.failed.push({name:o,error:p instanceof Error?p.message:String(p)})}else try{await this.addAgentInternal(c.entry,c.file),a.added.push(o)}catch(p){a.failed.push({name:o,error:p instanceof Error?p.message:String(p)})}}return l.info("manager",`reload done: +${a.added.length} -${a.removed.length} ~${a.restarted.length} =${a.unchanged.length} !${a.failed.length}`),a}async checkUpgrade(){return this.upgradeChecker?this.upgradeChecker.checkForUpdate():{available:!1}}triggerUpgrade(){this.upgradeChecker?.triggerCheck()}async probeAll(e={}){return _e(this.instances,e)}async probeOne(e,t={}){return we(this.instances,e,t)}listInstallable(){return this.installer.listInstallable()}async installAgent(e){const t=await this.installer.install(e);return oe(t),t}getInstallProgress(e){return this.installer.getProgress(e)??null}}async function _e(n,e){const t=e.concurrency??4,r=Date.now(),a=new Array(n.length);await new Promise(p=>{let g=0,f=0;const w=n.length;if(w===0){p();return}function d(m){const _=n[m];_.probe(e).then(y=>{a[m]=y,u()},y=>{a[m]={agent_name:_.name,client_type:"unknown",adapter_type:"acp",ok:!1,status:"error",probed_at:Date.now(),duration_ms:0,cached:!1,cli:{command:"",installed:!1,path:null,version:null,error:{code:"internal",message:y?.message??String(y)}},conversation:{attempted:!1,ok:!1,latency_ms:null},config:{model:null,base_url:null,source:{model:"unknown",base_url:"unknown"}},process:{started:!1,alive:!1,busy:!1}},u()})}function u(){f++,g<w?d(g++):f===w&&p()}const h=Math.min(t,w);for(let m=0;m<h;m++)d(g++)});const s=a.filter(p=>p.status==="healthy").length,o=a.filter(p=>p.status==="degraded").length,c=a.filter(p=>p.status==="unavailable").length,i=await le();return{ok:s===a.length&&a.length>0,total:a.length,healthy:s,degraded:o,unavailable:c,installed_clients:i,agents:a,probed_at:r,duration_ms:Date.now()-r}}async function we(n,e,t){const r=n.find(a=>a.name===e);if(!r)throw Object.assign(new Error(`Agent "${e}" not found`),{code:"NOT_FOUND"});return r.probe(t)}export{Oe as Manager,le as probeInstalledClientCommands,_e as probeInstances,we as probeOneInstance};
1
+ import{readFileSync as W,readdirSync as K,writeFileSync as G}from"node:fs";import{join as S}from"node:path";import{AgentInstance as I}from"./bridge/bridge.js";import{buildSharedInstanceConfig as z,diffSharedOwners as J,sharedInstanceKey as V,SharedOwnersCache as X}from"./manager-share-config.js";import{isAgentDeletedError as j}from"./core/aibot/errors.js";import{GRIX_PATHS as A,log as l}from"./core/log/index.js";import{resolveClientVersion as Y}from"./core/util/client-version.js";import{UpgradeChecker as Z}from"./core/upgrade/upgrade-checker.js";import{AgentGlobalConfigStore as ee}from"./core/persistence/agent-global-config-store.js";import{resolveSkillScanMode as te}from"./adapter/claude/skill-scanner.js";import{buildReportedSkills as ne,logDefaultSkillsCheck as ae,cleanupProjectedSkills as D}from"./default-skills/index.js";import{supportsDirectProviderConfig as re}from"./core/config/provider-env.js";import{resolveCopilotCommand as P}from"./core/runtime/copilot-resolve.js";import{resolveCursorCommand as O}from"./core/runtime/cursor-resolve.js";import{getCliVersion as se,resolveCliPath as oe}from"./core/util/cli-probe.js";import{AgentInstaller as ie}from"./core/installer/installer.js";import{reportInstallFailure as ce}from"./core/observability/sentry.js";const le=8e3;function de(){const n=P();return[{clientType:"openclaw",command:"openclaw"},{clientType:"claude",command:"claude"},{clientType:"codex",command:"codex"},{clientType:"gemini",command:"gemini"},{clientType:"qwen",command:"qwen"},{clientType:"hermes",command:"hermes"},{clientType:"reasonix",command:"reasonix"},{clientType:"codewhale",command:"codewhale"},{clientType:"opencode",command:"opencode"},{clientType:"cursor",command:O()},{clientType:"pi",command:"pi"},{clientType:"openhuman",command:"openhuman-core"},{clientType:"kiro",command:"kiro-cli"},{clientType:"kimi",command:"kimi"},{clientType:"copilot",command:n.command},{clientType:"agy",command:"agy"}]}async function pe(){return Promise.all(de().map(async n=>{const e=await oe(n.command);if(!e)return{client_type:n.clientType,command:n.command,installed:!1,path:null,version:null};const t=await se(e,n.versionArgs??["--version"]);return{client_type:n.clientType,command:n.command,installed:!0,path:e,version:t.version,error:t.error}}))}function me(n){switch(n){case"claude":return{adapterType:"claude",command:"claude"};case"codex":return{adapterType:"codex",command:"codex",options:{sandboxMode:"danger-full-access"}};case"gemini":return{adapterType:"acp",command:"gemini",autoInjectArgs:{acp:!0},enableSessionBinding:!0};case"qwen":return{adapterType:"acp",command:"qwen",adapterHint:"qwen/base",autoInjectArgs:{acp:!0},enableSessionBinding:!0};case"pi":return{adapterType:"pi",command:"pi"};case"cursor":return{adapterType:"cursor",command:O()};case"reasonix":return{adapterType:"acp",command:"reasonix",args:["acp"],enableSessionBinding:!0};case"codewhale":return{adapterType:"codewhale",command:"codewhale",enableSessionBinding:!0};case"openhuman":return{adapterType:"openhuman",command:"openhuman-core",enableSessionBinding:!0};case"kiro":return{adapterType:"acp",command:"kiro-cli",args:["acp"],enableSessionBinding:!0};case"kimi":return{adapterType:"acp",command:"kimi",args:["acp"],enableSessionBinding:!0};case"opencode":return{adapterType:"opencode",command:"opencode",args:["serve"],enableSessionBinding:!0};case"copilot":{const e=P();return{adapterType:"acp",command:e.command,args:[...e.prefixArgs,"--acp"],enableSessionBinding:!0}}case"agy":return{adapterType:"agy",command:"agy",enableSessionBinding:!0};case"hermes":return{adapterType:"acp",command:"hermes",args:["acp"],enableSessionBinding:!0};default:throw new Error(`Unsupported client_type: ${n}`)}}function q(n,e){return String(n??"").trim().toLowerCase().replace(/[^a-z0-9._-]+/g,"-").replace(/-+/g,"-").replace(/^-|-$/g,"")||String(e??"").trim().toLowerCase().replace(/[^a-z0-9._-]+/g,"-").replace(/-+/g,"-").replace(/^-|-$/g,"")||"default"}function ue(n,e){return S(A.data,`session-bindings-${q(n,e)}.json`)}function ge(n,e){return S(A.data,`active-events-${q(n,e)}.json`)}function he(...n){const e=[],t=new Set;for(const r of n)for(const s of r??[]){const i=String(s??"").trim(),o=i.toLowerCase();!i||t.has(o)||(t.add(o),e.push(i))}return e.length>0?e:void 0}function fe(n,e){const t={claude:{maxConcurrent:1,maxQueued:5,queueTimeoutMs:0},codex:{maxConcurrent:1,maxQueued:5,queueTimeoutMs:0},cursor:{maxConcurrent:1,maxQueued:3,queueTimeoutMs:0},acp:{maxConcurrent:1,maxQueued:3,queueTimeoutMs:0},pi:{maxConcurrent:1,maxQueued:5,queueTimeoutMs:0},codewhale:{maxConcurrent:1,maxQueued:3,queueTimeoutMs:0},openhuman:{maxConcurrent:1,maxQueued:3,queueTimeoutMs:0},opencode:{maxConcurrent:1,maxQueued:3,queueTimeoutMs:0},agy:{maxConcurrent:1,maxQueued:3,queueTimeoutMs:0}},r=t[n]??t.acp;return{maxConcurrent:e?.max_concurrent??r.maxConcurrent??1,maxQueued:e?.max_queued??r.maxQueued??3,queueTimeoutMs:e?.queue_timeout_ms??r.queueTimeoutMs??0,cancelableQueued:!0,cancelableRunning:!0}}function b(n,e={}){const t=Y(),r=String(n.client_type??"").trim().toLowerCase(),s=me(r),i=String(n.ws_url??"").trim(),o="get_session_usage",p="get_rate_limits",c="get_agent_global_config";if(!n.name?.trim())throw new Error("agent name is required");if(!i)throw new Error(`agent ${n.name}: ws_url is required`);if(!n.agent_id?.trim())throw new Error(`agent ${n.name}: agent_id is required`);if(!n.api_key?.trim())throw new Error(`agent ${n.name}: api_key is required`);const a=s.adapterType,u=a==="acp",m=r==="qwen",_={...s.options??{}},w=a==="codex"?{capabilities:["local_action_v1","agent_invoke"],localActions:["session_control","get_context","set_model","set_mode","set_reasoning_effort","set_service_tier","set_sandbox_mode","exec_approve","exec_reject","file_list","create_folder","turn_interrupt","permission_approve","permission_reject","thread_compact",o,p]}:null,d=a==="claude"?{localActions:["session_control","set_mode","set_model","claude_interaction_reply","exec_approve","exec_reject","file_list","create_folder","thread_compact",o,p]}:null,f=m?{capabilities:["stream_chunk","local_action_v1"],localActions:["exec_approve","exec_reject","permission_approve","permission_reject","session_control","set_model","set_mode","file_list","create_folder",o],adapterHint:"qwen/base"}:null,h=a==="pi"?{adapterHint:"pi/base",capabilities:["local_action_v1"],localActions:["session_control","set_model","get_context","file_list","create_folder",o]}:null,g=a==="openhuman"?{adapterHint:"openhuman/base",capabilities:["local_action_v1"],localActions:["session_control","set_model","file_list","create_folder",o]}:null,y=a==="cursor"?{adapterHint:"cursor/base",capabilities:["stream_chunk","local_action_v1"],localActions:["session_control","set_model","set_mode","get_context","file_list","create_folder",o,p]}:null,v=a==="codewhale"?{capabilities:["stream_chunk","local_action_v1"],localActions:["session_control","set_model","file_list","create_folder",o]}:null,k=a==="opencode"?{adapterHint:"opencode/base",capabilities:["stream_chunk","local_action_v1"],localActions:["exec_approve","exec_reject","permission_approve","permission_reject","session_control","set_model","set_mode","file_list","create_folder",o]}:null,$=a==="agy"?{adapterHint:"agy/base",capabilities:["stream_chunk","local_action_v1"],localActions:["session_control","set_model","file_list","create_folder",o]}:null,N=u&&!m?{localActions:["exec_approve","exec_reject","permission_approve","permission_reject","session_control","set_model","set_mode","file_list","create_folder",o]}:null,R=r==="kiro"?{localActions:["exec_approve","exec_reject","permission_approve","permission_reject","session_control","set_model","set_mode","file_list","create_folder","thread_compact",o,p]}:null,B=a==="codex"||a==="claude"||r==="gemini"?["session_control","set_model","set_mode"]:void 0,F=[o,p];u&&_.raw_transport===void 0&&(_.raw_transport=r==="gemini");const U=`${r}/base`,E=te(a,r),L=E==="kiro"?void 0:process.cwd(),T=ne({mode:E,projectDir:L}),M=T.filter(x=>x.source==="connector").map(x=>x.name);M.length>0&&!e.quiet&&l.info("manager",`[${n.name}] injecting connector skills: [${M.join(", ")}]`);let Q=T.length>0?T:void 0;return{name:n.name,adapterType:a,aibot:{url:i,agentId:n.agent_id,apiKey:n.api_key,clientType:r,clientVersion:t,adapterHint:s.adapterHint??f?.adapterHint??h?.adapterHint??g?.adapterHint??y?.adapterHint??k?.adapterHint??$?.adapterHint??U,capabilities:w?.capabilities??v?.capabilities??h?.capabilities??g?.capabilities??y?.capabilities??k?.capabilities??$?.capabilities??f?.capabilities??["stream_chunk","local_action_v1","connector_upgrade"],localActions:he(w?.localActions??v?.localActions??d?.localActions??h?.localActions??g?.localActions??y?.localActions??k?.localActions??$?.localActions??f?.localActions??R?.localActions??N?.localActions??["exec_approve","exec_reject"],B,F,["connector_rollback","connector_upgrade_push",c,"configure_gateway_provider"]),skills:Q},agent:{command:s.command,args:[...s.args??[],...n.args??[]],env:n.env,clientType:r,...n.provider?{provider:{...n.provider.base_url?.trim()?{baseUrl:n.provider.base_url.trim()}:{},...n.provider.api_key?.trim()?{apiKey:n.provider.api_key.trim()}:{},...n.provider.model?.trim()?{model:n.provider.model.trim()}:{}}}:{}},adapterOptions:_,acpAuthMethod:_.auth_method,acpInitialMode:_.initial_mode,acpMcpTools:_.acp_mcp_tools,promptTimeoutMs:n.prompt_timeout_ms,bindingsPath:ue(n.name,n.agent_id),activeEventStorePath:ge(n.name,n.agent_id),...s.enableSessionBinding||u?{enableSessionBinding:!0}:{},...s.autoInjectArgs||r==="reasonix"&&n.provider?.model?.trim()?{autoInjectArgs:{...s.autoInjectArgs??{},...r==="reasonix"&&n.provider?.model?.trim()?{model:n.provider.model.trim()}:{}}}:{},poolMaxSize:n.pool?.maxSize,poolIdleTimeoutMs:n.pool?.idleTimeoutMs,eventQueue:fe(a,n.event_queue),logDir:A.log,providerBaseUrl:n.provider_base_url?.trim()||void 0,providerApiKey:n.provider_api_key?.trim()||void 0}}function _e(){const n=process.env.GRIX_AGENT_STARTUP_WAIT_MS,e=Number(n);return Number.isFinite(e)&&e>=500?Math.floor(e):le}function H(n){const e=[],t=[],r=[],s=K(n).filter(i=>i.endsWith(".json")).sort();for(const i of s)try{const o=JSON.parse(W(S(n,i),"utf-8"));if(o===null||typeof o!="object"||Array.isArray(o)||!("agents"in o)){r.push(i);continue}if(!Array.isArray(o.agents)){t.push({file:i,error:"agents is not an array"});continue}if(o.agents.length===0){t.push({file:i,error:"agents array is empty"});continue}for(const p of o.agents)e.push({entry:p,file:i})}catch(o){t.push({file:i,error:o instanceof Error?o.message:String(o)})}return{entries:e,fileErrors:t,skipped:r}}function C(n){if(n===null||typeof n!="object")return JSON.stringify(n)??"null";if(Array.isArray(n))return`[${n.map(C).join(",")}]`;const e=n;return`{${Object.keys(e).sort().map(r=>`${JSON.stringify(r)}:${C(e[r])}`).join(",")}}`}function ye(n,e){return C(n)===C(e)}class Ne{instances=[];configMap=new Map;rawConfigMap=new Map;reloadChain=Promise.resolve();sharedInstances=new Map;shareSyncChains=new Map;stopping=!1;deletedAgentCleanups=new Set;upgradeChecker=null;globalConfigStore;configDir=A.config;sharedOwnersCache=new X(A.data);installer=new ie;async start(e){const t=e??A.config;this.configDir=t,l.info("manager",`Loading configs from ${t}`),ae(),D(),this.globalConfigStore=new ee(S(A.data,"agent-global-configs.json")),this.globalConfigStore.load();const{entries:r,fileErrors:s,skipped:i}=H(t);for(const a of s)l.error("manager",`Failed to load ${a.file}: ${a.error}`);for(const a of i)l.info("manager",`Skipped ${a}: not an agent config (no "agents" key)`);if(r.length===0&&s.length===0&&i.length===0)throw new Error(`No config files found in ${t}`);const o=[];let p=s.length;for(const{entry:a,file:u}of r)try{const m=b(a);o.push({config:m,entry:a,file:u}),l.info("manager",`Loaded ${m.name} (${m.adapterType??"acp"}) from ${u}`)}catch(m){const _=typeof a?.name=="string"?a.name:"<unknown>";l.error("manager",`Invalid agent config in ${u} (name=${_}): ${m}`),p++}let c=0;if(o.length>0){const a=_e();l.info("manager",`Starting ${o.length} agent(s), startup wait=${a}ms`);const u=()=>this.upgradeChecker?.triggerCheck(),m=d=>{this.instances=this.instances.filter(f=>f!==d)},_=o.map(({config:d,entry:f,file:h})=>{const g=new I(d,this.globalConfigStore);return g.setUpgradeTrigger(u),g.setShareSetHandler(y=>this.onShareSet(d,y)),g.setAgentDeletedHandler(()=>this.onAgentDeletedByPlatform(d.name)),this.instances.push(g),this.configMap.set(d.name,d),this.rawConfigMap.set(d.name,{entry:f,file:h}),{config:d,instance:g,startPromise:g.start()}}),w=await Promise.all(_.map(async d=>{const f=await new Promise(h=>{let g=!1;const y=setTimeout(()=>{g||(g=!0,h({kind:"timeout"}))},a);d.startPromise.then(()=>{g||(g=!0,clearTimeout(y),h({kind:"started"}))}).catch(v=>{g||(g=!0,clearTimeout(y),h({kind:"failed",error:v}))})});return{task:d,outcome:f}}));for(const{task:d,outcome:f}of w){if(f.kind==="started"){this.restoreCachedSharedInstances(d.config);continue}if(f.kind==="failed"){if(j(f.error)){this.onAgentDeletedByPlatform(d.config.name);continue}m(d.instance),l.error("manager",`Failed to start ${d.config.name}: ${f.error}`);continue}c++,l.warn("manager",`Startup pending for ${d.config.name}, continue retrying in background`),d.startPromise.then(()=>{l.info("manager",`Delayed start succeeded: ${d.config.name}`),this.restoreCachedSharedInstances(d.config)}).catch(h=>{if(j(h)){this.onAgentDeletedByPlatform(d.config.name);return}m(d.instance),l.error("manager",`Delayed start failed: ${d.config.name}: ${h}`)})}if(this.instances.length>0){const d=Math.max(0,this.instances.length-c);l.info("manager",`${d}/${o.length} agent(s) running now`)}c>0&&l.warn("manager",`${c} agent(s) still connecting in background`)}if(this.instances.length===0&&o.length>0)throw new Error("All agent configurations failed to start");o.length>0&&(this.upgradeChecker=new Z(o.map(({config:a})=>({apiKey:a.aibot.apiKey,wsUrl:a.aibot.url})),()=>this.instances.some(a=>a.hasPendingOrBackgroundWork())||[...this.sharedInstances.values()].some(a=>a.hasPendingOrBackgroundWork())),await this.upgradeChecker.start())}async stop(){l.info("manager","Stopping all agents..."),this.stopping=!0,this.upgradeChecker?.stop(),await Promise.allSettled([...this.shareSyncChains.values()]),this.shareSyncChains.clear();const e=[...this.sharedInstances.values()];this.sharedInstances.clear(),await Promise.allSettled([...this.instances.map(t=>t.stop()),...e.map(t=>t.stop())]),await this.globalConfigStore?.flush(),this.instances=[],D(),l.info("manager","All stopped")}onShareSet(e,t){this.sharedOwnersCache.save(e.name,t);const s=(this.shareSyncChains.get(e.name)??Promise.resolve()).catch(()=>{}).then(()=>this.syncSharedInstances(e,t));this.shareSyncChains.set(e.name,s)}async syncSharedInstances(e,t){if(this.stopping)return;const{toAdd:r,toRemove:s}=J(e.name,this.sharedInstances.keys(),t);for(const i of r){if(this.stopping)break;const o=V(e.name,i);try{const p=z(e,i),c=new I(p,this.globalConfigStore);this.sharedInstances.set(o,c),await c.start(),l.info("manager",`shared instance started: ${o}`)}catch(p){this.sharedInstances.delete(o);const c=p instanceof Error?p.message:String(p);/Auth failed/i.test(c)?(this.sharedOwnersCache.remove(e.name,i),l.warn("manager",`shared instance auth rejected, removed from cache: ${o}`)):l.error("manager",`start shared instance failed ${o}: ${p}`)}}for(const{key:i}of s){const o=this.sharedInstances.get(i);o&&(this.sharedInstances.delete(i),o.stop().catch(p=>l.error("manager",`stop shared instance failed ${i}: ${p}`)),l.info("manager",`shared instance stopped: ${i}`))}}restoreCachedSharedInstances(e){const t=this.sharedOwnersCache.load(e.name);t.length!==0&&(l.info("manager",`restoring ${t.length} cached shared instance(s) for ${e.name}`),this.onShareSet(e,t))}getAgentsStatus(){return this.instances.map(e=>e.getStatus())}async addAgent(e){await this.addAgentInternal(e,"agents.json"),this.persistAgentsConfig(),l.info("manager",`Added agent: ${e.name}`)}async addAgentInternal(e,t){const r=b(e);if(this.instances.some(s=>s.name===r.name))throw new Error(`Agent "${r.name}" already exists`);await this.startInstanceFromConfig(r,e,t)}async startInstanceFromConfig(e,t,r){const s=new I(e,this.globalConfigStore);s.setUpgradeTrigger(()=>this.upgradeChecker?.triggerCheck()),s.setShareSetHandler(i=>this.onShareSet(e,i)),s.setAgentDeletedHandler(()=>this.onAgentDeletedByPlatform(e.name)),s.setProviderConfigHandler(i=>this.setAgentProvider(e.name,i)),s.setRelayEnvSettledHandler(()=>this.relayEnvSettledHandler?.()),await s.start(),this.instances.push(s),this.configMap.set(e.name,e),this.rawConfigMap.set(e.name,{entry:t,file:r}),this.relayEnvSettledHandler?.()}async removeAgent(e){if(!this.instances.some(t=>t.name===e))throw Object.assign(new Error(`Agent "${e}" not found`),{code:"NOT_FOUND"});await this.removeAgentInternal(e),this.persistAgentsConfig(),l.info("manager",`Removed agent: ${e}`),this.relayEnvSettledHandler?.()}async removeAgentInternal(e,t){const r=this.instances.findIndex(p=>p.name===e),s=r===-1?void 0:this.instances[r];r!==-1&&this.instances.splice(r,1),this.configMap.delete(e),this.rawConfigMap.delete(e);const i=this.shareSyncChains.get(e);this.shareSyncChains.delete(e),i&&await i.catch(()=>{});const o=`${e}#shared:`;for(const[p,c]of[...this.sharedInstances.entries()])p.startsWith(o)&&(this.sharedInstances.delete(p),c.stop().catch(a=>l.error("manager",`stop shared instance failed ${p}: ${a}`)));t?.keepShareCache||this.sharedOwnersCache.delete(e),s&&await s.stop()}onAgentDeletedByPlatform(e){this.stopping||this.deletedAgentCleanups.has(e)||(this.deletedAgentCleanups.add(e),(async()=>{try{if(!(this.rawConfigMap.has(e)||this.instances.some(r=>r.name===e)))return;l.warn("manager",`Agent "${e}" \u5DF2\u5728\u5E73\u53F0\u5220\u9664\uFF0C\u505C\u6B62\u5B9E\u4F8B\u5E76\u6E05\u7406\u672C\u5730\u914D\u7F6E`),await this.removeAgentInternal(e),this.persistAgentsConfig(),l.info("manager",`Agent "${e}" \u5DF2\u5728\u5E73\u53F0\u5220\u9664\uFF0C\u672C\u5730\u914D\u7F6E\u5DF2\u6E05\u7406\uFF08agents.json \u5DF2\u79FB\u9664\u8BE5\u6761\u76EE\uFF09`)}catch(t){l.error("manager",`cleanup for deleted agent "${e}" failed: ${t}`)}finally{this.deletedAgentCleanups.delete(e)}})())}persistAgentsConfig(){const e=S(this.configDir,"agents.json");try{const t=[];for(const[,s]of this.configMap){const i=this.rawConfigMap.get(s.name)?.entry.provider;t.push({name:s.name,ws_url:s.aibot.url,agent_id:s.aibot.agentId,api_key:s.aibot.apiKey,client_type:s.aibot.clientType,...i?{provider:i}:{}})}G(e,JSON.stringify({agents:t},null,4)+`
2
+ `,"utf-8")}catch(t){l.error("manager",`Failed to persist agents config: ${t}`)}}isAgentBusy(e){return this.instances.find(r=>r.name===e)?.hasPendingWork()??!1}getAgentsWithStaleRelayEnv(){return this.instances.filter(e=>e.hasStaleRelayEnv()).map(e=>e.name)}markAgentRelayEnvStale(e){const t=this.instances.find(r=>r.name===e);return t?(t.markRelayEnvStale(),!0):!1}relayEnvSettledHandler;setRelayEnvSettledHandler(e){this.relayEnvSettledHandler=e}async restartAgent(e){const t=this.rawConfigMap.get(e);if(!t)throw Object.assign(new Error(`Agent "${e}" not found`),{code:"NOT_FOUND"});await this.replaceInstance(e,t.entry,t.file),l.info("manager",`Restarted agent: ${e}`)}async setAgentProvider(e,t){const r=this.rawConfigMap.get(e);if(!r)throw Object.assign(new Error(`Agent "${e}" not found`),{code:"NOT_FOUND"});const s={...r.entry,provider:t};await this.replaceInstance(e,s,r.file),this.persistAgentsConfig(),l.info("manager",`Updated provider config for agent: ${e}`)}async clearDirectProviderConfigs(){const e=[...this.rawConfigMap.entries()].filter(([,r])=>!!r.entry.provider&&re(r.entry.client_type)).map(([r])=>r),t=[];for(const r of e)try{await this.setAgentProvider(r,void 0),t.push(r)}catch(s){l.warn("manager",`Failed to clear provider config for "${r}": ${s instanceof Error?s.message:String(s)}`)}return t}async replaceInstance(e,t,r){const s=b(t);await this.removeAgentInternal(e,{keepShareCache:!0}),await this.startInstanceFromConfig(s,t,r),this.restoreCachedSharedInstances(s)}async reload(){const e=this.reloadChain.catch(()=>{}).then(()=>this.doReload());return this.reloadChain=e.catch(()=>{}),e}async doReload(){if(this.stopping)throw Object.assign(new Error("manager is stopping"),{code:"RELOAD_UNSAFE"});const{entries:e,fileErrors:t,skipped:r}=H(this.configDir);for(const c of r)l.info("manager",`reload: skipped ${c}: not an agent config (no "agents" key)`);if(t.length>0){const c=t.map(a=>`${a.file}: ${a.error}`).join("; ");throw Object.assign(new Error(`reload aborted: ${t.length} config file(s) failed to parse [${c}]`),{code:"RELOAD_UNSAFE"})}if(e.length===0)throw Object.assign(new Error("reload aborted: no valid agent config found"),{code:"RELOAD_UNSAFE"});const s=[];for(const{entry:c}of e)try{b(c,{quiet:!0})}catch(a){const u=c?.name,m=typeof u=="string"&&u.trim()?u:"<unknown>";s.push({name:m,error:a instanceof Error?a.message:String(a)})}if(s.length>0){const c=s.map(a=>`${a.name}: ${a.error}`).join("; ");throw Object.assign(new Error(`reload aborted: ${s.length} agent config(s) invalid [${c}]`),{code:"RELOAD_UNSAFE"})}const i=new Map;for(const c of e){const a=String(c.entry?.name??"").trim();a&&(i.has(a)&&l.warn("manager",`reload: duplicate agent name "${a}", last one wins`),i.set(a,c))}const o={added:[],removed:[],restarted:[],unchanged:[],failed:[]},p=new Set(this.rawConfigMap.keys());for(const c of p)if(!i.has(c))try{await this.removeAgentInternal(c),o.removed.push(c)}catch(a){o.failed.push({name:c,error:a instanceof Error?a.message:String(a)})}for(const[c,a]of i){const u=this.rawConfigMap.get(c);if(u)if(ye(u.entry,a.entry))this.rawConfigMap.set(c,a),o.unchanged.push(c);else try{await this.replaceInstance(c,a.entry,a.file),o.restarted.push(c)}catch(m){o.failed.push({name:c,error:m instanceof Error?m.message:String(m)})}else try{await this.addAgentInternal(a.entry,a.file),o.added.push(c)}catch(m){o.failed.push({name:c,error:m instanceof Error?m.message:String(m)})}}return l.info("manager",`reload done: +${o.added.length} -${o.removed.length} ~${o.restarted.length} =${o.unchanged.length} !${o.failed.length}`),o}async checkUpgrade(){return this.upgradeChecker?this.upgradeChecker.checkForUpdate():{available:!1}}triggerUpgrade(){this.upgradeChecker?.triggerCheck()}async probeAll(e={}){return we(this.instances,e)}async probeOne(e,t={}){return Ae(this.instances,e,t)}listInstallable(){return this.installer.listInstallable()}async installAgent(e){const t=await this.installer.install(e);return ce(t),t}getInstallProgress(e){return this.installer.getProgress(e)??null}}async function we(n,e){const t=e.concurrency??4,r=Date.now(),s=new Array(n.length);await new Promise(a=>{let u=0,m=0;const _=n.length;if(_===0){a();return}function w(h){const g=n[h];g.probe(e).then(y=>{s[h]=y,d()},y=>{s[h]={agent_name:g.name,client_type:"unknown",adapter_type:"acp",ok:!1,status:"error",probed_at:Date.now(),duration_ms:0,cached:!1,cli:{command:"",installed:!1,path:null,version:null,error:{code:"internal",message:y?.message??String(y)}},conversation:{attempted:!1,ok:!1,latency_ms:null},config:{model:null,base_url:null,source:{model:"unknown",base_url:"unknown"}},process:{started:!1,alive:!1,busy:!1}},d()})}function d(){m++,u<_?w(u++):m===_&&a()}const f=Math.min(t,_);for(let h=0;h<f;h++)w(u++)});const i=s.filter(a=>a.status==="healthy").length,o=s.filter(a=>a.status==="degraded").length,p=s.filter(a=>a.status==="unavailable").length,c=await pe();return{ok:i===s.length&&s.length>0,total:s.length,healthy:i,degraded:o,unavailable:p,installed_clients:c,agents:s,probed_at:r,duration_ms:Date.now()-r}}async function Ae(n,e,t){const r=n.find(s=>s.name===e);if(!r)throw Object.assign(new Error(`Agent "${e}" not found`),{code:"NOT_FOUND"});return r.probe(t)}export{Ne as Manager,pe as probeInstalledClientCommands,we as probeInstances,Ae as probeOneInstance};
@@ -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};