grix-connector 3.11.0 → 3.12.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
- 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
+ import{detectProvider as o,detectProviderFromModel as i,normalizeProviderId as t,queryProviderQuota as d,readKimiProviderSettings as v,resolveKimiProviderSettings as a}from"./providers.js";import{ProviderQuotaService as s,sharedProviderQuotaService as P,resolveQuotaBaseUrl as u}from"./service.js";import{providerQuotaToRateLimits as l,providerQuotaToCodexRateLimits as p}from"./presentation.js";export{s as ProviderQuotaService,o as detectProvider,i as detectProviderFromModel,t as normalizeProviderId,p as providerQuotaToCodexRateLimits,l as providerQuotaToRateLimits,d as queryProviderQuota,v as readKimiProviderSettings,a as resolveKimiProviderSettings,u as resolveQuotaBaseUrl,P as sharedProviderQuotaService};
@@ -0,0 +1 @@
1
+ function a(e){if(!e)return 0;const i=Date.parse(e);return Number.isFinite(i)?Math.floor(i/1e3):0}function d(e,i=Date.now()){const n=e.tiers.find(r=>r.name==="five_hour"),t=e.tiers.find(r=>r.name==="weekly_limit");return n||t?{...n?{fiveHour:{usedPercentage:n.usedPercent,resetsAt:a(n.resetsAt)}}:{},...t?{sevenDay:{usedPercentage:t.usedPercent,resetsAt:a(t.resetsAt)}}:{},sampledAt:i}:e.balance?{credit:{remaining:e.balance.remaining,total:e.balance.total,used:e.balance.used,unit:e.balance.unit,resetsAt:a(e.balance.resetsAt??null)},planName:e.planName,sampledAt:i}:null}function l(e,i=Date.now()){const n=e.tiers.find(s=>s.name==="five_hour"),t=e.tiers.find(s=>s.name==="weekly_limit");if(n||t){const s={credits:{hasCredits:!0,unlimited:!1,balance:null},sampledAt:i};return n&&(s.primary={usedPercent:n.usedPercent,windowMinutes:300,resetsAt:n.resetsAt}),t&&(s.secondary={usedPercent:t.usedPercent,windowMinutes:10080,resetsAt:t.resetsAt}),{rateLimits:s,primaryPercent:n?.usedPercent??0,secondaryPercent:t?.usedPercent??0,primaryWindowMin:n?300:0,secondaryWindowMin:t?10080:0}}if(!e.balance)return null;const r=e.balance,c=r.total&&r.total>0?Math.min(100,(r.used??r.total-r.remaining)/r.total*100):0;return{rateLimits:{primary:{usedPercent:c,windowMinutes:0,resetsAt:null},secondary:{usedPercent:0,windowMinutes:0,resetsAt:null},credits:{hasCredits:!0,unlimited:!1,balance:r.remaining},sampledAt:i},primaryPercent:c,secondaryPercent:0,primaryWindowMin:0,secondaryWindowMin:0}}export{l as providerQuotaToCodexRateLimits,d as providerQuotaToRateLimits};
@@ -1,2 +1,2 @@
1
- import{readFileSync as y,writeFileSync as D,mkdirSync as N,rmdirSync as x,statSync as G}from"node:fs";import{homedir as z}from"node:os";import{join as _}from"node:path";function F(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 m(o){if(typeof o=="number")return o;if(typeof o=="string"){const e=Number(o);return Number.isFinite(e)?e:null}return null}function v(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 v(e)}return null}function I(){return(process.env.KIMI_CODE_HOME||"").trim()||_(z(),".kimi-code")}function $(o){const e=o||I(),i=300*1e3;let t;try{const r=_(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=_(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 q="17e5f671-d194-4dfb-9706-5516cb48c098";function Y(){return(process.env.KIMI_CODE_OAUTH_HOST||process.env.KIMI_OAUTH_HOST||"https://auth.kimi.com").replace(/\/+$/,"")}const Z=30*1e3;async function re(o){const e=$(o);if(e.apiKey)return e;const i=await K(o);return{baseUrl:e.baseUrl,apiKey:i}}async function K(o){const e=o||I(),i=_(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=_(e,"credentials","kimi-code.lock.lock");if(V(r))try{const n=$(o);if(n.apiKey)return n.apiKey;let s="";try{s=y(_(e,"device_id"),"utf8").trim()}catch{}const c=await fetch(`${Y()}/api/oauth/token`,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded",...s?{"X-Msh-Device-Id":s}:{}},body:new URLSearchParams({client_id:q,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 p=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(p)&&p>0?p: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(p)&&p>0?p:900};return D(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=G(o);return Date.now()-e.mtimeMs<Z?!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=m(a.percentage)??0,s=typeof a.nextResetTime=="number"?a.nextResetTime:Number.MAX_SAFE_INTEGER,c=s===Number.MAX_SAFE_INTEGER?null:v(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=m(l.limit)??1,p=m(l.remaining)??0,h=S(l.resetTime),b=Math.max(0,u-p);r.push({name:"five_hour",label:"5h limit",usedPercent:u>0?Math.round(b/u*1e4)/100:0,resetsAt:h})}const s=a.usage;if(s){const c=m(s.limit)??1,l=m(s.remaining)??0,u=S(s.resetTime),p=Math.max(0,c-l);r.push({name:"weekly_limit",label:"Weekly limit",usedPercent:c>0?Math.round(p/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 w(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 p=await r.text().catch(()=>"");return d(i,t,`API error (HTTP ${r.status}): ${p.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 p=m(u.current_interval_total_count)??0,h=m(u.current_interval_usage_count)??0,b=typeof u.end_time=="number"?u.end_time:null;p>0&&c.push({name:"five_hour",label:"5h limit",usedPercent:Math.round(h/p*1e4)/100,resetsAt:b!==null?v(b):null});const g=m(u.current_weekly_total_count)??0,C=m(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(C/g*1e4)/100,resetsAt:P!==null?v(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=m(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=m(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=m(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 H(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=m(r.total_credits)??0,s=m(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 R(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=(m(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 B(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=>B(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,W=300*1e3;async function Q(o){const e=o.slice(0,8),i=T.get(e);if(i&&Date.now()-i.timestamp<=W){const r=[i.providerId],n=await Promise.allSettled(r.map(s=>U(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=>U(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 U(o,e){try{switch(o){case"zhipu":return E(e);case"kimi":return L(e);case"minimax_cn":return w(e,!0);case"minimax_en":return w(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 H(e);case"novita":return R(e)}}catch{return null}}async function B(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"),p=m(l.total_balance);return{provider:"deepseek",providerLabel:"DeepSeek",planName:null,tiers:[],balance:{remaining:p??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 p=m(u.limit)??1,h=m(u.remaining)??0,b=S(u.resetTime),g=Math.max(0,p-h);s.push({name:"five_hour",label:"5h limit",usedPercent:p>0?Math.round(g/p*1e4)/100:0,resetsAt:b})}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=m(s.total_credits)??0,l=m(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=m(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=[],p=m(l.current_interval_total_count)??0,h=m(l.current_interval_usage_count)??0;return p>0&&u.push({name:"five_hour",label:"5h limit",usedPercent:Math.round(h/p*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=m(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=(m(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 ie(o,e){if(!e.trim())return{provider:"unknown",providerLabel:"Unknown",planName:null,tiers:[],balance:null,success:!1,error:"API key is empty"};const i=F(o);if(i)switch(i.id){case"zhipu":return E(e);case"kimi":return L(e);case"minimax_cn":return w(e,!0);case"minimax_en":return w(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 H(e);case"novita":return R(e)}const t=await J(o,e);if(t)return t;const a=await Q(e);return a||{provider:"unknown",providerLabel:"Unknown",planName:null,tiers:[],balance:null,success:!1,error:`Could not identify provider for base URL: ${o}`}}export{F as detectProvider,ie as queryProviderQuota,$ as readKimiProviderSettings,re as resolveKimiProviderSettings};
1
+ import{createHash as L}from"node:crypto";import{readFileSync as y,writeFileSync as j,mkdirSync as P,rmdirSync as x,statSync as z}from"node:fs";import{homedir as O}from"node:os";import{join as _}from"node:path";const R=new Set(["zhipu","kimi","minimax_cn","minimax_en","deepseek","stepfun","siliconflow_cn","siliconflow_en","openrouter","novita"]),H={zai:"zhipu","z.ai":"zhipu",glm:"zhipu",bigmodel:"zhipu",moonshot:"kimi",minimax:"minimax_cn",siliconflow:"siliconflow_cn"};function U(s){const t=s?.trim().toLowerCase();if(!t)return null;const r=H[t]??t;return R.has(r)?r:null}function B(s){const t=s.toLowerCase();return t.includes("open.bigmodel.cn")||t.includes("bigmodel.cn")||t.includes("api.z.ai")?{id:"zhipu",label:"Zhipu GLM"}:t.includes("api.kimi.com")?{id:"kimi",label:"Kimi"}:t.includes("api.minimaxi.com")?{id:"minimax_cn",label:"MiniMax"}:t.includes("api.minimax.io")?{id:"minimax_en",label:"MiniMax"}:t.includes("api.deepseek.com")?{id:"deepseek",label:"DeepSeek"}:t.includes("api.stepfun.ai")||t.includes("api.stepfun.com")?{id:"stepfun",label:"StepFun"}:t.includes("api.siliconflow.cn")?{id:"siliconflow_cn",label:"SiliconFlow"}:t.includes("api.siliconflow.com")?{id:"siliconflow_en",label:"SiliconFlow"}:t.includes("openrouter.ai")?{id:"openrouter",label:"OpenRouter"}:t.includes("api.novita.ai")?{id:"novita",label:"Novita AI"}:null}function ie(s){const t=s?.trim().toLowerCase()??"";return t?/(^|[\/:_-])(glm|zhipu|zai)([\/:_.-]|$)/.test(t)?{id:"zhipu",label:"Zhipu GLM"}:/(^|[\/:_-])(kimi|moonshot)([\/:_.-]|$)/.test(t)?{id:"kimi",label:"Kimi"}:/(^|[\/:_-])deepseek([\/:_.-]|$)/.test(t)?{id:"deepseek",label:"DeepSeek"}:null:null}const f=1e4;function d(s,t,r){return{provider:s,providerLabel:t,planName:null,tiers:[],balance:null,success:!1,error:r}}function m(s){if(typeof s=="number")return s;if(typeof s=="string"){const t=Number(s);return Number.isFinite(t)?t:null}return null}function w(s){if(!Number.isFinite(s)||s<=0)return null;try{return new Date(s).toISOString()}catch{return null}}function v(s){if(typeof s=="string")return s;if(typeof s=="number"){const t=s<1e12?s*1e3:s;return w(t)}return null}function N(){return(process.env.KIMI_CODE_HOME||"").trim()||_(O(),".kimi-code")}function T(s){const t=s||N(),r=300*1e3;let e;try{const a=_(t,"config.toml"),n=y(a,"utf8"),i=n.indexOf('[providers."managed:kimi-code"]');if(i>=0){const c=n.indexOf(`
2
+ [`,i+1),u=(c>=0?n.slice(i,c):n.slice(i)).match(/^\s*base_url\s*=\s*"([^"]+)"/m);u&&(e=u[1].trim())}}catch{}let o;try{const a=_(t,"credentials","kimi-code.json"),n=JSON.parse(y(a,"utf8")),i=typeof n.access_token=="string"?n.access_token.trim():"",c=typeof n.expires_at=="number"?n.expires_at:null;i&&(!c||Date.now()<c*1e3-r)&&(o=i)}catch{}return{baseUrl:e,apiKey:o}}const D="17e5f671-d194-4dfb-9706-5516cb48c098";function F(){return(process.env.KIMI_CODE_OAUTH_HOST||process.env.KIMI_OAUTH_HOST||"https://auth.kimi.com").replace(/\/+$/,"")}const G=30*1e3;async function se(s){const t=T(s);if(t.apiKey)return t;const r=await C(s);return{baseUrl:t.baseUrl,apiKey:r}}async function C(s){const t=s||N(),r=_(t,"credentials","kimi-code.json");let e;try{e=JSON.parse(y(r,"utf8"))}catch{return}const o=typeof e.refresh_token=="string"?e.refresh_token.trim():"";if(!o)return;const a=_(t,"credentials","kimi-code.lock.lock");if(q(a))try{const n=T(s);if(n.apiKey)return n.apiKey;let i="";try{i=y(_(t,"device_id"),"utf8").trim()}catch{}const c=await fetch(`${F()}/api/oauth/token`,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded",...i?{"X-Msh-Device-Id":i}:{}},body:new URLSearchParams({client_id:D,grant_type:"refresh_token",refresh_token:o}),signal:AbortSignal.timeout(8e3)}),l=await c.json().catch(()=>({})),u=typeof l.access_token=="string"?l.access_token:"";if(c.status!==200||!u)return T(s).apiKey;const p=Number(l.expires_in),h={access_token:u,refresh_token:typeof l.refresh_token=="string"&&l.refresh_token?l.refresh_token:o,expires_at:Math.floor(Date.now()/1e3)+(Number.isFinite(p)&&p>0?p:900),scope:typeof l.scope=="string"&&l.scope?l.scope:e.scope??"kimi-code",token_type:typeof l.token_type=="string"&&l.token_type?l.token_type:"Bearer",expires_in:Number.isFinite(p)&&p>0?p:900};return j(r,JSON.stringify(h),{mode:384}),u}catch{return}finally{try{x(a)}catch{}}}function q(s){try{return P(s),!0}catch{}try{const t=z(s);return Date.now()-t.mtimeMs<G?!1:(x(s),P(s),!0)}catch{return!1}}async function K(s){const t="zhipu",r="Zhipu GLM";try{const e=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(e.status===401||e.status===403)return d(t,r,`Authentication failed (HTTP ${e.status})`);if(!e.ok){const c=await e.text().catch(()=>"");return d(t,r,`API error (HTTP ${e.status}): ${c.slice(0,200)}`)}const o=await e.json();if(o.success===!1)return d(t,r,`API error: ${o.msg??"Unknown error"}`);const a=o.data;if(!a)return d(t,r,"Missing data field");const n=typeof a.level=="string"?a.level:null,i=M(a);return{provider:t,providerLabel:r,planName:n,tiers:i,balance:null,success:!0,error:null}}catch(e){return d(t,r,`Network error: ${e instanceof Error?e.message:String(e)}`)}}function M(s){const t=Array.isArray(s.limits)?s.limits:[],r=[];for(const o of t){if(String(o.type??"").toUpperCase()!=="TOKENS_LIMIT")continue;const n=m(o.percentage)??0,i=m(o.nextResetTime)??Number.MAX_SAFE_INTEGER,c=i===Number.MAX_SAFE_INTEGER?null:w(i);r.push({percentage:n,resetMs:i,resetIso:c})}r.sort((o,a)=>o.resetMs-a.resetMs);const e=[];if(r.length>0){const o=r[0];e.push({name:"five_hour",label:"5h limit",usedPercent:Math.round(o.percentage*100)/100,resetsAt:o.resetIso})}if(r.length>1){const o=r[r.length-1];o.resetMs!==r[0].resetMs&&e.push({name:"weekly_limit",label:"Weekly limit",usedPercent:Math.round(o.percentage*100)/100,resetsAt:o.resetIso})}return e}async function Y(s){const t="kimi",r="Kimi";try{const e=await fetch("https://api.kimi.com/coding/v1/usages",{method:"GET",headers:{Authorization:`Bearer ${s}`,Accept:"application/json"},signal:AbortSignal.timeout(f)});if(e.status===401||e.status===403)return d(t,r,`Authentication failed (HTTP ${e.status})`);if(!e.ok){const c=await e.text().catch(()=>"");return d(t,r,`API error (HTTP ${e.status}): ${c.slice(0,200)}`)}const o=await e.json();if(!Array.isArray(o.limits)&&!o.usage)return d(t,r,"Unexpected quota response schema");const a=[],n=Array.isArray(o.limits)?o.limits:[];for(const c of n){const l=c.detail;if(!l)continue;const u=m(l.limit)??1,p=m(l.remaining)??0,h=v(l.resetTime),b=Math.max(0,u-p);a.push({name:"five_hour",label:"5h limit",usedPercent:u>0?Math.round(b/u*1e4)/100:0,resetsAt:h})}const i=o.usage;if(i){const c=m(i.limit)??1,l=m(i.remaining)??0,u=v(i.resetTime),p=Math.max(0,c-l);a.push({name:"weekly_limit",label:"Weekly limit",usedPercent:c>0?Math.round(p/c*1e4)/100:0,resetsAt:u})}return{provider:t,providerLabel:r,planName:null,tiers:a,balance:null,success:!0,error:null}}catch(e){return d(t,r,`Network error: ${e instanceof Error?e.message:String(e)}`)}}async function I(s,t){const r=t?"minimax_cn":"minimax_en",e="MiniMax",o=t?"api.minimaxi.com":"api.minimax.io";try{const a=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(a.status===401||a.status===403)return d(r,e,`Authentication failed (HTTP ${a.status})`);if(!a.ok){const p=await a.text().catch(()=>"");return d(r,e,`API error (HTTP ${a.status}): ${p.slice(0,200)}`)}const n=await a.json(),i=n.base_resp;if(i&&typeof i.status_code=="number"&&i.status_code!==0)return d(r,e,`API error (code ${i.status_code}): ${i.status_msg??"Unknown"}`);const c=[],u=(Array.isArray(n.model_remains)?n.model_remains:[])[0];if(u){const p=m(u.current_interval_total_count)??0,h=m(u.current_interval_usage_count)??0,b=typeof u.end_time=="number"?u.end_time:null;p>0&&c.push({name:"five_hour",label:"5h limit",usedPercent:Math.round(h/p*1e4)/100,resetsAt:b!==null?w(b):null});const g=m(u.current_weekly_total_count)??0,A=m(u.current_weekly_usage_count)??0,$=typeof u.weekly_end_time=="number"?u.weekly_end_time:null;g>0&&c.push({name:"weekly_limit",label:"Weekly limit",usedPercent:Math.round(A/g*1e4)/100,resetsAt:$!==null?w($):null})}return{provider:r,providerLabel:e,planName:null,tiers:c,balance:null,success:!0,error:null}}catch(a){return d(r,e,`Network error: ${a instanceof Error?a.message:String(a)}`)}}async function Z(s){const t="deepseek",r="DeepSeek";try{const e=await fetch("https://api.deepseek.com/user/balance",{method:"GET",headers:{Authorization:`Bearer ${s}`,Accept:"application/json"},signal:AbortSignal.timeout(f)});if(e.status===401||e.status===403)return d(t,r,`Authentication failed (HTTP ${e.status})`);if(!e.ok){const u=await e.text().catch(()=>"");return d(t,r,`API error (HTTP ${e.status}): ${u.slice(0,200)}`)}const o=await e.json(),a=o.is_available===!0,i=(Array.isArray(o.balance_infos)?o.balance_infos:[])[0];if(!i)return d(t,r,"No balance info returned");const c=String(i.currency??"CNY"),l=m(i.total_balance);return{provider:t,providerLabel:r,planName:null,tiers:[],balance:{remaining:l??0,total:null,used:null,unit:c},success:!0,error:a?null:"Insufficient balance"}}catch(e){return d(t,r,`Network error: ${e instanceof Error?e.message:String(e)}`)}}async function V(s){const t="stepfun",r="StepFun";try{const e=await fetch("https://api.stepfun.com/v1/accounts",{method:"GET",headers:{Authorization:`Bearer ${s}`,Accept:"application/json"},signal:AbortSignal.timeout(f)});if(e.status===401||e.status===403)return d(t,r,`Authentication failed (HTTP ${e.status})`);if(!e.ok){const n=await e.text().catch(()=>"");return d(t,r,`API error (HTTP ${e.status}): ${n.slice(0,200)}`)}const o=await e.json(),a=m(o.balance)??0;return{provider:t,providerLabel:r,planName:null,tiers:[],balance:{remaining:a,total:null,used:null,unit:"CNY"},success:!0,error:null}}catch(e){return d(t,r,`Network error: ${e instanceof Error?e.message:String(e)}`)}}async function E(s,t){const r=t?"siliconflow_cn":"siliconflow_en",e=t?"SiliconFlow":"SiliconFlow (EN)",o=t?"api.siliconflow.cn":"api.siliconflow.com",a=t?"CNY":"USD";try{const n=await fetch(`https://${o}/v1/user/info`,{method:"GET",headers:{Authorization:`Bearer ${s}`,Accept:"application/json"},signal:AbortSignal.timeout(f)});if(n.status===401||n.status===403)return d(r,e,`Authentication failed (HTTP ${n.status})`);if(!n.ok){const u=await n.text().catch(()=>"");return d(r,e,`API error (HTTP ${n.status}): ${u.slice(0,200)}`)}const c=(await n.json()).data;if(!c)return d(r,e,"Missing data field");const l=m(c.totalBalance)??0;return{provider:r,providerLabel:e,planName:null,tiers:[],balance:{remaining:l,total:null,used:null,unit:a},success:!0,error:null}}catch(n){return d(r,e,`Network error: ${n instanceof Error?n.message:String(n)}`)}}async function W(s){const t="openrouter",r="OpenRouter";try{const e=await fetch("https://openrouter.ai/api/v1/credits",{method:"GET",headers:{Authorization:`Bearer ${s}`,Accept:"application/json"},signal:AbortSignal.timeout(f)});if(e.status===401||e.status===403)return d(t,r,`Authentication failed (HTTP ${e.status})`);if(!e.ok){const l=await e.text().catch(()=>"");return d(t,r,`API error (HTTP ${e.status}): ${l.slice(0,200)}`)}const o=await e.json(),a=o.data??o,n=m(a.total_credits)??0,i=m(a.total_usage)??0,c=n-i;return{provider:t,providerLabel:r,planName:null,tiers:[],balance:{remaining:c,total:n,used:i,unit:"USD"},success:!0,error:null}}catch(e){return d(t,r,`Network error: ${e instanceof Error?e.message:String(e)}`)}}async function X(s){const t="novita",r="Novita AI";try{const e=await fetch("https://api.novita.ai/v3/user/balance",{method:"GET",headers:{Authorization:`Bearer ${s}`,Accept:"application/json"},signal:AbortSignal.timeout(f)});if(e.status===401||e.status===403)return d(t,r,`Authentication failed (HTTP ${e.status})`);if(!e.ok){const n=await e.text().catch(()=>"");return d(t,r,`API error (HTTP ${e.status}): ${n.slice(0,200)}`)}const o=await e.json(),a=(m(o.availableBalance)??0)/1e4;return{provider:t,providerLabel:r,planName:null,tiers:[],balance:{remaining:a,total:null,used:null,unit:"USD"},success:!0,error:null}}catch(e){return d(t,r,`Network error: ${e instanceof Error?e.message:String(e)}`)}}const k=new Map,J=300*1e3;async function Q(s,t){const r=L("sha256").update(t).digest("hex").slice(0,16),e=`${s.trim().replace(/\/+$/,"").toLowerCase()}|${r}`,o=k.get(e);if(o&&Date.now()-o.timestamp<=J){const i=await S(o.providerId,s,t);if(i)return i;k.delete(e)}else o&&k.delete(e);const a=["zhipu","deepseek","kimi","openrouter","stepfun","minimax_cn","siliconflow_cn","novita"],n=await Promise.allSettled(a.map(i=>S(i,s,t)));for(let i=0;i<n.length;i++){const c=n[i];if(c.status==="fulfilled"&&c.value?.success)return k.set(e,{providerId:a[i],timestamp:Date.now()}),c.value}return null}async function S(s,t,r){const e=t.replace(/\/+$/,""),o={Authorization:`Bearer ${r}`,Accept:"application/json"};try{switch(s){case"zhipu":{const a=await fetch(`${e}/api/monitor/usage/quota/limit`,{method:"GET",headers:{...o,Authorization:r,"Content-Type":"application/json","Accept-Language":"en-US,en"},signal:AbortSignal.timeout(f)});if(!a.ok)return null;const n=await a.json();if(n.success===!1)return null;const i=n.data;if(!i)return null;const c=typeof i.level=="string"?i.level:null,l=M(i);return{provider:"zhipu",providerLabel:"Zhipu GLM",planName:c,tiers:l,balance:null,success:!0,error:null}}case"deepseek":{const a=await fetch(`${e}/user/balance`,{method:"GET",headers:o,signal:AbortSignal.timeout(f)});if(!a.ok)return null;const n=await a.json(),i=n.is_available===!0,l=(Array.isArray(n.balance_infos)?n.balance_infos:[])[0];if(!l)return null;const u=String(l.currency??"CNY"),p=m(l.total_balance);return{provider:"deepseek",providerLabel:"DeepSeek",planName:null,tiers:[],balance:{remaining:p??0,total:null,used:null,unit:u},success:!0,error:i?null:"Insufficient balance"}}case"kimi":{const a=await fetch(`${e}/coding/v1/usages`,{method:"GET",headers:o,signal:AbortSignal.timeout(f)});if(!a.ok)return null;const n=await a.json();if(!Array.isArray(n.limits)&&!n.usage)return null;const i=[],c=Array.isArray(n.limits)?n.limits:[];for(const u of c){const p=u.detail;if(!p)continue;const h=m(p.limit)??1,b=m(p.remaining)??0,g=v(p.resetTime),A=Math.max(0,h-b);i.push({name:"five_hour",label:"5h limit",usedPercent:h>0?Math.round(A/h*1e4)/100:0,resetsAt:g})}const l=n.usage;if(l){const u=m(l.limit)??1,p=m(l.remaining)??0,h=v(l.resetTime),b=Math.max(0,u-p);i.push({name:"weekly_limit",label:"Weekly limit",usedPercent:u>0?Math.round(b/u*1e4)/100:0,resetsAt:h})}return{provider:"kimi",providerLabel:"Kimi",planName:null,tiers:i,balance:null,success:!0,error:null}}case"openrouter":{const a=await fetch(`${e}/api/v1/credits`,{method:"GET",headers:o,signal:AbortSignal.timeout(f)});if(!a.ok)return null;const n=await a.json(),i=n.data??n;if(i.total_credits===void 0&&i.total_usage===void 0)return null;const c=m(i.total_credits)??0,l=m(i.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 a=await fetch(`${e}/v1/accounts`,{method:"GET",headers:o,signal:AbortSignal.timeout(f)});if(!a.ok)return null;const n=await a.json();if(n.balance===void 0)return null;const i=m(n.balance)??0;return{provider:"stepfun",providerLabel:"StepFun",planName:null,tiers:[],balance:{remaining:i,total:null,used:null,unit:"CNY"},success:!0,error:null}}case"minimax_cn":case"minimax_en":{const a=await fetch(`${e}/v1/api/openplatform/coding_plan/remains`,{method:"GET",headers:o,signal:AbortSignal.timeout(f)});if(!a.ok)return null;const n=await a.json(),i=n.base_resp;if(i&&typeof i.status_code=="number"&&i.status_code!==0)return null;const l=(Array.isArray(n.model_remains)?n.model_remains:[])[0];if(!l)return null;const u=[],p=m(l.current_interval_total_count)??0,h=m(l.current_interval_usage_count)??0;return p>0&&u.push({name:"five_hour",label:"5h limit",usedPercent:Math.round(h/p*1e4)/100,resetsAt:null}),{provider:s,providerLabel:"MiniMax",planName:null,tiers:u,balance:null,success:!0,error:null}}case"siliconflow_cn":case"siliconflow_en":{const a=await fetch(`${e}/v1/user/info`,{method:"GET",headers:o,signal:AbortSignal.timeout(f)});if(!a.ok)return null;const i=(await a.json()).data;if(!i)return null;const c=m(i.totalBalance)??0;return{provider:s,providerLabel:"SiliconFlow",planName:null,tiers:[],balance:{remaining:c,total:null,used:null,unit:"CNY"},success:!0,error:null}}case"novita":{const a=await fetch(`${e}/v3/user/balance`,{method:"GET",headers:o,signal:AbortSignal.timeout(f)});if(!a.ok)return null;const n=await a.json();if(n.availableBalance===void 0)return null;const i=(m(n.availableBalance)??0)/1e4;return{provider:"novita",providerLabel:"Novita AI",planName:null,tiers:[],balance:{remaining:i,total:null,used:null,unit:"USD"},success:!0,error:null}}default:return null}}catch{return null}}async function ae(s,t,r){if(!t.trim())return{provider:"unknown",providerLabel:"Unknown",planName:null,tiers:[],balance:null,success:!1,error:"API key is empty"};const e=U(r),o=B(s);if(o)switch(o.id){case"zhipu":return K(t);case"kimi":return Y(t);case"minimax_cn":return I(t,!0);case"minimax_en":return I(t,!1);case"deepseek":return Z(t);case"stepfun":return V(t);case"siliconflow_cn":return E(t,!0);case"siliconflow_en":return E(t,!1);case"openrouter":return W(t);case"novita":return X(t)}if(e){const n=await S(e,s,t);return n||d(e,e,`Quota API unavailable through base URL: ${s}`)}const a=await Q(s,t);return a||{provider:"unknown",providerLabel:"Unknown",planName:null,tiers:[],balance:null,success:!1,error:`Could not identify provider for base URL: ${s}`}}export{B as detectProvider,ie as detectProviderFromModel,U as normalizeProviderId,ae as queryProviderQuota,T as readKimiProviderSettings,se as resolveKimiProviderSettings};
@@ -0,0 +1 @@
1
+ import{createHash as f}from"node:crypto";import{detectProvider as c,normalizeProviderId as l,queryProviderQuota as w}from"./providers.js";class y{ttlMs;now;cache=new Map;inFlight=new Map;constructor(e={}){this.ttlMs=e.ttlMs??6e4,this.now=e.now??Date.now}cacheKey(e){const t=l(e.providerId)??c(e.baseUrl)?.id??"unknown",i=p(e).toLowerCase(),a=e.accountKey?.trim()||"default",r=f("sha256").update(e.apiKey).digest("hex").slice(0,16);return`${t}|${i}|${a}|${r}`}async query(e,t={}){const i=this.cacheKey(e),a=this.now(),r=this.cache.get(i);if(!t.fresh&&r&&a-r.sampledAt<=this.ttlMs)return{...r,cached:!0};const s=this.inFlight.get(i);if(s)return{...await s,cached:!1};const o=(async()=>{const u=l(e.providerId)??c(e.baseUrl)?.id??void 0,h=await w(p(e),e.apiKey,u),d={quota:h,sampledAt:this.now(),cacheKey:i};return h.success&&this.cache.set(i,d),d})();this.inFlight.set(i,o);try{return{...await o,cached:!1}}finally{this.inFlight.delete(i)}}invalidate(e){if(!e){this.cache.clear();return}this.cache.delete(this.cacheKey(e))}}function p(n){const e=n.quotaBaseUrl?.trim();if(e)return e.replace(/\/+$/,"");const t=n.baseUrl.trim().replace(/\/+$/,"");if(c(t))return t;try{return new URL(t).origin}catch{return t}}const g=new y;export{y as ProviderQuotaService,p as resolveQuotaBaseUrl,g as sharedProviderQuotaService};
package/dist/manager.js CHANGED
@@ -1,2 +1,2 @@
1
- import{readFileSync as K,readdirSync as G,writeFileSync as z}from"node:fs";import{join as A}from"node:path";import{AgentInstance as T}from"./bridge/bridge.js";import{buildSharedInstanceConfig as J,diffSharedOwners as V,sharedInstanceKey as X,SharedOwnersCache as Y}from"./manager-share-config.js";import{isAgentDeletedError as D}from"./core/aibot/errors.js";import{GRIX_PATHS as w,log as l}from"./core/log/index.js";import{resolveClientVersion as Z}from"./core/util/client-version.js";import{UpgradeChecker as ee}from"./core/upgrade/upgrade-checker.js";import{SkillSyncer as te}from"./core/skill-sync/skill-syncer.js";import{AgentGlobalConfigStore as ne}from"./core/persistence/agent-global-config-store.js";import{resolveSkillScanMode as ae}from"./adapter/claude/skill-scanner.js";import{buildWireSkills as re}from"./core/skill-sync/sync-state.js";import{buildReportedSkills as se,logDefaultSkillsCheck as oe,cleanupProjectedSkills as P}from"./default-skills/index.js";import{supportsDirectProviderConfig as ie}from"./core/config/provider-env.js";import{resolveCopilotCommand as O}from"./core/runtime/copilot-resolve.js";import{resolveCursorCommand as H}from"./core/runtime/cursor-resolve.js";import{getCliVersion as ce,resolveCliPath as le}from"./core/util/cli-probe.js";import{AgentInstaller as de}from"./core/installer/installer.js";import{reportInstallFailure as pe}from"./core/observability/sentry.js";const me=8e3;function ue(){const n=O();return[{clientType:"openclaw",command:"openclaw"},{clientType:"claude",command:"claude"},{clientType:"codex",command:"codex"},{clientType:"qwen",command:"qwen"},{clientType:"hermes",command:"hermes"},{clientType:"reasonix",command:"reasonix"},{clientType:"codewhale",command:"codewhale"},{clientType:"opencode",command:"opencode"},{clientType:"cursor",command:H()},{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 ge(){return Promise.all(ue().map(async n=>{const e=await le(n.command);if(!e)return{client_type:n.clientType,command:n.command,installed:!1,path:null,version:null};const t=await ce(e,n.versionArgs??["--version"]);return{client_type:n.clientType,command:n.command,installed:!0,path:e,version:t.version,error:t.error}}))}function he(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:H()};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"],options:{initial_mode:"yolo"},enableSessionBinding:!0};case"opencode":return{adapterType:"opencode",command:"opencode",args:["serve"],enableSessionBinding:!0};case"copilot":{const e=O();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 fe(n,e){return A(w.data,`session-bindings-${q(n,e)}.json`)}function _e(n,e){return A(w.data,`active-events-${q(n,e)}.json`)}function ye(...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 we(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=Z(),r=String(n.client_type??"").trim().toLowerCase(),s=he(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,g=a==="acp",m=r==="qwen",_={...s.options??{}},S=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,u=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,C=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,R=g&&!m?{localActions:["exec_approve","exec_reject","permission_approve","permission_reject","session_control","set_model","set_mode","file_list","create_folder",o]}:null,B=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,F=a==="codex"||a==="claude"||r==="gemini"?["session_control","set_model","set_mode"]:void 0,U=[o,p];g&&_.raw_transport===void 0&&(_.raw_transport=r==="gemini");const L=`${r}/base`,I=ae(a,r),Q=I==="kiro"?void 0:process.cwd(),E=se({mode:I,projectDir:Q}),M=E.filter(x=>x.source==="connector").map(x=>x.name);M.length>0&&!e.quiet&&l.info("manager",`[${n.name}] injecting connector skills: [${M.join(", ")}]`);const j=re(E,w.skills);let W=j.length>0?j: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??u?.adapterHint??y?.adapterHint??C?.adapterHint??$?.adapterHint??L,capabilities:S?.capabilities??v?.capabilities??h?.capabilities??u?.capabilities??y?.capabilities??C?.capabilities??$?.capabilities??f?.capabilities??["stream_chunk","local_action_v1","connector_upgrade"],localActions:ye(S?.localActions??v?.localActions??d?.localActions??h?.localActions??u?.localActions??y?.localActions??C?.localActions??$?.localActions??f?.localActions??B?.localActions??R?.localActions??["exec_approve","exec_reject"],F,U,["connector_rollback","connector_upgrade_push",c,"configure_gateway_provider","skill_upload"]),skills:W},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:fe(n.name,n.agent_id),activeEventStorePath:_e(n.name,n.agent_id),...s.enableSessionBinding||g?{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:we(a,n.event_queue),logDir:w.log,providerBaseUrl:n.provider_base_url?.trim()||void 0,providerApiKey:n.provider_api_key?.trim()||void 0}}function Se(){const n=process.env.GRIX_AGENT_STARTUP_WAIT_MS,e=Number(n);return Number.isFinite(e)&&e>=500?Math.floor(e):me}function N(n){const e=[],t=[],r=[],s=G(n).filter(i=>i.endsWith(".json")).sort();for(const i of s)try{const o=JSON.parse(K(A(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 k(n){if(n===null||typeof n!="object")return JSON.stringify(n)??"null";if(Array.isArray(n))return`[${n.map(k).join(",")}]`;const e=n;return`{${Object.keys(e).sort().map(r=>`${JSON.stringify(r)}:${k(e[r])}`).join(",")}}`}function Ae(n,e){return k(n)===k(e)}class Le{instances=[];configMap=new Map;rawConfigMap=new Map;reloadChain=Promise.resolve();sharedInstances=new Map;shareSyncChains=new Map;stopping=!1;deletedAgentCleanups=new Set;upgradeChecker=null;skillSyncer=null;globalConfigStore;configDir=w.config;sharedOwnersCache=new Y(w.data);installer=new de;async start(e){const t=e??w.config;this.configDir=t,l.info("manager",`Loading configs from ${t}`),oe(),P(),this.globalConfigStore=new ne(A(w.data,"agent-global-configs.json")),this.globalConfigStore.load();const{entries:r,fileErrors:s,skipped:i}=N(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:g}of r)try{const m=b(a);o.push({config:m,entry:a,file:g}),l.info("manager",`Loaded ${m.name} (${m.adapterType??"acp"}) from ${g}`)}catch(m){const _=typeof a?.name=="string"?a.name:"<unknown>";l.error("manager",`Invalid agent config in ${g} (name=${_}): ${m}`),p++}let c=0;if(o.length>0){const a=Se();l.info("manager",`Starting ${o.length} agent(s), startup wait=${a}ms`);const g=()=>this.upgradeChecker?.triggerCheck(),m=d=>{this.instances=this.instances.filter(f=>f!==d)},_=o.map(({config:d,entry:f,file:h})=>{const u=new T(d,this.globalConfigStore);return u.setUpgradeTrigger(g),u.setShareSetHandler(y=>this.onShareSet(d,y)),u.setAgentDeletedHandler(()=>this.onAgentDeletedByPlatform(d.name)),u.setSkillSyncHandler(()=>this.skillSyncer?.triggerSync()),this.instances.push(u),this.configMap.set(d.name,d),this.rawConfigMap.set(d.name,{entry:f,file:h}),{config:d,instance:u,startPromise:u.start()}}),S=await Promise.all(_.map(async d=>{const f=await new Promise(h=>{let u=!1;const y=setTimeout(()=>{u||(u=!0,h({kind:"timeout"}))},a);d.startPromise.then(()=>{u||(u=!0,clearTimeout(y),h({kind:"started"}))}).catch(v=>{u||(u=!0,clearTimeout(y),h({kind:"failed",error:v}))})});return{task:d,outcome:f}}));for(const{task:d,outcome:f}of S){if(f.kind==="started"){this.restoreCachedSharedInstances(d.config);continue}if(f.kind==="failed"){if(D(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(D(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 ee(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()),o.length>0&&(this.skillSyncer=new te(o.map(({config:a})=>({apiKey:a.aibot.apiKey,wsUrl:a.aibot.url})),w.skills),await this.skillSyncer.start())}async stop(){l.info("manager","Stopping all agents..."),this.stopping=!0,this.upgradeChecker?.stop(),this.skillSyncer?.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=[],P(),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}=V(e.name,this.sharedInstances.keys(),t);for(const i of r){if(this.stopping)break;const o=X(e.name,i);try{const p=J(e,i),c=new T(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 T(e,this.globalConfigStore);s.setUpgradeTrigger(()=>this.upgradeChecker?.triggerCheck()),s.setShareSetHandler(i=>this.onShareSet(e,i)),s.setAgentDeletedHandler(()=>this.onAgentDeletedByPlatform(e.name)),s.setSkillSyncHandler(()=>this.skillSyncer?.triggerSync()),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=A(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}:{}})}z(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&&ie(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}=N(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 g=c?.name,m=typeof g=="string"&&g.trim()?g:"<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 g=this.rawConfigMap.get(c);if(g)if(Ae(g.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 ve(this.instances,e)}async probeOne(e,t={}){return be(this.instances,e,t)}listInstallable(){return this.installer.listInstallable()}async installAgent(e){const t=await this.installer.install(e);return pe(t),t}getInstallProgress(e){return this.installer.getProgress(e)??null}}async function ve(n,e){const t=e.concurrency??4,r=Date.now(),s=new Array(n.length);await new Promise(a=>{let g=0,m=0;const _=n.length;if(_===0){a();return}function S(h){const u=n[h];u.probe(e).then(y=>{s[h]=y,d()},y=>{s[h]={agent_name:u.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++,g<_?S(g++):m===_&&a()}const f=Math.min(t,_);for(let h=0;h<f;h++)S(g++)});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 ge();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 be(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{Le as Manager,ge as probeInstalledClientCommands,ve as probeInstances,be as probeOneInstance};
1
+ import{readFileSync as K,readdirSync as G,writeFileSync as z}from"node:fs";import{join as v}from"node:path";import{AgentInstance as T}from"./bridge/bridge.js";import{buildSharedInstanceConfig as J,diffSharedOwners as V,sharedInstanceKey as X,SharedOwnersCache as Y}from"./manager-share-config.js";import{isAgentDeletedError as D}from"./core/aibot/errors.js";import{GRIX_PATHS as w,log as l}from"./core/log/index.js";import{resolveClientVersion as Z}from"./core/util/client-version.js";import{UpgradeChecker as ee}from"./core/upgrade/upgrade-checker.js";import{SkillSyncer as te}from"./core/skill-sync/skill-syncer.js";import{AgentGlobalConfigStore as ne}from"./core/persistence/agent-global-config-store.js";import{resolveSkillScanMode as ae}from"./adapter/claude/skill-scanner.js";import{buildWireSkills as re}from"./core/skill-sync/sync-state.js";import{buildReportedSkills as se,logDefaultSkillsCheck as oe,cleanupProjectedSkills as P}from"./default-skills/index.js";import{supportsDirectProviderConfig as ie}from"./core/config/provider-env.js";import{resolveCopilotCommand as O}from"./core/runtime/copilot-resolve.js";import{resolveCursorCommand as q}from"./core/runtime/cursor-resolve.js";import{getCliVersion as ce,resolveCliPath as le}from"./core/util/cli-probe.js";import{AgentInstaller as de}from"./core/installer/installer.js";import{reportInstallFailure as pe}from"./core/observability/sentry.js";const me=8e3;function ue(){const t=O();return[{clientType:"openclaw",command:"openclaw"},{clientType:"claude",command:"claude"},{clientType:"codex",command:"codex"},{clientType:"qwen",command:"qwen"},{clientType:"hermes",command:"hermes"},{clientType:"reasonix",command:"reasonix"},{clientType:"codewhale",command:"codewhale"},{clientType:"opencode",command:"opencode"},{clientType:"cursor",command:q()},{clientType:"pi",command:"pi"},{clientType:"openhuman",command:"openhuman-core"},{clientType:"kiro",command:"kiro-cli"},{clientType:"kimi",command:"kimi"},{clientType:"copilot",command:t.command},{clientType:"agy",command:"agy"}]}async function ge(){return Promise.all(ue().map(async t=>{const e=await le(t.command);if(!e)return{client_type:t.clientType,command:t.command,installed:!1,path:null,version:null};const n=await ce(e,t.versionArgs??["--version"]);return{client_type:t.clientType,command:t.command,installed:!0,path:e,version:n.version,error:n.error}}))}function he(t){switch(t){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:q()};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"],options:{initial_mode:"yolo"},enableSessionBinding:!0};case"opencode":return{adapterType:"opencode",command:"opencode",args:["serve"],enableSessionBinding:!0};case"copilot":{const e=O();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: ${t}`)}}function H(t,e){return String(t??"").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 fe(t,e){return v(w.data,`session-bindings-${H(t,e)}.json`)}function _e(t,e){return v(w.data,`active-events-${H(t,e)}.json`)}function ye(...t){const e=[],n=new Set;for(const r of t)for(const s of r??[]){const i=String(s??"").trim(),o=i.toLowerCase();!i||n.has(o)||(n.add(o),e.push(i))}return e.length>0?e:void 0}function we(t,e){const n={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=n[t]??n.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(t,e={}){const n=Z(),r=String(t.client_type??"").trim().toLowerCase(),s=he(r),i=String(t.ws_url??"").trim(),o="get_session_usage",p="get_rate_limits",c="get_agent_global_config";if(!t.name?.trim())throw new Error("agent name is required");if(!i)throw new Error(`agent ${t.name}: ws_url is required`);if(!t.agent_id?.trim())throw new Error(`agent ${t.name}: agent_id is required`);if(!t.api_key?.trim())throw new Error(`agent ${t.name}: api_key is required`);const a=s.adapterType,g=a==="acp",m=r==="qwen",_={...s.options??{}},S=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,u=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,A=a==="codewhale"?{capabilities:["stream_chunk","local_action_v1"],localActions:["session_control","set_model","file_list","create_folder",o]}:null,C=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,R=g&&!m?{localActions:["exec_approve","exec_reject","permission_approve","permission_reject","session_control","set_model","set_mode","file_list","create_folder",o]}:null,B=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,U=a==="codex"||a==="claude"||r==="gemini"?["session_control","set_model","set_mode"]:void 0,F=[o,p];g&&_.raw_transport===void 0&&(_.raw_transport=r==="gemini");const L=`${r}/base`,I=ae(a,r),Q=I==="kiro"?void 0:process.cwd(),E=se({mode:I,projectDir:Q}),M=E.filter(x=>x.source==="connector").map(x=>x.name);M.length>0&&!e.quiet&&l.info("manager",`[${t.name}] injecting connector skills: [${M.join(", ")}]`);const j=re(E,w.skills);let W=j.length>0?j:void 0;return{name:t.name,adapterType:a,aibot:{url:i,agentId:t.agent_id,apiKey:t.api_key,clientType:r,clientVersion:n,adapterHint:s.adapterHint??f?.adapterHint??h?.adapterHint??u?.adapterHint??y?.adapterHint??C?.adapterHint??$?.adapterHint??L,capabilities:S?.capabilities??A?.capabilities??h?.capabilities??u?.capabilities??y?.capabilities??C?.capabilities??$?.capabilities??f?.capabilities??["stream_chunk","local_action_v1","connector_upgrade"],localActions:ye(S?.localActions??A?.localActions??d?.localActions??h?.localActions??u?.localActions??y?.localActions??C?.localActions??$?.localActions??f?.localActions??B?.localActions??R?.localActions??["exec_approve","exec_reject"],U,F,["connector_rollback","connector_upgrade_push",c,"configure_gateway_provider","skill_upload"]),skills:W},agent:{command:s.command,args:[...s.args??[],...t.args??[]],env:t.env,clientType:r,...t.provider?{provider:{...t.provider.provider_id?.trim()?{providerId:t.provider.provider_id.trim()}:{},...t.provider.base_url?.trim()?{baseUrl:t.provider.base_url.trim()}:{},...t.provider.quota_base_url?.trim()?{quotaBaseUrl:t.provider.quota_base_url.trim()}:{},...t.provider.api_key?.trim()?{apiKey:t.provider.api_key.trim()}:{},...t.provider.model?.trim()?{model:t.provider.model.trim()}:{}}}:{}},adapterOptions:_,acpAuthMethod:_.auth_method,acpInitialMode:_.initial_mode,acpMcpTools:_.acp_mcp_tools,promptTimeoutMs:t.prompt_timeout_ms,bindingsPath:fe(t.name,t.agent_id),activeEventStorePath:_e(t.name,t.agent_id),...s.enableSessionBinding||g?{enableSessionBinding:!0}:{},...s.autoInjectArgs||r==="reasonix"&&t.provider?.model?.trim()?{autoInjectArgs:{...s.autoInjectArgs??{},...r==="reasonix"&&t.provider?.model?.trim()?{model:t.provider.model.trim()}:{}}}:{},poolMaxSize:t.pool?.maxSize,poolIdleTimeoutMs:t.pool?.idleTimeoutMs,eventQueue:we(a,t.event_queue),logDir:w.log,providerBaseUrl:t.provider_base_url?.trim()||void 0,providerApiKey:t.provider_api_key?.trim()||void 0}}function Se(){const t=process.env.GRIX_AGENT_STARTUP_WAIT_MS,e=Number(t);return Number.isFinite(e)&&e>=500?Math.floor(e):me}function N(t){const e=[],n=[],r=[],s=G(t).filter(i=>i.endsWith(".json")).sort();for(const i of s)try{const o=JSON.parse(K(v(t,i),"utf-8"));if(o===null||typeof o!="object"||Array.isArray(o)||!("agents"in o)){r.push(i);continue}if(!Array.isArray(o.agents)){n.push({file:i,error:"agents is not an array"});continue}if(o.agents.length===0){n.push({file:i,error:"agents array is empty"});continue}for(const p of o.agents)e.push({entry:p,file:i})}catch(o){n.push({file:i,error:o instanceof Error?o.message:String(o)})}return{entries:e,fileErrors:n,skipped:r}}function k(t){if(t===null||typeof t!="object")return JSON.stringify(t)??"null";if(Array.isArray(t))return`[${t.map(k).join(",")}]`;const e=t;return`{${Object.keys(e).sort().map(r=>`${JSON.stringify(r)}:${k(e[r])}`).join(",")}}`}function ve(t,e){return k(t)===k(e)}class Le{instances=[];configMap=new Map;rawConfigMap=new Map;reloadChain=Promise.resolve();sharedInstances=new Map;shareSyncChains=new Map;stopping=!1;deletedAgentCleanups=new Set;upgradeChecker=null;skillSyncer=null;globalConfigStore;configDir=w.config;sharedOwnersCache=new Y(w.data);installer=new de;async start(e){const n=e??w.config;this.configDir=n,l.info("manager",`Loading configs from ${n}`),oe(),P(),this.globalConfigStore=new ne(v(w.data,"agent-global-configs.json")),this.globalConfigStore.load();const{entries:r,fileErrors:s,skipped:i}=N(n);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 ${n}`);const o=[];let p=s.length;for(const{entry:a,file:g}of r)try{const m=b(a);o.push({config:m,entry:a,file:g}),l.info("manager",`Loaded ${m.name} (${m.adapterType??"acp"}) from ${g}`)}catch(m){const _=typeof a?.name=="string"?a.name:"<unknown>";l.error("manager",`Invalid agent config in ${g} (name=${_}): ${m}`),p++}let c=0;if(o.length>0){const a=Se();l.info("manager",`Starting ${o.length} agent(s), startup wait=${a}ms`);const g=()=>this.upgradeChecker?.triggerCheck(),m=d=>{this.instances=this.instances.filter(f=>f!==d)},_=o.map(({config:d,entry:f,file:h})=>{const u=new T(d,this.globalConfigStore);return u.setUpgradeTrigger(g),u.setShareSetHandler(y=>this.onShareSet(d,y)),u.setAgentDeletedHandler(()=>this.onAgentDeletedByPlatform(d.name)),u.setSkillSyncHandler(()=>this.skillSyncer?.triggerSync()),this.instances.push(u),this.configMap.set(d.name,d),this.rawConfigMap.set(d.name,{entry:f,file:h}),{config:d,instance:u,startPromise:u.start()}}),S=await Promise.all(_.map(async d=>{const f=await new Promise(h=>{let u=!1;const y=setTimeout(()=>{u||(u=!0,h({kind:"timeout"}))},a);d.startPromise.then(()=>{u||(u=!0,clearTimeout(y),h({kind:"started"}))}).catch(A=>{u||(u=!0,clearTimeout(y),h({kind:"failed",error:A}))})});return{task:d,outcome:f}}));for(const{task:d,outcome:f}of S){if(f.kind==="started"){this.restoreCachedSharedInstances(d.config);continue}if(f.kind==="failed"){if(D(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(D(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 ee(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()),o.length>0&&(this.skillSyncer=new te(o.map(({config:a})=>({apiKey:a.aibot.apiKey,wsUrl:a.aibot.url})),w.skills),await this.skillSyncer.start())}async stop(){l.info("manager","Stopping all agents..."),this.stopping=!0,this.upgradeChecker?.stop(),this.skillSyncer?.stop(),await Promise.allSettled([...this.shareSyncChains.values()]),this.shareSyncChains.clear();const e=[...this.sharedInstances.values()];this.sharedInstances.clear(),await Promise.allSettled([...this.instances.map(n=>n.stop()),...e.map(n=>n.stop())]),await this.globalConfigStore?.flush(),this.instances=[],P(),l.info("manager","All stopped")}onShareSet(e,n){this.sharedOwnersCache.save(e.name,n);const s=(this.shareSyncChains.get(e.name)??Promise.resolve()).catch(()=>{}).then(()=>this.syncSharedInstances(e,n));this.shareSyncChains.set(e.name,s)}async syncSharedInstances(e,n){if(this.stopping)return;const{toAdd:r,toRemove:s}=V(e.name,this.sharedInstances.keys(),n);for(const i of r){if(this.stopping)break;const o=X(e.name,i);try{const p=J(e,i),c=new T(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 n=this.sharedOwnersCache.load(e.name);n.length!==0&&(l.info("manager",`restoring ${n.length} cached shared instance(s) for ${e.name}`),this.onShareSet(e,n))}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,n){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,n)}async startInstanceFromConfig(e,n,r){const s=new T(e,this.globalConfigStore);s.setUpgradeTrigger(()=>this.upgradeChecker?.triggerCheck()),s.setShareSetHandler(i=>this.onShareSet(e,i)),s.setAgentDeletedHandler(()=>this.onAgentDeletedByPlatform(e.name)),s.setSkillSyncHandler(()=>this.skillSyncer?.triggerSync()),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:n,file:r}),this.relayEnvSettledHandler?.()}async removeAgent(e){if(!this.instances.some(n=>n.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,n){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}`)));n?.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(n){l.error("manager",`cleanup for deleted agent "${e}" failed: ${n}`)}finally{this.deletedAgentCleanups.delete(e)}})())}persistAgentsConfig(){const e=v(this.configDir,"agents.json");try{const n=[];for(const[,s]of this.configMap){const i=this.rawConfigMap.get(s.name)?.entry.provider;n.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}:{}})}z(e,JSON.stringify({agents:n},null,4)+`
2
+ `,"utf-8")}catch(n){l.error("manager",`Failed to persist agents config: ${n}`)}}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 n=this.instances.find(r=>r.name===e);return n?(n.markRelayEnvStale(),!0):!1}relayEnvSettledHandler;setRelayEnvSettledHandler(e){this.relayEnvSettledHandler=e}async restartAgent(e){const n=this.rawConfigMap.get(e);if(!n)throw Object.assign(new Error(`Agent "${e}" not found`),{code:"NOT_FOUND"});await this.replaceInstance(e,n.entry,n.file),l.info("manager",`Restarted agent: ${e}`)}async setAgentProvider(e,n){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:n};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&&ie(r.entry.client_type)).map(([r])=>r),n=[];for(const r of e)try{await this.setAgentProvider(r,void 0),n.push(r)}catch(s){l.warn("manager",`Failed to clear provider config for "${r}": ${s instanceof Error?s.message:String(s)}`)}return n}async replaceInstance(e,n,r){const s=b(n);await this.removeAgentInternal(e,{keepShareCache:!0}),await this.startInstanceFromConfig(s,n,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:n,skipped:r}=N(this.configDir);for(const c of r)l.info("manager",`reload: skipped ${c}: not an agent config (no "agents" key)`);if(n.length>0){const c=n.map(a=>`${a.file}: ${a.error}`).join("; ");throw Object.assign(new Error(`reload aborted: ${n.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 g=c?.name,m=typeof g=="string"&&g.trim()?g:"<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 g=this.rawConfigMap.get(c);if(g)if(ve(g.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 Ae(this.instances,e)}async probeOne(e,n={}){return be(this.instances,e,n)}listInstallable(){return this.installer.listInstallable()}async installAgent(e){const n=await this.installer.install(e);return pe(n),n}getInstallProgress(e){return this.installer.getProgress(e)??null}}async function Ae(t,e){const n=e.concurrency??4,r=Date.now(),s=new Array(t.length);await new Promise(a=>{let g=0,m=0;const _=t.length;if(_===0){a();return}function S(h){const u=t[h];u.probe(e).then(y=>{s[h]=y,d()},y=>{s[h]={agent_name:u.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++,g<_?S(g++):m===_&&a()}const f=Math.min(n,_);for(let h=0;h<f;h++)S(g++)});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 ge();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 be(t,e,n){const r=t.find(s=>s.name===e);if(!r)throw Object.assign(new Error(`Agent "${e}" not found`),{code:"NOT_FOUND"});return r.probe(n)}export{Le as Manager,ge as probeInstalledClientCommands,Ae as probeInstances,be as probeOneInstance};
@@ -1 +1 @@
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};
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};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "grix-connector",
3
- "version": "3.11.0",
3
+ "version": "3.12.0",
4
4
  "description": "Connect local AI coding agents (Claude, Codex, Gemini, Qwen, DeepSeek, Cursor, OpenCode, Pi, OpenHuman, Reasonix) to the Grix scheduling platform. Also serves as an OpenClaw plugin for Grix channel transport.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",