grix-connector 4.5.0 → 4.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/assets/dsh-bridge/grix-dsh-bridge-4.6.0.tgz +0 -0
- package/dist/assets/dsh-bridge/manifest.json +6 -6
- package/dist/bridge/bridge.js +4 -4
- package/dist/bridge/local-action-toolbar.js +1 -1
- package/dist/bridge/toolbar-meta.js +1 -1
- package/dist/bridge/toolbar-selection.js +1 -0
- package/dist/core/installer/npm-registry.js +1 -1
- package/dist/core/mcp/tools.js +1 -1
- package/dist/core/upgrade/npm-upgrader.js +2 -2
- package/dist/core/upgrade/upgrade-checker.js +1 -1
- package/dist/core/upgrade/version-store.js +2 -2
- package/dist/default-skills/grix-scheduled-trigger/SKILL.md +162 -0
- package/dist/grix.js +7 -7
- package/dist/launcher.mjs +98 -35
- package/dist/manager.js +1 -1
- package/dist/mcp/stream-http/security.js +1 -1
- package/openclaw-plugin/index.js +70 -0
- package/package.json +1 -1
- package/scripts/launcher.mjs +98 -35
- package/dist/assets/dsh-bridge/grix-dsh-bridge-4.5.0.tgz +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{SESSION_MODE_IDS as i}from"../adapter/claude/protocol-contract.js";import{normalizeClaudeEffort as g}from"../core/config/claude-direct-env.js";const s={read:(e,o)=>e.getModelId(o),write:(e,o,d)=>e.setModelId(o,d)},c={read:(e,o)=>e.getModeId(o),write:(e,o,d)=>e.setModeId(o,d)},m={model:{globalKey:"modelId"},mode:{globalKey:"acpInitialMode"}},y={claude:{model:{session:s,globalKey:"modelId"},mode:{session:c,globalKey:"modeId",newSessionOnly:!0,normalize:e=>e.trim().toLowerCase()===i.approval?i.approval:i.fullAuto},effort:{session:{read:(e,o)=>e.getEffort(o),write:(e,o,d)=>e.setEffort(o,d)},globalKey:"effort",newSessionOnly:!0,normalize:e=>g(e)}},cursor:{model:{session:s,globalKey:"modelId"},mode:{session:{read:(e,o)=>e.getCursorModeId(o),write:(e,o,d)=>e.setCursorModeId(o,d)},globalKey:"cursorModeId"}},agy:{model:{session:s,globalKey:"modelId"}},opencode:{model:{session:s,globalKey:"modelId"},mode:{session:c,globalKey:"modeId"}},codewhale:{model:{session:s,globalKey:"modelId"},mode:{globalKey:"acpInitialMode"}},"deepseek-harness":{},acp:{model:{session:{read:(e,o)=>e.getAcpModelId(o),write:(e,o,d)=>e.setAcpModelId(o,d)},globalKey:"modelId"},mode:{session:{read:(e,o)=>e.getAcpModeId(o),write:(e,o,d)=>e.setAcpModeId(o,d)},globalKey:"acpInitialMode"}},codex:{model:{session:{read:(e,o)=>e.getCodexModelId(o),write:(e,o,d)=>e.setCodexModelId(o,d),ownedByAdapter:!0},globalKey:"codexModelId",newSessionOnly:!0},mode:{session:{read:(e,o)=>e.getCodexModeId(o),write:(e,o,d)=>e.setCodexModeId(o,d),ownedByAdapter:!0},globalKey:"codexModeId",newSessionOnly:!0},reasoningEffort:{session:{read:(e,o)=>e.getCodexReasoningEffort(o),write:(e,o,d)=>e.setCodexReasoningEffort(o,d),ownedByAdapter:!0},globalKey:"codexReasoningEffort"},serviceTier:{session:{read:(e,o)=>e.getCodexServiceTier(o),write:(e,o,d)=>e.setCodexServiceTier(o,d),ownedByAdapter:!0},globalKey:"codexServiceTier",newSessionOnly:!0,defaultClearsGlobal:!0},sandboxMode:{session:{read:(e,o)=>e.getCodexSandboxMode(o),write:(e,o,d)=>e.setCodexSandboxMode(o,d),ownedByAdapter:!0},globalKey:"codexSandboxMode",defaultClearsGlobal:!0}}};function I(e,o){if(!o)return!1;switch(e){case"claude":return!!o.claudeSessionId;case"codex":return!!o.codexThreadId;case"codewhale":return!!o.codewhaleThreadId;case"agy":return!!o.agyConversationId;case"pi":return!!o.piSessionPath;default:return!!o.acpSessionId}}function u(e,o){return(y[e]??m)[o]}function w(e,o,d,a){const l=u(o,a);if(!l)return;const t=l.session?.read(e.bindingStore,d);if(t)return t;if(!l.globalKey||l.newSessionOnly&&I(o,e.bindingStore.get(d)))return;const r=String(e.globalConfigStore?.get(e.name)?.[l.globalKey]??"").trim();if(!r)return;const n=l.normalize?l.normalize(r):r;if(n)return l.session?.write(e.bindingStore,d,n),n}function S(e,o,d,a,l,t){const r=u(o,a),n=l.trim();if(!r||!n)return!1;if(r.session&&!r.session.ownedByAdapter&&r.session.write(e.bindingStore,d,n),r.globalKey){const f=r.defaultClearsGlobal&&n.toLowerCase()==="default";e.globalConfigStore?.set(e.name,{[r.globalKey]:f?void 0:n,...t??{}})}return!!(r.session||r.globalKey)}export{y as TOOLBAR_FIELD_SPECS,u as getToolbarFieldSpec,I as hasNativeSession,S as persistToolbarSelection,w as resolveToolbarSelection};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
import{execFile as w,execFileSync as $}from"node:child_process";import{existsSync as O}from"node:fs";import{dirname as T,join as y}from"node:path";import{promisify as k}from"node:util";import{log as I}from"../log/logger.js";import{probeUrls as v}from"./speed-test.js";const C=k(w),A=1e4,x=2,M=3e3;function R(t,e={}){if((e.platform??process.platform)!=="win32")return{command:"npm",args:t};const i=e.env??process.env,o=e.nodePath??process.execPath,u=[typeof i.npm_execpath=="string"?i.npm_execpath:"",y(T(o),"node_modules","npm","bin","npm-cli.js")];try{const n=$("where.exe",["npm.cmd"],{encoding:"utf-8",timeout:3e3,windowsHide:!0}).split(/\r?\n/).map(l=>l.trim()).filter(Boolean);for(const l of n)u.push(y(T(l),"node_modules","npm","bin","npm-cli.js"))}catch{}const s=u.find(n=>n&&O(n));if(!s)throw new Error("Unable to locate npm-cli.js for safe Windows execution");return{command:o,args:[s,...t]}}class b extends Error{output;constructor(e,r){super(e),this.name="NpmInstallError",this.output=r}}const f=[{id:"official",label:"npm \u5B98\u65B9",url:"https://registry.npmjs.org"},{id:"npmmirror",label:"npmmirror (\u6DD8\u5B9D\u955C\u50CF)",url:"https://registry.npmmirror.com"}];async function P(){const t=await v(f.map(r=>({url:r.url,label:r.label})),5e3),e=[];for(const r of t)if(r.reachable){const i=f.find(o=>o.url===r.url);i&&e.push(i)}for(const r of t)if(!r.reachable){const i=f.find(o=>o.url===r.url);i&&!e.includes(i)&&e.push(i)}for(const r of f)e.includes(r)||e.push(r);return e}async function B(){try{const t=R(["config","get","registry"]),{stdout:e}=await C(t.command,t.args,{timeout:A,encoding:"utf-8"});return e.trim()}catch{return"https://registry.npmjs.org"}}async function J(t,e,r,i){const o=await P();if(o.length===0)throw new Error("All npm registries are unreachable. Check your network connection.");let u="",s="";for(const n of o)for(let l=0;l<=x;l++)try{return I.info("installer",`npm install ${t} using ${n.label} (${n.url})`),{output:await D(t,n.url,e,r,i),registry:n.label,registryUrl:n.url}}catch(a){const c=a instanceof Error?a.message:String(a),d=L(a);if(u=c,s=F(`${s}
|
|
2
2
|
[${n.label}] ${c}
|
|
3
|
-
${d}`.trim(),r),I.info("installer",`${n.label} failed: ${c.slice(0,200)}`),j(c)&&l<x){await new Promise(m=>setTimeout(m,M));continue}if(!U(c))throw new b(c,s);break}throw new b(`npm install failed on all registries. Last error: ${u}`,s)}function U(t){return/ECONNRESET|ETIMEDOUT|ECONNREFUSED|ENOTFOUND|EAI_AGAIN|ERR_SOCKET_TIMEOUT|getaddrinfo|socket hang up|network|fetch failed|timed out|tunneling socket|self.signed|404|ETARGET|No matching version|notarget/i.test(t)}function j(t){return/\bEBUSY\b|resource busy or locked|EPERM[^\n]*\brename\b|\bELOCKED\b/i.test(t)}function D(t,e,r,i,o){return new Promise((u,s)=>{const n=["install","-g",t,"--registry",e,"--prefer-online","--no-audit","--no-fund"];o?.prefix&&n.push("--prefix",o.prefix);const l=process.platform==="win32",a=R(n,{env:o?.env});let c=!1,d=Promise.resolve(),m;const g=w(a.command,a.args,{maxBuffer:i,windowsHide:!0,env:o?.env},async(p,_,N)=>{clearTimeout(m),await d;const E=`${_??""}
|
|
3
|
+
${d}`.trim(),r),I.info("installer",`${n.label} failed: ${c.slice(0,200)}`),j(c)&&l<x){await new Promise(m=>setTimeout(m,M));continue}if(!U(c))throw new b(c,s);break}throw new b(`npm install failed on all registries. Last error: ${u}`,s)}function U(t){return/ECONNRESET|ETIMEDOUT|ECONNREFUSED|ENOTFOUND|EAI_AGAIN|ERR_SOCKET_TIMEOUT|getaddrinfo|socket hang up|network|fetch failed|timed out|tunneling socket|self.signed|404|ETARGET|No matching version|notarget/i.test(t)}function j(t){return/\bEBUSY\b|resource busy or locked|EPERM[^\n]*\brename\b|\bELOCKED\b/i.test(t)}function D(t,e,r,i,o){return new Promise((u,s)=>{const n=["install","-g",t,"--registry",e,"--prefer-online","--no-audit","--no-fund"];o?.prefix&&n.push("--prefix",o.prefix),o?.ignoreScripts&&n.push("--ignore-scripts");const l=process.platform==="win32",a=R(n,{env:o?.env});let c=!1,d=Promise.resolve(),m;const g=w(a.command,a.args,{maxBuffer:i,windowsHide:!0,env:o?.env},async(p,_,N)=>{clearTimeout(m),await d;const E=`${_??""}
|
|
4
4
|
${N??""}`.trim();if(p){const h=E||p.message,S=c||p.killed===!0?`ETIMEDOUT: npm install exceeded ${r}ms (registry likely unreachable): ${h}`:h;s(new b(S,E||h));return}u(E)});m=setTimeout(()=>{c=!0,l&&g.pid?d=new Promise(p=>{w("taskkill",["/PID",String(g.pid),"/T","/F"],{windowsHide:!0,timeout:1e4},()=>{g.kill("SIGTERM"),p()})}):g.kill("SIGTERM")},r),m.unref?.()})}function L(t){if(t&&typeof t=="object"&&"output"in t){const e=t.output;return typeof e=="string"?e:""}return""}function F(t,e){return t.length<=e?t:t.slice(-e)}function Q(t){return f.map(e=>({label:e.label,command:`npm install -g ${t} --registry ${e.url}`}))}export{f as NPM_REGISTRIES,b as NpmInstallError,B as getCurrentRegistry,Q as getMirrorInstallCommands,U as isRetriableRegistryError,j as isTransientInstallError,J as npmInstallWithMirror,P as probeRegistries,R as resolveNpmInvocation};
|
package/dist/core/mcp/tools.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
const r=[{name:"grix_query",description:"Search contacts, sessions, message history, messages, or favorited sessions in the Grix/AIBot platform. message_history defaults to the newest one clean text/approval-card message and excludes process/tool cards.",inputSchema:{type:"object",properties:{action:{type:"string",enum:["contact_search","session_search","message_history","message_search","search_favorite_sessions"],description:"Query action type. search_favorite_sessions returns the owner's favorited sessions (supports keyword filter)."},id:{type:"string",description:"Contact ID (contact_search) or Session ID (session_search)."},peerId:{type:"string",pattern:"^[1-9]\\d{0,19}$",description:"Peer user ID (session_search only): locate the private chat between the current user and this account exactly, independent of title/nickname/remark. Takes precedence over id/keyword. Use this when you know the target user's ID (e.g. the agent owner). Returns error 4004 when no such private chat exists."},keyword:{type:"string",description:"Search keyword."},sessionType:{type:"integer",enum:[1,2],description:"Filter by session type (session_search only): 1=private chat, 2=group chat. Omit to return all."},limit:{type:"integer",description:"Max results. message_history defaults to 1 when omitted."},offset:{type:"integer",description:"Result offset."},sessionId:{type:"string",description:"Session ID (message_history, message_search)."},beforeId:{type:"string",description:"Pagination cursor (message_history, message_search)."}},required:["action"]},validation:{required:["action"],properties:{action:{type:"string",enum:["contact_search","session_search","message_history","message_search","search_favorite_sessions"]},id:{type:"string"},peerId:{type:"string",pattern:"^[1-9]\\d{0,19}$"},keyword:{type:"string",maxLength:200},sessionType:{type:"integer",enum:[1,2]},limit:{type:"integer",minimum:1,maximum:100},offset:{type:"integer",minimum:0},sessionId:{type:"string"},beforeId:{type:"string"}}}},{name:"grix_audit_data",description:"Read connector-backed conversation audit replay data from Grix/AIBot: manifest, span pages, and content chunks.",inputSchema:{type:"object",properties:{action:{type:"string",enum:["get_manifest","list_spans","get_content_chunk"],description:"Audit read action. Start with get_manifest, then call list_spans and/or get_content_chunk based on the manifest."},auditId:{type:"string",description:"Audit ID returned by the audited turn metadata."},revision:{type:"integer",description:"Optional manifest revision to read. Usually use the revision returned by get_manifest."},cursor:{type:"string",description:"Opaque pagination cursor returned by list_spans or get_content_chunk."},limit:{type:"integer",description:"Max span items for list_spans."},contentId:{type:"string",description:"Content reference ID from manifest.content_refs (get_content_chunk only)."},maxBytes:{type:"integer",description:"Max UTF-8 bytes to return for get_content_chunk."}},required:["action","auditId"]},validation:{required:["action","auditId"],properties:{action:{type:"string",enum:["get_manifest","list_spans","get_content_chunk"]},auditId:{type:"string"},revision:{type:"integer",minimum:1},cursor:{type:"string",maxLength:4096},limit:{type:"integer",minimum:1,maximum:200},contentId:{type:"string"},maxBytes:{type:"integer",minimum:1,maximum:131072}}}},{name:"grix_group",description:"Manage groups in the Grix/AIBot platform: create, get details, leave, dissolve, manage members and permissions.",inputSchema:{type:"object",properties:{action:{type:"string",enum:["create","detail","leave","add_members","remove_members","update_member_role","update_all_members_muted","update_member_speaking","dissolve"],description:"Group action type."},sessionId:{type:"string",description:"Group session ID."},name:{type:"string",description:"Group name (create)."},memberIds:{type:"array",items:{type:"string"},description:"Member IDs to add/remove."},memberTypes:{type:"array",items:{type:"integer",enum:[1,2]},description:"Member types (1=user, 2=agent)."},memberId:{type:"string",description:"Target member ID."},role:{type:"integer",enum:[1,2],description:"New role (1=admin, 2=member)."},memberType:{type:"integer",description:"Member type."},allMembersMuted:{type:"boolean",description:"Whether to mute all members."},isSpeakMuted:{type:"boolean",description:"Whether member is muted."},canSpeakWhenAllMuted:{type:"boolean",description:"Allow speaking when all muted."}},required:["action"]},validation:{required:["action"],properties:{action:{type:"string",enum:["create","detail","leave","add_members","remove_members","update_member_role","update_all_members_muted","update_member_speaking","dissolve"]},sessionId:{type:"string"},name:{type:"string",maxLength:128},memberIds:{type:"array",items:{type:"string"},maxItems:100},memberTypes:{type:"array",items:{type:"integer",enum:[1,2]}},memberId:{type:"string"},role:{type:"integer",enum:[1,2]},memberType:{type:"integer"},allMembersMuted:{type:"boolean"},isSpeakMuted:{type:"boolean"},canSpeakWhenAllMuted:{type:"boolean"}}}},{name:"grix_message_send",description:"Send a message to a session in the Grix/AIBot platform. Do NOT use this to answer the inbound event you are currently handling \u2014 answer in plain text (or the reply tool where available) instead, otherwise the user receives duplicate messages.",inputSchema:{type:"object",properties:{sessionId:{type:"string",description:"Target session ID"},content:{type:"string",description:"Message content"},msgType:{type:"integer",description:"Message type (1=text, default 1)"},quotedMessageId:{type:"string",description:"Message ID to reply to"},threadId:{type:"string",description:"Thread ID for threaded reply"}},required:["sessionId","content"]},validation:{required:["sessionId","content"],properties:{sessionId:{type:"string"},content:{type:"string",maxLength:1e4},msgType:{type:"integer"},quotedMessageId:{type:"string"},threadId:{type:"string"}}}},{name:"grix_message_unsend",description:"Recall/unsend a message in the Grix/AIBot platform.",inputSchema:{type:"object",properties:{sessionId:{type:"string",description:"Session ID"},msgId:{type:"string",description:"Message ID to unsend"}},required:["sessionId","msgId"]},validation:{required:["sessionId","msgId"],properties:{sessionId:{type:"string"},msgId:{type:"string"}}}},{name:"grix_file_link",description:"Create a direct, tailnet-only download link for a local file on this host. Use this whenever the user asks you to send, share, give, or deliver a file that exists on the machine where you run (a report, log, build artifact, export, or any local path). It returns a ready-to-use Markdown link in the `markdown` field \u2014 include that exact Markdown link in your reply so the user can click and download the file directly over the shared Tailscale network. The link is reachable only inside the tailnet, so just send it as-is \u2014 no need to worry about or mention any link lifetime. The download link is HTTPS, served by a built-in self-signed CA. The result also returns `ca_install_url`: the first time you share a link with a user (or whenever their browser warns the cert is untrusted), also give them this CA install link so they can install and trust it once \u2014 after that all download links work without warnings. Requires this host to be on a tailnet (Tailscale running).",inputSchema:{type:"object",properties:{file_path:{type:"string",description:"Absolute path to a local file on this host to share with the user."},ttl_ms:{type:"integer",description:"Optional link lifetime in milliseconds. Leave unset to use the long default; set only if you deliberately want a short-lived link."}},required:["file_path"]},validation:{required:["file_path"],properties:{file_path:{type:"string",maxLength:4096},ttl_ms:{type:"integer",minimum:1e4,maximum:864e5}}}},{name:"grix_file_upload",description:"Upload a local file to the Grix platform and send it as a media message in the target session. Supports images (jpg/png/webp/gif/bmp/heic/heif), videos (mp4/mov/m4v/webm/mkv/avi), documents (pdf/doc/docx/xls/xlsx/ppt/pptx/txt/md/csv/json/xml), and archives (zip/rar/7z/tar/gz). Max 50 MB. Use this instead of grix_file_link when the file should appear as a native attachment in the chat (visible inline for images/videos), rather than a tailnet download link.",inputSchema:{type:"object",properties:{file_path:{type:"string",description:"Absolute path to a local file to upload."},session_id:{type:"string",description:"Target session ID to send the file to."},caption:{type:"string",description:"Optional text caption for the media message."},reply_to_message_id:{type:"string",description:"Optional message ID to quote/reply to."}},required:["file_path","session_id"]},validation:{required:["file_path","session_id"],properties:{file_path:{type:"string",maxLength:4096},session_id:{type:"string"},caption:{type:"string",maxLength:2e3},reply_to_message_id:{type:"string"}}}},{name:"grix_admin",description:"Agent and category management in the Grix/AIBot platform: create agents, manage categories, rotate API keys.",inputSchema:{type:"object",properties:{action:{type:"string",enum:["create_agent","list_categories","create_category","update_category","assign_category","rotate_api_key"],description:"Admin action type."},agentName:{type:"string",description:"Agent name (create_agent)."},introduction:{type:"string",description:"Professional behavioral introduction describing the Agent purpose, responsibilities, operating expectations, and boundaries (create_agent)."},isMain:{type:"boolean",description:"Set as main agent (create_agent)."},agentId:{type:"string",description:"Agent ID (assign_category, rotate_api_key)."},categoryId:{type:"string",description:"Category ID (create_agent, update_category, assign_category)."},name:{type:"string",description:"Category name (create_category, update_category)."},parentId:{type:"string",description:"Parent category ID (create_category, update_category)."},sortOrder:{type:"integer",description:"Sort order (create_category, update_category)."}},required:["action"]},validation:{required:["action"],properties:{action:{type:"string",enum:["create_agent","list_categories","create_category","update_category","assign_category","rotate_api_key"]},agentName:{type:"string"},introduction:{type:"string"},isMain:{type:"boolean"},agentId:{type:"string"},categoryId:{type:"string"},name:{type:"string"},parentId:{type:"string"},sortOrder:{type:"integer"}}}},{name:"grix_call_owner",description:"Call your owner into this session to talk by voice. Use this when, during your work, you need to reach your owner \u2014 to discuss something or to get an approval/review. It sends the owner an offline notification; when they tap it they land directly in this conversation and a voice-brain call is started automatically. Requires the owner to have configured a voice brain. Rate-limited per session.",inputSchema:{type:"object",properties:{session_id:{type:"string",description:"The session ID to call the owner into."}},required:["session_id"]},validation:{required:["session_id"],properties:{session_id:{type:"string"}}}},{name:"grix_agent_update",description:"Update the display name and/or text introduction of one of your owner's agents, identified by its numeric agent ID. Provide agent_name, introduction, or both \u2014 at least one is required.",inputSchema:{type:"object",properties:{agent_id:{type:"string",description:"Target agent's numeric ID, passed as a string."},agent_name:{type:"string",description:"New display name (max 100 characters, must be unique among the owner's agents)."},introduction:{type:"string",description:"New text introduction (max 300 characters)."}},required:["agent_id"]},validation:{required:["agent_id"],properties:{agent_id:{type:"string"},agent_name:{type:"string",maxLength:100},introduction:{type:"string",maxLength:300}}}},{name:"grix_dispatch_agent",description:`Dispatch one of your owner's agents to do work in a given working directory. Provide the target agent numeric ID, the working directory, and a text description of the task. The backend creates a NEW private session between the owner and that agent for each dispatch (it does not reuse past sessions), binds the working directory when the agent type requires it (claude/codex/etc.), and sends the task into the session AS THE OWNER so the agent starts working. Because the task is delivered as the owner, write it in the owner's first-person voice and tone \u2014 phrase it the way the owner would speak directly to the agent (e.g. "\u5E2E\u6211\u2026", "\u4F60\u53BB\u2026"), NOT as a third-person relay or as yourself narrating on the owner's behalf. Provide a short title summarizing the core of the task \u2014 it becomes the new session's title; if omitted the backend derives one from the task text.`,inputSchema:{type:"object",properties:{agent_id:{type:"string",description:"Target agent's numeric ID, passed as a string."},cwd:{type:"string",description:"Absolute working directory where the agent should do the work."},task:{type:"string",description:"Text description of the task to perform, written in the owner's first-person voice and tone \u2014 it is delivered into the session as the owner, so phrase it as the owner speaking directly to the agent, not as a third-person relay."},title:{type:"string",description:"Optional short title (a few words) summarizing the core of the task; used as the new session's title. If omitted, the backend derives a title from the task text."}},required:["agent_id","cwd","task"]},validation:{required:["agent_id","cwd","task"],properties:{agent_id:{type:"string"},cwd:{type:"string",maxLength:4096},task:{type:"string",maxLength:1e4},title:{type:"string",maxLength:255}}}},{name:"grix_session_send",description:"Send a message into a session AS THE OWNER \u2014 it appears as if the owner sent it, NOT as you (the agent). Dispatch callbacks (report_dispatch_result) MUST use this tool, even when you are a member of the callback session: a message sent as yourself cannot quote-wake the dispatcher agent, and your membership is not checked. NEVER use this for your own ordinary replies in a conversation \u2014 that would make your words show up as the owner's message; reply normally (or use grix_message_send to send as yourself) for those. The owner must be a member of the target session. Requires the Send as Owner permission scope \u2014 if rejected for missing permission, surface the error as-is so the owner can grant it; do not fall back to grix_message_send. Optional quoted_message_id quotes a message in the same session (used by dispatch callbacks to wake the dispatcher agent).",inputSchema:{type:"object",properties:{session_id:{type:"string",description:"Target session ID."},content:{type:"string",description:"Message content to send as the owner."},quoted_message_id:{type:"string",description:"Optional message ID in the target session to quote/reply to.",pattern:"^[1-9]\\d*$"}},required:["session_id","content"]},validation:{required:["session_id","content"],properties:{session_id:{type:"string"},content:{type:"string",maxLength:1e4},quoted_message_id:{type:"string",pattern:"^[1-9]\\d*$"}}}},{name:"grix_chat_state_query",description:"Query the chat-level task states across all of your owner's sessions (including direct and group chats). Returns one entry per session with a single mutually-exclusive state: running (working), waiting_approval (blocked on your owner to approve/deny), waiting_question (asked the owner a question, awaiting their reply), completed, failed, or idle (no task / stopped). When session_id identifies one completed session, the entry also includes exactly one clean final_result; non-completed and list queries never include message content. Also returns the session title (task_title) for easy identification. Supports pagination (page/page_size) and optional state filtering. Use this to see at a glance which chats are done, still running, or waiting on the owner.",inputSchema:{type:"object",properties:{session_id:{type:"string",description:"(Optional) Query a single session by its ID. Omit to return all sessions."},page:{type:"number",description:"(Optional) Page number, starting from 1. Defaults to 1 if omitted."},page_size:{type:"number",description:"(Optional) Number of items per page, max 100. Defaults to 10 if omitted."},state:{type:"string",description:"(Optional) Filter by a specific state: running, waiting_approval, waiting_question, completed, failed, or idle. Omit to return all states."}}},validation:{required:[],properties:{}}},{name:"grix_chat_state_update",description:"Manually update the task state of a specific chat session. Use this to mark a chat as completed, failed, idle, or any other state when you need to override it manually. The reason is written to stop_reason and is optional.",inputSchema:{type:"object",properties:{session_id:{type:"string",description:"The session ID whose state to update."},state:{type:"string",enum:["running","waiting_approval","waiting_question","completed","failed","idle"],description:"The new state to set. Must be one of: running, waiting_approval, waiting_question, completed, failed, idle."},reason:{type:"string",description:"(Optional) Reason for the state change, written to stop_reason."}}},validation:{required:["session_id","state"],properties:{}}},{name:"grix_access_control",description:"Manage sender access control: pair approval, allow/remove senders, set policy.",inputSchema:{type:"object",properties:{action:{type:"string",enum:["pair_approve","pair_deny","allow_sender","remove_sender","set_policy"],description:"Access control action type."},code:{type:"string",description:"Pairing code (required for pair_approve/pair_deny)."},sender_id:{type:"string",description:"Sender ID (required for allow_sender/remove_sender)."},policy:{type:"string",enum:["allowlist","open","disabled"],description:"Access policy (required for set_policy)."}},required:["action"]},validation:{required:["action"],properties:{action:{type:"string",enum:["pair_approve","pair_deny","allow_sender","remove_sender","set_policy"]},code:{type:"string"},sender_id:{type:"string"},policy:{type:"string",enum:["allowlist","open","disabled"]}}}},{name:"grix_widget_visitor_ban",description:"Ban a widget visitor session: blocks the session and adds its most recent init IP to the owner's global IP ban list (7-day expiry).",inputSchema:{type:"object",properties:{session_id:{type:"string",description:"Widget visitor session ID (required)."}},required:["session_id"]},validation:{required:["session_id"],properties:{session_id:{type:"string"}}}},{name:"grix_egg_search",description:"Search the Grix egg market (published skill/persona packages the owner can hatch into a new agent or install into an existing one). Keyword matching is per-term AND over name + description + category, so search short keywords (one concept per call) rather than a whole sentence; omit keyword to browse a category. Results include id, name, description, category, install_count, can_create_agent, and existing_agent_client_types.",inputSchema:{type:"object",properties:{keyword:{type:"string",description:"Short search keyword(s); terms are ANDed. Omit to list by category."},categoryId:{type:"string",description:"Optional egg category ID to restrict results."},locale:{type:"string",description:"Optional locale such as zh-CN or en; defaults to the owner's locale."},page:{type:"integer",description:"Page number, starting from 1."},pageSize:{type:"integer",description:"Items per page (1-50), default 20."}}},validation:{required:[],properties:{keyword:{type:"string",maxLength:200},categoryId:{type:"string"},locale:{type:"string",maxLength:16},page:{type:"integer",minimum:1},pageSize:{type:"integer",minimum:1,maximum:50}}}},{name:"grix_egg_get",description:"Get one Grix egg by id \u2014 full description, current version, and whether it can create a new agent or install into existing agents. Use after grix_egg_search to show details before the owner decides to hatch.",inputSchema:{type:"object",properties:{id:{type:"string",description:"Egg ID from grix_egg_search."},locale:{type:"string",description:"Optional locale."},version:{type:"integer",description:"Optional specific version; defaults to the current one."}},required:["id"]},validation:{required:["id"],properties:{id:{type:"string"},locale:{type:"string",maxLength:16},version:{type:"integer",minimum:1}}}},{name:"grix_skill_set",description:"Create, update, or delete one of your owner's custom skills in the platform skill library. The skill library auto-syncs to every machine the owner runs an agent on (a per-machine `grix/skills` directory), so defining a skill here makes it available on all of them. A skill is a plain SKILL.md package (frontmatter + body), same standard as other Grix skills. Provide `name` and the full `content` (the SKILL.md text) to create or overwrite by name. To delete a skill, pass its `name` with an empty `content` string. How and when any agent actually loads/uses a synced skill is arranged by the owner (in a system prompt or in conversation); this tool only manages the library.",inputSchema:{type:"object",properties:{name:{type:"string",description:"Skill name (max 100 characters); unique within the owner. Used as the identifier and the synced directory name."},content:{type:"string",description:"Full SKILL.md text. Pass an empty string to delete the skill by name."}},required:["name","content"]},validation:{required:["name","content"],properties:{name:{type:"string",maxLength:100},content:{type:"string",maxLength:262144}}}},{name:"grix_skill_get",description:"Read one of your owner's custom skills by name (returns its full SKILL.md content), or list the owner's skill library when no name is given. Use this before editing to fetch current content, then call grix_skill_set with the revised text.",inputSchema:{type:"object",properties:{name:{type:"string",description:"Skill name to fetch. Omit to list all skills (names + versions) instead."}}},validation:{required:[],properties:{name:{type:"string",maxLength:100}}}}],a=[{name:"grix_reply",description:"Send your final reply \u2014 the conclusion the user is waiting for \u2014 to the specified session. This is the message the user treats as your answer; deliver your final result here, and use plain text only for brief progress notes while you work. Supports streaming in chunks; the frontend automatically aggregates them into one complete message. The connector quotes the message being answered automatically, so quoted_message_id is optional (set it only to quote a different earlier message).",inputSchema:{type:"object",properties:{event_id:{type:"string",description:"Associated event ID from the inbound event."},session_id:{type:"string",description:"Target session ID."},text:{type:"string",description:"Reply text content."},quoted_message_id:{type:"string",description:"Quoted message ID (optional)."},is_final:{type:"boolean",description:"Whether this is a stage-final reply. Advisory only \u2014 does not trigger event completion; completion is handled by the complete tool or Stop hook."}},required:["session_id","text"]},validation:{required:["session_id","text"],properties:{event_id:{type:"string"},session_id:{type:"string"},text:{type:"string",maxLength:5e4},quoted_message_id:{type:"string"},is_final:{type:"boolean"}}}},{name:"grix_complete",description:"Mark event processing as complete, notifying the backend that no more replies are expected.",inputSchema:{type:"object",properties:{event_id:{type:"string",description:"The event ID to complete."},status:{type:"string",enum:["responded","canceled","failed"],description:"Completion status."},msg:{type:"string",description:"Additional note (optional)."}},required:["event_id","status"]},validation:{required:["event_id","status"],properties:{event_id:{type:"string"},status:{type:"string",enum:["responded","canceled","failed"]},msg:{type:"string",maxLength:500}}}},{name:"grix_event_ack",description:"Acknowledge event receipt (usually done automatically by the Dispatcher; agents typically do not need to call this manually).",inputSchema:{type:"object",properties:{event_id:{type:"string",description:"The event ID to acknowledge."},session_id:{type:"string",description:"Session ID."}},required:["event_id"]},validation:{required:["event_id"],properties:{event_id:{type:"string"},session_id:{type:"string"}}}},{name:"grix_composing",description:'Set the "typing" indicator status for a session.',inputSchema:{type:"object",properties:{session_id:{type:"string",description:"Session ID."},active:{type:"boolean",description:"true = typing, false = stopped."},event_id:{type:"string",description:"Associated event ID (optional)."}},required:["session_id","active"]},validation:{required:["session_id","active"],properties:{session_id:{type:"string"},active:{type:"boolean"},event_id:{type:"string"}}}},{name:"grix_status",description:"Query the Grix connection status of the current MCP session.",inputSchema:{type:"object",properties:{}},validation:{required:[],properties:{}}}],b={grix_dispatch_agent:"grix-agent-dispatch",grix_agent_update:"grix-agent-dispatch",grix_query:"grix-query",grix_audit_data:"grix-audit-data",grix_group:"grix-group",grix_message_send:"message-send",grix_message_unsend:"message-unsend",grix_admin:"grix-admin",grix_call_owner:"grix-owner-relay",grix_session_send:"grix-owner-relay",grix_chat_state_query:"grix-chat-state",grix_chat_state_update:"grix-chat-state",grix_status:"grix-chat-state",grix_access_control:"grix-access-control",grix_widget_visitor_ban:"grix-widget-visitor-ban",grix_file_link:"tailnet-file-share"};function w(e){return` Before calling this tool, follow the \`${e}\` skill's procedure first; do not invoke this tool directly without going through that skill's guidance.`}for(const e of[...r,...a]){const t=b[e.name];t&&(e.description+=w(t))}const p=[{name:"reply",description:"Send a visible message back to the chat for this grix-claude event.",inputSchema:{type:"object",properties:{text:{type:"string",description:"The visible reply text to send."},chat_id:{type:"string",description:"The target chat/session id from the <channel> tag."},event_id:{type:"string",description:"The Aibot event_id from the <channel> tag."},reply_to:{type:"string",description:"Optional message_id to quote instead of the inbound trigger message."},final:{type:"boolean",description:"Advisory flag only. It does not complete the event; completion is handled by complete tool or Stop hook."}},required:["chat_id","event_id","text"]}},{name:"complete",description:"Finish an event without sending a visible reply so the backend does not time out.",inputSchema:{type:"object",properties:{event_id:{type:"string",description:"The Aibot event_id from the <channel> tag."},status:{type:"string",enum:["responded","canceled","failed"]},msg:{type:"string"},code:{type:"string"}},required:["event_id","status"]}}],c=new Set(p.map(e=>e.name)),x=new Set(r.map(e=>e.name)),k=new Set(a.map(e=>e.name)),I=/([A-Za-z0-9._-]+:[A-Za-z0-9._-]+:[A-Za-z0-9._-]+:[A-Za-z0-9._-]+)/,S=/[A-Za-z0-9._-]+/;function Z(e){return!!(x.has(e)||k.has(e)||c.has(e)||e.startsWith("mcp__grix"))}const q=[...r,...a],A=[...q,...p],V=new Map(A.map(e=>[e.name,e]));function X(e,t){return e==="reply"?{name:"grix_reply",args:_("grix_reply",{event_id:t.event_id,session_id:t.chat_id,text:t.text,quoted_message_id:t.reply_to,is_final:t.final})}:e==="complete"?{name:"grix_complete",args:_("grix_complete",{event_id:t.event_id,status:t.status,msg:t.msg,code:t.code})}:{name:e,args:t}}function l(e){const t=String(e??"").trim();return t?t.match(I)?.[1]:void 0}function u(e){const t=String(e??"").trim();if(!t)return;const n=l(t);if(n)return m(n);const i=t.match(/(?:chat_id|session_id)\s*=\s*"([A-Za-z0-9._-]+)"/)?.[1];if(i)return i;const o=t.match(/[A-Za-z0-9._-]+/g)??[];for(const s of o)if(!(s==="event_id"||s==="chat_id"||s==="session_id")&&s.length>0)return s;return t.match(S)?.[0]}function m(e){if(!e)return;const t=e.split(":",1)[0]?.trim();if(t)return u(t)}function _(e,t){if(e!=="grix_reply"&&e!=="grix_complete")return t;const n={...t},i=l(n.event_id);if(i&&(n.event_id=i),e==="grix_reply"){const o=m(i),d=String(n.session_id??""),s=u(n.session_id),v=/\bevent_id\b|["'<>\s]/.test(d);s&&!(v&&o)?n.session_id=s:o&&(n.session_id=o)}return n}function Y(e){return c.has(e)}function J(e,t){switch(e){case"grix_query":return T(t);case"grix_audit_data":return D(t);case"grix_group":return L(t);case"grix_message_send":return E(t);case"grix_message_unsend":return j(t);case"grix_file_link":return N(t);case"grix_file_upload":return z(t);case"grix_admin":return H(t);case"grix_call_owner":return P(t);case"grix_agent_update":return U(t);case"grix_dispatch_agent":return C(t);case"grix_session_send":return G(t);case"grix_chat_state_query":return R(t);case"grix_chat_state_update":return $(t);case"grix_access_control":return B(t);case"grix_widget_visitor_ban":return W(t);case"grix_egg_search":return O(t);case"grix_egg_get":return M(t);case"grix_skill_set":return F(t);case"grix_skill_get":return Q(t);default:throw new Error(`Unknown tool: ${e}`)}}const g={contact_search:"contact_search",session_search:"session_search",message_history:"message_history",message_search:"message_search",search_favorite_sessions:"search_favorite_sessions"};function T(e){const t=String(e.action??""),n=g[t];if(!n)throw new Error(`Unknown grix_query action: ${t}`);const i={};return e.id!=null&&(i.id=e.id),e.peerId!=null&&(i.peer_id=e.peerId),e.keyword!=null&&(i.keyword=e.keyword),e.sessionType!=null&&(i.session_type=e.sessionType),e.limit!=null?i.limit=e.limit:t==="message_history"&&(i.limit=1),e.offset!=null&&(i.offset=e.offset),e.sessionId!=null&&(i.session_id=e.sessionId),e.beforeId!=null&&(i.before_id=e.beforeId),{action:n,params:i}}function O(e){const t={};return e.keyword!=null&&(t.keyword=e.keyword),e.categoryId!=null&&(t.category_id=e.categoryId),e.locale!=null&&(t.locale=e.locale),e.page!=null&&(t.page=e.page),e.pageSize!=null&&(t.page_size=e.pageSize),{action:"egg_search",params:t}}function M(e){const t={id:e.id};return e.locale!=null&&(t.locale=e.locale),e.version!=null&&(t.version=e.version),{action:"egg_get",params:t}}const y={get_manifest:"audit_get_manifest",list_spans:"audit_list_spans",get_content_chunk:"audit_get_content_chunk"};function D(e){const t=String(e.action??""),n=y[t];if(!n)throw new Error(`Unknown grix_audit_data action: ${t}`);const i={audit_id:e.auditId};return e.revision!=null&&(i.revision=e.revision),e.cursor!=null&&(i.cursor=e.cursor),e.limit!=null&&(i.limit=e.limit),e.contentId!=null&&(i.content_id=e.contentId),e.maxBytes!=null&&(i.max_bytes=e.maxBytes),{action:n,params:i}}const h={create:"group_create",detail:"group_detail_read",leave:"group_leave_self",add_members:"group_member_add",remove_members:"group_member_remove",update_member_role:"group_member_role_update",update_all_members_muted:"group_all_members_muted_update",update_member_speaking:"group_member_speaking_update",dissolve:"group_dissolve"};function L(e){const t=String(e.action??""),n=h[t];if(!n)throw new Error(`Unknown grix_group action: ${t}`);const i={};return e.sessionId!=null&&(i.session_id=e.sessionId),e.name!=null&&(i.name=e.name),e.memberIds!=null&&(i.member_ids=e.memberIds),e.memberTypes!=null&&(i.member_types=e.memberTypes),e.memberId!=null&&(i.member_id=e.memberId),e.role!=null&&(i.role=e.role),e.memberType!=null&&(i.member_type=e.memberType),e.allMembersMuted!=null&&(i.all_members_muted=e.allMembersMuted),e.isSpeakMuted!=null&&(i.is_speak_muted=e.isSpeakMuted),e.canSpeakWhenAllMuted!=null&&(i.can_speak_when_all_muted=e.canSpeakWhenAllMuted),{action:n,params:i}}function E(e){const t={session_id:e.sessionId,msg_type:e.msgType??1,content:e.content};return e.quotedMessageId!=null&&(t.quoted_message_id=e.quotedMessageId),e.threadId!=null&&(t.thread_id=e.threadId),{action:"send_msg",params:t}}function j(e){return{action:"delete_msg",params:{session_id:e.sessionId,msg_id:e.msgId}}}function N(e){const t={file_path:e.file_path};return e.ttl_ms!=null&&(t.ttl_ms=e.ttl_ms),{action:"file_link",params:t}}function z(e){const t={file_path:e.file_path,session_id:e.session_id};return e.caption!=null&&(t.caption=e.caption),e.reply_to_message_id!=null&&(t.reply_to_message_id=e.reply_to_message_id),{action:"file_upload",params:t,timeoutMs:6e4}}const f={create_agent:"agent_api_create",list_categories:"agent_category_list",create_category:"agent_category_create",update_category:"agent_category_update",assign_category:"agent_category_assign",rotate_api_key:"agent_api_key_rotate"};function P(e){return{action:"call_owner",params:{session_id:e.session_id}}}function U(e){if(e.agent_name==null&&e.introduction==null)throw new Error("grix_agent_update requires agent_name or introduction");const t={agent_id:e.agent_id};return e.agent_name!=null&&(t.agent_name=e.agent_name),e.introduction!=null&&(t.introduction=e.introduction),{action:"agent_introduction_update",params:t}}function C(e){return{action:"dispatch_agent",params:{agent_id:e.agent_id,cwd:e.cwd,task:e.task,title:e.title},timeoutMs:75e3}}function G(e){const t={session_id:e.session_id,content:e.content};return e.quoted_message_id!=null&&String(e.quoted_message_id).trim()!==""&&(t.quoted_message_id=e.quoted_message_id),{action:"session_send",params:t}}function R(e){const t={};return e.session_id!=null&&(t.session_id=e.session_id),e.page!=null&&(t.page=e.page),e.page_size!=null&&(t.page_size=e.page_size),e.state!=null&&(t.state=e.state),{action:"chat_state_query",params:t}}function B(e){const t=String(e.action??""),n=K[t];if(!n)throw new Error(`Unknown grix_access_control action: ${t}`);const i={};return e.code!=null&&(i.code=e.code),e.sender_id!=null&&(i.sender_id=e.sender_id),e.policy!=null&&(i.policy=e.policy),{action:"claude_access_control",params:{verb:n,payload:i},timeoutMs:3e4}}function W(e){return{action:"widget_visitor_ban",params:{session_id:e.session_id}}}function $(e){const t={session_id:e.session_id,state:e.state};return e.reason!=null&&(t.reason=e.reason),{action:"chat_state_update",params:t}}function F(e){return{action:"skill_set",params:{name:e.name,content:e.content},timeoutMs:3e4}}function Q(e){const t={};return e.name!=null&&(t.name=e.name),{action:"skill_get",params:t}}function H(e){const t=String(e.action??""),n=f[t];if(!n)throw new Error(`Unknown grix_admin action: ${t}`);const i={};return e.agentName!=null&&(i.agent_name=e.agentName),e.introduction!=null&&(i.introduction=e.introduction),e.isMain!=null&&(i.is_main=e.isMain),e.agentId!=null&&(i.agent_id=e.agentId),e.categoryId!=null&&(i.category_id=e.categoryId),e.name!=null&&(i.name=e.name),e.parentId!=null&&(i.parent_id=e.parentId),e.sortOrder!=null&&(i.sort_order=e.sortOrder),{action:n,params:i}}const ee=new Set([...Object.values(g),...Object.values(y),...Object.values(h),...Object.values(f),"send_msg","delete_msg","file_link","file_upload","call_owner","agent_introduction_update","dispatch_agent","session_send","chat_state_query","chat_state_update","claude_access_control","widget_visitor_ban","skill_set","skill_get"]),K={pair_approve:"pair_approve",pair_deny:"pair_deny",allow_sender:"sender_allow",remove_sender:"sender_remove",set_policy:"policy_set"};export{K as ACCESS_CONTROL_ACTION_MAP,q as ALL_TOOLS,a as EVENT_TOOLS,A as EXPOSED_TOOLS,ee as PHASE1_INVOKE_ACTIONS,x as PHASE1_TOOL_NAMES,k as PHASE2_TOOL_NAMES,r as TOOLS,p as TOOL_ALIASES,V as TOOL_MAP,Y as isAlias,Z as isGrixInternalToolName,X as mapToolAlias,_ as normalizeEventToolArgs,J as toolCallToInvoke};
|
|
1
|
+
const r=[{name:"grix_query",description:"Search contacts, sessions, message history, messages, or favorited sessions in the Grix/AIBot platform. message_history defaults to the newest one clean text/approval-card message and excludes process/tool cards.",inputSchema:{type:"object",properties:{action:{type:"string",enum:["contact_search","session_search","message_history","message_search","search_favorite_sessions"],description:"Query action type. search_favorite_sessions returns the owner's favorited sessions (supports keyword filter)."},id:{type:"string",description:"Contact ID (contact_search) or Session ID (session_search)."},peerId:{type:"string",pattern:"^[1-9]\\d{0,19}$",description:"Peer user ID (session_search only): locate the private chat between the current user and this account exactly, independent of title/nickname/remark. Takes precedence over id/keyword. Use this when you know the target user's ID (e.g. the agent owner). Returns error 4004 when no such private chat exists."},keyword:{type:"string",description:"Search keyword."},sessionType:{type:"integer",enum:[1,2],description:"Filter by session type (session_search only): 1=private chat, 2=group chat. Omit to return all."},limit:{type:"integer",description:"Max results. message_history defaults to 1 when omitted."},offset:{type:"integer",description:"Result offset."},sessionId:{type:"string",description:"Session ID (message_history, message_search)."},beforeId:{type:"string",description:"Pagination cursor (message_history, message_search)."}},required:["action"]},validation:{required:["action"],properties:{action:{type:"string",enum:["contact_search","session_search","message_history","message_search","search_favorite_sessions"]},id:{type:"string"},peerId:{type:"string",pattern:"^[1-9]\\d{0,19}$"},keyword:{type:"string",maxLength:200},sessionType:{type:"integer",enum:[1,2]},limit:{type:"integer",minimum:1,maximum:100},offset:{type:"integer",minimum:0},sessionId:{type:"string"},beforeId:{type:"string"}}}},{name:"grix_audit_data",description:"Read connector-backed conversation audit replay data from Grix/AIBot: manifest, span pages, and content chunks.",inputSchema:{type:"object",properties:{action:{type:"string",enum:["get_manifest","list_spans","get_content_chunk"],description:"Audit read action. Start with get_manifest, then call list_spans and/or get_content_chunk based on the manifest."},auditId:{type:"string",description:"Audit ID returned by the audited turn metadata."},revision:{type:"integer",description:"Optional manifest revision to read. Usually use the revision returned by get_manifest."},cursor:{type:"string",description:"Opaque pagination cursor returned by list_spans or get_content_chunk."},limit:{type:"integer",description:"Max span items for list_spans."},contentId:{type:"string",description:"Content reference ID from manifest.content_refs (get_content_chunk only)."},maxBytes:{type:"integer",description:"Max UTF-8 bytes to return for get_content_chunk."}},required:["action","auditId"]},validation:{required:["action","auditId"],properties:{action:{type:"string",enum:["get_manifest","list_spans","get_content_chunk"]},auditId:{type:"string"},revision:{type:"integer",minimum:1},cursor:{type:"string",maxLength:4096},limit:{type:"integer",minimum:1,maximum:200},contentId:{type:"string"},maxBytes:{type:"integer",minimum:1,maximum:131072}}}},{name:"grix_group",description:"Manage groups in the Grix/AIBot platform: create, get details, leave, dissolve, manage members and permissions.",inputSchema:{type:"object",properties:{action:{type:"string",enum:["create","detail","leave","add_members","remove_members","update_member_role","update_all_members_muted","update_member_speaking","dissolve"],description:"Group action type."},sessionId:{type:"string",description:"Group session ID."},name:{type:"string",description:"Group name (create)."},memberIds:{type:"array",items:{type:"string"},description:"Member IDs to add/remove."},memberTypes:{type:"array",items:{type:"integer",enum:[1,2]},description:"Member types (1=user, 2=agent)."},memberId:{type:"string",description:"Target member ID."},role:{type:"integer",enum:[1,2],description:"New role (1=admin, 2=member)."},memberType:{type:"integer",description:"Member type."},allMembersMuted:{type:"boolean",description:"Whether to mute all members."},isSpeakMuted:{type:"boolean",description:"Whether member is muted."},canSpeakWhenAllMuted:{type:"boolean",description:"Allow speaking when all muted."}},required:["action"]},validation:{required:["action"],properties:{action:{type:"string",enum:["create","detail","leave","add_members","remove_members","update_member_role","update_all_members_muted","update_member_speaking","dissolve"]},sessionId:{type:"string"},name:{type:"string",maxLength:128},memberIds:{type:"array",items:{type:"string"},maxItems:100},memberTypes:{type:"array",items:{type:"integer",enum:[1,2]}},memberId:{type:"string"},role:{type:"integer",enum:[1,2]},memberType:{type:"integer"},allMembersMuted:{type:"boolean"},isSpeakMuted:{type:"boolean"},canSpeakWhenAllMuted:{type:"boolean"}}}},{name:"grix_message_send",description:"Send a message to a session in the Grix/AIBot platform. Do NOT use this to answer the inbound event you are currently handling \u2014 answer in plain text (or the reply tool where available) instead, otherwise the user receives duplicate messages.",inputSchema:{type:"object",properties:{sessionId:{type:"string",description:"Target session ID"},content:{type:"string",description:"Message content"},msgType:{type:"integer",description:"Message type (1=text, default 1)"},quotedMessageId:{type:"string",description:"Message ID to reply to"},threadId:{type:"string",description:"Thread ID for threaded reply"}},required:["sessionId","content"]},validation:{required:["sessionId","content"],properties:{sessionId:{type:"string"},content:{type:"string",maxLength:1e4},msgType:{type:"integer"},quotedMessageId:{type:"string"},threadId:{type:"string"}}}},{name:"grix_message_unsend",description:"Recall/unsend a message in the Grix/AIBot platform.",inputSchema:{type:"object",properties:{sessionId:{type:"string",description:"Session ID"},msgId:{type:"string",description:"Message ID to unsend"}},required:["sessionId","msgId"]},validation:{required:["sessionId","msgId"],properties:{sessionId:{type:"string"},msgId:{type:"string"}}}},{name:"grix_file_link",description:"Create a direct, tailnet-only download link for a local file on this host. Use this whenever the user asks you to send, share, give, or deliver a file that exists on the machine where you run (a report, log, build artifact, export, or any local path). It returns a ready-to-use Markdown link in the `markdown` field \u2014 include that exact Markdown link in your reply so the user can click and download the file directly over the shared Tailscale network. The link is reachable only inside the tailnet, so just send it as-is \u2014 no need to worry about or mention any link lifetime. The download link is HTTPS, served by a built-in self-signed CA. The result also returns `ca_install_url`: the first time you share a link with a user (or whenever their browser warns the cert is untrusted), also give them this CA install link so they can install and trust it once \u2014 after that all download links work without warnings. Requires this host to be on a tailnet (Tailscale running).",inputSchema:{type:"object",properties:{file_path:{type:"string",description:"Absolute path to a local file on this host to share with the user."},ttl_ms:{type:"integer",description:"Optional link lifetime in milliseconds. Leave unset to use the long default; set only if you deliberately want a short-lived link."}},required:["file_path"]},validation:{required:["file_path"],properties:{file_path:{type:"string",maxLength:4096},ttl_ms:{type:"integer",minimum:1e4,maximum:864e5}}}},{name:"grix_file_upload",description:"Upload a local file to the Grix platform and send it as a media message in the target session. Supports images (jpg/png/webp/gif/bmp/heic/heif), videos (mp4/mov/m4v/webm/mkv/avi), documents (pdf/doc/docx/xls/xlsx/ppt/pptx/txt/md/csv/json/xml), and archives (zip/rar/7z/tar/gz). Max 50 MB. Use this instead of grix_file_link when the file should appear as a native attachment in the chat (visible inline for images/videos), rather than a tailnet download link.",inputSchema:{type:"object",properties:{file_path:{type:"string",description:"Absolute path to a local file to upload."},session_id:{type:"string",description:"Target session ID to send the file to."},caption:{type:"string",description:"Optional text caption for the media message."},reply_to_message_id:{type:"string",description:"Optional message ID to quote/reply to."}},required:["file_path","session_id"]},validation:{required:["file_path","session_id"],properties:{file_path:{type:"string",maxLength:4096},session_id:{type:"string"},caption:{type:"string",maxLength:2e3},reply_to_message_id:{type:"string"}}}},{name:"grix_admin",description:"Agent and category management in the Grix/AIBot platform: create agents, manage categories, rotate API keys.",inputSchema:{type:"object",properties:{action:{type:"string",enum:["create_agent","list_categories","create_category","update_category","assign_category","rotate_api_key"],description:"Admin action type."},agentName:{type:"string",description:"Agent name (create_agent)."},introduction:{type:"string",description:"Professional behavioral introduction describing the Agent purpose, responsibilities, operating expectations, and boundaries (create_agent)."},isMain:{type:"boolean",description:"Set as main agent (create_agent)."},agentId:{type:"string",description:"Agent ID (assign_category, rotate_api_key)."},categoryId:{type:"string",description:"Category ID (create_agent, update_category, assign_category)."},name:{type:"string",description:"Category name (create_category, update_category)."},parentId:{type:"string",description:"Parent category ID (create_category, update_category)."},sortOrder:{type:"integer",description:"Sort order (create_category, update_category)."}},required:["action"]},validation:{required:["action"],properties:{action:{type:"string",enum:["create_agent","list_categories","create_category","update_category","assign_category","rotate_api_key"]},agentName:{type:"string"},introduction:{type:"string"},isMain:{type:"boolean"},agentId:{type:"string"},categoryId:{type:"string"},name:{type:"string"},parentId:{type:"string"},sortOrder:{type:"integer"}}}},{name:"grix_call_owner",description:"Call your owner into this session to talk by voice. Use this when, during your work, you need to reach your owner \u2014 to discuss something or to get an approval/review. It sends the owner an offline notification; when they tap it they land directly in this conversation and a voice-brain call is started automatically. Requires the owner to have configured a voice brain. Rate-limited per session.",inputSchema:{type:"object",properties:{session_id:{type:"string",description:"The session ID to call the owner into."}},required:["session_id"]},validation:{required:["session_id"],properties:{session_id:{type:"string"}}}},{name:"grix_agent_update",description:"Update the display name and/or text introduction of one of your owner's agents, identified by its numeric agent ID. Provide agent_name, introduction, or both \u2014 at least one is required.",inputSchema:{type:"object",properties:{agent_id:{type:"string",description:"Target agent's numeric ID, passed as a string."},agent_name:{type:"string",description:"New display name (max 100 characters, must be unique among the owner's agents)."},introduction:{type:"string",description:"New text introduction (max 300 characters)."}},required:["agent_id"]},validation:{required:["agent_id"],properties:{agent_id:{type:"string"},agent_name:{type:"string",maxLength:100},introduction:{type:"string",maxLength:300}}}},{name:"grix_dispatch_agent",description:`Dispatch one of your owner's agents to do work in a given working directory. Provide the target agent numeric ID, the working directory, and a text description of the task. The backend creates a NEW private session between the owner and that agent for each dispatch (it does not reuse past sessions), binds the working directory when the agent type requires it (claude/codex/etc.), and sends the task into the session AS THE OWNER so the agent starts working. Because the task is delivered as the owner, write it in the owner's first-person voice and tone \u2014 phrase it the way the owner would speak directly to the agent (e.g. "\u5E2E\u6211\u2026", "\u4F60\u53BB\u2026"), NOT as a third-person relay or as yourself narrating on the owner's behalf. Provide a short title summarizing the core of the task \u2014 it becomes the new session's title; if omitted the backend derives one from the task text.`,inputSchema:{type:"object",properties:{agent_id:{type:"string",description:"Target agent's numeric ID, passed as a string."},cwd:{type:"string",description:"Absolute working directory where the agent should do the work."},task:{type:"string",description:"Text description of the task to perform, written in the owner's first-person voice and tone \u2014 it is delivered into the session as the owner, so phrase it as the owner speaking directly to the agent, not as a third-person relay."},title:{type:"string",description:"Optional short title (a few words) summarizing the core of the task; used as the new session's title. If omitted, the backend derives a title from the task text."}},required:["agent_id","cwd","task"]},validation:{required:["agent_id","cwd","task"],properties:{agent_id:{type:"string"},cwd:{type:"string",maxLength:4096},task:{type:"string",maxLength:1e4},title:{type:"string",maxLength:255}}}},{name:"grix_session_send",description:"Send a message into a session AS THE OWNER \u2014 it appears as if the owner sent it, NOT as you (the agent). Dispatch callbacks (report_dispatch_result) MUST use this tool, even when you are a member of the callback session: a message sent as yourself cannot quote-wake the dispatcher agent, and your membership is not checked. NEVER use this for your own ordinary replies in a conversation \u2014 that would make your words show up as the owner's message; reply normally (or use grix_message_send to send as yourself) for those. The owner must be a member of the target session. Requires the Send as Owner permission scope \u2014 if rejected for missing permission, surface the error as-is so the owner can grant it; do not fall back to grix_message_send. Optional quoted_message_id quotes a message in the same session (used by dispatch callbacks to wake the dispatcher agent).",inputSchema:{type:"object",properties:{session_id:{type:"string",description:"Target session ID."},content:{type:"string",description:"Message content to send as the owner."},quoted_message_id:{type:"string",description:"Optional message ID in the target session to quote/reply to.",pattern:"^[1-9]\\d*$"}},required:["session_id","content"]},validation:{required:["session_id","content"],properties:{session_id:{type:"string"},content:{type:"string",maxLength:1e4},quoted_message_id:{type:"string",pattern:"^[1-9]\\d*$"}}}},{name:"grix_chat_state_query",description:"Query the chat-level task states across all of your owner's sessions (including direct and group chats). Returns one entry per session with a single mutually-exclusive state: running (working), waiting_approval (blocked on your owner to approve/deny), waiting_question (asked the owner a question, awaiting their reply), completed, failed, or idle (no task / stopped). When session_id identifies one completed session, the entry also includes exactly one clean final_result; non-completed and list queries never include message content. Also returns the session title (task_title) for easy identification. Supports pagination (page/page_size) and optional state filtering. Use this to see at a glance which chats are done, still running, or waiting on the owner.",inputSchema:{type:"object",properties:{session_id:{type:"string",description:"(Optional) Query a single session by its ID. Omit to return all sessions."},page:{type:"number",description:"(Optional) Page number, starting from 1. Defaults to 1 if omitted."},page_size:{type:"number",description:"(Optional) Number of items per page, max 100. Defaults to 10 if omitted."},state:{type:"string",description:"(Optional) Filter by a specific state: running, waiting_approval, waiting_question, completed, failed, or idle. Omit to return all states."}}},validation:{required:[],properties:{}}},{name:"grix_chat_state_update",description:"Manually update the task state of a specific chat session. Use this to mark a chat as completed, failed, idle, or any other state when you need to override it manually. The reason is written to stop_reason and is optional.",inputSchema:{type:"object",properties:{session_id:{type:"string",description:"The session ID whose state to update."},state:{type:"string",enum:["running","waiting_approval","waiting_question","completed","failed","idle"],description:"The new state to set. Must be one of: running, waiting_approval, waiting_question, completed, failed, idle."},reason:{type:"string",description:"(Optional) Reason for the state change, written to stop_reason."}}},validation:{required:["session_id","state"],properties:{}}},{name:"grix_access_control",description:"Manage sender access control: pair approval, allow/remove senders, set policy.",inputSchema:{type:"object",properties:{action:{type:"string",enum:["pair_approve","pair_deny","allow_sender","remove_sender","set_policy"],description:"Access control action type."},code:{type:"string",description:"Pairing code (required for pair_approve/pair_deny)."},sender_id:{type:"string",description:"Sender ID (required for allow_sender/remove_sender)."},policy:{type:"string",enum:["allowlist","open","disabled"],description:"Access policy (required for set_policy)."}},required:["action"]},validation:{required:["action"],properties:{action:{type:"string",enum:["pair_approve","pair_deny","allow_sender","remove_sender","set_policy"]},code:{type:"string"},sender_id:{type:"string"},policy:{type:"string",enum:["allowlist","open","disabled"]}}}},{name:"grix_webhook_create",description:'Create a webhook endpoint for a session this agent belongs to. POSTing {"content":"..."} to the returned URL sends that text into the session as the owner and wakes the agent \u2014 use it for human/timer-driven scheduled or recurring triggers (cron, schtasks, launchd). Not for agent-to-agent dispatch callbacks.',inputSchema:{type:"object",properties:{session_id:{type:"string",description:"Session ID the webhook posts into (required). Must be a session this agent is a member of \u2014 normally the current chat_id."},expires_at:{type:"string",description:"Optional RFC3339 expiry; omit for a non-expiring endpoint."}},required:["session_id"]},validation:{required:["session_id"],properties:{session_id:{type:"string"},expires_at:{type:"string"}}}},{name:"grix_webhook_list",description:"List the active webhook endpoints (with full URLs) of a session this agent belongs to. Call this BEFORE grix_webhook_create and reuse an existing endpoint instead of creating another one.",inputSchema:{type:"object",properties:{session_id:{type:"string",description:"Session ID (required). Must be a session this agent is a member of \u2014 normally the current chat_id."}},required:["session_id"]},validation:{required:["session_id"],properties:{session_id:{type:"string"}}}},{name:"grix_webhook_delete",description:"Delete a webhook endpoint by id (from grix_webhook_list / grix_webhook_create). Only endpoints of sessions this agent belongs to. Use when tearing down scheduled triggers or removing duplicate endpoints.",inputSchema:{type:"object",properties:{id:{type:"string",description:"Webhook endpoint id (required, pass as string)."}},required:["id"]},validation:{required:["id"],properties:{id:{type:"string"}}}},{name:"grix_widget_visitor_ban",description:"Ban a widget visitor session: blocks the session and adds its most recent init IP to the owner's global IP ban list (7-day expiry).",inputSchema:{type:"object",properties:{session_id:{type:"string",description:"Widget visitor session ID (required)."}},required:["session_id"]},validation:{required:["session_id"],properties:{session_id:{type:"string"}}}},{name:"grix_egg_search",description:"Search the Grix egg market (published skill/persona packages the owner can hatch into a new agent or install into an existing one). Keyword matching is per-term AND over name + description + category, so search short keywords (one concept per call) rather than a whole sentence; omit keyword to browse a category. Results include id, name, description, category, install_count, can_create_agent, and existing_agent_client_types.",inputSchema:{type:"object",properties:{keyword:{type:"string",description:"Short search keyword(s); terms are ANDed. Omit to list by category."},categoryId:{type:"string",description:"Optional egg category ID to restrict results."},locale:{type:"string",description:"Optional locale such as zh-CN or en; defaults to the owner's locale."},page:{type:"integer",description:"Page number, starting from 1."},pageSize:{type:"integer",description:"Items per page (1-50), default 20."}}},validation:{required:[],properties:{keyword:{type:"string",maxLength:200},categoryId:{type:"string"},locale:{type:"string",maxLength:16},page:{type:"integer",minimum:1},pageSize:{type:"integer",minimum:1,maximum:50}}}},{name:"grix_egg_get",description:"Get one Grix egg by id \u2014 full description, current version, and whether it can create a new agent or install into existing agents. Use after grix_egg_search to show details before the owner decides to hatch.",inputSchema:{type:"object",properties:{id:{type:"string",description:"Egg ID from grix_egg_search."},locale:{type:"string",description:"Optional locale."},version:{type:"integer",description:"Optional specific version; defaults to the current one."}},required:["id"]},validation:{required:["id"],properties:{id:{type:"string"},locale:{type:"string",maxLength:16},version:{type:"integer",minimum:1}}}},{name:"grix_skill_set",description:"Create, update, or delete one of your owner's custom skills in the platform skill library. The skill library auto-syncs to every machine the owner runs an agent on (a per-machine `grix/skills` directory), so defining a skill here makes it available on all of them. A skill is a plain SKILL.md package (frontmatter + body), same standard as other Grix skills. Provide `name` and the full `content` (the SKILL.md text) to create or overwrite by name. To delete a skill, pass its `name` with an empty `content` string. How and when any agent actually loads/uses a synced skill is arranged by the owner (in a system prompt or in conversation); this tool only manages the library.",inputSchema:{type:"object",properties:{name:{type:"string",description:"Skill name (max 100 characters); unique within the owner. Used as the identifier and the synced directory name."},content:{type:"string",description:"Full SKILL.md text. Pass an empty string to delete the skill by name."}},required:["name","content"]},validation:{required:["name","content"],properties:{name:{type:"string",maxLength:100},content:{type:"string",maxLength:262144}}}},{name:"grix_skill_get",description:"Read one of your owner's custom skills by name (returns its full SKILL.md content), or list the owner's skill library when no name is given. Use this before editing to fetch current content, then call grix_skill_set with the revised text.",inputSchema:{type:"object",properties:{name:{type:"string",description:"Skill name to fetch. Omit to list all skills (names + versions) instead."}}},validation:{required:[],properties:{name:{type:"string",maxLength:100}}}}],a=[{name:"grix_reply",description:"Send your final reply \u2014 the conclusion the user is waiting for \u2014 to the specified session. This is the message the user treats as your answer; deliver your final result here, and use plain text only for brief progress notes while you work. Supports streaming in chunks; the frontend automatically aggregates them into one complete message. The connector quotes the message being answered automatically, so quoted_message_id is optional (set it only to quote a different earlier message).",inputSchema:{type:"object",properties:{event_id:{type:"string",description:"Associated event ID from the inbound event."},session_id:{type:"string",description:"Target session ID."},text:{type:"string",description:"Reply text content."},quoted_message_id:{type:"string",description:"Quoted message ID (optional)."},is_final:{type:"boolean",description:"Whether this is a stage-final reply. Advisory only \u2014 does not trigger event completion; completion is handled by the complete tool or Stop hook."}},required:["session_id","text"]},validation:{required:["session_id","text"],properties:{event_id:{type:"string"},session_id:{type:"string"},text:{type:"string",maxLength:5e4},quoted_message_id:{type:"string"},is_final:{type:"boolean"}}}},{name:"grix_complete",description:"Mark event processing as complete, notifying the backend that no more replies are expected.",inputSchema:{type:"object",properties:{event_id:{type:"string",description:"The event ID to complete."},status:{type:"string",enum:["responded","canceled","failed"],description:"Completion status."},msg:{type:"string",description:"Additional note (optional)."}},required:["event_id","status"]},validation:{required:["event_id","status"],properties:{event_id:{type:"string"},status:{type:"string",enum:["responded","canceled","failed"]},msg:{type:"string",maxLength:500}}}},{name:"grix_event_ack",description:"Acknowledge event receipt (usually done automatically by the Dispatcher; agents typically do not need to call this manually).",inputSchema:{type:"object",properties:{event_id:{type:"string",description:"The event ID to acknowledge."},session_id:{type:"string",description:"Session ID."}},required:["event_id"]},validation:{required:["event_id"],properties:{event_id:{type:"string"},session_id:{type:"string"}}}},{name:"grix_composing",description:'Set the "typing" indicator status for a session.',inputSchema:{type:"object",properties:{session_id:{type:"string",description:"Session ID."},active:{type:"boolean",description:"true = typing, false = stopped."},event_id:{type:"string",description:"Associated event ID (optional)."}},required:["session_id","active"]},validation:{required:["session_id","active"],properties:{session_id:{type:"string"},active:{type:"boolean"},event_id:{type:"string"}}}},{name:"grix_status",description:"Query the Grix connection status of the current MCP session.",inputSchema:{type:"object",properties:{}},validation:{required:[],properties:{}}}],w={grix_dispatch_agent:"grix-agent-dispatch",grix_agent_update:"grix-agent-dispatch",grix_query:"grix-query",grix_audit_data:"grix-audit-data",grix_group:"grix-group",grix_message_send:"message-send",grix_message_unsend:"message-unsend",grix_admin:"grix-admin",grix_call_owner:"grix-owner-relay",grix_session_send:"grix-owner-relay",grix_chat_state_query:"grix-chat-state",grix_chat_state_update:"grix-chat-state",grix_status:"grix-chat-state",grix_access_control:"grix-access-control",grix_widget_visitor_ban:"grix-widget-visitor-ban",grix_webhook_create:"grix-scheduled-trigger",grix_webhook_list:"grix-scheduled-trigger",grix_webhook_delete:"grix-scheduled-trigger",grix_file_link:"tailnet-file-share"};function v(e){return` Before calling this tool, follow the \`${e}\` skill's procedure first; do not invoke this tool directly without going through that skill's guidance.`}for(const e of[...r,...a]){const t=w[e.name];t&&(e.description+=v(t))}const p=[{name:"reply",description:"Send a visible message back to the chat for this grix-claude event.",inputSchema:{type:"object",properties:{text:{type:"string",description:"The visible reply text to send."},chat_id:{type:"string",description:"The target chat/session id from the <channel> tag."},event_id:{type:"string",description:"The Aibot event_id from the <channel> tag."},reply_to:{type:"string",description:"Optional message_id to quote instead of the inbound trigger message."},final:{type:"boolean",description:"Advisory flag only. It does not complete the event; completion is handled by complete tool or Stop hook."}},required:["chat_id","event_id","text"]}},{name:"complete",description:"Finish an event without sending a visible reply so the backend does not time out.",inputSchema:{type:"object",properties:{event_id:{type:"string",description:"The Aibot event_id from the <channel> tag."},status:{type:"string",enum:["responded","canceled","failed"]},msg:{type:"string"},code:{type:"string"}},required:["event_id","status"]}}],c=new Set(p.map(e=>e.name)),x=new Set(r.map(e=>e.name)),k=new Set(a.map(e=>e.name)),I=/([A-Za-z0-9._-]+:[A-Za-z0-9._-]+:[A-Za-z0-9._-]+:[A-Za-z0-9._-]+)/,S=/[A-Za-z0-9._-]+/;function V(e){return!!(x.has(e)||k.has(e)||c.has(e)||e.startsWith("mcp__grix"))}const q=[...r,...a],A=[...q,...p],X=new Map(A.map(e=>[e.name,e]));function Y(e,t){return e==="reply"?{name:"grix_reply",args:g("grix_reply",{event_id:t.event_id,session_id:t.chat_id,text:t.text,quoted_message_id:t.reply_to,is_final:t.final})}:e==="complete"?{name:"grix_complete",args:g("grix_complete",{event_id:t.event_id,status:t.status,msg:t.msg,code:t.code})}:{name:e,args:t}}function l(e){const t=String(e??"").trim();return t?t.match(I)?.[1]:void 0}function u(e){const t=String(e??"").trim();if(!t)return;const n=l(t);if(n)return _(n);const i=t.match(/(?:chat_id|session_id)\s*=\s*"([A-Za-z0-9._-]+)"/)?.[1];if(i)return i;const o=t.match(/[A-Za-z0-9._-]+/g)??[];for(const s of o)if(!(s==="event_id"||s==="chat_id"||s==="session_id")&&s.length>0)return s;return t.match(S)?.[0]}function _(e){if(!e)return;const t=e.split(":",1)[0]?.trim();if(t)return u(t)}function g(e,t){if(e!=="grix_reply"&&e!=="grix_complete")return t;const n={...t},i=l(n.event_id);if(i&&(n.event_id=i),e==="grix_reply"){const o=_(i),d=String(n.session_id??""),s=u(n.session_id),b=/\bevent_id\b|["'<>\s]/.test(d);s&&!(b&&o)?n.session_id=s:o&&(n.session_id=o)}return n}function J(e){return c.has(e)}function ee(e,t){switch(e){case"grix_query":return T(t);case"grix_audit_data":return D(t);case"grix_group":return L(t);case"grix_message_send":return E(t);case"grix_message_unsend":return j(t);case"grix_file_link":return N(t);case"grix_file_upload":return C(t);case"grix_admin":return K(t);case"grix_call_owner":return P(t);case"grix_agent_update":return U(t);case"grix_dispatch_agent":return z(t);case"grix_session_send":return R(t);case"grix_chat_state_query":return G(t);case"grix_chat_state_update":return $(t);case"grix_access_control":return B(t);case"grix_widget_visitor_ban":return W(t);case"grix_webhook_create":return F(t);case"grix_webhook_list":return{action:"webhook_list",params:{session_id:t.session_id}};case"grix_webhook_delete":return{action:"webhook_delete",params:{id:t.id}};case"grix_egg_search":return O(t);case"grix_egg_get":return M(t);case"grix_skill_set":return Q(t);case"grix_skill_get":return H(t);default:throw new Error(`Unknown tool: ${e}`)}}const m={contact_search:"contact_search",session_search:"session_search",message_history:"message_history",message_search:"message_search",search_favorite_sessions:"search_favorite_sessions"};function T(e){const t=String(e.action??""),n=m[t];if(!n)throw new Error(`Unknown grix_query action: ${t}`);const i={};return e.id!=null&&(i.id=e.id),e.peerId!=null&&(i.peer_id=e.peerId),e.keyword!=null&&(i.keyword=e.keyword),e.sessionType!=null&&(i.session_type=e.sessionType),e.limit!=null?i.limit=e.limit:t==="message_history"&&(i.limit=1),e.offset!=null&&(i.offset=e.offset),e.sessionId!=null&&(i.session_id=e.sessionId),e.beforeId!=null&&(i.before_id=e.beforeId),{action:n,params:i}}function O(e){const t={};return e.keyword!=null&&(t.keyword=e.keyword),e.categoryId!=null&&(t.category_id=e.categoryId),e.locale!=null&&(t.locale=e.locale),e.page!=null&&(t.page=e.page),e.pageSize!=null&&(t.page_size=e.pageSize),{action:"egg_search",params:t}}function M(e){const t={id:e.id};return e.locale!=null&&(t.locale=e.locale),e.version!=null&&(t.version=e.version),{action:"egg_get",params:t}}const h={get_manifest:"audit_get_manifest",list_spans:"audit_list_spans",get_content_chunk:"audit_get_content_chunk"};function D(e){const t=String(e.action??""),n=h[t];if(!n)throw new Error(`Unknown grix_audit_data action: ${t}`);const i={audit_id:e.auditId};return e.revision!=null&&(i.revision=e.revision),e.cursor!=null&&(i.cursor=e.cursor),e.limit!=null&&(i.limit=e.limit),e.contentId!=null&&(i.content_id=e.contentId),e.maxBytes!=null&&(i.max_bytes=e.maxBytes),{action:n,params:i}}const y={create:"group_create",detail:"group_detail_read",leave:"group_leave_self",add_members:"group_member_add",remove_members:"group_member_remove",update_member_role:"group_member_role_update",update_all_members_muted:"group_all_members_muted_update",update_member_speaking:"group_member_speaking_update",dissolve:"group_dissolve"};function L(e){const t=String(e.action??""),n=y[t];if(!n)throw new Error(`Unknown grix_group action: ${t}`);const i={};return e.sessionId!=null&&(i.session_id=e.sessionId),e.name!=null&&(i.name=e.name),e.memberIds!=null&&(i.member_ids=e.memberIds),e.memberTypes!=null&&(i.member_types=e.memberTypes),e.memberId!=null&&(i.member_id=e.memberId),e.role!=null&&(i.role=e.role),e.memberType!=null&&(i.member_type=e.memberType),e.allMembersMuted!=null&&(i.all_members_muted=e.allMembersMuted),e.isSpeakMuted!=null&&(i.is_speak_muted=e.isSpeakMuted),e.canSpeakWhenAllMuted!=null&&(i.can_speak_when_all_muted=e.canSpeakWhenAllMuted),{action:n,params:i}}function E(e){const t={session_id:e.sessionId,msg_type:e.msgType??1,content:e.content};return e.quotedMessageId!=null&&(t.quoted_message_id=e.quotedMessageId),e.threadId!=null&&(t.thread_id=e.threadId),{action:"send_msg",params:t}}function j(e){return{action:"delete_msg",params:{session_id:e.sessionId,msg_id:e.msgId}}}function N(e){const t={file_path:e.file_path};return e.ttl_ms!=null&&(t.ttl_ms=e.ttl_ms),{action:"file_link",params:t}}function C(e){const t={file_path:e.file_path,session_id:e.session_id};return e.caption!=null&&(t.caption=e.caption),e.reply_to_message_id!=null&&(t.reply_to_message_id=e.reply_to_message_id),{action:"file_upload",params:t,timeoutMs:6e4}}const f={create_agent:"agent_api_create",list_categories:"agent_category_list",create_category:"agent_category_create",update_category:"agent_category_update",assign_category:"agent_category_assign",rotate_api_key:"agent_api_key_rotate"};function P(e){return{action:"call_owner",params:{session_id:e.session_id}}}function U(e){if(e.agent_name==null&&e.introduction==null)throw new Error("grix_agent_update requires agent_name or introduction");const t={agent_id:e.agent_id};return e.agent_name!=null&&(t.agent_name=e.agent_name),e.introduction!=null&&(t.introduction=e.introduction),{action:"agent_introduction_update",params:t}}function z(e){return{action:"dispatch_agent",params:{agent_id:e.agent_id,cwd:e.cwd,task:e.task,title:e.title},timeoutMs:75e3}}function R(e){const t={session_id:e.session_id,content:e.content};return e.quoted_message_id!=null&&String(e.quoted_message_id).trim()!==""&&(t.quoted_message_id=e.quoted_message_id),{action:"session_send",params:t}}function G(e){const t={};return e.session_id!=null&&(t.session_id=e.session_id),e.page!=null&&(t.page=e.page),e.page_size!=null&&(t.page_size=e.page_size),e.state!=null&&(t.state=e.state),{action:"chat_state_query",params:t}}function B(e){const t=String(e.action??""),n=Z[t];if(!n)throw new Error(`Unknown grix_access_control action: ${t}`);const i={};return e.code!=null&&(i.code=e.code),e.sender_id!=null&&(i.sender_id=e.sender_id),e.policy!=null&&(i.policy=e.policy),{action:"claude_access_control",params:{verb:n,payload:i},timeoutMs:3e4}}function W(e){return{action:"widget_visitor_ban",params:{session_id:e.session_id}}}function F(e){const t={session_id:e.session_id};return e.expires_at!=null&&(t.expires_at=e.expires_at),{action:"webhook_create",params:t}}function $(e){const t={session_id:e.session_id,state:e.state};return e.reason!=null&&(t.reason=e.reason),{action:"chat_state_update",params:t}}function Q(e){return{action:"skill_set",params:{name:e.name,content:e.content},timeoutMs:3e4}}function H(e){const t={};return e.name!=null&&(t.name=e.name),{action:"skill_get",params:t}}function K(e){const t=String(e.action??""),n=f[t];if(!n)throw new Error(`Unknown grix_admin action: ${t}`);const i={};return e.agentName!=null&&(i.agent_name=e.agentName),e.introduction!=null&&(i.introduction=e.introduction),e.isMain!=null&&(i.is_main=e.isMain),e.agentId!=null&&(i.agent_id=e.agentId),e.categoryId!=null&&(i.category_id=e.categoryId),e.name!=null&&(i.name=e.name),e.parentId!=null&&(i.parent_id=e.parentId),e.sortOrder!=null&&(i.sort_order=e.sortOrder),{action:n,params:i}}const te=new Set([...Object.values(m),...Object.values(h),...Object.values(y),...Object.values(f),"send_msg","delete_msg","file_link","file_upload","call_owner","agent_introduction_update","dispatch_agent","session_send","chat_state_query","chat_state_update","claude_access_control","widget_visitor_ban","webhook_create","webhook_list","webhook_delete","skill_set","skill_get"]),Z={pair_approve:"pair_approve",pair_deny:"pair_deny",allow_sender:"sender_allow",remove_sender:"sender_remove",set_policy:"policy_set"};export{Z as ACCESS_CONTROL_ACTION_MAP,q as ALL_TOOLS,a as EVENT_TOOLS,A as EXPOSED_TOOLS,te as PHASE1_INVOKE_ACTIONS,x as PHASE1_TOOL_NAMES,k as PHASE2_TOOL_NAMES,r as TOOLS,p as TOOL_ALIASES,X as TOOL_MAP,J as isAlias,V as isGrixInternalToolName,Y as mapToolAlias,g as normalizeEventToolArgs,ee as toolCallToInvoke};
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{execFile as wt,execFileSync as R}from"node:child_process";import{randomUUID as Q}from"node:crypto";import{promisify as yt}from"node:util";import{uptime as St}from"node:os";import{closeSync as Et,copyFileSync as kt,cpSync as It,existsSync as d,linkSync as Pt,mkdirSync as E,mkdtempSync as tt,openSync as vt,readFileSync as P,renameSync as h,rmSync as p,statfsSync as xt,symlinkSync as $t,unlinkSync as H,writeFileSync as x}from"node:fs";import{basename as v,dirname as w,join as l,relative as et,resolve as u,sep as b}from"node:path";import{GRIX_PATHS as $}from"../log/index.js";import{appendRotatingFileSync as Dt}from"../log/rotation.js";import{getMachineName as Nt}from"../util/host-info.js";import{resolveInstallId as At}from"../util/install-id.js";import{NPM_REGISTRIES as Rt,npmInstallWithMirror as rt,probeRegistries as bt,resolveNpmInvocation as M}from"../installer/npm-registry.js";import{resolveWindowsServiceRuntimeDir as nt}from"../../service/service-paths.js";const it=yt(wt);class f extends Error{code;constructor(e,r){super(r),this.name="UpgradeError",this.code=e}}const Ut=/^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?(?:\+[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?$/;function O(t){const e=String(t??"").trim().replace(/^v(?=\d)/,"");if(!Ut.test(e))throw new f("INVALID_VERSION",`Invalid connector upgrade version: ${String(t)}`);return e}const Y=".grix-upgrade-backup",Ft=".grix-upgrade-failed",ot=".grix-upgrade-backup",at=".grix-upgrade-next",Lt="upgrade-wrapper.pause.json",Tt="upgrade-rollback.intent.json",Mt=".grix-connector-upgrade.lock",ct=[250,750],C=[100,250,750,1500];function st(){return l($.log,"upgrade.log")}function I(){return l($.data,"upgrade-pending.json")}function le(){let t=null;try{t=_()}catch{return{in_progress:!1}}if(!t)return{in_progress:!1};const e=t.phase??"staged";return{in_progress:!(e==="failed"||e==="rolled_back"),phase:e,from:t.from_version,target:t.target_version}}function de(){return d(I())}function ge(t,e="failed"){const r=new Date().toISOString().replace(/[:.]/g,""),n=l($.data,`upgrade-pending.${e}-${r}.json`);try{return x(n,JSON.stringify(t,null,2),"utf-8"),n}catch{return null}}function _(){const t=I();if(!d(t))return null;try{return JSON.parse(P(t,"utf-8"))}catch{return null}}function pe(t,e,r){const n={from_version:t,target_version:e,upgraded_at:new Date().toISOString(),crash_count:0,transaction_id:Q(),...r?{phase:r}:{}},i=I();E(w(i),{recursive:!0});let a;try{a=vt(i,"wx",384)}catch(o){throw o?.code==="EEXIST"?new f("UPGRADE_IN_PROGRESS","Another connector upgrade transaction is already active"):o}try{x(a,JSON.stringify(n),"utf-8")}catch(o){try{H(i)}catch{}throw o}finally{Et(a)}return n}function _e(t){const e=t.package_dir;if(!e)return null;const r=u(e);if(!r.includes(`${b}node_modules${b}`))return null;try{const n=JSON.parse(P(l(r,"package.json"),"utf-8"));return n.name!=="grix-connector"||typeof n.version!="string"?null:n.version.trim().replace(/^v/,"")||null}catch{return null}}function Ot(t,e){E(w(t),{recursive:!0});const r=`${t}.tmp-${process.pid}`;x(r,JSON.stringify(e),"utf-8"),h(r,t)}function D(t,e){const r=_();if(!r)throw new f("PENDING_MISSING","Upgrade transaction is missing");if(e&&r.transaction_id!==e)throw new f("UPGRADE_TRANSACTION_CHANGED","Upgrade transaction ownership changed");const n={...r,...t};return Ot(I(),n),n}function j(){return l($.data,Lt)}function Ct(){return l($.data,Tt)}function me(t,e){const r=_();if(!r?.transaction_id||r.phase!=="staged"||e&&r.transaction_id!==e)throw new f("INVALID_UPGRADE_STATE","Windows upgrade is not staged for activation");const n=j();E(w(n),{recursive:!0});const i=`${n}.tmp-${process.pid}`;return x(i,JSON.stringify({transaction_id:r.transaction_id}),"utf-8"),h(i,n),D({phase:"handoff_ready",old_pid:t,old_command_line:Gt(t)??void 0,old_boot_time:Date.now()-St()*1e3,activation_started_at:new Date().toISOString(),wrapper_pause_file:n},r.transaction_id)}function Gt(t){if(process.platform!=="win32"||!Number.isInteger(t)||t<=0)return null;try{const e=`${process.env.SystemRoot||"C:\\Windows"}\\System32\\WindowsPowerShell\\v1.0\\powershell.exe`;return R(e,["-NoProfile","-NonInteractive","-WindowStyle","Hidden","-Command",`(Get-CimInstance Win32_Process -Filter 'ProcessId=${t}').CommandLine`],{encoding:"utf-8",timeout:3e3,windowsHide:!0}).trim()||null}catch{return null}}function he(t,e){const r=_();if(!r?.transaction_id||r.transaction_id!==t||r.phase!=="verifying")throw new f("INVALID_UPGRADE_STATE","Windows upgrade is not awaiting verification");const n=Ct();E(w(n),{recursive:!0});const i=`${n}.tmp-${process.pid}`;x(i,JSON.stringify({transaction_id:t,message:e}),"utf-8"),h(i,n);const a=j();E(w(a),{recursive:!0});const o=`${a}.tmp-${process.pid}`;return x(o,JSON.stringify({transaction_id:t}),"utf-8"),h(o,a),D({phase:"rolling_back",guardian_pid:void 0,error_code:"STARTUP_CRASH",error_msg:e,wrapper_pause_file:a},t)}function we(){try{p(j(),{force:!0})}catch{}}function Vt(t=_()){if(z(t)){try{p(u(t.staging_prefix),{recursive:!0,force:!0})}catch{}if(ut(t))try{p(u(t.runtime_next_dir),{recursive:!0,force:!0})}catch{}}}function ut(t){if(!t?.runtime_dir||!t.runtime_backup_dir||!t.runtime_next_dir)return!1;const e=u(t.runtime_dir),r=nt($.base),n=l(w(w(r)),"runtime"),i=e===u(n),a=e===u(r)&&/^GrixConnectorDaemon-[0-9a-f]{12}$/.test(v(e))&&v(w(e))==="runtimes"&&v(w(w(e)))==="grix-connector";return(i||a)&&u(t.runtime_backup_dir)===`${e}${ot}`&&u(t.runtime_next_dir)===`${e}${at}`}function J(t){return l(u(t),Mt)}function K(t){try{const e=d(t)&&!d(l(t,"owner.json"))?t:l(t,"owner.json"),r=JSON.parse(P(e,"utf-8"));return typeof r.transaction_id=="string"&&typeof r.pending_file=="string"?r:null}catch{return null}}function ft(t){if(!t||!d(t.pending_file))return!1;try{return JSON.parse(P(t.pending_file,"utf-8")).transaction_id===t.transaction_id}catch{return!0}}function Wt(t){const e=_();if(!e?.transaction_id)throw new f("PENDING_MISSING","Upgrade transaction disappeared before npm staging");const r=J(t),n={transaction_id:e.transaction_id,pending_file:I()};D({npm_prefix:u(t),npm_lock_file:r},e.transaction_id);const i=`${r}.candidate-${e.transaction_id}-${Q()}`;try{x(i,JSON.stringify(n),{encoding:"utf-8",flag:"wx"}),Pt(i,r),p(i,{force:!0})}catch(a){try{p(i,{force:!0})}catch{}if(!d(r))throw a;const o=K(r);if(o?.transaction_id===e.transaction_id&&u(o.pending_file)===u(I()))return;throw new f(ft(o)?"UPGRADE_IN_PROGRESS":"UPGRADE_LOCK_RECOVERY_REQUIRED",ft(o)?`Another Windows service root is upgrading the shared npm prefix ${u(t)}`:`A stale or incomplete Windows upgrade lock requires recovery at ${r}`)}}function Bt(t=_(),e={}){if(!t?.transaction_id||!t.npm_prefix||!t.npm_lock_file)return!0;const r=J(t.npm_prefix);if(u(t.npm_lock_file)!==r)return!1;if(!d(r))return!0;const n=K(r);if(!n)return!1;if(n.transaction_id!==t.transaction_id||u(n.pending_file)!==u(I()))return!0;const i=e.remove??p,a=e.sleep??(o=>{Atomics.wait(new Int32Array(new SharedArrayBuffer(4)),0,0,o)});for(let o=0;;o+=1)try{return i(r,{recursive:!0,force:!0}),!d(r)}catch(c){const s=c?.code;if((s==="EBUSY"||s==="EPERM"||s==="EACCES")&&o<C.length){a(C[o]);continue}return m(`shared npm-prefix lock release failed at ${r}: ${c instanceof Error?c.message:String(c)}`),!1}}function G(t,e){const r=u(t);return u(e)===`${r}${Y}`&&r.includes(`${b}node_modules${b}`)&&v(r)==="grix-connector"}function Ht(t,e){const r=et(u(t),u(e));return r!==""&&r!==".."&&!r.startsWith(`..${b}`)}function z(t){if(!t?.npm_prefix||!t.staging_prefix||!t.staged_package_dir)return!1;const e=u(t.npm_prefix),r=u(t.staging_prefix),n=u(t.staged_package_dir);return w(r)===e&&v(r).startsWith(".grix-upgrade-stage-")&&Ht(r,n)&&v(w(n))==="node_modules"&&v(n)==="grix-connector"}function Yt(t,e){const r=_();!r||!G(t,e)||D({package_dir:u(t),backup_dir:u(e)},r.transaction_id)}function jt(t){if(!G(t.packageDir,t.backupDir))throw new f("UNSAFE_UPGRADE_PATH","Refusing to record an unsafe global package path");if(!z({npm_prefix:t.npmPrefix,staging_prefix:t.stagingPrefix,staged_package_dir:t.stagedPackageDir}))throw new f("UNSAFE_UPGRADE_PATH","Refusing to record an unsafe npm staging path");const e=_();if(!e)throw new f("PENDING_MISSING","Upgrade transaction disappeared during staging");let r;try{const n=JSON.parse(P(l(t.packageDir,"package.json"),"utf-8"));if(n.name!=="grix-connector"||typeof n.version!="string")throw new Error("invalid metadata");r=O(n.version)}catch(n){throw new f("VERSION_VERIFY_FAILED",`Unable to identify the active global package before staging: ${n instanceof Error?n.message:String(n)}`)}D({phase:"staged",activation_strategy:t.activationStrategy,package_from_version:r,npm_prefix:u(t.npmPrefix),package_dir:u(t.packageDir),backup_dir:u(t.backupDir),staged_package_dir:u(t.stagedPackageDir),staging_prefix:u(t.stagingPrefix)},e.transaction_id)}function Jt(t,e,r={}){const n=_();if(!n?.transaction_id)throw new f("PENDING_MISSING","Upgrade transaction disappeared before runtime staging");const i=nt($.base),a=`${i}${ot}`,o=`${i}${at}`;D({runtime_dir:i,runtime_backup_dir:a,runtime_next_dir:o},n.transaction_id);const c=r.copyTree??It,s=r.copyFile??kt,g=r.sleep??(k=>{Atomics.wait(new Int32Array(new SharedArrayBuffer(4)),0,0,k)});for(let k=0;;k+=1)try{E(w(i),{recursive:!0}),p(o,{recursive:!0,force:!0}),E(o,{recursive:!0}),c(l(t.stagedPackageDir,"dist"),l(o,"dist"),{recursive:!0,force:!0}),s(l(t.stagedPackageDir,"package.json"),l(o,"package.json"));const y=l(t.stagedPackageDir,"node_modules");d(y)&&c(y,l(o,"node_modules"),{recursive:!0,force:!0});const S=F(o,"grix-connector",e);L(o,S);return}catch(y){try{p(o,{recursive:!0,force:!0})}catch{}const S=y?.code;if(S==="ENOSPC")throw new f("DISK_FULL",`Insufficient space for the isolated Windows runtime: ${y.message}`);if((S==="EBUSY"||S==="EPERM"||S==="EACCES"||S==="ENOTEMPTY")&&k<ct.length){g(ct[k]);continue}throw new f("RUNTIME_STAGE_FAILED",`Could not prepare the isolated Windows runtime before shutdown: ${y instanceof Error?y.message:String(y)}`)}}function V(t,e){if(!d(t))return!0;const r=e.remove??p,n=e.sleep??(i=>{Atomics.wait(new Int32Array(new SharedArrayBuffer(4)),0,0,i)});for(let i=0;;i+=1)try{if(r(t,{recursive:!0,force:!0}),!d(t))return!0;throw Object.assign(new Error(`path still exists after removal: ${t}`),{code:"EBUSY"})}catch(a){const o=a?.code;if((o==="EBUSY"||o==="EPERM"||o==="EACCES"||o==="ENOTEMPTY")&&i<C.length){n(C[i]);continue}return m(`upgrade artifact cleanup failed at ${t}: ${a instanceof Error?a.message:String(a)}`),!1}}function ye(t=_(),e={}){if(t?.transaction_id&&_()?.transaction_id!==t.transaction_id)return!1;let r=!0,n=process.platform!=="win32";if(t?.npm_lock_file||t?.npm_prefix){if(!t.transaction_id||!t.npm_lock_file||!t.npm_prefix)return!1;const o=J(t.npm_prefix);if(u(t.npm_lock_file)!==o)return!1;if(d(o)){const c=K(o);if(c?.transaction_id!==t.transaction_id||u(c.pending_file)!==u(I()))return!1;n=!0}else{if(t.backup_dir&&d(t.backup_dir))return!1;n=!1}}const i=t?.package_dir,a=t?.backup_dir;return n&&i&&a&&G(i,a)&&(r=V(a,e)&&r),z(t)&&(r=V(u(t.staging_prefix),e)&&r),ut(t)&&(r=V(u(t.runtime_backup_dir),e)&&r,r=V(u(t.runtime_next_dir),e)&&r),r}function Se(t=_()){const e=t?.package_dir,r=t?.backup_dir;if(!e||!r||!G(e,r)||!d(r))return!1;const n=`${e}${Ft}`;try{return p(n,{recursive:!0,force:!0}),d(e)&&h(e,n),h(r,e),p(n,{recursive:!0,force:!0}),m(`atomic rollback restored ${e}`),!0}catch(i){try{!d(e)&&d(n)&&h(n,e)}catch{}return m(`atomic rollback failed: ${i instanceof Error?i.message:String(i)}`),!1}}function lt(t,e={}){const r=I();if(t){const n=_();if(!n)return!0;if(n.transaction_id!==t)return!1}if(d(r))try{(e.unlink??H)(r)}catch{return!1}return!d(r)}function Ee(t=_(),e={}){if(!t?.transaction_id)return lt(void 0,{unlink:e.unlinkPending});const r=_();if(r?.transaction_id!==t.transaction_id)return!1;const n=r.phase==="finalizing"?r:D({phase:"finalizing"},t.transaction_id);return Bt(n,{remove:e.removeLock,sleep:e.sleep})?lt(t.transaction_id,{unlink:e.unlinkPending}):!1}function m(t){const e=`[${new Date().toISOString()}] ${t}
|
|
2
|
-
`;try{Dt(st(),
|
|
1
|
+
import{execFile as wt,execFileSync as R}from"node:child_process";import{randomUUID as Q}from"node:crypto";import{promisify as yt}from"node:util";import{uptime as St}from"node:os";import{closeSync as Et,copyFileSync as kt,cpSync as It,existsSync as d,linkSync as Pt,mkdirSync as E,mkdtempSync as tt,openSync as vt,readFileSync as P,renameSync as h,rmSync as p,statfsSync as xt,symlinkSync as $t,unlinkSync as H,writeFileSync as x}from"node:fs";import{basename as v,dirname as w,join as l,relative as rt,resolve as u,sep as b}from"node:path";import{GRIX_PATHS as $}from"../log/index.js";import{appendRotatingFileSync as Dt}from"../log/rotation.js";import{getMachineName as Nt}from"../util/host-info.js";import{resolveInstallId as At}from"../util/install-id.js";import{NPM_REGISTRIES as Rt,npmInstallWithMirror as et,probeRegistries as bt,resolveNpmInvocation as M}from"../installer/npm-registry.js";import{resolveWindowsServiceRuntimeDir as nt}from"../../service/service-paths.js";const it=yt(wt);class f extends Error{code;constructor(r,e){super(e),this.name="UpgradeError",this.code=r}}const Ut=/^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?(?:\+[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?$/;function O(t){const r=String(t??"").trim().replace(/^v(?=\d)/,"");if(!Ut.test(r))throw new f("INVALID_VERSION",`Invalid connector upgrade version: ${String(t)}`);return r}const Y=".grix-upgrade-backup",Ft=".grix-upgrade-failed",ot=".grix-upgrade-backup",at=".grix-upgrade-next",Lt="upgrade-wrapper.pause.json",Tt="upgrade-rollback.intent.json",Mt=".grix-connector-upgrade.lock",ct=[250,750],C=[100,250,750,1500];function st(){return l($.log,"upgrade.log")}function I(){return l($.data,"upgrade-pending.json")}function lr(){let t=null;try{t=_()}catch{return{in_progress:!1}}if(!t)return{in_progress:!1};const r=t.phase??"staged";return{in_progress:!(r==="failed"||r==="rolled_back"),phase:r,from:t.from_version,target:t.target_version}}function dr(){return d(I())}function gr(t,r="failed"){const e=new Date().toISOString().replace(/[:.]/g,""),n=l($.data,`upgrade-pending.${r}-${e}.json`);try{return x(n,JSON.stringify(t,null,2),"utf-8"),n}catch{return null}}function _(){const t=I();if(!d(t))return null;try{return JSON.parse(P(t,"utf-8"))}catch{return null}}function pr(t,r,e){const n={from_version:t,target_version:r,upgraded_at:new Date().toISOString(),crash_count:0,transaction_id:Q(),...e?{phase:e}:{}},i=I();E(w(i),{recursive:!0});let a;try{a=vt(i,"wx",384)}catch(o){throw o?.code==="EEXIST"?new f("UPGRADE_IN_PROGRESS","Another connector upgrade transaction is already active"):o}try{x(a,JSON.stringify(n),"utf-8")}catch(o){try{H(i)}catch{}throw o}finally{Et(a)}return n}function _r(t){const r=t.package_dir;if(!r)return null;const e=u(r);if(!e.includes(`${b}node_modules${b}`))return null;try{const n=JSON.parse(P(l(e,"package.json"),"utf-8"));return n.name!=="grix-connector"||typeof n.version!="string"?null:n.version.trim().replace(/^v/,"")||null}catch{return null}}function Ot(t,r){E(w(t),{recursive:!0});const e=`${t}.tmp-${process.pid}`;x(e,JSON.stringify(r),"utf-8"),h(e,t)}function D(t,r){const e=_();if(!e)throw new f("PENDING_MISSING","Upgrade transaction is missing");if(r&&e.transaction_id!==r)throw new f("UPGRADE_TRANSACTION_CHANGED","Upgrade transaction ownership changed");const n={...e,...t};return Ot(I(),n),n}function j(){return l($.data,Lt)}function Ct(){return l($.data,Tt)}function mr(t,r){const e=_();if(!e?.transaction_id||e.phase!=="staged"||r&&e.transaction_id!==r)throw new f("INVALID_UPGRADE_STATE","Windows upgrade is not staged for activation");const n=j();E(w(n),{recursive:!0});const i=`${n}.tmp-${process.pid}`;return x(i,JSON.stringify({transaction_id:e.transaction_id}),"utf-8"),h(i,n),D({phase:"handoff_ready",old_pid:t,old_command_line:Gt(t)??void 0,old_boot_time:Date.now()-St()*1e3,activation_started_at:new Date().toISOString(),wrapper_pause_file:n},e.transaction_id)}function Gt(t){if(process.platform!=="win32"||!Number.isInteger(t)||t<=0)return null;try{const r=`${process.env.SystemRoot||"C:\\Windows"}\\System32\\WindowsPowerShell\\v1.0\\powershell.exe`;return R(r,["-NoProfile","-NonInteractive","-WindowStyle","Hidden","-Command",`(Get-CimInstance Win32_Process -Filter 'ProcessId=${t}').CommandLine`],{encoding:"utf-8",timeout:3e3,windowsHide:!0}).trim()||null}catch{return null}}function hr(t,r){const e=_();if(!e?.transaction_id||e.transaction_id!==t||e.phase!=="verifying")throw new f("INVALID_UPGRADE_STATE","Windows upgrade is not awaiting verification");const n=Ct();E(w(n),{recursive:!0});const i=`${n}.tmp-${process.pid}`;x(i,JSON.stringify({transaction_id:t,message:r}),"utf-8"),h(i,n);const a=j();E(w(a),{recursive:!0});const o=`${a}.tmp-${process.pid}`;return x(o,JSON.stringify({transaction_id:t}),"utf-8"),h(o,a),D({phase:"rolling_back",guardian_pid:void 0,error_code:"STARTUP_CRASH",error_msg:r,wrapper_pause_file:a},t)}function wr(){try{p(j(),{force:!0})}catch{}}function Vt(t=_()){if(z(t)){try{p(u(t.staging_prefix),{recursive:!0,force:!0})}catch{}if(ut(t))try{p(u(t.runtime_next_dir),{recursive:!0,force:!0})}catch{}}}function ut(t){if(!t?.runtime_dir||!t.runtime_backup_dir||!t.runtime_next_dir)return!1;const r=u(t.runtime_dir),e=nt($.base),n=l(w(w(e)),"runtime"),i=r===u(n),a=r===u(e)&&/^GrixConnectorDaemon-[0-9a-f]{12}$/.test(v(r))&&v(w(r))==="runtimes"&&v(w(w(r)))==="grix-connector";return(i||a)&&u(t.runtime_backup_dir)===`${r}${ot}`&&u(t.runtime_next_dir)===`${r}${at}`}function J(t){return l(u(t),Mt)}function K(t){try{const r=d(t)&&!d(l(t,"owner.json"))?t:l(t,"owner.json"),e=JSON.parse(P(r,"utf-8"));return typeof e.transaction_id=="string"&&typeof e.pending_file=="string"?e:null}catch{return null}}function ft(t){if(!t||!d(t.pending_file))return!1;try{return JSON.parse(P(t.pending_file,"utf-8")).transaction_id===t.transaction_id}catch{return!0}}function Wt(t){const r=_();if(!r?.transaction_id)throw new f("PENDING_MISSING","Upgrade transaction disappeared before npm staging");const e=J(t),n={transaction_id:r.transaction_id,pending_file:I()};D({npm_prefix:u(t),npm_lock_file:e},r.transaction_id);const i=`${e}.candidate-${r.transaction_id}-${Q()}`;try{x(i,JSON.stringify(n),{encoding:"utf-8",flag:"wx"}),Pt(i,e),p(i,{force:!0})}catch(a){try{p(i,{force:!0})}catch{}if(!d(e))throw a;const o=K(e);if(o?.transaction_id===r.transaction_id&&u(o.pending_file)===u(I()))return;throw new f(ft(o)?"UPGRADE_IN_PROGRESS":"UPGRADE_LOCK_RECOVERY_REQUIRED",ft(o)?`Another Windows service root is upgrading the shared npm prefix ${u(t)}`:`A stale or incomplete Windows upgrade lock requires recovery at ${e}`)}}function Bt(t=_(),r={}){if(!t?.transaction_id||!t.npm_prefix||!t.npm_lock_file)return!0;const e=J(t.npm_prefix);if(u(t.npm_lock_file)!==e)return!1;if(!d(e))return!0;const n=K(e);if(!n)return!1;if(n.transaction_id!==t.transaction_id||u(n.pending_file)!==u(I()))return!0;const i=r.remove??p,a=r.sleep??(o=>{Atomics.wait(new Int32Array(new SharedArrayBuffer(4)),0,0,o)});for(let o=0;;o+=1)try{return i(e,{recursive:!0,force:!0}),!d(e)}catch(c){const s=c?.code;if((s==="EBUSY"||s==="EPERM"||s==="EACCES")&&o<C.length){a(C[o]);continue}return m(`shared npm-prefix lock release failed at ${e}: ${c instanceof Error?c.message:String(c)}`),!1}}function G(t,r){const e=u(t);return u(r)===`${e}${Y}`&&e.includes(`${b}node_modules${b}`)&&v(e)==="grix-connector"}function Ht(t,r){const e=rt(u(t),u(r));return e!==""&&e!==".."&&!e.startsWith(`..${b}`)}function z(t){if(!t?.npm_prefix||!t.staging_prefix||!t.staged_package_dir)return!1;const r=u(t.npm_prefix),e=u(t.staging_prefix),n=u(t.staged_package_dir);return w(e)===r&&v(e).startsWith(".grix-upgrade-stage-")&&Ht(e,n)&&v(w(n))==="node_modules"&&v(n)==="grix-connector"}function Yt(t,r){const e=_();!e||!G(t,r)||D({package_dir:u(t),backup_dir:u(r)},e.transaction_id)}function jt(t){if(!G(t.packageDir,t.backupDir))throw new f("UNSAFE_UPGRADE_PATH","Refusing to record an unsafe global package path");if(!z({npm_prefix:t.npmPrefix,staging_prefix:t.stagingPrefix,staged_package_dir:t.stagedPackageDir}))throw new f("UNSAFE_UPGRADE_PATH","Refusing to record an unsafe npm staging path");const r=_();if(!r)throw new f("PENDING_MISSING","Upgrade transaction disappeared during staging");let e;try{const n=JSON.parse(P(l(t.packageDir,"package.json"),"utf-8"));if(n.name!=="grix-connector"||typeof n.version!="string")throw new Error("invalid metadata");e=O(n.version)}catch(n){throw new f("VERSION_VERIFY_FAILED",`Unable to identify the active global package before staging: ${n instanceof Error?n.message:String(n)}`)}D({phase:"staged",activation_strategy:t.activationStrategy,package_from_version:e,npm_prefix:u(t.npmPrefix),package_dir:u(t.packageDir),backup_dir:u(t.backupDir),staged_package_dir:u(t.stagedPackageDir),staging_prefix:u(t.stagingPrefix)},r.transaction_id)}function Jt(t,r,e={}){const n=_();if(!n?.transaction_id)throw new f("PENDING_MISSING","Upgrade transaction disappeared before runtime staging");const i=nt($.base),a=`${i}${ot}`,o=`${i}${at}`;D({runtime_dir:i,runtime_backup_dir:a,runtime_next_dir:o},n.transaction_id);const c=e.copyTree??It,s=e.copyFile??kt,g=e.sleep??(k=>{Atomics.wait(new Int32Array(new SharedArrayBuffer(4)),0,0,k)});for(let k=0;;k+=1)try{E(w(i),{recursive:!0}),p(o,{recursive:!0,force:!0}),E(o,{recursive:!0}),c(l(t.stagedPackageDir,"dist"),l(o,"dist"),{recursive:!0,force:!0}),s(l(t.stagedPackageDir,"package.json"),l(o,"package.json"));const y=l(t.stagedPackageDir,"node_modules");d(y)&&c(y,l(o,"node_modules"),{recursive:!0,force:!0});const S=F(o,"grix-connector",r);L(o,S);return}catch(y){try{p(o,{recursive:!0,force:!0})}catch{}const S=y?.code;if(S==="ENOSPC")throw new f("DISK_FULL",`Insufficient space for the isolated Windows runtime: ${y.message}`);if((S==="EBUSY"||S==="EPERM"||S==="EACCES"||S==="ENOTEMPTY")&&k<ct.length){g(ct[k]);continue}throw new f("RUNTIME_STAGE_FAILED",`Could not prepare the isolated Windows runtime before shutdown: ${y instanceof Error?y.message:String(y)}`)}}function V(t,r){if(!d(t))return!0;const e=r.remove??p,n=r.sleep??(i=>{Atomics.wait(new Int32Array(new SharedArrayBuffer(4)),0,0,i)});for(let i=0;;i+=1)try{if(e(t,{recursive:!0,force:!0}),!d(t))return!0;throw Object.assign(new Error(`path still exists after removal: ${t}`),{code:"EBUSY"})}catch(a){const o=a?.code;if((o==="EBUSY"||o==="EPERM"||o==="EACCES"||o==="ENOTEMPTY")&&i<C.length){n(C[i]);continue}return m(`upgrade artifact cleanup failed at ${t}: ${a instanceof Error?a.message:String(a)}`),!1}}function yr(t=_(),r={}){if(t?.transaction_id&&_()?.transaction_id!==t.transaction_id)return!1;let e=!0,n=process.platform!=="win32";if(t?.npm_lock_file||t?.npm_prefix){if(!t.transaction_id||!t.npm_lock_file||!t.npm_prefix)return!1;const o=J(t.npm_prefix);if(u(t.npm_lock_file)!==o)return!1;if(d(o)){const c=K(o);if(c?.transaction_id!==t.transaction_id||u(c.pending_file)!==u(I()))return!1;n=!0}else{if(t.backup_dir&&d(t.backup_dir))return!1;n=!1}}const i=t?.package_dir,a=t?.backup_dir;return n&&i&&a&&G(i,a)&&(e=V(a,r)&&e),z(t)&&(e=V(u(t.staging_prefix),r)&&e),ut(t)&&(e=V(u(t.runtime_backup_dir),r)&&e,e=V(u(t.runtime_next_dir),r)&&e),e}function Sr(t=_()){const r=t?.package_dir,e=t?.backup_dir;if(!r||!e||!G(r,e)||!d(e))return!1;const n=`${r}${Ft}`;try{return p(n,{recursive:!0,force:!0}),d(r)&&h(r,n),h(e,r),p(n,{recursive:!0,force:!0}),m(`atomic rollback restored ${r}`),!0}catch(i){try{!d(r)&&d(n)&&h(n,r)}catch{}return m(`atomic rollback failed: ${i instanceof Error?i.message:String(i)}`),!1}}function lt(t,r={}){const e=I();if(t){const n=_();if(!n)return!0;if(n.transaction_id!==t)return!1}if(d(e))try{(r.unlink??H)(e)}catch{return!1}return!d(e)}function Er(t=_(),r={}){if(!t?.transaction_id)return lt(void 0,{unlink:r.unlinkPending});const e=_();if(e?.transaction_id!==t.transaction_id)return!1;const n=e.phase==="finalizing"?e:D({phase:"finalizing"},t.transaction_id);return Bt(n,{remove:r.removeLock,sleep:r.sleep})?lt(t.transaction_id,{unlink:r.unlinkPending}):!1}function m(t){const r=`[${new Date().toISOString()}] ${t}
|
|
2
|
+
`;try{Dt(st(),r)}catch{}}function kr(t=4096){const r=st();if(!d(r))return"";try{const e=P(r,"utf-8");return e.length<=t?e:e.slice(-t)}catch{return""}}function dt(){try{const t=M(["--version"]);return R(t.command,t.args,{encoding:"utf-8",timeout:1e4,windowsHide:!0}).trim()}catch{throw new f("NPM_NOT_FOUND","npm is not available or timed out")}}const U=1e6;function gt(){let t;try{t=X(["prefix","-g"])}catch{return U}try{const r=xt(t),e=Math.floor(Number(r.bavail)*Number(r.bsize)/(1024*1024));return!Number.isFinite(e)||e<0?U:Math.min(e,U)}catch{return U}}function Ir(){let t;try{t=dt()}catch(e){return{ok:!1,errorCode:"NPM_NOT_FOUND",errorMsg:e instanceof Error?e.message:"npm not available"}}const r=gt();return r<100?{ok:!1,errorCode:"DISK_FULL",errorMsg:`Only ${r}MB free disk space (need >= 100MB)`,npmVersion:t,diskFreeMb:r}:{ok:!0,npmVersion:t,diskFreeMb:r}}function Pr(){let t="";try{t=dt()}catch{}let r=U;try{r=gt()}catch{}return{npm_version:t,node_version:process.version,disk_free_mb:r,platform:process.platform,arch:process.arch,host_name:Nt(),install_id:At()}}function F(t,r,e){const n=l(t,"package.json");let i;try{i=JSON.parse(P(n,"utf-8"))}catch{throw new f("VERSION_VERIFY_FAILED",`Unable to read staged package metadata: ${n}`)}if(i.name!==r||i.version?.replace(/^v(?=\d)/,"")!==e)throw new f("VERSION_MISMATCH",`Staged package ${String(i.name)}@${String(i.version)} does not match ${r}@${e}`);return i}function Kt(t){return typeof t.bin=="string"?[[v(t.name),t.bin]]:!t.bin||typeof t.bin!="object"?[]:Object.entries(t.bin).filter(r=>typeof r[1]=="string")}function L(t,r){const e=Kt(r);if(e.length===0)throw new f("VERSION_VERIFY_FAILED",`Staged package ${r.name} has no executable bin`);for(const[,n]of e){const i=l(t,n);if(!d(i))throw new f("VERSION_VERIFY_FAILED",`Staged package entrypoint is missing: ${i}`)}return e}function zt(t,r,e){if(process.platform==="win32")return;const n=l(t,"bin");E(n,{recursive:!0});for(const[i,a]of e){const o=l(n,i);if(d(o))continue;const c=`${o}.grix-upgrade-${process.pid}`;try{H(c)}catch{}$t(rt(n,l(r,a)),c),h(c,o)}}function Xt(t){const r=u(t.activePackageDir),e=u(t.stagedPackageDir),n=`${r}${Y}`,i=F(e,t.packageName,t.version),a=L(e,i);d(n)&&(d(r)?p(n,{recursive:!0,force:!0}):h(n,r)),Yt(r,n);let o=!1;try{d(r)&&(h(r,n),o=!0),h(e,r);const c=F(r,t.packageName,t.version),s=L(r,c);return zt(t.npmPrefix,r,s),{packageDir:r,backupDir:n}}catch(c){try{d(r)&&p(r,{recursive:!0,force:!0}),o&&d(n)&&h(n,r)}catch{}throw c}}function X(t,r=1e4){try{const e=M(t);return R(e.command,e.args,{encoding:"utf-8",timeout:r,windowsHide:!0}).trim()}catch{throw new f("NPM_NOT_FOUND",`Unable to resolve npm ${t.join(" ")}`)}}function W(t){return X(t)}const pt=Rt.find(t=>t.id==="official")?.url??"https://registry.npmjs.org";async function qt(t,r,e){const n=["view",`${t}@${r}`,"dist.integrity","--registry",e,"--no-audit","--no-fund"],i=M(n),{stdout:a}=await it(i.command,i.args,{timeout:1e4,windowsHide:!0});return a.trim()}async function _t(t,r,e,n={}){if(!e)return;let i;try{i=new URL(e).origin}catch{return}if(i===new URL(pt).origin)return;const a=n.fetchIntegrity??qt;let o;try{o=await a(t,r,pt)}catch(s){m(`integrity cross-check skipped: official registry unreachable (${s instanceof Error?s.message:String(s)})`);return}if(!o){m("integrity cross-check skipped: official registry returned no dist.integrity");return}let c;try{c=await a(t,r,e)}catch(s){m(`integrity cross-check skipped: mirror metadata unreadable (${s instanceof Error?s.message:String(s)})`);return}if(!c){m("integrity cross-check skipped: mirror returned no dist.integrity");return}if(c!==o)throw new f("INTEGRITY_MISMATCH",`Mirror ${e} reports dist.integrity ${c} but official npm reports ${o} for ${t}@${r}`);m(`integrity cross-check passed for ${t}@${r} via ${e}`)}async function mt(t,r,e,n={},i=process.platform==="win32"?"staged":"atomic"){const a=`${t}@${r}`,o=W(["prefix","-g"]),c=W(["root","-g"]);E(o,{recursive:!0}),i==="staged"&&Wt(o);const s=tt(l(o,".grix-upgrade-stage-"));try{m(`staging npm install ${a} under ${s}`);const g=n.installWithMirror??et,{registry:k,registryUrl:y}=await g(a,e,10*1024*1024,{prefix:s,...i==="staged"?{env:{...process.env,GRIX_UPGRADE_STAGING:"1"}}:{}});await(n.verifyIntegrityConsensus??_t)(t,r,y);const N=W(["root","-g","--prefix",s]),A=l(N,t),B=F(A,t,r),T=L(A,B),ht=l(A,T[0][1]),q=R(process.execPath,[ht,"--version"],{encoding:"utf-8",timeout:15e3,maxBuffer:2*1024*1024,windowsHide:!0}).trim().replace(/^v(?=\d)/,"");if(q!==r)throw new f("VERSION_MISMATCH",`Staged CLI reported ${q||"<empty>"}, expected ${r}`);const Z=l(c,t);return{npmPrefix:o,packageDir:Z,backupDir:`${u(Z)}${Y}`,stagedPackageDir:A,stagingPrefix:s,registry:k}}catch(g){try{p(s,{recursive:!0,force:!0})}catch{}throw g}}async function Zt(t,r,e,n){const i=await mt(t,r,e,n,"atomic");try{const a=Xt({packageName:t,version:r,npmPrefix:i.npmPrefix,activePackageDir:i.packageDir,stagedPackageDir:i.stagedPackageDir});m(`atomic package switch succeeded via ${i.registry}; backup=${a.backupDir}`)}finally{try{p(i.stagingPrefix,{recursive:!0,force:!0})}catch{}}}async function vr(t,r,e=12e4,n={}){r=O(r);const i=_()?.transaction_id,a=`${t}@${r}`,o=n.activationMode??(process.platform==="win32"?"staged":"atomic");m(`npm install -g ${a} starting (${o} staging with mirror fallback)`);try{if(o==="staged"){const c=await mt(t,r,e,n,o);try{jt({...c,activationStrategy:process.platform==="win32"?"wrapper-runtime":"service-start"}),process.platform==="win32"&&Jt(c,r)}catch(s){try{p(c.stagingPrefix,{recursive:!0,force:!0})}catch{}throw s}return m(`staged package verified via ${c.registry}; waiting for guardian activation`),{activationRequired:!0,packageDir:c.packageDir,backupDir:c.backupDir,stagedPackageDir:c.stagedPackageDir,stagingPrefix:c.stagingPrefix}}else return await Zt(t,r,e,n),{activationRequired:!1}}catch(c){if(o==="staged"){const g=_();i&&g?.transaction_id===i&&Vt(g)}if(c instanceof f)throw c;const s=c instanceof Error?c.message:String(c);throw m(`npm install failed: ${s}`),/timed out|ETIMEDOUT/i.test(s)?new f("NPM_TIMEOUT",`npm install timed out after ${e/1e3}s (tried all mirrors): ${s}`):s.includes("EACCES")||s.includes("permission denied")?new f("NPM_INSTALL_FAILED",`Permission denied: ${s}`):s.includes("ENOSPC")||s.includes("no space left")?new f("DISK_FULL",`Disk full: ${s}`):s.includes("404")||s.includes("not found")?new f("NPM_INSTALL_FAILED",`Package not found: ${s}`):new f("NPM_INSTALL_FAILED",s)}}function xr(t){let r;try{r=X(["root","-g"])}catch{throw new f("VERSION_VERIFY_FAILED","Unable to resolve the global npm package directory")}let e;try{const n=l(r,"grix-connector","package.json"),i=JSON.parse(P(n,"utf-8"));if(i.name!=="grix-connector"||typeof i.version!="string"||!i.version.trim())throw new Error("invalid package metadata");e=i.version.trim().replace(/^v(?=\d)/,"")}catch{throw new f("VERSION_VERIFY_FAILED","Unable to read the installed grix-connector version")}if(e!==t)throw new f("VERSION_MISMATCH",`Installed version ${e} does not match expected ${t}`);return e}async function $r(t,r){r=O(r);let e=!1;for(const n of await bt())try{const i=["view",`${t}@${r}`,"version","--registry",n.url,"--no-audit","--no-fund"],a=M(i),{stdout:o}=await it(a.command,a.args,{timeout:8e3,windowsHide:!0});if(o.trim())return!0}catch(i){const a=i,o=a.stderr||a.message||String(i);/ETARGET|No matching version|notarget|E404/i.test(o)&&(e=!0)}return!e}async function Dr(t,r,e,n=12e4,i={}){e=O(e);const a=`${r}@${e}`,o=l(t,"versions",e);E(l(t,"versions"),{recursive:!0});const c=tt(l(t,"versions",`.stage-${e}-`));m(`installing ${a} into version store under ${c}`);try{const s=i.installWithMirror??et,{registry:g,registryUrl:k}=await s(a,n,10*1024*1024,{prefix:c,env:{...process.env,GRIX_UPGRADE_STAGING:"1"},ignoreScripts:!0});await(i.verifyIntegrityConsensus??_t)(r,e,k);const S=W(["root","-g","--prefix",c]),N=l(S,r),A=F(N,r,e),B=L(N,A),T=R(process.execPath,[l(N,B[0][1]),"--version"],{encoding:"utf-8",timeout:15e3,maxBuffer:2*1024*1024,windowsHide:!0}).trim().replace(/^v(?=\d)/,"");if(T!==e)throw new f("VERSION_MISMATCH",`Installed CLI reported ${T||"<empty>"}, expected ${e}`);return p(o,{recursive:!0,force:!0}),h(N,o),m(`${a} verified via ${g}; placed at ${o}`),{registry:g,versionDir:o}}catch(s){if(s instanceof f)throw s;const g=s instanceof Error?s.message:String(s);throw m(`version store install failed: ${g}`),/timed out|ETIMEDOUT/i.test(g)?new f("NPM_TIMEOUT",`npm install timed out after ${n/1e3}s (tried all mirrors): ${g}`):g.includes("ENOSPC")||g.includes("no space left")?new f("DISK_FULL",`Disk full: ${g}`):new f("NPM_INSTALL_FAILED",g)}finally{try{p(c,{recursive:!0,force:!0})}catch{}}}export{f as UpgradeError,Wt as acquireNpmPrefixUpgradeLock,gr as archivePendingJournal,mr as armWindowsUpgradeHandoff,hr as armWindowsUpgradeRollback,gt as checkDiskSpace,dt as checkNpmAvailable,yr as cleanupPendingBackup,Pr as collectEnvInfo,Xt as commitStagedGlobalPackage,Vt as discardPendingStaging,Er as finalizePendingTransaction,kr as getUpgradeLogTail,Dr as installVersionIntoStore,$r as isVersionAvailable,O as normalizeUpgradeVersion,vr as npmInstall,dr as pendingExists,Ir as preflightCheck,Jt as prepareStagedWindowsRuntime,_ as readPending,_r as readPendingPackageVersion,Bt as releasePendingUpgradeLock,lt as removePending,wr as removeWindowsHandoffFiles,Sr as rollbackPendingInstall,D as updatePending,m as upgradeLog,lr as upgradeStatusSnapshot,xr as verifyInstalledVersion,_t as verifyMirrorIntegrityConsensus,Ct as windowsRollbackIntentPath,j as windowsWrapperPausePath,pr as writePending,Ot as writePendingData};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{closeSync as O,existsSync as y,mkdirSync as L,openSync as he,readFileSync as P,realpathSync as pe,renameSync as fe,writeFileSync as ge}from"node:fs";import{join as g,dirname as me}from"node:path";import{spawn as W}from"node:child_process";import{log as m}from"../log/index.js";import{GRIX_PATHS as _}from"../log/index.js";import{resolveClientVersion as A}from"../util/client-version.js";import{UpgradeError as S,armWindowsUpgradeHandoff as C,archivePendingJournal as _e,armWindowsUpgradeRollback as we,cleanupPendingBackup as H,collectEnvInfo as ve,discardPendingStaging as $,finalizePendingTransaction as x,getUpgradeLogTail as E,isVersionAvailable as F,npmInstall as ye,normalizeUpgradeVersion as Se,pendingExists as q,preflightCheck as B,readPending as I,readPendingPackageVersion as j,removeWindowsHandoffFiles as z,rollbackPendingInstall as be,updatePending as U,upgradeLog as n,verifyInstalledVersion as Te,writePending as Ae}from"./npm-upgrader.js";import{isRetriableRegistryError as K,isTransientInstallError as V}from"../installer/npm-registry.js";import{installVersionIntoStore as ke}from"./npm-upgrader.js";import{ensureLauncher as Pe,markHealthy as Re,pruneVersions as $e,readPointer as X,readRollbackRecord as Ee,readSwitchRecord as Ie,resolveLauncherStore as J,rollbackRecordPath as Ue,switchCurrent as De,switchRecordPath as Ne,versionDir as Y,writeSwitchRecord as Me}from"./version-store.js";import{rmSync as D}from"node:fs";import{randomUUID as Ge}from"node:crypto";const Oe=360*60*1e3,Le=300*1e3,Z=1800*1e3,We=2,Ce=3,He=10,R="grix-connector",Q=1e4,N=15,ee=2e3,te=8e3,xe=1e3,Fe=2e3,qe=120*1e3,re=1e4,Be=6e4,je=3,ze=600*1e3,Ke=9e4,Ve=new Set(["preparing","staged","handoff_ready","activating","verifying","healthy","rolling_back","rolled_back","finalizing","failed"]),ae=600*1e3,M=1800*1e3;function G(o,e=ae){const t=Date.parse(o.activation_started_at??o.upgraded_at??"");return Number.isNaN(t)?!0:Date.now()-t>e}function ie(o){const e=Date.parse(o.verification_started_at??o.activation_started_at??"");return Number.isNaN(e)||Date.now()-e>qe}function Xe(o){if((o.guardian_handback_count??0)>=je)return!1;const e=Date.parse(o.guardian_handback_at??"");return Number.isNaN(e)?!0:Date.now()-e>=ze}function Je(o){if(!Number.isInteger(o)||(o??0)<=0)return!1;try{return process.kill(o,0),!0}catch{return!1}}function ne(o){try{return pe(o)}catch{return o}}function se(){try{L(g(_.base,"log"),{recursive:!0});const o=he(g(_.base,"log","upgrade.log"),"a");return{stdio:["ignore",o,o],logFd:o}}catch{return{stdio:"ignore"}}}function k(){const o=g(_.base,"bin"),e=g(o,"upgrade-guardian.mjs");if(y(e))return ne(e);const t=g(o,"upgrade-guardian.fallback.mjs");return y(t)?ne(t):null}function oe(o){if(!Je(o.guardian_pid))return!1;try{const t=JSON.parse(P(g(_.data,"upgrade-monitor-heartbeat.json"),"utf-8"));if(t.transaction_id===o.transaction_id&&t.pid===o.guardian_pid&&typeof t.updated_at=="number"&&Date.now()-t.updated_at<=re)return!0}catch{}const e=Date.parse(o.guardian_started_at??"");return Number.isFinite(e)&&Date.now()-e<=re}function ce(o){return new URL(o.replace(/^wss:/,"https:").replace(/^ws:/,"http:")).origin}function de(){return g(_.data,"upgrade-state.json")}function le(){const o=de();if(!y(o))return{daily_attempts:{},version_attempts:{}};try{return JSON.parse(P(o,"utf-8"))}catch{return{daily_attempts:{},version_attempts:{}}}}function Ye(o){const e=de();L(me(e),{recursive:!0});const t=e+".tmp";ge(t,JSON.stringify(o),"utf-8"),fe(t,e)}function ue(){return new Date().toISOString().slice(0,10)}class ht{agentConfigs;isBusy;closeAdmission;options;timer=null;initialTimer=null;pendingTimer=null;healthyTimer=null;guardianWaitLoggedAt=0;guardianWaitLoggedPhase="";recordedRollbackFailures=new Set;posixGuardianResumeAttempted=new Set;running=!1;stopped=!1;constructor(e,t,r,a={}){this.agentConfigs=e,this.isBusy=t,this.closeAdmission=r,this.options=a}async start(){await this.handleLauncherStartup(),await this.handlePendingOnStartup(),this.initialTimer=setTimeout(()=>{this.stopped||(this.runCheck(),!this.stopped&&(this.timer=setInterval(()=>this.runCheck(),Oe)))},Le)}stop(){this.stopped=!0,this.initialTimer&&(clearTimeout(this.initialTimer),this.initialTimer=null),this.timer&&(clearInterval(this.timer),this.timer=null),this.pendingTimer&&(clearTimeout(this.pendingTimer),this.pendingTimer=null),this.healthyTimer&&(clearTimeout(this.healthyTimer),this.healthyTimer=null)}triggerCheck(){this.stopped||this.runCheck()}async checkForUpdate(){return this.agentConfigs.length===0?{available:!1}:(await Promise.all(this.agentConfigs.map(r=>this.queryUpgrade(r)))).find(r=>r.available&&r.release)??{available:!1}}async handlePendingOnStartup(){if(!q())return;let e=I();if(!e){n("daemon startup: pending journal is unreadable; preserving it and blocking automatic upgrade for manual recovery");return}if(e.phase&&!Ve.has(e.phase)&&(e=U({phase:"failed",error_code:"INVALID_UPGRADE_STATE",error_msg:`Unknown upgrade transaction phase: ${String(e.phase)}`},e.transaction_id)),e.phase==="finalizing"){this.settleTransaction(e)||(n(`terminal transaction ${e.transaction_id??"legacy"} is waiting for its shared lock to be released`),this.schedulePendingReconcile());return}if(e.phase==="preparing"||e.phase==="staged"){const c=!!(e.wrapper_pause_file&&y(e.wrapper_pause_file));if(e.phase==="staged"&&c){this.schedulePendingReconcile();return}$(e),e=U({phase:"rolled_back",error_code:"UPGRADE_INTERRUPTED",error_msg:"Upgrade stopped before Windows activation started"})}if(e.phase==="verifying"){if(oe(e)&&!ie(e)){this.schedulePendingReconcile();return}if(ie(e)&&e.transaction_id&&["win32","darwin"].includes(this.options.platform??process.platform)&&this.options.requestGracefulShutdown){const c=`Target ${e.target_version} did not become healthy before the Windows verification deadline`;we(e.transaction_id,c),n(`${c}; requesting graceful shutdown for offline rollback`),await this.options.requestGracefulShutdown("upgrade");return}await this.resumeWindowsGuardian(),this.schedulePendingReconcile();return}const t=new Set(["handoff_ready","activating","rolling_back"]);if(e.phase&&t.has(e.phase)){if(oe(e)){this.logGuardianWait(e),this.schedulePendingReconcile();return}if(G(e)&&e.transaction_id&&(this.options.platform??process.platform)==="win32"&&this.options.requestGracefulShutdown&&Xe(e)){const c=`Guardian did not settle ${e.phase} for ${e.target_version} before the Windows activation deadline`;U({guardian_handback_count:(e.guardian_handback_count??0)+1,guardian_handback_at:new Date().toISOString()},e.transaction_id),n(`${c}; requesting graceful shutdown so the wrapper re-runs the guardian`),await this.options.requestGracefulShutdown("upgrade");return}if(G(e))n(`daemon startup: guardian did not settle ${e.phase} for ${e.target_version} and cannot be resumed; collapsing the transaction by version verdict`),z(),$(e);else{await this.resumeWindowsGuardian(),this.logGuardianWait(e),this.schedulePendingReconcile();return}}if(e.phase==="failed"){const c=j(e),f=A();if(!c||c!==f){n(`daemon startup: upgrade transaction requires manual recovery: ${e.error_msg??e.error_code??"unknown error"} (runtime=${f}, active package=${c??"unknown"})`);return}const b=_e(e);n(`daemon startup: failed transaction ${e.transaction_id??"legacy"} (${e.error_code??"unknown error"}) is converging on the runnable ${f}${b?`; diagnostics kept at ${b}`:""}`)}const r=j(e),a=A(),i=e.phase==="healthy",l=e.phase==="rolled_back",d=!r||r===e.target_version,s=i?a===e.target_version&&r===e.target_version:!l&&a===e.target_version&&d;if(!s&&d&&!l){if(!G(e)){if((this.options.platform??process.platform)!=="win32"&&r===e.target_version){const c=e.transaction_id??`${e.target_version}@${e.upgraded_at}`;this.posixGuardianResumeAttempted.has(c)||(this.posixGuardianResumeAttempted.add(c),n(`resuming POSIX guardian for stalled activation of ${e.target_version}`),this.startGuardian())}this.logThrottled("posix-activation-wait",`daemon startup: runtime ${a} is waiting for guardian activation of ${e.target_version} (active package=${r??"unknown"})`),this.schedulePendingReconcile();return}n(`daemon startup: activation wait for ${e.target_version} expired (> ${ae/6e4}min); no guardian will finish the switch, reporting by version verdict`)}const u=s?{from_version:e.from_version,to_version:e.target_version,status:"success"}:{from_version:e.from_version,to_version:e.target_version,status:"rolled_back",error_code:e.error_code??"STARTUP_CRASH",error_msg:e.error_msg,crash_count:e.crash_count};if(n(s?`daemon startup: version matches target ${e.target_version}, upgrade succeeded`:`daemon startup: runtime=${a}, active package=${r??"unknown"}, target=${e.target_version}; rolled back`),!s){const c=e.transaction_id??`${e.target_version}@${e.upgraded_at}`;this.recordedRollbackFailures.has(c)||(this.recordedRollbackFailures.add(c),this.recordFailure(e.target_version))}if(s){const c=await this.confirmHealthz(te,i?e.target_version:void 0,i?e.old_pid:void 0,i?e.activation_started_at:void 0);if(i&&!c){n("guardian marked healthy but target-version healthz is not yet observable; retrying reconciliation"),this.schedulePendingReconcile();return}n(c?"healthz confirmed healthy, reporting success":"healthz not confirmed within window; reporting success on version-match (guardian still guards crashes)")}if(!H(e)){n(`terminal transaction ${e.transaction_id??"legacy"} is waiting for antivirus-held artifact cleanup`),this.schedulePendingReconcile();return}if(await this.reportUpgrade(u)){this.settleTransaction(e)||(n(`terminal transaction ${e.transaction_id??"legacy"} could not release its shared lock; retrying reconciliation`),this.schedulePendingReconcile());return}this.retryStartupReport(u,e,!1)}settleTransaction(e){if(!x(e))return!1;try{this.options.onTransactionSettled?.()}catch(t){n(`onTransactionSettled hook failed: ${t instanceof Error?t.message:String(t)}`)}return!0}schedulePendingReconcile(){this.stopped||this.pendingTimer||(this.pendingTimer=setTimeout(()=>{this.pendingTimer=null,!this.stopped&&this.handlePendingOnStartup().catch(e=>{m.warn("upgrade",`Pending reconciliation failed: ${e instanceof Error?e.message:e}`),this.schedulePendingReconcile()})},Fe))}logThrottled(e,t){const r=Date.now();e===this.guardianWaitLoggedPhase&&r-this.guardianWaitLoggedAt<Be||(this.guardianWaitLoggedPhase=e,this.guardianWaitLoggedAt=r,n(t))}logGuardianWait(e){const t=String(e.phase??"");this.logThrottled(`guardian:${t}`,`transaction ${e.transaction_id??"legacy"} is ${t}; waiting for guardian verdict`)}async resumeWindowsGuardian(){if(this.options.resumeWindowsGuardian){await this.options.resumeWindowsGuardian();return}const e=k();if(!e)return;const{stdio:t,logFd:r}=se();try{W(process.execPath,[e,"activate","--root",_.base],{detached:!0,stdio:t,windowsHide:!0}).unref()}finally{if(r!==void 0)try{O(r)}catch{}}}async retryStartupReport(e,t,r){const a=t?.transaction_id,i=()=>{const l=I();if(!(a&&l?.transaction_id!==a)){if(r&&!H(t)){n(`terminal backup cleanup is still blocked for ${a??"legacy transaction"}`),this.schedulePendingReconcile();return}this.settleTransaction(t)||(n(`terminal shared lock release is still blocked for ${a??"legacy transaction"}`),this.schedulePendingReconcile())}};for(let l=2;l<=N;l++){if(this.stopped)return;const d=ee*(l-1)+Math.floor(Math.random()*1e3);if(await new Promise(s=>setTimeout(s,d)),this.stopped)return;if(await this.reportUpgrade(e)){i();return}}n(`startup report (${e.status}) not delivered after ${N} attempts; clearing pending to unblock future checks`),i()}async confirmHealthz(e,t,r,a){let i="19579";try{const s=g(_.data,"health-port");y(s)&&(i=P(s,"utf-8").trim()||i)}catch{}const l=`http://127.0.0.1:${i}/healthz`,d=Date.now()+e;for(;Date.now()<d&&!this.stopped;){try{const s=await fetch(l,{signal:AbortSignal.timeout(2e3)});if(s.ok){if(!t)return!0;const u=await s.json();if(u.status==="ok"&&u.version===t&&typeof u.pid=="number"&&u.pid>0&&u.pid!==r)try{const c=JSON.parse(P(g(_.base,"daemon-status.json"),"utf-8")),f=Date.parse(a??"");return!!(c.state==="running"&&c.pid===u.pid&&typeof c.updated_at=="number"&&Number.isFinite(f)&&c.updated_at>=f)}catch{return!1}}}catch{}await new Promise(s=>setTimeout(s,xe))}return!1}async runCheck(){if(!this.running){this.running=!0;try{await this.check()}catch(e){m.error("upgrade",`Check failed: ${e instanceof Error?e.message:e}`)}finally{this.running=!1}}}async check(){if(q()){await this.handlePendingOnStartup();return}if(this.agentConfigs.length===0)return;const r=(await Promise.all(this.agentConfigs.map(h=>this.queryUpgrade(h)))).find(h=>h.available&&h.release)?.release;if(!r)return;const a=A();let i;try{i=Se(r.version)}catch(h){const p=h;await this.reportUpgrade({from_version:a,to_version:String(r.version),status:"failed",error_code:p.code??"INVALID_VERSION",error_msg:p.message});return}const l=r.force,d=this.options.platform??process.platform,s=this.options.env??process.env,u=J(s);if(u&&this.options.requestGracefulShutdown){await this.runLauncherUpgrade(u,a,i,!!l);return}if(d==="win32"){const h=k(),p=s.GRIX_WRAPPER_SUPERVISED==="1";if(!p||!h||!this.options.requestGracefulShutdown){const w=`Windows automatic upgrade requires a refreshed service installation (${[...p?[]:["managed wrapper"],...h?[]:["external guardian"],...this.options.requestGracefulShutdown?[]:["graceful shutdown hook"]].join(", ")})`;n(w),await this.reportUpgrade({from_version:a,to_version:i,status:"failed",error_code:"WINDOWS_UPGRADE_UNSUPPORTED",error_msg:w});return}}const c=d==="darwin"&&!!k()&&!!this.options.requestGracefulShutdown;if(d==="darwin"&&!c&&n("macOS transactional upgrade prerequisites missing; falling back to the legacy guardian flow"),!l&&!this.checkRateLimit(i))return;const f=B();if(!f.ok){await this.reportUpgrade({from_version:a,to_version:i,status:"failed",error_code:f.errorCode,error_msg:f.errorMsg});return}if(!await F(R,i)){n(`target ${i} not resolvable on any registry yet; skipping this cycle (no failure recorded)`);return}n(`upgrade start: ${a} -> ${i}`);const b=Date.now();let v;try{v=Ae(a,i,d==="win32"||c?"preparing":void 0).transaction_id,(await ye(R,i,void 0,c?{activationMode:"staged"}:{}))?.activationRequired||Te(i),n("npm install verified, reporting installed"),await this.reportUpgrade({from_version:a,to_version:i,status:"installed",duration_ms:Date.now()-b}),n("shutting down for restart");const p=await this.waitUntilIdle();if(p!=="idle")throw n(`upgrade aborted before lifecycle handoff: ${p}`),this.options.reopenLifecycleAdmission?.(),new S("UPGRADE_ABORTED",`Upgrade was aborted before lifecycle handoff (${p})`);if(d==="win32"){if(!k())throw new S("WINDOWS_UPGRADE_UNSUPPORTED","Windows external guardian disappeared before lifecycle handoff");C(process.pid,v),n("Windows handoff armed; requesting graceful daemon shutdown"),await this.options.requestGracefulShutdown("upgrade")}else if(c){if(!k())throw new S("MACOS_UPGRADE_UNSUPPORTED","macOS guardian helper disappeared before lifecycle handoff");C(process.pid,v),n("macOS handoff armed; spawning guardian before graceful daemon shutdown"),await this.resumeWindowsGuardian(),await this.options.requestGracefulShutdown("upgrade")}else this.startGuardian(),process.kill(process.pid,"SIGTERM")}catch(h){const p=h instanceof S?h.code:"NPM_INSTALL_FAILED",T=h instanceof Error?h.message:String(h);n(`upgrade failed: ${p} ${T}`);const w=I();if(v&&w?.transaction_id===v&&(d==="win32"||c?(z(),$(w)):be(w)&&n("upgrade failed before restart; previous package restored atomically"),x(w)||(n(`failed transaction ${v} is waiting for shared lock release`),this.schedulePendingReconcile()),this.options.reopenLifecycleAdmission?.()),p==="UPGRADE_IN_PROGRESS")return;K(T)||V(T)||p==="NPM_TIMEOUT"?n(`failure is retriable (${p}); skipping cooldown, will retry next cycle`):this.recordFailure(i),await this.reportUpgrade({from_version:a,to_version:i,status:"failed",error_code:p,error_msg:T,duration_ms:Date.now()-b,upgrade_log:E()})}}async runLauncherUpgrade(e,t,r,a){if(!a&&!this.checkRateLimit(r))return;const i=B();if(!i.ok){await this.reportUpgrade({from_version:t,to_version:r,status:"failed",error_code:i.errorCode,error_msg:i.errorMsg});return}if(!await F(R,r)){n(`target ${r} not resolvable on any registry yet; skipping this cycle (no failure recorded)`);return}n(`launcher upgrade start: ${t} -> ${r} (store=${e})`);const l=Date.now();try{await ke(e,R,r),await this.reportUpgrade({from_version:t,to_version:r,status:"installed",duration_ms:Date.now()-l});const d=await this.waitUntilIdle();if(d!=="idle")throw this.options.reopenLifecycleAdmission?.(),new S("UPGRADE_ABORTED",`Upgrade was aborted before lifecycle handoff (${d})`);De(e,r),Me(e,{transaction_id:Ge(),from_version:t,to_version:r,started_at:new Date().toISOString()}),n(`current pointer switched to ${r}; requesting restart via launcher`),await this.options.requestGracefulShutdown("upgrade-switch")}catch(d){const s=d instanceof S?d.code:"NPM_INSTALL_FAILED",u=d instanceof Error?d.message:String(d);if(n(`launcher upgrade failed: ${s} ${u}`),X(e,"current")!==r)try{D(Y(e,r),{recursive:!0,force:!0})}catch{}K(u)||V(u)||s==="NPM_TIMEOUT"?n(`failure is retriable (${s}); skipping cooldown, will retry next cycle`):this.recordFailure(r),await this.reportUpgrade({from_version:t,to_version:r,status:"failed",error_code:s,error_msg:u,duration_ms:Date.now()-l,upgrade_log:E()})}}async handleLauncherStartup(){const e=this.options.env??process.env,t=J(e);if(!t)return;const r=A();try{Pe(t,Y(t,r))}catch(s){n(`failed to refresh launcher: ${s instanceof Error?s.message:String(s)}`)}this.healthyTimer=setTimeout(()=>{this.healthyTimer=null;try{Re(t,r)}catch(s){n(`failed to write health marker: ${s instanceof Error?s.message:String(s)}`)}},Ke),this.healthyTimer.unref?.();const a=Ee(t),i=Ie(t);if(!a&&!i)return;const l=()=>{try{D(Ue(t),{force:!0})}catch{}try{D(Ne(t),{force:!0})}catch{}const s=$e(t);s.length>0&&n(`pruned version directories: ${s.join(", ")}`)};let d;if(a&&a.from_version===r){const s=`exit_code=${a.exit_code??"n/a"} crashes=${a.crash_count??"n/a"} uptime_ms=${a.uptime_ms??"n/a"}`;n(`daemon startup: launcher rolled back ${a.to_version} -> ${r} (${s})`),this.recordFailure(a.to_version),d={from_version:a.from_version,to_version:a.to_version,status:"rolled_back",error_code:"STARTUP_CRASH",error_msg:`Target ${a.to_version} crashed before becoming healthy (${s}); launcher restored ${r}`,crash_count:a.crash_count,upgrade_log:E()}}else if(i&&i.to_version===r&&X(t,"current")===r){n(`daemon startup: running switched target ${r}, upgrade succeeded`);const s=await this.confirmHealthz(te);n(s?"healthz confirmed healthy, reporting success":"healthz not confirmed within window; reporting success on version-match"),d={from_version:i.from_version,to_version:i.to_version,status:"success"}}else if(i&&i.from_version===r)n(`daemon startup: expected ${i.to_version} but ${r} is running; reporting rolled_back`),this.recordFailure(i.to_version),d={from_version:i.from_version,to_version:i.to_version,status:"rolled_back",error_code:"STARTUP_CRASH",error_msg:`Launcher is running ${r} instead of switched target ${i.to_version}`};else{n(`daemon startup: stale launcher records (self=${r}); discarding`),l();return}if(await this.reportUpgrade(d)){l();return}(async()=>{for(let s=2;s<=N;s++){if(this.stopped||(await new Promise(u=>setTimeout(u,ee*(s-1)+Math.floor(Math.random()*1e3))),this.stopped))return;if(await this.reportUpgrade(d))break}l()})()}async waitUntilIdle(){if(this.isBusy?.()){n(`active tasks detected, waiting up to ${Math.round(M/6e4)}min until idle before restart (admission stays open while waiting)`);const e=Date.now()+M,t=5e3,r=600*1e3;let a=Date.now();for(;this.isBusy()&&!this.stopped;){if(Date.now()>=e)return m.warn("upgrade",`Idle wait exceeded ${Math.round(M/6e4)}min; abandoning this upgrade attempt. Admission was never closed, so agents kept serving. A busy state that never clears usually means an adapter leaked its busy flag \u2014 check for a turn that never completed.`),n("idle wait timed out; upgrade attempt abandoned without ever closing admission"),"timeout";await new Promise(i=>setTimeout(i,t)),Date.now()-a>=r&&(a=Date.now(),n("still waiting for active tasks to finish before restart"))}}return this.stopped?"stopped":(this.closeAdmission?.(),this.isBusy?.()?(n("work arrived while closing admission; reopening and abandoning this upgrade attempt"),this.options.reopenLifecycleAdmission?.(),"busy-again"):(n("all tasks completed, proceeding with restart"),"idle"))}checkRateLimit(e){const t=le();if(t.last_failure_at){const l=Date.now()-new Date(t.last_failure_at).getTime();if(l<Z)return m.info("upgrade",`In cooldown, ${(Z-l)/6e4}m remaining`),!1}const r=t.version_attempts[e]??0;if(r>=Ce)return m.info("upgrade",`Version ${e} already tried ${r} times, skipping`),!1;const a=ue(),i=t.daily_attempts[a]??0;return i>=We?(m.info("upgrade",`Already ${i} attempts today, skipping`),!1):!0}recordFailure(e){const t=le(),r=ue();t.last_failure_at=new Date().toISOString(),t.last_failure_version=e,t.daily_attempts[r]=(t.daily_attempts[r]??0)+1,t.version_attempts[e]=(t.version_attempts[e]??0)+1;for(const i of Object.keys(t.daily_attempts))i<r&&delete t.daily_attempts[i];const a=Object.keys(t.version_attempts);for(const i of a.slice(0,Math.max(0,a.length-He)))i!==e&&delete t.version_attempts[i];Ye(t)}async queryUpgrade(e){const r=`${ce(e.wsUrl)}/v1/agent-api/upgrade/check?`+new URLSearchParams({client_type:"grix-connector",client_version:A(),channel:e.channel??"stable",platform:process.platform,arch:process.arch}).toString();try{const a=await fetch(r,{headers:{Authorization:`Bearer ${e.apiKey}`},signal:AbortSignal.timeout(Q)});if(!a.ok)return m.warn("upgrade",`Check API returned ${a.status}`),{available:!1};const i=await a.json();return i.code!==0?{available:!1}:i.data}catch(a){return m.warn("upgrade",`Check API error: ${a instanceof Error?a.message:a}`),{available:!1}}}async reportUpgrade(e){if(this.agentConfigs.length===0)return!1;const t={client_type:"grix-connector",...e.npm_version?e:{...e,...ve()}};return(await Promise.all(this.agentConfigs.map(async a=>{const l=`${ce(a.wsUrl)}/v1/agent-api/upgrade/report`;try{return(await fetch(l,{method:"POST",headers:{Authorization:`Bearer ${a.apiKey}`,"Content-Type":"application/json"},body:JSON.stringify(t),signal:AbortSignal.timeout(Q)})).ok}catch{return!1}}))).some(a=>a)}startGuardian(){const e=g(_.base,"bin","upgrade-guardian.sh");if(process.platform==="win32"||!y(e)){m.info("upgrade","Guardian not available on this platform, skipping");return}const{stdio:t,logFd:r}=se();try{const a=W(e,[],{detached:!0,stdio:t});a.unref(),n(`guardian started (pid ${a.pid})`)}catch(a){m.warn("upgrade",`Failed to start guardian: ${a instanceof Error?a.message:a}`)}finally{if(r!==void 0)try{O(r)}catch{}}}}export{ht as UpgradeChecker};
|
|
1
|
+
import{closeSync as O,existsSync as v,mkdirSync as L,openSync as ge,readFileSync as R,realpathSync as _e,renameSync as me,writeFileSync as we}from"node:fs";import{join as g,dirname as ve}from"node:path";import{spawn as C}from"node:child_process";import{log as _}from"../log/index.js";import{GRIX_PATHS as m}from"../log/index.js";import{resolveClientVersion as A}from"../util/client-version.js";import{UpgradeError as b,armWindowsUpgradeHandoff as W,archivePendingJournal as ye,armWindowsUpgradeRollback as be,cleanupPendingBackup as H,collectEnvInfo as Se,discardPendingStaging as I,finalizePendingTransaction as x,getUpgradeLogTail as $,isVersionAvailable as F,npmInstall as Te,normalizeUpgradeVersion as Ae,pendingExists as q,preflightCheck as B,readPending as U,readPendingPackageVersion as j,removeWindowsHandoffFiles as z,rollbackPendingInstall as ke,updatePending as D,upgradeLog as n,verifyInstalledVersion as Pe,writePending as Re}from"./npm-upgrader.js";import{isRetriableRegistryError as K,isTransientInstallError as X}from"../installer/npm-registry.js";import{installVersionIntoStore as $e}from"./npm-upgrader.js";import{ensureLauncher as Ee,markHealthy as Ie,pruneVersions as Ue,readPointer as Y,readRollbackRecord as De,readSwitchRecord as Ne,resolveLauncherStore as J,rollbackRecordPath as V,switchCurrent as Me,switchRecordPath as Z,versionDir as Q,versionEntry as Ge,writeSwitchRecord as ee}from"./version-store.js";import{rmSync as k}from"node:fs";import{randomUUID as Oe}from"node:crypto";const Le=360*60*1e3,Ce=300*1e3,te=1800*1e3,We=2,He=3,xe=10,E="grix-connector",re=1e4,N=15,ie=2e3,ae=8e3,Fe=1e3,qe=2e3,Be=120*1e3,ne=1e4,je=6e4,ze=3,Ke=600*1e3,Xe=9e4,Ye=3e4,Je=new Set(["preparing","staged","handoff_ready","activating","verifying","healthy","rolling_back","rolled_back","finalizing","failed"]),se=600*1e3,M=1800*1e3;function G(d,e=se){const t=Date.parse(d.activation_started_at??d.upgraded_at??"");return Number.isNaN(t)?!0:Date.now()-t>e}function oe(d){const e=Date.parse(d.verification_started_at??d.activation_started_at??"");return Number.isNaN(e)||Date.now()-e>Be}function Ve(d){if((d.guardian_handback_count??0)>=ze)return!1;const e=Date.parse(d.guardian_handback_at??"");return Number.isNaN(e)?!0:Date.now()-e>=Ke}function Ze(d){if(!Number.isInteger(d)||(d??0)<=0)return!1;try{return process.kill(d,0),!0}catch{return!1}}function ce(d){try{return _e(d)}catch{return d}}function de(){try{L(g(m.base,"log"),{recursive:!0});const d=ge(g(m.base,"log","upgrade.log"),"a");return{stdio:["ignore",d,d],logFd:d}}catch{return{stdio:"ignore"}}}function P(){const d=g(m.base,"bin"),e=g(d,"upgrade-guardian.mjs");if(v(e))return ce(e);const t=g(d,"upgrade-guardian.fallback.mjs");return v(t)?ce(t):null}function le(d){if(!Ze(d.guardian_pid))return!1;try{const t=JSON.parse(R(g(m.data,"upgrade-monitor-heartbeat.json"),"utf-8"));if(t.transaction_id===d.transaction_id&&t.pid===d.guardian_pid&&typeof t.updated_at=="number"&&Date.now()-t.updated_at<=ne)return!0}catch{}const e=Date.parse(d.guardian_started_at??"");return Number.isFinite(e)&&Date.now()-e<=ne}function ue(d){return new URL(d.replace(/^wss:/,"https:").replace(/^ws:/,"http:")).origin}function he(){return g(m.data,"upgrade-state.json")}function pe(){const d=he();if(!v(d))return{daily_attempts:{},version_attempts:{}};try{return JSON.parse(R(d,"utf-8"))}catch{return{daily_attempts:{},version_attempts:{}}}}function Qe(d){const e=he();L(ve(e),{recursive:!0});const t=e+".tmp";we(t,JSON.stringify(d),"utf-8"),me(t,e)}function fe(){return new Date().toISOString().slice(0,10)}class ft{agentConfigs;isBusy;closeAdmission;options;timer=null;initialTimer=null;pendingTimer=null;healthyTimer=null;guardianWaitLoggedAt=0;guardianWaitLoggedPhase="";recordedRollbackFailures=new Set;posixGuardianResumeAttempted=new Set;running=!1;stopped=!1;constructor(e,t,r,a={}){this.agentConfigs=e,this.isBusy=t,this.closeAdmission=r,this.options=a}async start(){await this.handleLauncherStartup(),await this.handlePendingOnStartup(),this.initialTimer=setTimeout(()=>{this.stopped||(this.runCheck(),!this.stopped&&(this.timer=setInterval(()=>this.runCheck(),Le)))},Ce)}stop(){this.stopped=!0,this.initialTimer&&(clearTimeout(this.initialTimer),this.initialTimer=null),this.timer&&(clearInterval(this.timer),this.timer=null),this.pendingTimer&&(clearTimeout(this.pendingTimer),this.pendingTimer=null),this.healthyTimer&&(clearTimeout(this.healthyTimer),this.healthyTimer=null)}triggerCheck(){this.stopped||this.runCheck()}async checkForUpdate(){return this.agentConfigs.length===0?{available:!1}:(await Promise.all(this.agentConfigs.map(r=>this.queryUpgrade(r)))).find(r=>r.available&&r.release)??{available:!1}}async handlePendingOnStartup(){if(!q())return;let e=U();if(!e){n("daemon startup: pending journal is unreadable; preserving it and blocking automatic upgrade for manual recovery");return}if(e.phase&&!Je.has(e.phase)&&(e=D({phase:"failed",error_code:"INVALID_UPGRADE_STATE",error_msg:`Unknown upgrade transaction phase: ${String(e.phase)}`},e.transaction_id)),e.phase==="finalizing"){this.settleTransaction(e)||(n(`terminal transaction ${e.transaction_id??"legacy"} is waiting for its shared lock to be released`),this.schedulePendingReconcile());return}if(e.phase==="preparing"||e.phase==="staged"){const o=!!(e.wrapper_pause_file&&v(e.wrapper_pause_file));if(e.phase==="staged"&&o){this.schedulePendingReconcile();return}I(e),e=D({phase:"rolled_back",error_code:"UPGRADE_INTERRUPTED",error_msg:"Upgrade stopped before Windows activation started"})}if(e.phase==="verifying"){if(le(e)&&!oe(e)){this.schedulePendingReconcile();return}if(oe(e)&&e.transaction_id&&["win32","darwin"].includes(this.options.platform??process.platform)&&this.options.requestGracefulShutdown){const o=`Target ${e.target_version} did not become healthy before the Windows verification deadline`;be(e.transaction_id,o),n(`${o}; requesting graceful shutdown for offline rollback`),await this.options.requestGracefulShutdown("upgrade");return}await this.resumeWindowsGuardian(),this.schedulePendingReconcile();return}const t=new Set(["handoff_ready","activating","rolling_back"]);if(e.phase&&t.has(e.phase)){if(le(e)){this.logGuardianWait(e),this.schedulePendingReconcile();return}if(G(e)&&e.transaction_id&&(this.options.platform??process.platform)==="win32"&&this.options.requestGracefulShutdown&&Ve(e)){const o=`Guardian did not settle ${e.phase} for ${e.target_version} before the Windows activation deadline`;D({guardian_handback_count:(e.guardian_handback_count??0)+1,guardian_handback_at:new Date().toISOString()},e.transaction_id),n(`${o}; requesting graceful shutdown so the wrapper re-runs the guardian`),await this.options.requestGracefulShutdown("upgrade");return}if(G(e))n(`daemon startup: guardian did not settle ${e.phase} for ${e.target_version} and cannot be resumed; collapsing the transaction by version verdict`),z(),I(e);else{await this.resumeWindowsGuardian(),this.logGuardianWait(e),this.schedulePendingReconcile();return}}if(e.phase==="failed"){const o=j(e),f=A();if(!o||o!==f){n(`daemon startup: upgrade transaction requires manual recovery: ${e.error_msg??e.error_code??"unknown error"} (runtime=${f}, active package=${o??"unknown"})`);return}const S=ye(e);n(`daemon startup: failed transaction ${e.transaction_id??"legacy"} (${e.error_code??"unknown error"}) is converging on the runnable ${f}${S?`; diagnostics kept at ${S}`:""}`)}const r=j(e),a=A(),i=e.phase==="healthy",s=e.phase==="rolled_back",u=!r||r===e.target_version,l=i?a===e.target_version&&r===e.target_version:!s&&a===e.target_version&&u;if(!l&&u&&!s){if(!G(e)){if((this.options.platform??process.platform)!=="win32"&&r===e.target_version){const o=e.transaction_id??`${e.target_version}@${e.upgraded_at}`;this.posixGuardianResumeAttempted.has(o)||(this.posixGuardianResumeAttempted.add(o),n(`resuming POSIX guardian for stalled activation of ${e.target_version}`),this.startGuardian())}this.logThrottled("posix-activation-wait",`daemon startup: runtime ${a} is waiting for guardian activation of ${e.target_version} (active package=${r??"unknown"})`),this.schedulePendingReconcile();return}n(`daemon startup: activation wait for ${e.target_version} expired (> ${se/6e4}min); no guardian will finish the switch, reporting by version verdict`)}const c=l?{from_version:e.from_version,to_version:e.target_version,status:"success"}:{from_version:e.from_version,to_version:e.target_version,status:"rolled_back",error_code:e.error_code??"STARTUP_CRASH",error_msg:e.error_msg,crash_count:e.crash_count};if(n(l?`daemon startup: version matches target ${e.target_version}, upgrade succeeded`:`daemon startup: runtime=${a}, active package=${r??"unknown"}, target=${e.target_version}; rolled back`),!l){const o=e.transaction_id??`${e.target_version}@${e.upgraded_at}`;this.recordedRollbackFailures.has(o)||(this.recordedRollbackFailures.add(o),this.recordFailure(e.target_version))}if(l){const o=await this.confirmHealthz(ae,i?e.target_version:void 0,i?e.old_pid:void 0,i?e.activation_started_at:void 0);if(i&&!o){n("guardian marked healthy but target-version healthz is not yet observable; retrying reconciliation"),this.schedulePendingReconcile();return}n(o?"healthz confirmed healthy, reporting success":"healthz not confirmed within window; reporting success on version-match (guardian still guards crashes)")}if(!H(e)){n(`terminal transaction ${e.transaction_id??"legacy"} is waiting for antivirus-held artifact cleanup`),this.schedulePendingReconcile();return}if(await this.reportUpgrade(c)){this.settleTransaction(e)||(n(`terminal transaction ${e.transaction_id??"legacy"} could not release its shared lock; retrying reconciliation`),this.schedulePendingReconcile());return}this.retryStartupReport(c,e,!1)}settleTransaction(e){if(!x(e))return!1;try{this.options.onTransactionSettled?.()}catch(t){n(`onTransactionSettled hook failed: ${t instanceof Error?t.message:String(t)}`)}return!0}schedulePendingReconcile(){this.stopped||this.pendingTimer||(this.pendingTimer=setTimeout(()=>{this.pendingTimer=null,!this.stopped&&this.handlePendingOnStartup().catch(e=>{_.warn("upgrade",`Pending reconciliation failed: ${e instanceof Error?e.message:e}`),this.schedulePendingReconcile()})},qe))}logThrottled(e,t){const r=Date.now();e===this.guardianWaitLoggedPhase&&r-this.guardianWaitLoggedAt<je||(this.guardianWaitLoggedPhase=e,this.guardianWaitLoggedAt=r,n(t))}logGuardianWait(e){const t=String(e.phase??"");this.logThrottled(`guardian:${t}`,`transaction ${e.transaction_id??"legacy"} is ${t}; waiting for guardian verdict`)}async resumeWindowsGuardian(){if(this.options.resumeWindowsGuardian){await this.options.resumeWindowsGuardian();return}const e=P();if(!e)return;const{stdio:t,logFd:r}=de();try{C(process.execPath,[e,"activate","--root",m.base],{detached:!0,stdio:t,windowsHide:!0}).unref()}finally{if(r!==void 0)try{O(r)}catch{}}}async retryStartupReport(e,t,r){const a=t?.transaction_id,i=()=>{const s=U();if(!(a&&s?.transaction_id!==a)){if(r&&!H(t)){n(`terminal backup cleanup is still blocked for ${a??"legacy transaction"}`),this.schedulePendingReconcile();return}this.settleTransaction(t)||(n(`terminal shared lock release is still blocked for ${a??"legacy transaction"}`),this.schedulePendingReconcile())}};for(let s=2;s<=N;s++){if(this.stopped)return;const u=ie*(s-1)+Math.floor(Math.random()*1e3);if(await new Promise(l=>setTimeout(l,u)),this.stopped)return;if(await this.reportUpgrade(e)){i();return}}n(`startup report (${e.status}) not delivered after ${N} attempts; clearing pending to unblock future checks`),i()}async confirmHealthz(e,t,r,a){let i="19579";try{const l=g(m.data,"health-port");v(l)&&(i=R(l,"utf-8").trim()||i)}catch{}const s=`http://127.0.0.1:${i}/healthz`,u=Date.now()+e;for(;Date.now()<u&&!this.stopped;){try{const l=await fetch(s,{signal:AbortSignal.timeout(2e3)});if(l.ok){if(!t)return!0;const c=await l.json();if(c.status==="ok"&&c.version===t&&typeof c.pid=="number"&&c.pid>0&&c.pid!==r)try{const o=JSON.parse(R(g(m.base,"daemon-status.json"),"utf-8")),f=Date.parse(a??"");return!!(o.state==="running"&&o.pid===c.pid&&typeof o.updated_at=="number"&&Number.isFinite(f)&&o.updated_at>=f)}catch{return!1}}}catch{}await new Promise(l=>setTimeout(l,Fe))}return!1}async runCheck(){if(!this.running){this.running=!0;try{await this.check()}catch(e){_.error("upgrade",`Check failed: ${e instanceof Error?e.message:e}`)}finally{this.running=!1}}}async check(){if(q()){await this.handlePendingOnStartup();return}if(this.agentConfigs.length===0)return;const r=(await Promise.all(this.agentConfigs.map(h=>this.queryUpgrade(h)))).find(h=>h.available&&h.release)?.release;if(!r)return;const a=A();let i;try{i=Ae(r.version)}catch(h){const p=h;await this.reportUpgrade({from_version:a,to_version:String(r.version),status:"failed",error_code:p.code??"INVALID_VERSION",error_msg:p.message});return}const s=r.force,u=this.options.platform??process.platform,l=this.options.env??process.env,c=J(l);if(c&&this.options.requestGracefulShutdown){await this.runLauncherUpgrade(c,a,i,!!s);return}if(u==="win32"){const h=P(),p=l.GRIX_WRAPPER_SUPERVISED==="1";if(!p||!h||!this.options.requestGracefulShutdown){const w=`Windows automatic upgrade requires a refreshed service installation (${[...p?[]:["managed wrapper"],...h?[]:["external guardian"],...this.options.requestGracefulShutdown?[]:["graceful shutdown hook"]].join(", ")})`;n(w),await this.reportUpgrade({from_version:a,to_version:i,status:"failed",error_code:"WINDOWS_UPGRADE_UNSUPPORTED",error_msg:w});return}}const o=u==="darwin"&&!!P()&&!!this.options.requestGracefulShutdown;if(u==="darwin"&&!o&&n("macOS transactional upgrade prerequisites missing; falling back to the legacy guardian flow"),!s&&!this.checkRateLimit(i))return;const f=B();if(!f.ok){await this.reportUpgrade({from_version:a,to_version:i,status:"failed",error_code:f.errorCode,error_msg:f.errorMsg});return}if(!await F(E,i)){n(`target ${i} not resolvable on any registry yet; skipping this cycle (no failure recorded)`);return}n(`upgrade start: ${a} -> ${i}`);const S=Date.now();let y;try{y=Re(a,i,u==="win32"||o?"preparing":void 0).transaction_id,(await Te(E,i,void 0,o?{activationMode:"staged"}:{}))?.activationRequired||Pe(i),n("npm install verified, reporting installed"),await this.reportUpgrade({from_version:a,to_version:i,status:"installed",duration_ms:Date.now()-S}),n("shutting down for restart");const p=await this.waitUntilIdle();if(p!=="idle")throw n(`upgrade aborted before lifecycle handoff: ${p}`),this.options.reopenLifecycleAdmission?.(),new b("UPGRADE_ABORTED",`Upgrade was aborted before lifecycle handoff (${p})`);if(u==="win32"){if(!P())throw new b("WINDOWS_UPGRADE_UNSUPPORTED","Windows external guardian disappeared before lifecycle handoff");W(process.pid,y),n("Windows handoff armed; requesting graceful daemon shutdown"),await this.options.requestGracefulShutdown("upgrade")}else if(o){if(!P())throw new b("MACOS_UPGRADE_UNSUPPORTED","macOS guardian helper disappeared before lifecycle handoff");W(process.pid,y),n("macOS handoff armed; spawning guardian before graceful daemon shutdown"),await this.resumeWindowsGuardian(),await this.options.requestGracefulShutdown("upgrade")}else this.startGuardian(),process.kill(process.pid,"SIGTERM")}catch(h){const p=h instanceof b?h.code:"NPM_INSTALL_FAILED",T=h instanceof Error?h.message:String(h);n(`upgrade failed: ${p} ${T}`);const w=U();if(y&&w?.transaction_id===y&&(u==="win32"||o?(z(),I(w)):ke(w)&&n("upgrade failed before restart; previous package restored atomically"),x(w)||(n(`failed transaction ${y} is waiting for shared lock release`),this.schedulePendingReconcile()),this.options.reopenLifecycleAdmission?.()),p==="UPGRADE_IN_PROGRESS")return;K(T)||X(T)||p==="NPM_TIMEOUT"?n(`failure is retriable (${p}); skipping cooldown, will retry next cycle`):this.recordFailure(i),await this.reportUpgrade({from_version:a,to_version:i,status:"failed",error_code:p,error_msg:T,duration_ms:Date.now()-S,upgrade_log:$()})}}async runLauncherUpgrade(e,t,r,a){if(!a&&!this.checkRateLimit(r))return;const i=B();if(!i.ok){await this.reportUpgrade({from_version:t,to_version:r,status:"failed",error_code:i.errorCode,error_msg:i.errorMsg});return}if(!await F(E,r)){n(`target ${r} not resolvable on any registry yet; skipping this cycle (no failure recorded)`);return}n(`launcher upgrade start: ${t} -> ${r} (store=${e})`);const s=Date.now();try{v(Ge(e,r))?n(`${r} already present in version store; skipping install`):await $e(e,E,r),await this.reportUpgrade({from_version:t,to_version:r,status:"installed",duration_ms:Date.now()-s});const u=await this.waitUntilIdle();if(u!=="idle")throw this.options.reopenLifecycleAdmission?.(),new b("UPGRADE_ABORTED",`Upgrade was aborted before lifecycle handoff (${u})`);const l={transaction_id:Oe(),from_version:t,to_version:r,started_at:new Date().toISOString()};ee(e,{...l,switched:!1}),Me(e,r),ee(e,{...l,switched:!0}),n(`current pointer switched to ${r}; requesting restart via launcher`),await this.options.requestGracefulShutdown("upgrade-switch")}catch(u){const l=u instanceof b?u.code:"NPM_INSTALL_FAILED",c=u instanceof Error?u.message:String(u);if(n(`launcher upgrade failed: ${l} ${c}`),l==="UPGRADE_ABORTED"){n("machine stayed busy; keeping the verified version dir and retrying next cycle without penalty"),await this.reportUpgrade({from_version:t,to_version:r,status:"failed",error_code:l,error_msg:c,duration_ms:Date.now()-s,upgrade_log:$()});return}if(Y(e,"current")!==r)try{k(Q(e,r),{recursive:!0,force:!0})}catch{}K(c)||X(c)||l==="NPM_TIMEOUT"?n(`failure is retriable (${l}); skipping cooldown, will retry next cycle`):this.recordFailure(r),await this.reportUpgrade({from_version:t,to_version:r,status:"failed",error_code:l,error_msg:c,duration_ms:Date.now()-s,upgrade_log:$()})}}async handleLauncherStartup(){const e=this.options.env??process.env,t=J(e);if(!t)return;const r=A();try{Ee(t,Q(t,r))}catch(c){n(`failed to refresh launcher: ${c instanceof Error?c.message:String(c)}`)}const a=c=>{this.healthyTimer=setTimeout(()=>{if(this.healthyTimer=null,!this.stopped){if(this.options.isConnected&&!this.options.isConnected()){n(`uptime reached but no agent websocket is ready yet; deferring health marker for ${r}`),a(Ye);return}try{Ie(t,r)}catch(o){n(`failed to write health marker: ${o instanceof Error?o.message:String(o)}`)}}},c),this.healthyTimer.unref?.()};a(Xe);const i=De(t),s=Ne(t);if(!i&&!s)return;const u=()=>{try{k(V(t),{force:!0})}catch{}try{k(Z(t),{force:!0})}catch{}const c=Ue(t);c.length>0&&n(`pruned version directories: ${c.join(", ")}`)};let l;if(i&&i.from_version===r){const c=`exit_code=${i.exit_code??"n/a"} crashes=${i.crash_count??"n/a"} uptime_ms=${i.uptime_ms??"n/a"}`;n(`daemon startup: launcher rolled back ${i.to_version} -> ${r} (${c})`),this.recordFailure(i.to_version),l={from_version:i.from_version,to_version:i.to_version,status:"rolled_back",error_code:"STARTUP_CRASH",error_msg:`Target ${i.to_version} crashed before becoming healthy (${c}); launcher restored ${r}`,crash_count:i.crash_count,upgrade_log:$()}}else if(s&&s.to_version===r&&Y(t,"current")===r){n(`daemon startup: running switched target ${r}, upgrade succeeded`);const c=await this.confirmHealthz(ae);n(c?"healthz confirmed healthy, reporting success":"healthz not confirmed within window; reporting success on version-match"),l={from_version:s.from_version,to_version:s.to_version,status:"success"}}else if(s&&s.from_version===r&&s.switched===!1){n(`daemon startup: switch record for ${s.to_version} was never applied; discarding`);try{k(Z(t),{force:!0})}catch{}try{k(V(t),{force:!0})}catch{}return}else if(s&&s.from_version===r)n(`daemon startup: expected ${s.to_version} but ${r} is running; reporting rolled_back`),this.recordFailure(s.to_version),l={from_version:s.from_version,to_version:s.to_version,status:"rolled_back",error_code:"STARTUP_CRASH",error_msg:`Launcher is running ${r} instead of switched target ${s.to_version}`};else{n(`daemon startup: stale launcher records (self=${r}); discarding`),u();return}if(await this.reportUpgrade(l)){u();return}(async()=>{for(let c=2;c<=N;c++){if(this.stopped||(await new Promise(o=>setTimeout(o,ie*(c-1)+Math.floor(Math.random()*1e3))),this.stopped))return;if(await this.reportUpgrade(l))break}u()})()}async waitUntilIdle(){if(this.isBusy?.()){n(`active tasks detected, waiting up to ${Math.round(M/6e4)}min until idle before restart (admission stays open while waiting)`);const e=Date.now()+M,t=5e3,r=600*1e3;let a=Date.now();for(;this.isBusy()&&!this.stopped;){if(Date.now()>=e)return _.warn("upgrade",`Idle wait exceeded ${Math.round(M/6e4)}min; abandoning this upgrade attempt. Admission was never closed, so agents kept serving. A busy state that never clears usually means an adapter leaked its busy flag \u2014 check for a turn that never completed.`),n("idle wait timed out; upgrade attempt abandoned without ever closing admission"),"timeout";await new Promise(i=>setTimeout(i,t)),Date.now()-a>=r&&(a=Date.now(),n("still waiting for active tasks to finish before restart"))}}return this.stopped?"stopped":(this.closeAdmission?.(),this.isBusy?.()?(n("work arrived while closing admission; reopening and abandoning this upgrade attempt"),this.options.reopenLifecycleAdmission?.(),"busy-again"):(n("all tasks completed, proceeding with restart"),"idle"))}checkRateLimit(e){const t=pe();if(t.last_failure_at){const s=Date.now()-new Date(t.last_failure_at).getTime();if(s<te)return _.info("upgrade",`In cooldown, ${(te-s)/6e4}m remaining`),!1}const r=t.version_attempts[e]??0;if(r>=He)return _.info("upgrade",`Version ${e} already tried ${r} times, skipping`),!1;const a=fe(),i=t.daily_attempts[a]??0;return i>=We?(_.info("upgrade",`Already ${i} attempts today, skipping`),!1):!0}recordFailure(e){const t=pe(),r=fe();t.last_failure_at=new Date().toISOString(),t.last_failure_version=e,t.daily_attempts[r]=(t.daily_attempts[r]??0)+1,t.version_attempts[e]=(t.version_attempts[e]??0)+1;for(const i of Object.keys(t.daily_attempts))i<r&&delete t.daily_attempts[i];const a=Object.keys(t.version_attempts);for(const i of a.slice(0,Math.max(0,a.length-xe)))i!==e&&delete t.version_attempts[i];Qe(t)}async queryUpgrade(e){const r=`${ue(e.wsUrl)}/v1/agent-api/upgrade/check?`+new URLSearchParams({client_type:"grix-connector",client_version:A(),channel:e.channel??"stable",platform:process.platform,arch:process.arch}).toString();try{const a=await fetch(r,{headers:{Authorization:`Bearer ${e.apiKey}`},signal:AbortSignal.timeout(re)});if(!a.ok)return _.warn("upgrade",`Check API returned ${a.status}`),{available:!1};const i=await a.json();return i.code!==0?{available:!1}:i.data}catch(a){return _.warn("upgrade",`Check API error: ${a instanceof Error?a.message:a}`),{available:!1}}}async reportUpgrade(e){if(this.agentConfigs.length===0)return!1;const t={client_type:"grix-connector",...e.npm_version?e:{...e,...Se()}};return(await Promise.all(this.agentConfigs.map(async a=>{const s=`${ue(a.wsUrl)}/v1/agent-api/upgrade/report`;try{return(await fetch(s,{method:"POST",headers:{Authorization:`Bearer ${a.apiKey}`,"Content-Type":"application/json"},body:JSON.stringify(t),signal:AbortSignal.timeout(re)})).ok}catch{return!1}}))).some(a=>a)}startGuardian(){const e=g(m.base,"bin","upgrade-guardian.sh");if(process.platform==="win32"||!v(e)){_.info("upgrade","Guardian not available on this platform, skipping");return}const{stdio:t,logFd:r}=de();try{const a=C(e,[],{detached:!0,stdio:t});a.unref(),n(`guardian started (pid ${a.pid})`)}catch(a){_.warn("upgrade",`Failed to start guardian: ${a instanceof Error?a.message:a}`)}finally{if(r!==void 0)try{O(r)}catch{}}}}export{ft as UpgradeChecker};
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{cpSync as
|
|
2
|
-
`)}function
|
|
1
|
+
import{cpSync as _,existsSync as p,mkdirSync as f,readdirSync as x,readFileSync as i,renameSync as S,rmSync as a,writeFileSync as v}from"node:fs";import{join as n}from"node:path";const m="launcher.mjs",b=75,w=/LAUNCHER_TEMPLATE_VERSION\s*=\s*(\d+)/;function y(r){return n(r,m)}function g(r,t){return n(r,"versions",t)}function L(r,t){return n(g(r,t),"dist","grix.js")}function P(r,t){return n(r,"health",`${t}.ok`)}function R(r){return n(r,"upgrade.json")}function I(r){return n(r,"rollback.json")}function d(r,t){try{return i(n(r,t),"utf8").trim()||null}catch{return null}}function l(r,t){const e=`${r}.tmp-${process.pid}`;v(e,t,"utf8"),S(e,r)}function E(r,t,e){f(r,{recursive:!0}),l(n(r,t),`${e}
|
|
2
|
+
`)}function k(r){try{const t=i(r,"utf8").match(w);return t?Number(t[1]):0}catch{return-1}}function O(r){for(const t of[n(r,"scripts",m),n(r,"dist",m)])if(p(t))return t;return null}function j(r,t){const e=O(t);if(!e)return!1;const o=y(r),c=k(e);return k(o)>=c||(f(r,{recursive:!0}),l(o,i(e,"utf8"))),!0}function C(r,t,e){const o=g(r,e);if(p(L(r,e)))return o;const c=`${o}.staging-${process.pid}`;a(c,{recursive:!0,force:!0}),f(c,{recursive:!0});try{for(const s of["package.json","dist","node_modules","scripts"]){const u=n(t,s);p(u)&&_(u,n(c,s),{recursive:!0,force:!0})}a(o,{recursive:!0,force:!0}),S(c,o)}finally{a(c,{recursive:!0,force:!0})}return o}function $(r,t){const e=d(r,"current");return e&&e!==t&&E(r,"previous",e),E(r,"current",t),e}function H(r,t){l(R(r),JSON.stringify(t))}function M(r){return N(R(r))}function U(r){return N(I(r))}function N(r){try{return JSON.parse(i(r,"utf8"))}catch{return null}}function F(r){try{v(n(r,"managed-stop"),new Date().toISOString(),"utf8")}catch{}}function J(r,t){f(n(r,"health"),{recursive:!0}),l(P(r,t),new Date().toISOString())}function G(r){const t=new Set([d(r,"current"),d(r,"previous")].filter(Boolean)),e=n(r,"versions"),o=[];let c=[];try{c=x(e)}catch{return o}for(const u of c)if(!t.has(u))try{a(n(e,u),{recursive:!0,force:!0}),o.push(u)}catch{}const s=n(r,"health");try{for(const u of x(s)){const h=u.replace(/\.ok$/,"");t.has(h)||a(n(s,u),{force:!0})}}catch{}return o}function X(r=process.env){if(r.GRIX_LAUNCHER_MANAGED!=="1")return null;const t=String(r.GRIX_LAUNCHER_STORE??"").trim();return t&&p(y(t))?t:null}function B(r,t){const e=s=>{const[u,h]=s.replace(/^v(?=\d)/,"").split("-",2);return{nums:u.split(".").map(A=>Number.parseInt(A,10)||0),pre:h??""}},o=e(r),c=e(t);for(let s=0;s<Math.max(o.nums.length,c.nums.length);s+=1){const u=(o.nums[s]??0)-(c.nums[s]??0);if(u!==0)return u<0?-1:1}return o.pre===c.pre?0:o.pre?c.pre?o.pre<c.pre?-1:1:-1:1}function q(r){try{const t=JSON.parse(i(n(r,"package.json"),"utf8"));return typeof t.version=="string"?t.version.replace(/^v(?=\d)/,""):null}catch{return null}}export{m as LAUNCHER_FILE,b as RESTART_EXIT_CODE,C as bootstrapVersionFromPackage,B as compareVersions,j as ensureLauncher,P as healthMarkerPath,y as launcherPath,J as markHealthy,F as markManagedStop,O as packagedLauncherPath,G as pruneVersions,k as readLauncherTemplateVersion,q as readPackageVersionAt,d as readPointer,U as readRollbackRecord,M as readSwitchRecord,X as resolveLauncherStore,I as rollbackRecordPath,$ as switchCurrent,R as switchRecordPath,g as versionDir,L as versionEntry,E as writePointer,H as writeSwitchRecord};
|