grix-connector 4.5.0 → 4.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- import c from"node:http";import{randomUUID as d}from"node:crypto";import{log as o}from"../../core/log/index.js";function l(t){t.writeHead(401,{"content-type":"application/json"}),t.end(JSON.stringify({error:"unauthorized"}))}function u(t){t.writeHead(404,{"content-type":"application/json"}),t.end(JSON.stringify({error:"not_found"}))}function h(t,e){t.writeHead(400,{"content-type":"application/json"}),t.end(JSON.stringify({error:e}))}function p(t,e={ok:!0}){t.writeHead(200,{"content-type":"application/json"}),t.end(JSON.stringify(e))}async function v(t){const e=[];for await(const n of t)e.push(n);const r=Buffer.concat(e).toString("utf8").trim();return r?JSON.parse(r):{}}function k(t){const e=(t.headers.authorization??"").trim();return e.toLowerCase().startsWith("bearer ")?e.slice(7).trim():""}class w{host="127.0.0.1";port=0;token;callbacks;server=null;address=null;constructor(e){this.token=d(),this.callbacks=e}getToken(){return this.token}getURL(){return this.address?`http://${this.address.address}:${this.address.port}`:""}async start(){this.server||(this.server=c.createServer(async(e,r)=>{try{await this.handleRequest(e,r)}catch(n){h(r,n instanceof Error?n.message:String(n))}}),await new Promise((e,r)=>{this.server.once("error",r),this.server.listen(this.port,this.host,()=>{this.server.off("error",r),e()})}),this.address=this.server.address(),o.info("claude-bridge",`Bridge server listening on ${this.getURL()}`))}async stop(){if(!this.server)return;const e=this.server;this.server=null,this.address=null,e.closeIdleConnections?.(),e.closeAllConnections?.(),await new Promise((r,n)=>{e.close(s=>s?n(s):r())})}async handleRequest(e,r){if(k(e)!==this.token){l(r);return}if(e.method!=="POST"){r.writeHead(405,{"content-type":"application/json"}),r.end(JSON.stringify({error:"method_not_allowed"}));return}const n=new URL(e.url,"http://localhost").pathname,s=await v(e),i=f.get(n);if(!i){u(r);return}const a=await i(this.callbacks,s);p(r,a??{ok:!0})}}const f=new Map([["/v1/worker/register",async(t,e)=>(o.info("claude-bridge",`Worker registered: ${e.worker_id} (pid=${e.pid})`),t.onRegisterWorker(e))],["/v1/worker/status",async(t,e)=>(o.info("claude-bridge",`Worker status: ${e.status}`),t.onStatusUpdate(e))],["/v1/worker/send-text",async(t,e)=>t.onSendText(e)],["/v1/worker/send-stream-chunk",async(t,e)=>t.onSendStreamChunk(e)],["/v1/worker/send-media",async(t,e)=>t.onSendMedia(e)],["/v1/worker/delete-message",async(t,e)=>t.onDeleteMessage(e)],["/v1/worker/ack-event",async(t,e)=>t.onAckEvent(e)],["/v1/worker/event-result",async(t,e)=>t.onSendEventResult(e)],["/v1/worker/event-stop-ack",async(t,e)=>t.onSendEventStopAck(e)],["/v1/worker/event-stop-result",async(t,e)=>t.onSendEventStopResult(e)],["/v1/worker/session-composing",async(t,e)=>t.onSetSessionComposing(e)],["/v1/worker/agent-invoke",async(t,e)=>t.onAgentInvoke(e)],["/v1/worker/local-action-result",async(t,e)=>t.onLocalActionResult(e)]]);export{w as ClaudeBridgeServer};
1
+ import c from"node:http";import{randomUUID as d}from"node:crypto";import{log as o}from"../../core/log/index.js";function l(t){t.writeHead(401,{"content-type":"application/json"}),t.end(JSON.stringify({error:"unauthorized"}))}function u(t){t.writeHead(404,{"content-type":"application/json"}),t.end(JSON.stringify({error:"not_found"}))}function h(t,e){t.writeHead(400,{"content-type":"application/json"}),t.end(JSON.stringify({error:e}))}function p(t,e={ok:!0}){t.writeHead(200,{"content-type":"application/json"}),t.end(JSON.stringify(e))}async function v(t){const e=[];for await(const r of t)e.push(r);const n=Buffer.concat(e).toString("utf8").trim();return n?JSON.parse(n):{}}function k(t){const e=(t.headers.authorization??"").trim();return e.toLowerCase().startsWith("bearer ")?e.slice(7).trim():""}class w{host="127.0.0.1";port=0;token;callbacks;server=null;address=null;constructor(e){this.token=d(),this.callbacks=e}getToken(){return this.token}getURL(){return this.address?`http://${this.address.address}:${this.address.port}`:""}async start(){this.server||(this.server=c.createServer(async(e,n)=>{try{await this.handleRequest(e,n)}catch(r){h(n,r instanceof Error?r.message:String(r))}}),await new Promise((e,n)=>{this.server.once("error",n),this.server.listen(this.port,this.host,()=>{this.server.off("error",n),e()})}),this.address=this.server.address(),o.info("claude-bridge",`Bridge server listening on ${this.getURL()}`))}async stop(){if(!this.server)return;const e=this.server;this.server=null,this.address=null,e.closeIdleConnections?.(),e.closeAllConnections?.(),await new Promise((n,r)=>{e.close(s=>s?r(s):n())})}async handleRequest(e,n){if(k(e)!==this.token){l(n);return}if(e.method!=="POST"){n.writeHead(405,{"content-type":"application/json"}),n.end(JSON.stringify({error:"method_not_allowed"}));return}const r=new URL(e.url,"http://localhost").pathname,s=await v(e),i=f.get(r);if(!i){u(n);return}const a=await i(this.callbacks,s);p(n,a??{ok:!0})}}const f=new Map([["/v1/worker/register",async(t,e)=>(o.info("claude-bridge",`Worker registered: ${e.worker_id} (pid=${e.pid})`),t.onRegisterWorker(e))],["/v1/worker/status",async(t,e)=>(o.info("claude-bridge",`Worker status: ${e.status}`),t.onStatusUpdate(e))],["/v1/worker/send-text",async(t,e)=>t.onSendText(e)],["/v1/worker/send-stream-chunk",async(t,e)=>t.onSendStreamChunk(e)],["/v1/worker/send-media",async(t,e)=>t.onSendMedia(e)],["/v1/worker/delete-message",async(t,e)=>t.onDeleteMessage(e)],["/v1/worker/ack-event",async(t,e)=>t.onAckEvent(e)],["/v1/worker/event-result",async(t,e)=>t.onSendEventResult(e)],["/v1/worker/event-stop-ack",async(t,e)=>t.onSendEventStopAck(e)],["/v1/worker/event-stop-result",async(t,e)=>t.onSendEventStopResult(e)],["/v1/worker/session-composing",async(t,e)=>t.onSetSessionComposing(e)],["/v1/worker/agent-invoke",async(t,e)=>t.onAgentInvoke(e)],["/v1/worker/local-action-result",async(t,e)=>t.onLocalActionResult(e)]]);export{w as ClaudeBridgeServer};
@@ -1 +1 @@
1
- import{randomUUID as x}from"node:crypto";import{CallToolRequestSchema as S,ListToolsRequestSchema as w}from"@modelcontextprotocol/sdk/types.js";import{log as f}from"../../core/log/index.js";import{toolCallToInvoke as k}from"../../core/mcp/tools.js";const E=new Set(["contact_search","session_search","message_history","message_search","group_create","group_detail_read","group_leave_self","group_member_add","group_member_remove","group_member_role_update","group_all_members_muted_update","group_member_speaking_update","group_dissolve","send_msg","delete_msg","agent_api_create","agent_category_list","agent_category_create","agent_category_update","agent_category_assign","agent_api_key_rotate"]),y=3e4,I=[{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."},files:{type:"array",items:{type:"string"},description:"Optional absolute local file paths. Each file is uploaded through Agent API OSS presign before sending."},final:{type:"boolean",description:"Whether this is the final reply for the event. Defaults to false \u2014 the event stays open while Claude continues working, and auto-completes after inactivity. Set true only when this is definitively the last message for the event."}},required:["chat_id","event_id"]}},{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"]},code:{type:"string"},msg:{type:"string"}},required:["event_id","status"]}},{name:"delete_message",description:"Delete a previously sent message in the same grix-claude chat.",inputSchema:{type:"object",properties:{chat_id:{type:"string"},message_id:{type:"string"}},required:["chat_id","message_id"]}},{name:"status",description:"Show grix-claude runtime status, upstream access state, bridge health, and startup hints.",inputSchema:{type:"object",properties:{}}},{name:"send",description:"Send a message to a chat session proactively, without requiring an inbound event. Use for notifications or scheduled reports.",inputSchema:{type:"object",properties:{chat_id:{type:"string",description:"The target chat/session id."},text:{type:"string",description:"The message text to send."}},required:["chat_id","text"]}},{name:"access_pair",description:"Forward a sender pairing approval code to upstream access control.",inputSchema:{type:"object",properties:{code:{type:"string"}},required:["code"]}},{name:"access_deny",description:"Forward a sender pairing denial code to upstream access control.",inputSchema:{type:"object",properties:{code:{type:"string"}},required:["code"]}},{name:"allow_sender",description:"Ask upstream access control to allow a sender_id.",inputSchema:{type:"object",properties:{sender_id:{type:"string"}},required:["sender_id"]}},{name:"remove_sender",description:"Ask upstream access control to remove a sender_id.",inputSchema:{type:"object",properties:{sender_id:{type:"string"}},required:["sender_id"]}},{name:"access_policy",description:"Ask upstream access control to update the sender access policy.",inputSchema:{type:"object",properties:{policy:{type:"string",enum:["allowlist","open","disabled"]}},required:["policy"]}},{name:"grix_query",description:"Search contacts, sessions, message history, or messages by keyword in the Grix/AIBot platform.",inputSchema:{type:"object",properties:{action:{type:"string",enum:["contact_search","session_search","message_history","message_search"]},keyword:{type:"string"},id:{type:"string"},sessionId:{type:"string"},limit:{type:"number"},offset:{type:"number"},beforeId:{type:"string"}},required:["action"]}},{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"]},sessionId:{type:"string"},name:{type:"string"},memberIds:{type:"array",items:{type:"string"}},memberTypes:{type:"array",items:{type:"integer",enum:[1,2]}},memberId:{type:"string"},role:{type:"integer",enum:[1,2]},memberType:{type:"integer",description:"Member type (for update_member_role / update_member_speaking)."},allMembersMuted:{type:"boolean"},isSpeakMuted:{type:"boolean"},canSpeakWhenAllMuted:{type:"boolean",description:"Allow speaking when all muted (for update_member_speaking)."}},required:["action"]}},{name:"grix_message_send",description:"Send a message to a session in the Grix/AIBot platform.",inputSchema:{type:"object",properties:{sessionId:{type:"string"},content:{type:"string"},msgType:{type:"number"},quotedMessageId:{type:"string"},threadId:{type:"string"}},required:["sessionId","content"]}},{name:"grix_message_unsend",description:"Recall/unsend a message in the Grix/AIBot platform.",inputSchema:{type:"object",properties:{sessionId:{type:"string"},msgId:{type:"string"}},required:["sessionId","msgId"]}},{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"]},agentId:{type:"string"},agentName:{type:"string"},introduction:{type:"string"},isMain:{type:"boolean"},categoryId:{type:"string"},name:{type:"string"},parentId:{type:"string"},sortOrder:{type:"number"}},required:["action"]}}];function q(n,e){n.setRequestHandler(w,async()=>({tools:I})),n.setRequestHandler(S,async i=>{const{name:r,arguments:t}=i.params,s=t??{};try{switch(r){case"reply":return await A(s,e);case"complete":return await $(s,e);case"delete_message":return await T(s,e);case"status":return j(e);case"send":return await M(s,e);case"access_pair":case"access_deny":case"allow_sender":case"remove_sender":case"access_policy":return await R(r,s,e);case"grix_query":case"grix_group":case"grix_message_send":case"grix_message_unsend":case"grix_admin":return await O(r,s,e);default:return{content:[{type:"text",text:`Unknown tool: ${r}`}],isError:!0}}}catch(a){return f.error("claude-tools",`Tool ${r} error: ${a}`),{content:[{type:"text",text:`Error: ${a instanceof Error?a.message:String(a)}`}],isError:!0}}})}async function A(n,e){const i=e.getActiveEvent();if(!i)return{content:[{type:"text",text:"No active event to reply to"}],isError:!0};const r=String(n.text??""),t=String(n.chat_id??""),s=String(n.event_id??i.eventId),a=n.reply_to,d=n.files,_=n.final===!0;if(!t||!s)return{content:[{type:"text",text:"reply requires chat_id and event_id"}],isError:!0};if(!r.trim()&&(!d||d.length===0))return{content:[{type:"text",text:"reply requires at least one of text or files"}],isError:!0};const{text:g,quotedMessageId:v}=e.resolveQuotedMessageId(a,r),b=[];let m=0;const h=`reply_${s}_${Date.now()}`;try{if(g){const p=e.splitText(g);for(let o=0;o<p.length;o++){if(!e.isEventActive(s))return c("ignored: event no longer active");m++,e.bridge.sendStreamChunk(s,t,p[o],++i.chunkSeq,!1,h)}}if(d&&d.length>0)for(const p of d){if(!e.isEventActive(s))return c("ignored: event no longer active");m++;const o=await e.uploadFile(p,t),l=`${x()}_${m}`;e.bridge.sendMedia(s,t,o.access_url,o.file_name,v,l,o.extra),f.info("claude-tools",`File sent: ${o.file_name}`)}e.bridge.sendStreamChunk(s,t,"",++i.chunkSeq,!0,h)}catch(p){if(g&&b.length===0)try{const o=`fallback_${s}_${Date.now()}`,l=e.splitText(g);for(let u=0;u<l.length;u++)e.bridge.sendStreamChunk("",t,l[u],u+1,!1,o);return e.bridge.sendStreamChunk("",t,"",l.length+1,!0,o),e.markReplySent(s),_&&e.finalizeEvent(s,"responded"),c("sent via fallback")}catch{}if(!e.isEventActive(s))return c("ignored: event no longer active");throw e.bridge.sendEventResult(s,"failed",String(p),"send_msg_failed"),p}return e.markReplySent(s),_?e.finalizeEvent(s,"responded"):(i.responded=!0,e.clearActiveEvent("completed")),c("Reply sent")}async function $(n,e){const i=e.getActiveEvent(),r=String(n.event_id??""),t=n.status??"",s=n.code,a=n.msg;if(!r||!t)return{content:[{type:"text",text:"complete requires event_id and status"}],isError:!0};const d=["responded","canceled","failed"];return d.includes(t)?e.isEventActive(r)?(e.bridge.sendEventResult(r,t,a,s),e.clearActiveEvent(t),c("Event completed")):c("ignored: event no longer active"):{content:[{type:"text",text:`status must be one of: ${d.join(", ")}`}],isError:!0}}async function T(n,e){const i=String(n.chat_id??""),r=String(n.message_id??"");if(!i||!r)return{content:[{type:"text",text:"chat_id and message_id are required"}],isError:!0};try{return await e.bridge.agentInvoke("grix_message_unsend",{sessionId:i,msgId:r},y),c(`deleted (${r})`)}catch(t){return{content:[{type:"text",text:`Delete failed: ${t}`}],isError:!0}}}function j(n){const e=n.getStatusInfo();return{content:[{type:"text",text:JSON.stringify({alive:e.alive,active_event:e.activeEvent,pending_approvals:e.pendingPermissions,pending_questions:e.pendingElicitations})}]}}async function M(n,e){const i=String(n.chat_id??""),r=String(n.text??"");if(!i||!r)return{content:[{type:"text",text:"chat_id and text are required"}],isError:!0};try{const t=e.splitText(r),s=`send_${i}_${Date.now()}`;for(let a=0;a<t.length;a++)e.bridge.sendStreamChunk("",i,t[a],a+1,!1,s);return e.bridge.sendStreamChunk("",i,"",t.length+1,!0,s),c("sent")}catch(t){return{content:[{type:"text",text:`Send failed: ${t}`}],isError:!0}}}const C={access_pair:{verb:"pair_approve",payloadKey:"code"},access_deny:{verb:"pair_deny",payloadKey:"code"},allow_sender:{verb:"sender_allow",payloadKey:"sender_id"},remove_sender:{verb:"sender_remove",payloadKey:"sender_id"},access_policy:{verb:"policy_set",payloadKey:"policy"}};async function R(n,e,i){try{const r=C[n];if(!r)throw new Error(`Unknown access control tool: ${n}`);const t={};e.code!=null&&(t.code=e.code),e.sender_id!=null&&(t.sender_id=e.sender_id),e.policy!=null&&(t.policy=e.policy);const s=await i.bridge.agentInvoke("claude_access_control",{verb:r.verb,payload:t},y);return{content:[{type:"text",text:typeof s=="string"?s:JSON.stringify(s)}]}}catch(r){return{content:[{type:"text",text:`${n} failed: ${r}`}],isError:!0}}}async function O(n,e,i){try{const r=k(n,e);if(!E.has(r.action))throw new Error(`Action not allowed: ${r.action}`);const t=await i.bridge.agentInvoke(r.action,r.params,y);if(t&&Number(t.code??0)!==0)throw new Error(String(t.msg??"invoke failed"));return{content:[{type:"text",text:t?.data!=null?typeof t.data=="string"?t.data:JSON.stringify(t.data):JSON.stringify(t)}]}}catch(r){return{content:[{type:"text",text:`${n} failed: ${r}`}],isError:!0}}}function c(n){return{content:[{type:"text",text:n}]}}export{q as registerClaudeTools};
1
+ import{randomUUID as x}from"node:crypto";import{CallToolRequestSchema as S,ListToolsRequestSchema as w}from"@modelcontextprotocol/sdk/types.js";import{log as f}from"../../core/log/index.js";import{toolCallToInvoke as k}from"../../core/mcp/tools.js";const E=new Set(["contact_search","session_search","message_history","message_search","group_create","group_detail_read","group_leave_self","group_member_add","group_member_remove","group_member_role_update","group_all_members_muted_update","group_member_speaking_update","group_dissolve","send_msg","delete_msg","agent_api_create","agent_category_list","agent_category_create","agent_category_update","agent_category_assign","agent_api_key_rotate"]),y=3e4,I=[{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."},files:{type:"array",items:{type:"string"},description:"Optional absolute local file paths. Each file is uploaded through Agent API OSS presign before sending."},final:{type:"boolean",description:"Whether this is the final reply for the event. Defaults to false \u2014 the event stays open while Claude continues working, and auto-completes after inactivity. Set true only when this is definitively the last message for the event."}},required:["chat_id","event_id"]}},{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"]},code:{type:"string"},msg:{type:"string"}},required:["event_id","status"]}},{name:"delete_message",description:"Delete a previously sent message in the same grix-claude chat.",inputSchema:{type:"object",properties:{chat_id:{type:"string"},message_id:{type:"string"}},required:["chat_id","message_id"]}},{name:"status",description:"Show grix-claude runtime status, upstream access state, bridge health, and startup hints.",inputSchema:{type:"object",properties:{}}},{name:"send",description:"Send a message to a chat session proactively, without requiring an inbound event. Use for notifications or scheduled reports.",inputSchema:{type:"object",properties:{chat_id:{type:"string",description:"The target chat/session id."},text:{type:"string",description:"The message text to send."}},required:["chat_id","text"]}},{name:"access_pair",description:"Forward a sender pairing approval code to upstream access control.",inputSchema:{type:"object",properties:{code:{type:"string"}},required:["code"]}},{name:"access_deny",description:"Forward a sender pairing denial code to upstream access control.",inputSchema:{type:"object",properties:{code:{type:"string"}},required:["code"]}},{name:"allow_sender",description:"Ask upstream access control to allow a sender_id.",inputSchema:{type:"object",properties:{sender_id:{type:"string"}},required:["sender_id"]}},{name:"remove_sender",description:"Ask upstream access control to remove a sender_id.",inputSchema:{type:"object",properties:{sender_id:{type:"string"}},required:["sender_id"]}},{name:"access_policy",description:"Ask upstream access control to update the sender access policy.",inputSchema:{type:"object",properties:{policy:{type:"string",enum:["allowlist","open","disabled"]}},required:["policy"]}},{name:"grix_query",description:"Search contacts, sessions, message history, or messages by keyword in the Grix/AIBot platform.",inputSchema:{type:"object",properties:{action:{type:"string",enum:["contact_search","session_search","message_history","message_search"]},keyword:{type:"string"},id:{type:"string"},sessionId:{type:"string"},limit:{type:"number"},offset:{type:"number"},beforeId:{type:"string"}},required:["action"]}},{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"]},sessionId:{type:"string"},name:{type:"string"},memberIds:{type:"array",items:{type:"string"}},memberTypes:{type:"array",items:{type:"integer",enum:[1,2]}},memberId:{type:"string"},role:{type:"integer",enum:[1,2]},memberType:{type:"integer",description:"Member type (for update_member_role / update_member_speaking)."},allMembersMuted:{type:"boolean"},isSpeakMuted:{type:"boolean"},canSpeakWhenAllMuted:{type:"boolean",description:"Allow speaking when all muted (for update_member_speaking)."}},required:["action"]}},{name:"grix_message_send",description:"Send a message to a session in the Grix/AIBot platform.",inputSchema:{type:"object",properties:{sessionId:{type:"string"},content:{type:"string"},msgType:{type:"number"},quotedMessageId:{type:"string"},threadId:{type:"string"}},required:["sessionId","content"]}},{name:"grix_message_unsend",description:"Recall/unsend a message in the Grix/AIBot platform.",inputSchema:{type:"object",properties:{sessionId:{type:"string"},msgId:{type:"string"}},required:["sessionId","msgId"]}},{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"]},agentId:{type:"string"},agentName:{type:"string"},introduction:{type:"string"},isMain:{type:"boolean"},categoryId:{type:"string"},name:{type:"string"},parentId:{type:"string"},sortOrder:{type:"number"}},required:["action"]}}];function q(r,e){r.setRequestHandler(w,async()=>({tools:I})),r.setRequestHandler(S,async i=>{const{name:n,arguments:t}=i.params,s=t??{};try{switch(n){case"reply":return await A(s,e);case"complete":return await $(s,e);case"delete_message":return await T(s,e);case"status":return j(e);case"send":return await M(s,e);case"access_pair":case"access_deny":case"allow_sender":case"remove_sender":case"access_policy":return await R(n,s,e);case"grix_query":case"grix_group":case"grix_message_send":case"grix_message_unsend":case"grix_admin":return await O(n,s,e);default:return{content:[{type:"text",text:`Unknown tool: ${n}`}],isError:!0}}}catch(a){return f.error("claude-tools",`Tool ${n} error: ${a}`),{content:[{type:"text",text:`Error: ${a instanceof Error?a.message:String(a)}`}],isError:!0}}})}async function A(r,e){const i=e.getActiveEvent();if(!i)return{content:[{type:"text",text:"No active event to reply to"}],isError:!0};const n=String(r.text??""),t=String(r.chat_id??""),s=String(r.event_id??i.eventId),a=r.reply_to,d=r.files,_=r.final===!0;if(!t||!s)return{content:[{type:"text",text:"reply requires chat_id and event_id"}],isError:!0};if(!n.trim()&&(!d||d.length===0))return{content:[{type:"text",text:"reply requires at least one of text or files"}],isError:!0};const{text:g,quotedMessageId:v}=e.resolveQuotedMessageId(a,n),b=[];let m=0;const h=`reply_${s}_${Date.now()}`;try{if(g){const p=e.splitText(g);for(let o=0;o<p.length;o++){if(!e.isEventActive(s))return c("ignored: event no longer active");m++,e.bridge.sendStreamChunk(s,t,p[o],++i.chunkSeq,!1,h)}}if(d&&d.length>0)for(const p of d){if(!e.isEventActive(s))return c("ignored: event no longer active");m++;const o=await e.uploadFile(p,t),l=`${x()}_${m}`;e.bridge.sendMedia(s,t,o.access_url,o.file_name,v,l,o.extra),f.info("claude-tools",`File sent: ${o.file_name}`)}e.bridge.sendStreamChunk(s,t,"",++i.chunkSeq,!0,h)}catch(p){if(g&&b.length===0)try{const o=`fallback_${s}_${Date.now()}`,l=e.splitText(g);for(let u=0;u<l.length;u++)e.bridge.sendStreamChunk("",t,l[u],u+1,!1,o);return e.bridge.sendStreamChunk("",t,"",l.length+1,!0,o),e.markReplySent(s),_&&e.finalizeEvent(s,"responded"),c("sent via fallback")}catch{}if(!e.isEventActive(s))return c("ignored: event no longer active");throw e.bridge.sendEventResult(s,"failed",String(p),"send_msg_failed"),p}return e.markReplySent(s),_?e.finalizeEvent(s,"responded"):(i.responded=!0,e.clearActiveEvent("completed")),c("Reply sent")}async function $(r,e){const i=e.getActiveEvent(),n=String(r.event_id??""),t=r.status??"",s=r.code,a=r.msg;if(!n||!t)return{content:[{type:"text",text:"complete requires event_id and status"}],isError:!0};const d=["responded","canceled","failed"];return d.includes(t)?e.isEventActive(n)?(e.bridge.sendEventResult(n,t,a,s),e.clearActiveEvent(t),c("Event completed")):c("ignored: event no longer active"):{content:[{type:"text",text:`status must be one of: ${d.join(", ")}`}],isError:!0}}async function T(r,e){const i=String(r.chat_id??""),n=String(r.message_id??"");if(!i||!n)return{content:[{type:"text",text:"chat_id and message_id are required"}],isError:!0};try{return await e.bridge.agentInvoke("grix_message_unsend",{sessionId:i,msgId:n},y),c(`deleted (${n})`)}catch(t){return{content:[{type:"text",text:`Delete failed: ${t}`}],isError:!0}}}function j(r){const e=r.getStatusInfo();return{content:[{type:"text",text:JSON.stringify({alive:e.alive,active_event:e.activeEvent,pending_approvals:e.pendingPermissions,pending_questions:e.pendingElicitations})}]}}async function M(r,e){const i=String(r.chat_id??""),n=String(r.text??"");if(!i||!n)return{content:[{type:"text",text:"chat_id and text are required"}],isError:!0};try{const t=e.splitText(n),s=`send_${i}_${Date.now()}`;for(let a=0;a<t.length;a++)e.bridge.sendStreamChunk("",i,t[a],a+1,!1,s);return e.bridge.sendStreamChunk("",i,"",t.length+1,!0,s),c("sent")}catch(t){return{content:[{type:"text",text:`Send failed: ${t}`}],isError:!0}}}const C={access_pair:{verb:"pair_approve",payloadKey:"code"},access_deny:{verb:"pair_deny",payloadKey:"code"},allow_sender:{verb:"sender_allow",payloadKey:"sender_id"},remove_sender:{verb:"sender_remove",payloadKey:"sender_id"},access_policy:{verb:"policy_set",payloadKey:"policy"}};async function R(r,e,i){try{const n=C[r];if(!n)throw new Error(`Unknown access control tool: ${r}`);const t={};e.code!=null&&(t.code=e.code),e.sender_id!=null&&(t.sender_id=e.sender_id),e.policy!=null&&(t.policy=e.policy);const s=await i.bridge.agentInvoke("claude_access_control",{verb:n.verb,payload:t},y);return{content:[{type:"text",text:typeof s=="string"?s:JSON.stringify(s)}]}}catch(n){return{content:[{type:"text",text:`${r} failed: ${n}`}],isError:!0}}}async function O(r,e,i){try{const n=k(r,e);if(!E.has(n.action))throw new Error(`Action not allowed: ${n.action}`);const t=await i.bridge.agentInvoke(n.action,n.params,y);if(t&&Number(t.code??0)!==0)throw new Error(String(t.msg??"invoke failed"));return{content:[{type:"text",text:t?.data!=null?typeof t.data=="string"?t.data:JSON.stringify(t.data):JSON.stringify(t)}]}}catch(n){return{content:[{type:"text",text:`${r} failed: ${n}`}],isError:!0}}}function c(r){return{content:[{type:"text",text:r}]}}export{q as registerClaudeTools};
@@ -1 +1 @@
1
- import{log as l}from"../../core/log/index.js";class c{controlURL="";token="";isConfigured(){return!!(this.controlURL&&this.token)}configure(t,e){this.controlURL=t.replace(/\/+$/,""),this.token=e.trim(),l.info("claude-worker-client",`Configured with control URL: ${this.controlURL}`)}async post(t,e,s){if(!this.isConfigured())throw new Error("worker control not configured");const i=new AbortController,o=setTimeout(()=>i.abort(),s);try{const r=await fetch(`${this.controlURL}${t}`,{method:"POST",headers:{"content-type":"application/json",authorization:`Bearer ${this.token}`},body:JSON.stringify(e),signal:i.signal}),n=await r.text(),a=n.trim()?JSON.parse(n):{};if(!r.ok)throw new Error(a.error||`worker control failed ${r.status}`);return a}finally{clearTimeout(o)}}isRetryableError(t){const e=t instanceof Error?t.message:String(t);return/fetch failed|network|ECONNRESET|ETIMEDOUT|EAI_AGAIN|socket hang up|aborted/i.test(e)}async postWithRetry(t,e,s,i=1){let o;for(let r=0;r<=i;r++)try{return r>0&&l.info("claude-worker-client",`Retrying ${t} attempt=${r+1}`),await this.post(t,e,s)}catch(n){if(o=n,r>=i||!this.isRetryableError(n))break;await new Promise(a=>setTimeout(a,150))}throw o instanceof Error?o:new Error(String(o))}async deliverEvent(t){return this.postWithRetry("/v1/worker/deliver-event",{payload:t},1e4,1)}async deliverStop(t){return this.postWithRetry("/v1/worker/deliver-stop",{payload:t},1e4,1)}async deliverLocalAction(t){return this.postWithRetry("/v1/worker/deliver-local-action",{payload:t},1e4,1)}async ping(){try{return await this.postWithRetry("/v1/worker/ping",{},5e3,1),!0}catch{return!1}}}export{c as ClaudeWorkerClient};
1
+ import{log as l}from"../../core/log/index.js";class c{controlURL="";token="";isConfigured(){return!!(this.controlURL&&this.token)}configure(r,e){this.controlURL=r.replace(/\/+$/,""),this.token=e.trim(),l.info("claude-worker-client",`Configured with control URL: ${this.controlURL}`)}async post(r,e,s){if(!this.isConfigured())throw new Error("worker control not configured");const i=new AbortController,o=setTimeout(()=>i.abort(),s);try{const t=await fetch(`${this.controlURL}${r}`,{method:"POST",headers:{"content-type":"application/json",authorization:`Bearer ${this.token}`},body:JSON.stringify(e),signal:i.signal}),n=await t.text(),a=n.trim()?JSON.parse(n):{};if(!t.ok)throw new Error(a.error||`worker control failed ${t.status}`);return a}finally{clearTimeout(o)}}isRetryableError(r){const e=r instanceof Error?r.message:String(r);return/fetch failed|network|ECONNRESET|ETIMEDOUT|EAI_AGAIN|socket hang up|aborted/i.test(e)}async postWithRetry(r,e,s,i=1){let o;for(let t=0;t<=i;t++)try{return t>0&&l.info("claude-worker-client",`Retrying ${r} attempt=${t+1}`),await this.post(r,e,s)}catch(n){if(o=n,t>=i||!this.isRetryableError(n))break;await new Promise(a=>setTimeout(a,150))}throw o instanceof Error?o:new Error(String(o))}async deliverEvent(r){return this.postWithRetry("/v1/worker/deliver-event",{payload:r},1e4,1)}async deliverStop(r){return this.postWithRetry("/v1/worker/deliver-stop",{payload:r},1e4,1)}async deliverLocalAction(r){return this.postWithRetry("/v1/worker/deliver-local-action",{payload:r},1e4,1)}async ping(){try{return await this.postWithRetry("/v1/worker/ping",{},5e3,1),!0}catch{return!1}}}export{c as ClaudeWorkerClient};
@@ -1,2 +1,2 @@
1
- import{spawn as x,execSync as y}from"node:child_process";import{randomUUID as v}from"node:crypto";import{mkdir as S}from"node:fs/promises";import{readFileSync as C}from"node:fs";import{join as d}from"node:path";import{homedir as T,tmpdir as I}from"node:os";import{log as o}from"../../core/log/index.js";import{MCP_HTTP_CHANNEL_NAME as l}from"./protocol-contract.js";function P(e){let t=null,r=0,n=!1,i=!1;const a=v(),s=e.gatewayUrl??"http://127.0.0.1:19580/mcp";return{async start(){await F(e.command,s,e.env);const c=E(e.grix),u=[...e.args??[],"--name",`grix-mcp-${e.name}`,"--session-id",a];e.fullAuto&&u.push("--dangerously-skip-permissions"),u.push("--dangerously-load-development-channels",`server:${l}`,"--append-system-prompt",c);const f=d(I(),`grix-mcp-claude-${e.name}`);await S(f,{recursive:!0});const{expectPath:$,pidPath:g}=await M(f,e.command,u),_={...process.env,...e.env??{}};t=x("/usr/bin/expect",[$],{cwd:e.cwd,env:_,stdio:["ignore","pipe","pipe"],detached:!0}),o.info("mcp-http-launcher",`\u542F\u52A8 Claude: name=${e.name} cwd=${e.cwd} pid=${t.pid}`),r=await k(g),n=!0,o.info("mcp-http-launcher",`Claude \u5B50\u8FDB\u7A0B PID: ${r}`),t.on("exit",(m,p)=>{o.info("mcp-http-launcher",`Claude \u9000\u51FA: code=${m} signal=${p}`),n=!1,t=null,r=0,i||(o.info("mcp-http-launcher","3 \u79D2\u540E\u81EA\u52A8\u91CD\u542F..."),setTimeout(()=>{i||this.start().catch(w=>{o.error("mcp-http-launcher",`\u91CD\u542F\u5931\u8D25: ${w}`)})},3e3))}),t.stdout?.on("data",m=>{const p=m.toString().trim();p&&o.info("mcp-http-launcher",`[stdout] ${p.slice(0,300)}`)}),t.stderr?.on("data",m=>{const p=m.toString().trim();p&&o.info("mcp-http-launcher",`[stderr] ${p.slice(0,300)}`)})},async stop(){if(i=!0,n=!1,r>0)try{process.kill(r,"SIGTERM")}catch{}if(t?.pid){try{process.kill(-t.pid,"SIGTERM")}catch{}await new Promise(c=>{const u=setTimeout(()=>{if(r>0)try{process.kill(r,"SIGKILL")}catch{}if(t?.pid)try{process.kill(-t.pid,"SIGKILL")}catch{}c()},5e3);t?.once("exit",()=>{clearTimeout(u),c()})})}t=null,r=0},getStatus(){return{name:e.name,alive:n,pid:r}}}}function E(e){return["You are connected to a chat via the grix MCP server.",`On startup, immediately call grix_authorize with: agentId="${e.agentId}", apiKey="${e.apiKey}", wsUrl="${e.wsUrl}", clientType="${e.clientType}".`,"When you receive a <channel> message, you MUST respond by calling the grix_reply tool (or the grix_complete tool if no response is needed).","Never write your reply as plain text \u2014 it will NOT reach the user. Only the grix_reply tool delivers your response to the chat.","The <channel> message contains event_id and session_id \u2014 pass them to grix_reply."].join(" ")}async function F(e,t,r){const n=d(T(),".claude.json");let i=null;try{const s=C(n,"utf8");i=JSON.parse(s)?.mcpServers?.[l]??null}catch{}if(i&&String(i.type??"").trim()==="http"&&String(i.url??"").trim()===t)return;o.info("mcp-http-launcher",`\u6CE8\u518C MCP Server: ${l} -> ${t}`);const a={...process.env,...r??{}};try{y(`${e} mcp remove -s user ${l}`,{encoding:"utf8",timeout:1e4,env:a,stdio:"pipe"})}catch{}y(`${e} mcp add --scope user --transport http ${l} ${t}`,{encoding:"utf8",timeout:1e4,env:a,stdio:"pipe"})}async function M(e,t,r){const{writeFile:n}=await import("node:fs/promises"),i=d(e,"claude.pid"),a=d(e,"claude.expect"),s=["log_user 1","set timeout -1","set startup_prompt_armed 1",`set claude_command [list {${h(t)}}${r.map(c=>` {${h(c)}}`).join("")}]`,"spawn -noecho {*}$claude_command",`set pid_file [open {${h(i)}} w]`,"puts $pid_file [exp_pid -i $spawn_id]","close $pid_file","expect {"," -re {(?i)(Quick.*safety.*check|trust.*folder)} {",' if {$startup_prompt_armed} { send -- "1\\r"; after 300 }; exp_continue'," }"," -re {(?i)I am using this for local development} {",' if {$startup_prompt_armed} { send -- "1\\r"; after 300 }; exp_continue'," }"," -re {(?i)(Enter.*confirm|Press.*Enter|Hit.*Enter)} {",' if {$startup_prompt_armed} { send -- "\\r"; after 300 }; exp_continue'," }"," -re {Listening for channel} {"," set startup_prompt_armed 0"," after 1000",' send -- "Call grix_authorize now as instructed in your system prompt.\\r"'," }"," -re {bypass permissions} {"," set startup_prompt_armed 0"," after 1000",' send -- "Call grix_authorize now as instructed in your system prompt.\\r"'," }"," eof {}","}","expect eof",""];return await n(i,"","utf8"),await n(a,s.join(`
2
- `),"utf8"),{expectPath:a,pidPath:i}}function h(e){return e.replace(/[\\{}$\[\]"]/g,"\\$&")}async function k(e,t=1e4){const{readFile:r}=await import("node:fs/promises"),n=Math.ceil(t/100);for(let i=0;i<n;i++){try{const a=await r(e,"utf8"),s=parseInt(String(a).trim(),10);if(Number.isFinite(s)&&s>0)return s}catch{}await new Promise(a=>setTimeout(a,100))}return 0}export{P as createMcpHttpLauncher};
1
+ import{spawn as x,execSync as y}from"node:child_process";import{randomUUID as v}from"node:crypto";import{mkdir as S}from"node:fs/promises";import{readFileSync as C}from"node:fs";import{join as d}from"node:path";import{homedir as T,tmpdir as I}from"node:os";import{log as o}from"../../core/log/index.js";import{MCP_HTTP_CHANNEL_NAME as m}from"./protocol-contract.js";function P(t){let e=null,r=0,n=!1,i=!1;const a=v(),s=t.gatewayUrl??"http://127.0.0.1:19580/mcp";return{async start(){await F(t.command,s,t.env);const c=E(t.grix),u=[...t.args??[],"--name",`grix-mcp-${t.name}`,"--session-id",a];t.fullAuto&&u.push("--dangerously-skip-permissions"),u.push("--dangerously-load-development-channels",`server:${m}`,"--append-system-prompt",c);const f=d(I(),`grix-mcp-claude-${t.name}`);await S(f,{recursive:!0});const{expectPath:$,pidPath:g}=await M(f,t.command,u),_={...process.env,...t.env??{}};e=x("/usr/bin/expect",[$],{cwd:t.cwd,env:_,stdio:["ignore","pipe","pipe"],detached:!0}),o.info("mcp-http-launcher",`\u542F\u52A8 Claude: name=${t.name} cwd=${t.cwd} pid=${e.pid}`),r=await k(g),n=!0,o.info("mcp-http-launcher",`Claude \u5B50\u8FDB\u7A0B PID: ${r}`),e.on("exit",(l,p)=>{o.info("mcp-http-launcher",`Claude \u9000\u51FA: code=${l} signal=${p}`),n=!1,e=null,r=0,i||(o.info("mcp-http-launcher","3 \u79D2\u540E\u81EA\u52A8\u91CD\u542F..."),setTimeout(()=>{i||this.start().catch(w=>{o.error("mcp-http-launcher",`\u91CD\u542F\u5931\u8D25: ${w}`)})},3e3))}),e.stdout?.on("data",l=>{const p=l.toString().trim();p&&o.info("mcp-http-launcher",`[stdout] ${p.slice(0,300)}`)}),e.stderr?.on("data",l=>{const p=l.toString().trim();p&&o.info("mcp-http-launcher",`[stderr] ${p.slice(0,300)}`)})},async stop(){if(i=!0,n=!1,r>0)try{process.kill(r,"SIGTERM")}catch{}if(e?.pid){try{process.kill(-e.pid,"SIGTERM")}catch{}await new Promise(c=>{const u=setTimeout(()=>{if(r>0)try{process.kill(r,"SIGKILL")}catch{}if(e?.pid)try{process.kill(-e.pid,"SIGKILL")}catch{}c()},5e3);e?.once("exit",()=>{clearTimeout(u),c()})})}e=null,r=0},getStatus(){return{name:t.name,alive:n,pid:r}}}}function E(t){return["You are connected to a chat via the grix MCP server.",`On startup, immediately call grix_authorize with: agentId="${t.agentId}", apiKey="${t.apiKey}", wsUrl="${t.wsUrl}", clientType="${t.clientType}".`,"When you receive a <channel> message, you MUST respond by calling the grix_reply tool (or the grix_complete tool if no response is needed).","Never write your reply as plain text \u2014 it will NOT reach the user. Only the grix_reply tool delivers your response to the chat.","The <channel> message contains event_id and session_id \u2014 pass them to grix_reply."].join(" ")}async function F(t,e,r){const n=d(T(),".claude.json");let i=null;try{const s=C(n,"utf8");i=JSON.parse(s)?.mcpServers?.[m]??null}catch{}if(i&&String(i.type??"").trim()==="http"&&String(i.url??"").trim()===e)return;o.info("mcp-http-launcher",`\u6CE8\u518C MCP Server: ${m} -> ${e}`);const a={...process.env,...r??{}};try{y(`${t} mcp remove -s user ${m}`,{encoding:"utf8",timeout:1e4,env:a,stdio:"pipe"})}catch{}y(`${t} mcp add --scope user --transport http ${m} ${e}`,{encoding:"utf8",timeout:1e4,env:a,stdio:"pipe"})}async function M(t,e,r){const{writeFile:n}=await import("node:fs/promises"),i=d(t,"claude.pid"),a=d(t,"claude.expect"),s=["log_user 1","set timeout -1","set startup_prompt_armed 1",`set claude_command [list {${h(e)}}${r.map(c=>` {${h(c)}}`).join("")}]`,"spawn -noecho {*}$claude_command",`set pid_file [open {${h(i)}} w]`,"puts $pid_file [exp_pid -i $spawn_id]","close $pid_file","expect {"," -re {(?i)(Quick.*safety.*check|trust.*folder)} {",' if {$startup_prompt_armed} { send -- "1\\r"; after 300 }; exp_continue'," }"," -re {(?i)I am using this for local development} {",' if {$startup_prompt_armed} { send -- "1\\r"; after 300 }; exp_continue'," }"," -re {(?i)(Enter.*confirm|Press.*Enter|Hit.*Enter)} {",' if {$startup_prompt_armed} { send -- "\\r"; after 300 }; exp_continue'," }"," -re {Listening for channel} {"," set startup_prompt_armed 0"," after 1000",' send -- "Call grix_authorize now as instructed in your system prompt.\\r"'," }"," -re {bypass permissions} {"," set startup_prompt_armed 0"," after 1000",' send -- "Call grix_authorize now as instructed in your system prompt.\\r"'," }"," eof {}","}","expect eof",""];return await n(i,"","utf8"),await n(a,s.join(`
2
+ `),"utf8"),{expectPath:a,pidPath:i}}function h(t){return t.replace(/[\\{}$\[\]"]/g,"\\$&")}async function k(t,e=1e4){const{readFile:r}=await import("node:fs/promises"),n=Math.ceil(e/100);for(let i=0;i<n;i++){try{const a=await r(t,"utf8"),s=parseInt(String(a).trim(),10);if(Number.isFinite(s)&&s>0)return s}catch{}await new Promise(a=>setTimeout(a,100))}return 0}export{P as createMcpHttpLauncher};
@@ -1 +1 @@
1
- class m{defaultTimeoutMs;onTimeout;timers=new Map;constructor(t){this.defaultTimeoutMs=t.defaultTimeoutMs??9e4,this.onTimeout=t.onTimeout}arm(t,e){this.cancel(t);const s=e?.timeoutMs??this.defaultTimeoutMs,i=Date.now()+s,o=setTimeout(()=>{this.timers.delete(t),this.onTimeout(t).catch(()=>{})},s);return this.timers.set(t,o),i}cancel(t){const e=this.timers.get(t);e&&(clearTimeout(e),this.timers.delete(t))}has(t){return this.timers.has(t)}close(){for(const t of this.timers.values())clearTimeout(t);this.timers.clear()}}export{m as ResultTimeoutManager};
1
+ class m{defaultTimeoutMs;onTimeout;timers=new Map;constructor(e){this.defaultTimeoutMs=e.defaultTimeoutMs??9e4,this.onTimeout=e.onTimeout}arm(e,t){this.cancel(e);const s=t?.timeoutMs??this.defaultTimeoutMs,i=Date.now()+s,o=setTimeout(()=>{this.timers.delete(e),this.onTimeout(e).catch(()=>{})},s);return this.timers.set(e,o),i}cancel(e){const t=this.timers.get(e);t&&(clearTimeout(t),this.timers.delete(e))}has(e){return this.timers.has(e)}close(){for(const e of this.timers.values())clearTimeout(e);this.timers.clear()}}export{m as ResultTimeoutManager};
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "schemaVersion": 1,
3
- "bridgeVersion": "4.5.0",
4
- "tarball": "grix-dsh-bridge-4.5.0.tgz",
3
+ "bridgeVersion": "4.5.1",
4
+ "tarball": "grix-dsh-bridge-4.5.1.tgz",
5
5
  "size": 22625,
6
6
  "unpackedSize": 90315,
7
- "shasum": "7f657f7147e704eab948ac9c1f24e4a02e276ce6",
8
- "integrity": "sha512-e42nSO3ckiYwjpvCsMaw71wEEUo4hPkjtrIntdM5b/jKwUh+mJo6dZU4AX3B+Dz3LdOO7wGB/jKMvZ6FNCx9FQ=="
7
+ "shasum": "aeca2a9d33c43d6e1778a6685522ecdb2eeb5570",
8
+ "integrity": "sha512-tp26OlO9Sik1sDWM3+CrUmXKw3L2dbqdvAg3jhegddFv7DP33Y7Y4FcQ0jOuH+PUVLZ7oSeXi1KSZtTyPaWHVg=="
9
9
  }
@@ -1 +1 @@
1
- import{readJSONFile as o,writeJSONFileAtomic as e}from"../util/json-file.js";function n(t){const r=o(t);return r&&typeof r=="object"&&"owners"in r&&Array.isArray(r.owners)?r.owners.filter(i=>typeof i=="string"&&i.trim().length>0):[]}async function s(t,r){await e(t,{owners:r})}export{n as readAllowlist,s as writeAllowlist};
1
+ import{readJSONFile as o,writeJSONFileAtomic as i}from"../util/json-file.js";function n(t){const r=o(t);return r&&typeof r=="object"&&"owners"in r&&Array.isArray(r.owners)?r.owners.filter(e=>typeof e=="string"&&e.trim().length>0):[]}async function s(t,r){await i(t,{owners:r})}export{n as readAllowlist,s as writeAllowlist};
@@ -1 +1 @@
1
- import{readdir as r,stat as m}from"node:fs/promises";import{join as l,extname as d}from"node:path";const x={pdf:"application/pdf",doc:"application/msword",docx:"application/vnd.openxmlformats-officedocument.wordprocessingml.document",xls:"application/vnd.ms-excel",xlsx:"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",ppt:"application/vnd.ms-powerpoint",pptx:"application/vnd.openxmlformats-officedocument.presentationml.presentation",txt:"text/plain",md:"text/markdown",csv:"text/csv",json:"application/json",xml:"application/xml",yaml:"text/yaml",yml:"text/yaml",html:"text/html",css:"text/css",js:"text/javascript",ts:"text/typescript",zip:"application/zip",rar:"application/x-rar-compressed","7z":"application/x-7z-compressed",tar:"application/x-tar",gz:"application/gzip",jpg:"image/jpeg",jpeg:"image/jpeg",png:"image/png",gif:"image/gif",webp:"image/webp",svg:"image/svg+xml",mp4:"video/mp4",mov:"video/quicktime",avi:"video/x-msvideo",mkv:"video/x-matroska",webm:"video/webm",mp3:"audio/mpeg",wav:"audio/wav",flac:"audio/flac",aac:"audio/aac"};function n(a){const p=d(a).slice(1).toLowerCase();return x[p]}async function f(a,p=!1){const c=await r(a,{withFileTypes:!0}),s=[];for(const t of c){if(!p&&t.name.startsWith("."))continue;const i=l(a,t.name),e={id:i,name:t.name,is_directory:t.isDirectory()};try{if(t.isDirectory()){const o=await m(i);e.modified_at=o.mtime.toISOString()}else{const o=await m(i);e.size=o.size,e.modified_at=o.mtime.toISOString(),e.mime_type=n(t.name)}}catch{}s.push(e)}return s.sort((t,i)=>t.is_directory!==i.is_directory?t.is_directory?-1:1:t.name.localeCompare(i.name)),s}export{f as listFiles,n as resolveMimeType};
1
+ import{readdir as r,stat as m}from"node:fs/promises";import{join as l,extname as d}from"node:path";const x={pdf:"application/pdf",doc:"application/msword",docx:"application/vnd.openxmlformats-officedocument.wordprocessingml.document",xls:"application/vnd.ms-excel",xlsx:"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",ppt:"application/vnd.ms-powerpoint",pptx:"application/vnd.openxmlformats-officedocument.presentationml.presentation",txt:"text/plain",md:"text/markdown",csv:"text/csv",json:"application/json",xml:"application/xml",yaml:"text/yaml",yml:"text/yaml",html:"text/html",css:"text/css",js:"text/javascript",ts:"text/typescript",zip:"application/zip",rar:"application/x-rar-compressed","7z":"application/x-7z-compressed",tar:"application/x-tar",gz:"application/gzip",jpg:"image/jpeg",jpeg:"image/jpeg",png:"image/png",gif:"image/gif",webp:"image/webp",svg:"image/svg+xml",mp4:"video/mp4",mov:"video/quicktime",avi:"video/x-msvideo",mkv:"video/x-matroska",webm:"video/webm",mp3:"audio/mpeg",wav:"audio/wav",flac:"audio/flac",aac:"audio/aac"};function n(a){const p=d(a).slice(1).toLowerCase();return x[p]}async function f(a,p=!1){const c=await r(a,{withFileTypes:!0}),s=[];for(const i of c){if(!p&&i.name.startsWith("."))continue;const t=l(a,i.name),e={id:t,name:i.name,is_directory:i.isDirectory()};try{if(i.isDirectory()){const o=await m(t);e.modified_at=o.mtime.toISOString()}else{const o=await m(t);e.size=o.size,e.modified_at=o.mtime.toISOString(),e.mime_type=n(i.name)}}catch{}s.push(e)}return s.sort((i,t)=>i.is_directory!==t.is_directory?i.is_directory?-1:1:i.name.localeCompare(t.name)),s}export{f as listFiles,n as resolveMimeType};
@@ -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};
@@ -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(),e)}catch{}}function ke(t=4096){const e=st();if(!d(e))return"";try{const r=P(e,"utf-8");return r.length<=t?r:r.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 e=xt(t),r=Math.floor(Number(e.bavail)*Number(e.bsize)/(1024*1024));return!Number.isFinite(r)||r<0?U:Math.min(r,U)}catch{return U}}function Ie(){let t;try{t=dt()}catch(r){return{ok:!1,errorCode:"NPM_NOT_FOUND",errorMsg:r instanceof Error?r.message:"npm not available"}}const e=gt();return e<100?{ok:!1,errorCode:"DISK_FULL",errorMsg:`Only ${e}MB free disk space (need >= 100MB)`,npmVersion:t,diskFreeMb:e}:{ok:!0,npmVersion:t,diskFreeMb:e}}function Pe(){let t="";try{t=dt()}catch{}let e=U;try{e=gt()}catch{}return{npm_version:t,node_version:process.version,disk_free_mb:e,platform:process.platform,arch:process.arch,host_name:Nt(),install_id:At()}}function F(t,e,r){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!==e||i.version?.replace(/^v(?=\d)/,"")!==r)throw new f("VERSION_MISMATCH",`Staged package ${String(i.name)}@${String(i.version)} does not match ${e}@${r}`);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(e=>typeof e[1]=="string")}function L(t,e){const r=Kt(e);if(r.length===0)throw new f("VERSION_VERIFY_FAILED",`Staged package ${e.name} has no executable bin`);for(const[,n]of r){const i=l(t,n);if(!d(i))throw new f("VERSION_VERIFY_FAILED",`Staged package entrypoint is missing: ${i}`)}return r}function zt(t,e,r){if(process.platform==="win32")return;const n=l(t,"bin");E(n,{recursive:!0});for(const[i,a]of r){const o=l(n,i);if(d(o))continue;const c=`${o}.grix-upgrade-${process.pid}`;try{H(c)}catch{}$t(et(n,l(e,a)),c),h(c,o)}}function Xt(t){const e=u(t.activePackageDir),r=u(t.stagedPackageDir),n=`${e}${Y}`,i=F(r,t.packageName,t.version),a=L(r,i);d(n)&&(d(e)?p(n,{recursive:!0,force:!0}):h(n,e)),Yt(e,n);let o=!1;try{d(e)&&(h(e,n),o=!0),h(r,e);const c=F(e,t.packageName,t.version),s=L(e,c);return zt(t.npmPrefix,e,s),{packageDir:e,backupDir:n}}catch(c){try{d(e)&&p(e,{recursive:!0,force:!0}),o&&d(n)&&h(n,e)}catch{}throw c}}function X(t,e=1e4){try{const r=M(t);return R(r.command,r.args,{encoding:"utf-8",timeout:e,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,e,r){const n=["view",`${t}@${e}`,"dist.integrity","--registry",r,"--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,e,r,n={}){if(!r)return;let i;try{i=new URL(r).origin}catch{return}if(i===new URL(pt).origin)return;const a=n.fetchIntegrity??qt;let o;try{o=await a(t,e,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,e,r)}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 ${r} reports dist.integrity ${c} but official npm reports ${o} for ${t}@${e}`);m(`integrity cross-check passed for ${t}@${e} via ${r}`)}async function mt(t,e,r,n={},i=process.platform==="win32"?"staged":"atomic"){const a=`${t}@${e}`,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??rt,{registry:k,registryUrl:y}=await g(a,r,10*1024*1024,{prefix:s,...i==="staged"?{env:{...process.env,GRIX_UPGRADE_STAGING:"1"}}:{}});await(n.verifyIntegrityConsensus??_t)(t,e,y);const N=W(["root","-g","--prefix",s]),A=l(N,t),B=F(A,t,e),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!==e)throw new f("VERSION_MISMATCH",`Staged CLI reported ${q||"<empty>"}, expected ${e}`);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,e,r,n){const i=await mt(t,e,r,n,"atomic");try{const a=Xt({packageName:t,version:e,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 ve(t,e,r=12e4,n={}){e=O(e);const i=_()?.transaction_id,a=`${t}@${e}`,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,e,r,n,o);try{jt({...c,activationStrategy:process.platform==="win32"?"wrapper-runtime":"service-start"}),process.platform==="win32"&&Jt(c,e)}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,e,r,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 ${r/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 xe(t){let e;try{e=X(["root","-g"])}catch{throw new f("VERSION_VERIFY_FAILED","Unable to resolve the global npm package directory")}let r;try{const n=l(e,"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");r=i.version.trim().replace(/^v(?=\d)/,"")}catch{throw new f("VERSION_VERIFY_FAILED","Unable to read the installed grix-connector version")}if(r!==t)throw new f("VERSION_MISMATCH",`Installed version ${r} does not match expected ${t}`);return r}async function $e(t,e){e=O(e);let r=!1;for(const n of await bt())try{const i=["view",`${t}@${e}`,"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)&&(r=!0)}return!r}async function De(t,e,r,n=12e4,i={}){r=O(r);const a=`${e}@${r}`,o=l(t,"versions",r);E(l(t,"versions"),{recursive:!0});const c=tt(l(t,"versions",`.stage-${r}-`));m(`installing ${a} into version store under ${c}`);try{const s=i.installWithMirror??rt,{registry:g,registryUrl:k}=await s(a,n,10*1024*1024,{prefix:c,env:{...process.env,GRIX_UPGRADE_STAGING:"1"}});await(i.verifyIntegrityConsensus??_t)(e,r,k);const S=W(["root","-g","--prefix",c]),N=l(S,e),A=F(N,e,r),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!==r)throw new f("VERSION_MISMATCH",`Installed CLI reported ${T||"<empty>"}, expected ${r}`);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,ge as archivePendingJournal,me as armWindowsUpgradeHandoff,he as armWindowsUpgradeRollback,gt as checkDiskSpace,dt as checkNpmAvailable,ye as cleanupPendingBackup,Pe as collectEnvInfo,Xt as commitStagedGlobalPackage,Vt as discardPendingStaging,Ee as finalizePendingTransaction,ke as getUpgradeLogTail,De as installVersionIntoStore,$e as isVersionAvailable,O as normalizeUpgradeVersion,ve as npmInstall,de as pendingExists,Ie as preflightCheck,Jt as prepareStagedWindowsRuntime,_ as readPending,_e as readPendingPackageVersion,Bt as releasePendingUpgradeLock,lt as removePending,we as removeWindowsHandoffFiles,Se as rollbackPendingInstall,D as updatePending,m as upgradeLog,le as upgradeStatusSnapshot,xe as verifyInstalledVersion,_t as verifyMirrorIntegrityConsensus,Ct as windowsRollbackIntentPath,j as windowsWrapperPausePath,pe as writePending,Ot as writePendingData};
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 A,existsSync as p,mkdirSync as f,readdirSync as x,readFileSync as i,renameSync as v,rmSync as a,writeFileSync as _}from"node:fs";import{join as o}from"node:path";const m="launcher.mjs",b=75,L=/LAUNCHER_TEMPLATE_VERSION\s*=\s*(\d+)/;function S(r){return o(r,m)}function y(r,e){return o(r,"versions",e)}function P(r,e){return o(y(r,e),"dist","grix.js")}function w(r,e){return o(r,"health",`${e}.ok`)}function R(r){return o(r,"upgrade.json")}function T(r){return o(r,"rollback.json")}function d(r,e){try{return i(o(r,e),"utf8").trim()||null}catch{return null}}function l(r,e){const t=`${r}.tmp-${process.pid}`;_(t,e,"utf8"),v(t,r)}function g(r,e,t){f(r,{recursive:!0}),l(o(r,e),`${t}
2
- `)}function E(r){try{const e=i(r,"utf8").match(L);return e?Number(e[1]):0}catch{return-1}}function I(r){for(const e of[o(r,"scripts",m),o(r,"dist",m)])if(p(e))return e;return null}function j(r,e){const t=I(e);if(!t)return!1;const n=S(r),c=E(t);return E(n)>=c||(f(r,{recursive:!0}),l(n,i(t,"utf8"))),!0}function C(r,e,t){const n=y(r,t);if(p(P(r,t)))return n;const c=`${n}.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=o(e,s);p(u)&&A(u,o(c,s),{recursive:!0,force:!0})}a(n,{recursive:!0,force:!0}),v(c,n)}finally{a(c,{recursive:!0,force:!0})}return n}function $(r,e){const t=d(r,"current");return t&&t!==e&&g(r,"previous",t),g(r,"current",e),t}function H(r,e){l(R(r),JSON.stringify(e))}function U(r){return k(R(r))}function F(r){return k(T(r))}function k(r){try{return JSON.parse(i(r,"utf8"))}catch{return null}}function J(r,e){f(o(r,"health"),{recursive:!0}),l(w(r,e),new Date().toISOString())}function M(r){const e=new Set([d(r,"current"),d(r,"previous")].filter(Boolean)),t=o(r,"versions"),n=[];let c=[];try{c=x(t)}catch{return n}for(const u of c)if(!e.has(u))try{a(o(t,u),{recursive:!0,force:!0}),n.push(u)}catch{}const s=o(r,"health");try{for(const u of x(s)){const h=u.replace(/\.ok$/,"");e.has(h)||a(o(s,u),{force:!0})}}catch{}return n}function G(r=process.env){if(r.GRIX_LAUNCHER_MANAGED!=="1")return null;const e=String(r.GRIX_LAUNCHER_STORE??"").trim();return e&&p(S(e))?e:null}function X(r,e){const t=s=>{const[u,h]=s.replace(/^v(?=\d)/,"").split("-",2);return{nums:u.split(".").map(N=>Number.parseInt(N,10)||0),pre:h??""}},n=t(r),c=t(e);for(let s=0;s<Math.max(n.nums.length,c.nums.length);s+=1){const u=(n.nums[s]??0)-(c.nums[s]??0);if(u!==0)return u<0?-1:1}return n.pre===c.pre?0:n.pre?c.pre?n.pre<c.pre?-1:1:-1:1}function B(r){try{const e=JSON.parse(i(o(r,"package.json"),"utf8"));return typeof e.version=="string"?e.version.replace(/^v(?=\d)/,""):null}catch{return null}}export{m as LAUNCHER_FILE,b as RESTART_EXIT_CODE,C as bootstrapVersionFromPackage,X as compareVersions,j as ensureLauncher,w as healthMarkerPath,S as launcherPath,J as markHealthy,I as packagedLauncherPath,M as pruneVersions,E as readLauncherTemplateVersion,B as readPackageVersionAt,d as readPointer,F as readRollbackRecord,U as readSwitchRecord,G as resolveLauncherStore,T as rollbackRecordPath,$ as switchCurrent,R as switchRecordPath,y as versionDir,P as versionEntry,g as writePointer,H as writeSwitchRecord};
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};