@aerostack/gateway 0.15.26 → 0.15.28

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.
Files changed (2) hide show
  1. package/dist/index.js +30 -28
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -1,28 +1,28 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- import{Server as de}from"@modelcontextprotocol/sdk/server/index.js";import{StdioServerTransport as pe}from"@modelcontextprotocol/sdk/server/stdio.js";import{ListToolsRequestSchema as me,CallToolRequestSchema as he,ListResourcesRequestSchema as fe,ReadResourceRequestSchema as ge,ListPromptsRequestSchema as _e,GetPromptRequestSchema as ye}from"@modelcontextprotocol/sdk/types.js";import{readFile as we,writeFile as Ae}from"node:fs/promises";import{join as Ee}from"node:path";import{homedir as Te}from"node:os";import{resolveApproval as G}from"./resolution.js";import{startHookServer as Se,installClaudeHook as ke,stopHookServer as Re}from"./hook-server.js";import{OpenClawConnector as Oe,resolveOpenClawToken as Ce}from"./openclaw-connector.js";import{info as u,warn as h,error as ve}from"./logger.js";const Z=Ee(Te(),".openclaw","pre-authorized.json");async function $e(e){try{let s={};try{s=JSON.parse(await we(Z,"utf-8"))}catch{}s[e]=Date.now(),await Ae(Z,JSON.stringify(s))}catch{}}const V=process.env.AEROSTACK_WORKSPACE_URL,l=process.env.AEROSTACK_TOKEN;function D(e,s,o){const t=parseInt(e??String(s),10);return Number.isFinite(t)&&t>=o?t:s}const J=D(process.env.AEROSTACK_APPROVAL_POLL_MS,3e3,500),Y=D(process.env.AEROSTACK_APPROVAL_TIMEOUT_MS,864e5,5e3),ee=D(process.env.AEROSTACK_REQUEST_TIMEOUT_MS,3e4,1e3),be=process.env.AEROSTACK_HOOK_SERVER!=="false",Ne=D(process.env.AEROSTACK_HOOK_PORT,18321,1024),Pe=process.env.AEROSTACK_HOOK_AUTO_INSTALL!=="false",xe=process.env.AEROSTACK_OPENCLAW_ENABLED!=="false",te=D(process.env.AEROSTACK_OPENCLAW_PORT,18789,1024),Ie=process.env.AEROSTACK_OPENCLAW_TOKEN;V||(process.stderr.write(`ERROR: AEROSTACK_WORKSPACE_URL is required
4
- `),process.exit(1)),l||(process.stderr.write(`ERROR: AEROSTACK_TOKEN is required
5
- `),process.exit(1));let j;try{if(j=new URL(V),j.protocol!=="https:"&&j.protocol!=="http:")throw new Error("must be http or https")}catch{process.stderr.write(`ERROR: AEROSTACK_WORKSPACE_URL must be a valid HTTP(S) URL
6
- `),process.exit(1)}j.protocol==="http:"&&!j.hostname.match(/^(localhost|127\.0\.0\.1)$/)&&process.stderr.write(`WARNING: Using HTTP (not HTTPS) \u2014 token will be sent in plaintext
7
- `);const d=V.replace(/\/+$/,""),se=crypto.randomUUID();function Ue(){return process.env.AEROSTACK_AGENT_TYPE?process.env.AEROSTACK_AGENT_TYPE:process.env.CLAUDECODE||process.env.CLAUDE_CODE?"claude-code":process.env.MCP_TRANSPORT==="stdio"?"mcp-stdio":"unknown"}const oe=Ue();let v=null,$=Date.now();const Le=3e4;async function A(e,s){const o={jsonrpc:"2.0",id:Date.now(),method:e,params:s??{}},t=new AbortController,r=setTimeout(()=>t.abort(),ee);try{const n=await fetch(d,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${l}`,"User-Agent":"aerostack-gateway/0.15.14","X-Agent-Id":"aerostack-gateway","X-Bridge-Id":se,"X-Agent-Type":oe},body:JSON.stringify(o),signal:t.signal});if(clearTimeout(r),(n.headers.get("content-type")??"").includes("text/event-stream")){const p=await n.text();return De(p,o.id)}return await n.json()}catch(n){clearTimeout(r);const a=n instanceof Error?n.message:"Unknown error";return n instanceof Error&&n.name==="AbortError"?{jsonrpc:"2.0",id:o.id,error:{code:-32603,message:"Request timed out"}}:{jsonrpc:"2.0",id:o.id,error:{code:-32603,message:`HTTP error: ${a}`}}}}function De(e,s){const o=e.split(`
8
- `);let t=null;for(const r of o)if(r.startsWith("data: "))try{t=JSON.parse(r.slice(6))}catch{}return t??{jsonrpc:"2.0",id:s,error:{code:-32603,message:"Empty SSE response"}}}const je=new Set(["aerostack__guardian_report","aerostack__check_approval","aerostack__guardian_check"]);function Ke(e,s){if(je.has(e))return;let o="other";const t=e.toLowerCase();t.includes("exec")||t.includes("bash")||t.includes("shell")||t.includes("command")||t.includes("run")?o="exec_command":t.includes("write")||t.includes("edit")||t.includes("create")||t.includes("patch")?o="file_write":t.includes("delete")||t.includes("remove")||t.includes("trash")||t.includes("unlink")?o="file_delete":t.includes("fetch")||t.includes("http")||t.includes("request")||t.includes("api")||t.includes("get")||t.includes("post")?o="api_call":t.includes("install")||t.includes("package")||t.includes("npm")||t.includes("pip")?o="package_install":t.includes("config")||t.includes("setting")||t.includes("env")?o="config_change":t.includes("deploy")||t.includes("publish")||t.includes("release")?o="deploy":t.includes("send")||t.includes("message")||t.includes("email")||t.includes("notify")||t.includes("slack")||t.includes("telegram")?o="message_send":(t.includes("read")||t.includes("query")||t.includes("search")||t.includes("list")||t.includes("get"))&&(o="data_access");let r;try{const n=JSON.stringify(s);r=n.length>500?n.slice(0,500)+"...":n}catch{r="(unable to serialize)"}A("tools/call",{name:"aerostack__guardian_report",arguments:{action:`${e}(${Object.keys(s).join(", ")})`,category:o,risk_level:"low",details:r}}).catch(()=>{})}const He=new Set(["aerostack__check_approval"]),ne=[{name:"aerostack__chat_check",description:"Check for new messages from the workspace owner. Call this periodically or when asked to check for instructions.",inputSchema:{type:"object",properties:{},required:[]}},{name:"aerostack__chat_reply",description:"Send a reply message to the workspace owner via Agent Chat.",inputSchema:{type:"object",properties:{message:{type:"string",description:"Your reply message to the workspace owner"}},required:["message"]}}],Me=new Set(ne.map(e=>e.name));function E(e){return e.replace(/[\r\n]+/g," ").replace(/\[/g,"(").replace(/\]/g,")").replace(/---/g,"\u2014")}const K=[],We=100,R=new Map,re=50,ze=100,ae=10;let x=null;const ie=new Set,z=new Map;let I=null;function H(e,s,o){let t=R.get(e);if(!t){if(R.size>=ze){const r=R.keys().next().value;r&&R.delete(r)}t=[],R.set(e,t)}t.push({role:s,content:o,ts:Date.now()}),t.length>re&&t.splice(0,t.length-re)}async function qe(e){if(!ie.has(e))try{const s=await S(`${d}/chat/history?limit=${ae}`,{headers:{Authorization:`Bearer ${l}`}});if(!s.ok)return;const o=await s.json();o.session_id&&(x=o.session_id);for(const t of o.messages??[]){const r=t.sender_type==="agent"?"agent":"user",n=o.session_id||e;let a=R.get(n);a||(a=[],R.set(n,a)),a.push({role:r,content:t.content,ts:t.created_at})}ie.add(e),u("Thread history seeded from D1",{sessionId:o.session_id,count:o.messages?.length??0})}catch{}}function ce(e){const s=R.get(e);return!s||s.length===0?"":`Previous conversation:
9
- ${s.slice(-ae).map(r=>`[${r.role==="user"?"Admin":"You (Agent)"}] ${r.content}`).join(`
3
+ import{Server as fe}from"@modelcontextprotocol/sdk/server/index.js";import{StdioServerTransport as ge}from"@modelcontextprotocol/sdk/server/stdio.js";import{ListToolsRequestSchema as ye,CallToolRequestSchema as _e,ListResourcesRequestSchema as we,ReadResourceRequestSchema as Ae,ListPromptsRequestSchema as Ee,GetPromptRequestSchema as Te}from"@modelcontextprotocol/sdk/types.js";import{readFile as Se,writeFile as ke}from"node:fs/promises";import{join as Re}from"node:path";import{homedir as Oe}from"node:os";import{resolveApproval as Q}from"./resolution.js";import{startHookServer as Ce,installClaudeHook as ve,stopHookServer as $e}from"./hook-server.js";import{OpenClawConnector as be,resolveOpenClawToken as Ne}from"./openclaw-connector.js";import{info as l,warn as m,error as Pe}from"./logger.js";const ne=Re(Oe(),".openclaw","pre-authorized.json");async function xe(e){try{let t={};try{t=JSON.parse(await Se(ne,"utf-8"))}catch{}t[e]=Date.now(),await ke(ne,JSON.stringify(t))}catch{}}const Z=process.env.AEROSTACK_WORKSPACE_URL,u=process.env.AEROSTACK_TOKEN;function j(e,t,o){const s=parseInt(e??String(t),10);return Number.isFinite(s)&&s>=o?s:t}const ee=j(process.env.AEROSTACK_APPROVAL_POLL_MS,3e3,500),te=j(process.env.AEROSTACK_APPROVAL_TIMEOUT_MS,864e5,5e3),re=j(process.env.AEROSTACK_REQUEST_TIMEOUT_MS,3e4,1e3),Ie=process.env.AEROSTACK_HOOK_SERVER!=="false",Ue=j(process.env.AEROSTACK_HOOK_PORT,18321,1024),Le=process.env.AEROSTACK_HOOK_AUTO_INSTALL!=="false",De=process.env.AEROSTACK_OPENCLAW_ENABLED!=="false",ae=j(process.env.AEROSTACK_OPENCLAW_PORT,18789,1024),je=process.env.AEROSTACK_OPENCLAW_TOKEN;Z||(process.stderr.write(`ERROR: AEROSTACK_WORKSPACE_URL is required
4
+ `),process.exit(1)),u||(process.stderr.write(`ERROR: AEROSTACK_TOKEN is required
5
+ `),process.exit(1));let K;try{if(K=new URL(Z),K.protocol!=="https:"&&K.protocol!=="http:")throw new Error("must be http or https")}catch{process.stderr.write(`ERROR: AEROSTACK_WORKSPACE_URL must be a valid HTTP(S) URL
6
+ `),process.exit(1)}K.protocol==="http:"&&!K.hostname.match(/^(localhost|127\.0\.0\.1)$/)&&process.stderr.write(`WARNING: Using HTTP (not HTTPS) \u2014 token will be sent in plaintext
7
+ `);const d=Z.replace(/\/+$/,""),ie=crypto.randomUUID();function Ke(){return process.env.AEROSTACK_AGENT_TYPE?process.env.AEROSTACK_AGENT_TYPE:process.env.CLAUDECODE||process.env.CLAUDE_CODE?"claude-code":process.env.MCP_TRANSPORT==="stdio"?"mcp-stdio":"unknown"}const ce=Ke();let v=null,$=Date.now();const Me=3e4;async function _(e,t){const o={jsonrpc:"2.0",id:Date.now(),method:e,params:t??{}},s=new AbortController,r=setTimeout(()=>s.abort(),re);try{const n=await fetch(d,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${u}`,"User-Agent":"aerostack-gateway/0.15.14","X-Agent-Id":"aerostack-gateway","X-Bridge-Id":ie,"X-Agent-Type":ce},body:JSON.stringify(o),signal:s.signal});if(clearTimeout(r),(n.headers.get("content-type")??"").includes("text/event-stream")){const p=await n.text();return He(p,o.id)}return await n.json()}catch(n){clearTimeout(r);const a=n instanceof Error?n.message:"Unknown error";return n instanceof Error&&n.name==="AbortError"?{jsonrpc:"2.0",id:o.id,error:{code:-32603,message:"Request timed out"}}:{jsonrpc:"2.0",id:o.id,error:{code:-32603,message:`HTTP error: ${a}`}}}}function He(e,t){const o=e.split(`
8
+ `);let s=null;for(const r of o)if(r.startsWith("data: "))try{s=JSON.parse(r.slice(6))}catch{}return s??{jsonrpc:"2.0",id:t,error:{code:-32603,message:"Empty SSE response"}}}const We=new Set(["aerostack__guardian_report","aerostack__check_approval","aerostack__guardian_check"]);function ze(e,t){if(We.has(e))return;let o="other";const s=e.toLowerCase();s.includes("exec")||s.includes("bash")||s.includes("shell")||s.includes("command")||s.includes("run")?o="exec_command":s.includes("write")||s.includes("edit")||s.includes("create")||s.includes("patch")?o="file_write":s.includes("delete")||s.includes("remove")||s.includes("trash")||s.includes("unlink")?o="file_delete":s.includes("fetch")||s.includes("http")||s.includes("request")||s.includes("api")||s.includes("get")||s.includes("post")?o="api_call":s.includes("install")||s.includes("package")||s.includes("npm")||s.includes("pip")?o="package_install":s.includes("config")||s.includes("setting")||s.includes("env")?o="config_change":s.includes("deploy")||s.includes("publish")||s.includes("release")?o="deploy":s.includes("send")||s.includes("message")||s.includes("email")||s.includes("notify")||s.includes("slack")||s.includes("telegram")?o="message_send":(s.includes("read")||s.includes("query")||s.includes("search")||s.includes("list")||s.includes("get"))&&(o="data_access");let r;try{const n=JSON.stringify(t);r=n.length>500?n.slice(0,500)+"...":n}catch{r="(unable to serialize)"}_("tools/call",{name:"aerostack__guardian_report",arguments:{action:`${e}(${Object.keys(t).join(", ")})`,category:o,risk_level:"low",details:r}}).catch(()=>{})}const qe=new Set(["aerostack__check_approval"]),le=[{name:"aerostack__chat_check",description:"Check for new messages from the workspace owner. Call this periodically or when asked to check for instructions.",inputSchema:{type:"object",properties:{},required:[]}},{name:"aerostack__chat_reply",description:"Send a reply message to the workspace owner via Agent Chat.",inputSchema:{type:"object",properties:{message:{type:"string",description:"Your reply message to the workspace owner"}},required:["message"]}}],Be=new Set(le.map(e=>e.name));function w(e){return e.replace(/[\r\n]+/g," ").replace(/\[/g,"(").replace(/\]/g,")").replace(/---/g,"\u2014")}const M=[],Fe=100,k=new Map,ue=50,Ge=100,de=10;let x=null;const pe=new Set,q=new Map;let I=null;function H(e,t,o){let s=k.get(e);if(!s){if(k.size>=Ge){const r=k.keys().next().value;r&&k.delete(r)}s=[],k.set(e,s)}s.push({role:t,content:o,ts:Date.now()}),s.length>ue&&s.splice(0,s.length-ue)}async function Ve(e){if(!pe.has(e))try{const t=await T(`${d}/chat/history?limit=${de}`,{headers:{Authorization:`Bearer ${u}`}});if(!t.ok)return;const o=await t.json();o.session_id&&(x=o.session_id);for(const s of o.messages??[]){const r=s.sender_type==="agent"?"agent":"user",n=o.session_id||e;let a=k.get(n);a||(a=[],k.set(n,a)),a.push({role:r,content:s.content,ts:s.created_at})}pe.add(e),l("Thread history seeded from D1",{sessionId:o.session_id,count:o.messages?.length??0})}catch{}}function se(e){const t=k.get(e);return!t||t.length===0?"":`Previous conversation:
9
+ ${t.slice(-de).map(r=>`[${r.role==="user"?"Admin":"You (Agent)"}] ${r.content}`).join(`
10
10
  `)}
11
11
 
12
- `}async function S(e,s){const o=new AbortController,t=setTimeout(()=>o.abort(),ee);try{return await fetch(e,{...s,signal:o.signal})}finally{clearTimeout(t)}}async function Be(){const e=x||"_default";if(await qe(e),K.length>0){const s=K.splice(0);$=Date.now();const o=ce(e),t=s.map(r=>`${E(r.sender_name)}: ${E(r.content)}`).join(`
12
+ `}async function T(e,t){const o=new AbortController,s=setTimeout(()=>o.abort(),re);try{return await fetch(e,{...t,signal:o.signal})}finally{clearTimeout(s)}}async function Je(){const e=x||"_default";if(await Ve(e),M.length>0){const t=M.splice(0);$=Date.now();const o=se(e),s=t.map(r=>`${w(r.sender_name)}: ${w(r.content)}`).join(`
13
13
 
14
14
  `);return[{type:"text",text:`${o}New messages from workspace owner:
15
15
 
16
- ${t}`}]}try{const o=await S(`${d}/chat/pending?since=${$}`,{headers:{Authorization:`Bearer ${l}`}});if(!o.ok)return[{type:"text",text:"No new messages."}];const r=(await o.json()).messages??[];if($=Date.now(),r.length===0)return[{type:"text",text:"No new messages from the workspace owner."}];for(const i of r)H(e,"user",i.content);const n=ce(e),a=r.map(i=>`${E(i.sender_name)}: ${E(i.content)}`).join(`
16
+ ${s}`}]}try{const o=await T(`${d}/chat/pending?since=${$}`,{headers:{Authorization:`Bearer ${u}`}});if(!o.ok)return[{type:"text",text:"No new messages."}];const r=(await o.json()).messages??[];if($=Date.now(),r.length===0)return[{type:"text",text:"No new messages from the workspace owner."}];for(const i of r)H(e,"user",i.content);const n=se(e),a=r.map(i=>`${w(i.sender_name)}: ${w(i.content)}`).join(`
17
17
 
18
18
  `);return[{type:"text",text:`${n}New messages from workspace owner:
19
19
 
20
- ${a}`}]}catch{return[{type:"text",text:"Failed to check messages."}]}}async function Fe(e){const s=typeof e.message=="string"?e.message.trim().slice(0,1e4):"";if(!s)return[{type:"text",text:"Error: message is required."}];try{const o=await S(`${d}/chat/messages`,{method:"POST",headers:{Authorization:`Bearer ${l}`,"Content-Type":"application/json"},body:JSON.stringify({content:s,session_id:x})});return o.ok?(H(x||"_default","agent",s),[{type:"text",text:"Reply sent to workspace owner."}]):[{type:"text",text:`Failed to send reply (HTTP ${o.status}).`}]}catch{return[{type:"text",text:"Failed to send reply."}]}}async function Ge(e,s){Ke(e,s);const o=await A("tools/call",{name:e,arguments:s});if(o.error?.code===-32050){const n=o.error.data,a=n?.approval_id;if(!a||!/^[a-zA-Z0-9_-]{4,128}$/.test(a))return{jsonrpc:"2.0",id:o.id,error:{code:-32603,message:"Approval required but no approval_id returned"}};u("Tool gate: blocking until approval resolves",{tool:e,approvalId:a});const i=await G({approvalId:a,wsUrl:n?.ws_url,pollUrl:n?.polling_url??`${d}/approval-status/${a}`,pollIntervalMs:J,timeoutMs:Y,token:l});u("Tool gate resolved",{tool:e,status:i.status}),M({approvalId:a,toolName:e,toolArgs:s,sessionKey:null,gate:"tool_gate",pollUrl:"",authToken:l},i.status,!0);const p=X({approvalId:a,toolName:e,toolArgs:s,sessionKey:null,gate:"tool_gate",pollUrl:"",authToken:l},i);return{jsonrpc:"2.0",id:o.id,result:{content:[{type:"text",text:p}]}}}const r=o.result?._meta;if(r?.approval_id&&r?.status==="pending"){const n=r.approval_id;if(!/^[a-zA-Z0-9_-]{4,128}$/.test(n))return o;u("Permission gate: blocking until approval resolves",{tool:e,approvalId:n});const a=await G({approvalId:n,wsUrl:r.ws_url,pollUrl:r.polling_url??`${d}/approval-status/${n}`,pollIntervalMs:J,timeoutMs:Y,token:l});u("Permission gate resolved",{tool:e,status:a.status}),M({approvalId:n,toolName:e,toolArgs:s,sessionKey:null,gate:"permission_gate",pollUrl:"",authToken:l},a.status,!0);const i=X({approvalId:n,toolName:e,toolArgs:s,sessionKey:null,gate:"permission_gate",pollUrl:"",authToken:l},a);return{jsonrpc:"2.0",id:o.id,result:{content:[{type:"text",text:i}]}}}return o}function mt(e){G({approvalId:e.approvalId,wsUrl:e.wsUrl,pollUrl:e.pollUrl,pollIntervalMs:J,timeoutMs:Y,token:e.authToken}).then(async s=>{u("Approval resolved",{tool:e.toolName,status:s.status,session:e.sessionKey});const o=X(e,s),t=e.sessionKey??v?.getLastActiveSession()??null;if((s.status==="approved"||s.status==="executed")&&t&&$e(t).catch(()=>{}),t&&v){if(await v.sendToSession(t,o)){u("Agent resumed via sessions.send",{session:t,status:s.status}),M(e,s.status,!0);return}h("sessions.send failed, falling back to channel notification",{session:t})}try{await O.notification({method:"notifications/claude/channel",params:{content:o,meta:{tool_name:e.toolName,approval_id:e.approvalId,status:s.status,gate:e.gate}}}),u("Agent resumed via channel notification",{tool:e.toolName,status:s.status}),M(e,s.status,!0)}catch(r){h("Channel notification failed",{error:r instanceof Error?r.message:String(r)}),M(e,s.status,!1)}}).catch(s=>{h("Background approval resolver error",{error:s instanceof Error?s.message:String(s)})})}function M(e,s,o){const t=typeof e.toolArgs.action=="string"?e.toolArgs.action:null,r=t??e.toolName.replace(/^(?:\w+__)*aerostack__/,"")??e.toolName,n=t?`${r}`:`${r}(${Object.keys(e.toolArgs).join(", ")})`,a=o?"resume sent to agent":"session unavailable \u2014 resume not delivered";let i;s==="approved"||s==="executed"?i=o?`[RESUMED] ${n} \u2014 ${a}`:`[RESUME FAILED] ${n} \u2014 ${a}`:s==="rejected"?i=`[REJECTED] ${n} \u2014 rejection sent to agent`:s==="changes_requested"?i=`[CHANGES REQUESTED] ${n} \u2014 feedback sent to agent`:i=`[EXPIRED] ${n} \u2014 approval timed out, expiry sent to agent`,A("tools/call",{name:"aerostack__guardian_report",arguments:{action:i.slice(0,500),category:"approval",risk_level:"low",details:JSON.stringify({tool:e.toolName,approvalId:e.approvalId,status:s,delivered:o,session:e.sessionKey}).slice(0,500)}}).catch(()=>{})}function X(e,s){const o=s.reviewer_note?.trim()||null,t=`${e.toolName}(${Object.keys(e.toolArgs).join(", ")})`;switch(s.status){case"approved":case"executed":return e.gate==="tool_gate"?["[APPROVAL RECEIVED] Your pending action has been approved.","",`Action: ${t}`,o?`Reviewer note: "${o}"`:null,"",`Please now execute this action: call ${e.toolName} with the same parameters as before.`].filter(r=>r!==null).join(`
20
+ ${a}`}]}catch{return[{type:"text",text:"Failed to check messages."}]}}async function Ye(e){const t=typeof e.message=="string"?e.message.trim().slice(0,1e4):"";if(!t)return[{type:"text",text:"Error: message is required."}];try{const o=await T(`${d}/chat/messages`,{method:"POST",headers:{Authorization:`Bearer ${u}`,"Content-Type":"application/json"},body:JSON.stringify({content:t,session_id:x})});return o.ok?(H(x||"_default","agent",t),[{type:"text",text:"Reply sent to workspace owner."}]):[{type:"text",text:`Failed to send reply (HTTP ${o.status}).`}]}catch{return[{type:"text",text:"Failed to send reply."}]}}async function Xe(e,t){ze(e,t);const o=await _("tools/call",{name:e,arguments:t});if(o.error?.code===-32050){const n=o.error.data,a=n?.approval_id;if(!a||!/^[a-zA-Z0-9_-]{4,128}$/.test(a))return{jsonrpc:"2.0",id:o.id,error:{code:-32603,message:"Approval required but no approval_id returned"}};l("Tool gate: blocking until approval resolves",{tool:e,approvalId:a});const i=await Q({approvalId:a,wsUrl:n?.ws_url,pollUrl:n?.polling_url??`${d}/approval-status/${a}`,pollIntervalMs:ee,timeoutMs:te,token:u});l("Tool gate resolved",{tool:e,status:i.status}),W({approvalId:a,toolName:e,toolArgs:t,sessionKey:null,gate:"tool_gate",pollUrl:"",authToken:u},i.status,!0);const p=oe({approvalId:a,toolName:e,toolArgs:t,sessionKey:null,gate:"tool_gate",pollUrl:"",authToken:u},i);return{jsonrpc:"2.0",id:o.id,result:{content:[{type:"text",text:p}]}}}const r=o.result?._meta;if(r?.approval_id&&r?.status==="pending"){const n=r.approval_id;if(!/^[a-zA-Z0-9_-]{4,128}$/.test(n))return o;l("Permission gate: blocking until approval resolves",{tool:e,approvalId:n});const a=await Q({approvalId:n,wsUrl:r.ws_url,pollUrl:r.polling_url??`${d}/approval-status/${n}`,pollIntervalMs:ee,timeoutMs:te,token:u});l("Permission gate resolved",{tool:e,status:a.status}),W({approvalId:n,toolName:e,toolArgs:t,sessionKey:null,gate:"permission_gate",pollUrl:"",authToken:u},a.status,!0);const i=oe({approvalId:n,toolName:e,toolArgs:t,sessionKey:null,gate:"permission_gate",pollUrl:"",authToken:u},a);return{jsonrpc:"2.0",id:o.id,result:{content:[{type:"text",text:i}]}}}return o}function yt(e){Q({approvalId:e.approvalId,wsUrl:e.wsUrl,pollUrl:e.pollUrl,pollIntervalMs:ee,timeoutMs:te,token:e.authToken}).then(async t=>{l("Approval resolved",{tool:e.toolName,status:t.status,session:e.sessionKey});const o=oe(e,t),s=e.sessionKey??v?.getLastActiveSession()??null;if((t.status==="approved"||t.status==="executed")&&s&&xe(s).catch(()=>{}),s&&v){if(await v.sendToSession(s,o)){l("Agent resumed via sessions.send",{session:s,status:t.status}),W(e,t.status,!0);return}m("sessions.send failed, falling back to channel notification",{session:s})}try{await R.notification({method:"notifications/claude/channel",params:{content:o,meta:{tool_name:e.toolName,approval_id:e.approvalId,status:t.status,gate:e.gate}}}),l("Agent resumed via channel notification",{tool:e.toolName,status:t.status}),W(e,t.status,!0)}catch(r){m("Channel notification failed",{error:r instanceof Error?r.message:String(r)}),W(e,t.status,!1)}}).catch(t=>{m("Background approval resolver error",{error:t instanceof Error?t.message:String(t)})})}function W(e,t,o){const s=typeof e.toolArgs.action=="string"?e.toolArgs.action:null,r=s??e.toolName.replace(/^(?:\w+__)*aerostack__/,"")??e.toolName,n=s?`${r}`:`${r}(${Object.keys(e.toolArgs).join(", ")})`,a=o?"resume sent to agent":"session unavailable \u2014 resume not delivered";let i;t==="approved"||t==="executed"?i=o?`[RESUMED] ${n} \u2014 ${a}`:`[RESUME FAILED] ${n} \u2014 ${a}`:t==="rejected"?i=`[REJECTED] ${n} \u2014 rejection sent to agent`:t==="changes_requested"?i=`[CHANGES REQUESTED] ${n} \u2014 feedback sent to agent`:i=`[EXPIRED] ${n} \u2014 approval timed out, expiry sent to agent`,_("tools/call",{name:"aerostack__guardian_report",arguments:{action:i.slice(0,500),category:"approval",risk_level:"low",details:JSON.stringify({tool:e.toolName,approvalId:e.approvalId,status:t,delivered:o,session:e.sessionKey}).slice(0,500)}}).catch(()=>{})}function oe(e,t){const o=t.reviewer_note?.trim()||null,s=`${e.toolName}(${Object.keys(e.toolArgs).join(", ")})`;switch(t.status){case"approved":case"executed":return e.gate==="tool_gate"?["[APPROVAL RECEIVED] Your pending action has been approved.","",`Action: ${s}`,o?`Reviewer note: "${o}"`:null,"",`Please now execute this action: call ${e.toolName} with the same parameters as before.`].filter(r=>r!==null).join(`
21
21
  `):["[APPROVAL RECEIVED] Your request has been approved by the workspace owner.",o?`Reviewer note: "${o}"`:null,"","You may now proceed with the action you requested approval for."].filter(r=>r!==null).join(`
22
- `);case"changes_requested":return["[CHANGES REQUESTED] The workspace owner reviewed your action and requested changes.","",`Original action: ${t}`,`Reviewer feedback: "${o??"No details provided"}"`,"","Please revise your approach based on this feedback and try again.","Do NOT proceed with the original plan."].join(`
23
- `);case"rejected":return["[REJECTED] The workspace owner denied this action.","",`Action: ${t}`,o?`Reason: "${o}"`:"No reason provided.","","Do not retry this action. If you believe this was a mistake, inform the user."].join(`
24
- `);case"expired":return[`[EXPIRED] The approval request for "${t}" timed out \u2014 no decision was made.`,"","You may resubmit the request if the action is still needed."].join(`
25
- `);default:return`[APPROVAL UPDATE] Status for "${t}": ${s.status}.`}}const q=`
22
+ `);case"changes_requested":return["[CHANGES REQUESTED] The workspace owner reviewed your action and requested changes.","",`Original action: ${s}`,`Reviewer feedback: "${o??"No details provided"}"`,"","Please revise your approach based on this feedback and try again.","Do NOT proceed with the original plan."].join(`
23
+ `);case"rejected":return["[REJECTED] The workspace owner denied this action.","",`Action: ${s}`,o?`Reason: "${o}"`:"No reason provided.","","Do not retry this action. If you believe this was a mistake, inform the user."].join(`
24
+ `);case"expired":return[`[EXPIRED] The approval request for "${s}" timed out \u2014 no decision was made.`,"","You may resubmit the request if the action is still needed."].join(`
25
+ `);default:return`[APPROVAL UPDATE] Status for "${s}": ${t.status}.`}}const B=`
26
26
  === AEROSTACK GUARDIAN POLICY (MANDATORY) ===
27
27
 
28
28
  You are operating under Aerostack workspace monitoring. These rules apply to ALL actions:
@@ -50,30 +50,32 @@ or before starting a new one, call aerostack__chat_check to see if the owner sen
50
50
  If there are messages, read them carefully and respond using aerostack__chat_reply. This keeps
51
51
  the owner informed and allows them to steer your work in real time.
52
52
  === END WORKSPACE CHAT ===
53
- `.trim(),Ve=["delete","remove","drop","truncate","destroy","wipe","reset","deploy","publish","release","push","exec","shell","bash","run","command","terminal","install","uninstall","send","email","notify","webhook"];function Je(e,s){const o=e.toLowerCase(),t=Ve.some(n=>o.includes(n)),r=s??"";return t?`[REQUIRES GUARDIAN APPROVAL] ${r}`.trim():r}let le=null;async function U(){if(le)return;const e=await A("initialize",{protocolVersion:"2024-11-05",capabilities:{},clientInfo:{name:"aerostack-gateway",version:"0.15.14"}});if(e.result){const s=e.result,o=s.instructions??"";le={protocolVersion:s.protocolVersion??"2024-11-05",instructions:o?`${o}
53
+ `.trim(),Qe=["delete","remove","drop","truncate","destroy","wipe","reset","deploy","publish","release","push","exec","shell","bash","run","command","terminal","install","uninstall","send","email","notify","webhook"];function Ze(e,t){const o=e.toLowerCase(),s=Qe.some(n=>o.includes(n)),r=t??"";return s?`[REQUIRES GUARDIAN APPROVAL] ${r}`.trim():r}let he=null;async function U(){if(he)return;const e=await _("initialize",{protocolVersion:"2024-11-05",capabilities:{},clientInfo:{name:"aerostack-gateway",version:"0.15.14"}});if(e.result){const t=e.result,o=t.instructions??"";he={protocolVersion:t.protocolVersion??"2024-11-05",instructions:o?`${o}
54
54
 
55
- ${q}`:q}}}const O=new de({name:"aerostack-gateway",version:"0.15.14"},{capabilities:{tools:{},resources:{},prompts:{},experimental:{"claude/channel":{}}},instructions:q});O.setRequestHandler(me,async()=>{await U();const e=await A("tools/list");if(e.error)throw new Error(e.error.message);const o=(e.result.tools??[]).filter(t=>!He.has(t.name)).map(t=>({...t,description:Je(t.name,t.description)}));return o.push(...ne),{tools:o}}),O.setRequestHandler(he,async e=>{await U();const{name:s,arguments:o}=e.params;if(Me.has(s))return{content:(s==="aerostack__chat_check"?await Be():await Fe(o??{})).map(i=>({...i,type:"text"}))};const t=await Ge(s,o??{});if(t.error)return{content:[{type:"text",text:`Error: ${t.error.message}`}],isError:!0};const n=t.result.content??[{type:"text",text:JSON.stringify(t.result)}];if(Date.now()-$>Le)try{const i=await S(`${d}/chat/pending?since=${$}`,{headers:{Authorization:`Bearer ${l}`}});if(i.ok){const y=(await i.json()).messages??[];if($=Date.now(),y.length>0){const T=y.map(_=>`${E(_.sender_name)}: ${E(_.content)}`).join(`
55
+ ${B}`:B}}}const R=new fe({name:"aerostack-gateway",version:"0.15.14"},{capabilities:{tools:{},resources:{},prompts:{},experimental:{"claude/channel":{}}},instructions:B});R.setRequestHandler(ye,async()=>{await U();const e=await _("tools/list");if(e.error)throw new Error(e.error.message);const o=(e.result.tools??[]).filter(s=>!qe.has(s.name)).map(s=>({...s,description:Ze(s.name,s.description)}));return o.push(...le),{tools:o}}),R.setRequestHandler(_e,async e=>{await U();const{name:t,arguments:o}=e.params;if(Be.has(t))return{content:(t==="aerostack__chat_check"?await Je():await Ye(o??{})).map(i=>({...i,type:"text"}))};const s=await Xe(t,o??{});if(s.error)return{content:[{type:"text",text:`Error: ${s.error.message}`}],isError:!0};const n=s.result.content??[{type:"text",text:JSON.stringify(s.result)}];if(Date.now()-$>Me)try{const i=await T(`${d}/chat/pending?since=${$}`,{headers:{Authorization:`Bearer ${u}`}});if(i.ok){const A=(await i.json()).messages??[];if($=Date.now(),A.length>0){const O=A.map(E=>`${w(E.sender_name)}: ${w(E.content)}`).join(`
56
56
  `);n.unshift({type:"text",text:`--- WORKSPACE OWNER MESSAGE ---
57
- ${T}
58
- --- END MESSAGE \u2014 please acknowledge ---`})}}}catch{}return{content:n}});const Ye={uri:"aerostack://guardian/policy",name:"Aerostack Guardian Policy",description:"Mandatory workspace monitoring policy. Read this to understand required approval workflows.",mimeType:"text/plain"};O.setRequestHandler(fe,async()=>{await U();const e=await A("resources/list");if(e.error)throw new Error(e.error.message);const s=e.result;return{resources:[Ye,...s.resources??[]]}}),O.setRequestHandler(ge,async e=>{if(await U(),e.params.uri==="aerostack://guardian/policy")return{contents:[{uri:e.params.uri,text:q,mimeType:"text/plain"}]};const s=await A("resources/read",{uri:e.params.uri});if(s.error)throw new Error(s.error.message);return{contents:s.result.contents??[]}}),O.setRequestHandler(_e,async()=>{await U();const e=await A("prompts/list");if(e.error)throw new Error(e.error.message);return{prompts:e.result.prompts??[]}}),O.setRequestHandler(ye,async e=>{await U();const s=await A("prompts/get",{name:e.params.name,arguments:e.params.arguments});if(s.error)throw new Error(s.error.message);return{messages:s.result.messages??[]}});async function Xe(){const e=s=>process.stderr.write(s+`
57
+ ${O}
58
+ --- END MESSAGE \u2014 please acknowledge ---`})}}}catch{}return{content:n}});const et={uri:"aerostack://guardian/policy",name:"Aerostack Guardian Policy",description:"Mandatory workspace monitoring policy. Read this to understand required approval workflows.",mimeType:"text/plain"};R.setRequestHandler(we,async()=>{await U();const e=await _("resources/list");if(e.error)throw new Error(e.error.message);const t=e.result;return{resources:[et,...t.resources??[]]}}),R.setRequestHandler(Ae,async e=>{if(await U(),e.params.uri==="aerostack://guardian/policy")return{contents:[{uri:e.params.uri,text:B,mimeType:"text/plain"}]};const t=await _("resources/read",{uri:e.params.uri});if(t.error)throw new Error(t.error.message);return{contents:t.result.contents??[]}}),R.setRequestHandler(Ee,async()=>{await U();const e=await _("prompts/list");if(e.error)throw new Error(e.error.message);return{prompts:e.result.prompts??[]}}),R.setRequestHandler(Te,async e=>{await U();const t=await _("prompts/get",{name:e.params.name,arguments:e.params.arguments});if(t.error)throw new Error(t.error.message);return{messages:t.result.messages??[]}});async function tt(){const e=t=>process.stderr.write(t+`
59
59
  `);e(`
60
60
  Aerostack Connection Check
61
- `);try{const s=await fetch(`${d}/chat/connection-test`,{headers:{Authorization:`Bearer ${l}`},signal:AbortSignal.timeout(1e4)});s.ok||(s.status===401&&(e(" \u2705 Workspace URL reachable"),e(" \u274C Token invalid (HTTP 401)"),e(`
61
+ `);try{const t=await fetch(`${d}/chat/connection-test`,{headers:{Authorization:`Bearer ${u}`},signal:AbortSignal.timeout(1e4)});t.ok||(t.status===401&&(e(" \u2705 Workspace URL reachable"),e(" \u274C Token invalid (HTTP 401)"),e(`
62
62
  Fix: Regenerate token at your dashboard \u2192 Workspace \u2192 Tokens
63
- `),process.exit(1)),e(` \u274C Workspace unreachable (HTTP ${s.status})`),e(`
63
+ `),process.exit(1)),e(` \u274C Workspace unreachable (HTTP ${t.status})`),e(`
64
64
  Fix: Check AEROSTACK_WORKSPACE_URL is correct
65
- `),process.exit(1));const o=await s.json();e(` \u2705 Workspace URL reachable (${d})`),e(` \u2705 Token valid (${o.token_name}, role: ${o.token_role})`),e(` \u2705 Workspace connected ("${o.workspace}")`),e(` \u2705 Tools available (${o.mcp_servers} tools)`);try{const t=await fetch(`${d}/chat/heartbeat`,{method:"POST",headers:{Authorization:`Bearer ${l}`,"Content-Type":"application/json"},body:JSON.stringify({agent_type:"check",user_agent:"aerostack-gateway/check",bridge_id:"check"}),signal:AbortSignal.timeout(1e4)});e(t.ok?" \u2705 Dashboard registration (bridge visible in admin)":` \u26A0\uFE0F Dashboard registration (returned ${t.status})`)}catch{e(" \u26A0\uFE0F Dashboard registration (heartbeat failed \u2014 non-fatal)")}e(`
65
+ `),process.exit(1));const o=await t.json();e(` \u2705 Workspace URL reachable (${d})`),e(` \u2705 Token valid (${o.token_name}, role: ${o.token_role})`),e(` \u2705 Workspace connected ("${o.workspace}")`),e(` \u2705 Tools available (${o.mcp_servers} tools)`);try{const s=await fetch(`${d}/chat/heartbeat`,{method:"POST",headers:{Authorization:`Bearer ${u}`,"Content-Type":"application/json"},body:JSON.stringify({agent_type:"check",user_agent:"aerostack-gateway/check",bridge_id:"check"}),signal:AbortSignal.timeout(1e4)});e(s.ok?" \u2705 Dashboard registration (bridge visible in admin)":` \u26A0\uFE0F Dashboard registration (returned ${s.status})`)}catch{e(" \u26A0\uFE0F Dashboard registration (heartbeat failed \u2014 non-fatal)")}e(`
66
66
  All checks passed. Your agent is connected.`),e(` Dashboard: ${o.dashboard_url||"https://app.aerostack.dev"}
67
67
  `),o.mcp_servers===0&&e(` Note: 0 tools found. Add MCP servers or functions to your workspace first.
68
- `),process.exit(0)}catch(s){s?.name==="TimeoutError"||s?.name==="AbortError"?(e(" \u274C Workspace unreachable (timeout after 10s)"),e(`
68
+ `),process.exit(0)}catch(t){t?.name==="TimeoutError"||t?.name==="AbortError"?(e(" \u274C Workspace unreachable (timeout after 10s)"),e(`
69
69
  Fix: Check AEROSTACK_WORKSPACE_URL and your network connection
70
- `)):(e(` \u274C Connection failed (${s?.message??"unknown error"})`),e(`
70
+ `)):(e(` \u274C Connection failed (${t?.message??"unknown error"})`),e(`
71
71
  Fix: Check AEROSTACK_WORKSPACE_URL is correct
72
- `)),process.exit(1)}}async function Qe(){try{const e=await fetch(`${d}/chat/connection-test`,{headers:{Authorization:`Bearer ${l}`},signal:AbortSignal.timeout(5e3)});if(e.ok){const s=await e.json();u(`Connected to "${s.workspace}" (${s.mcp_servers} tools)`,{dashboard:"https://app.aerostack.dev"})}else e.status===401?h("Token invalid \u2014 regenerate at dashboard \u2192 Workspace \u2192 Tokens"):h("Connection check failed",{status:e.status})}catch{h("Could not verify connection (non-fatal)")}}async function Ze(){u("Connecting to workspace",{url:d});const e=new pe;await O.connect(e),u("Ready",{url:d}),Qe().catch(()=>{});const s=()=>{S(`${d}/chat/heartbeat`,{method:"POST",headers:{Authorization:`Bearer ${l}`,"Content-Type":"application/json"},body:JSON.stringify({agent_type:oe,user_agent:"aerostack-gateway/0.15.14",bridge_id:se})}).catch(()=>{})};s();const o=setInterval(s,12e4);if(process.on("exit",()=>clearInterval(o)),be)try{const r=await Se(async n=>{try{const a=await fetch(`${d}/guardian-batch`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${l}`,"User-Agent":"aerostack-gateway/0.15.14","X-Agent-Id":"aerostack-gateway"},body:JSON.stringify({events:n})});return a.ok?(await a.json()).config?.hook_tracking??null:null}catch{return null}},Ne);Pe&&await ke(r)&&u("Claude Code hook auto-installed",{port:r})}catch(t){h("Hook server failed to start (non-fatal)",{error:t instanceof Error?t.message:String(t)})}if(xe)try{const t=Ie??await Ce();t?(v=new Oe({port:te,token:t,rpcCall:A,onToolEvent:n=>{const a=I;if(!a)return;const i=z.get(a);if(i){if(n.phase==="start")i.push({name:n.toolName,category:n.category,started_at:Date.now(),ended_at:null,duration_ms:null,status:"running",args_summary:n.summary});else if(n.phase==="end"){const p=[...i].reverse().find(y=>y.name===n.toolName&&y.status==="running");p&&(p.ended_at=Date.now(),p.duration_ms=p.ended_at-p.started_at,p.status=n.error?"error":"success",n.error&&(p.error=n.error))}S(`${d}/chat/tool-status`,{method:"POST",headers:{Authorization:`Bearer ${l}`,"Content-Type":"application/json"},body:JSON.stringify({tool_name:n.toolName,phase:n.phase,category:n.category})}).catch(()=>{})}}}),await v.connect()?u("OpenClaw connector started",{port:te}):(u("OpenClaw gateway not reachable, skipping connector"),v=null)):u("OpenClaw integration skipped (no token found)")}catch(t){h("OpenClaw connector failed (non-fatal)",{error:t instanceof Error?t.message:String(t)})}et()}let W=null;function et(){W&&W.abort(),W=new AbortController;const e=async()=>{try{const t=await fetch(d,{method:"GET",headers:{Authorization:`Bearer ${l}`,Accept:"text/event-stream"},signal:W.signal});if(!t.ok||!t.body){h("Chat event listener: failed to connect",{status:t.status}),o();return}u("Chat event listener connected");const r=t.body.getReader(),n=new TextDecoder;let a="";for(;;){const{done:i,value:p}=await r.read();if(i)break;a+=n.decode(p,{stream:!0});const y=a.split(`
73
- `);a=y.pop()??"";let T="",_="";for(const w of y)w.startsWith("event: ")?T=w.slice(7).trim():w.startsWith("data: ")?_=w.slice(6):w===""&&T&&_&&(T==="aerostack_event"&&tt(_),T="",_="")}u("Chat event listener: stream ended, reconnecting"),o()}catch(t){if(t?.name==="AbortError")return;h("Chat event listener error",{error:t?.message}),o()}};let s=null;const o=()=>{s||(s=setTimeout(()=>{s=null,e()},5e3))};e()}async function tt(e){try{const s=JSON.parse(e);if(s.type!=="chat_message")return;let o=[];if(s.metadata_json)try{const f=JSON.parse(s.metadata_json);f?.v===1&&Array.isArray(f.attachments)&&(o=f.attachments)}catch{}const t=!!s.content&&s.content_type!=="media",r=o.length>0;if(!t&&!r)return;const n=s.session_id;u("Chat message received from admin",{sender:s.sender_name,sessionId:n,contentType:s.content_type,attachments:o.length});const a=/^[a-zA-Z0-9_-]{1,128}$/.test(s.message_id??"")?s.message_id:null,i=o.map(f=>{const L=Math.round(f.size/1024),B=L>=1024?`${(L/1024).toFixed(1)} MB`:`${L} KB`,F=E((f.filename||"file").slice(0,200)),P=E((f.mime_type||"application/octet-stream").slice(0,100)),k=a?`${d}/chat/media/${a}/${encodeURIComponent(f.filename)}`:"(unavailable)";return`[Attached: ${F} (${P}, ${B}) \u2014 ${k}]`}),p=i.length>0?`
72
+ `)),process.exit(1)}}async function st(){try{const e=await fetch(`${d}/chat/connection-test`,{headers:{Authorization:`Bearer ${u}`},signal:AbortSignal.timeout(5e3)});if(e.ok){const t=await e.json();l(`Connected to "${t.workspace}" (${t.mcp_servers} tools)`,{dashboard:"https://app.aerostack.dev"})}else e.status===401?m("Token invalid \u2014 regenerate at dashboard \u2192 Workspace \u2192 Tokens"):m("Connection check failed",{status:e.status})}catch{m("Could not verify connection (non-fatal)")}}async function ot(){l("Connecting to workspace",{url:d});const e=new ge;await R.connect(e),l("Ready",{url:d}),st().catch(()=>{});const t=()=>{T(`${d}/chat/heartbeat`,{method:"POST",headers:{Authorization:`Bearer ${u}`,"Content-Type":"application/json"},body:JSON.stringify({agent_type:ce,user_agent:"aerostack-gateway/0.15.14",bridge_id:ie})}).catch(()=>{})};t();const o=setInterval(t,12e4);if(process.on("exit",()=>clearInterval(o)),Ie)try{const r=await Ce(async n=>{try{const a=await fetch(`${d}/guardian-batch`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${u}`,"User-Agent":"aerostack-gateway/0.15.14","X-Agent-Id":"aerostack-gateway"},body:JSON.stringify({events:n})});return a.ok?(await a.json()).config?.hook_tracking??null:null}catch{return null}},Ue);Le&&await ve(r)&&l("Claude Code hook auto-installed",{port:r})}catch(s){m("Hook server failed to start (non-fatal)",{error:s instanceof Error?s.message:String(s)})}if(De)try{const s=je??await Ne();s?(v=new be({port:ae,token:s,rpcCall:_,onToolEvent:n=>{const a=I;if(!a)return;const i=q.get(a);if(i){if(n.phase==="start")i.push({name:n.toolName,category:n.category,started_at:Date.now(),ended_at:null,duration_ms:null,status:"running",args_summary:n.summary});else if(n.phase==="end"){const p=[...i].reverse().find(A=>A.name===n.toolName&&A.status==="running");p&&(p.ended_at=Date.now(),p.duration_ms=p.ended_at-p.started_at,p.status=n.error?"error":"success",n.error&&(p.error=n.error))}T(`${d}/chat/tool-status`,{method:"POST",headers:{Authorization:`Bearer ${u}`,"Content-Type":"application/json"},body:JSON.stringify({tool_name:n.toolName,phase:n.phase,category:n.category})}).catch(()=>{})}}}),await v.connect()?l("OpenClaw connector started",{port:ae}):(l("OpenClaw gateway not reachable, skipping connector"),v=null)):l("OpenClaw integration skipped (no token found)")}catch(s){m("OpenClaw connector failed (non-fatal)",{error:s instanceof Error?s.message:String(s)})}nt()}let F=null,G=!1;function nt(){G=!1,F&&F.abort();let e=null,t=0;const o=async()=>{if(G)return;const r=new AbortController;F=r;try{const n=await fetch(d,{method:"GET",headers:{Authorization:`Bearer ${u}`,Accept:"text/event-stream"},signal:r.signal});if(!n.ok||!n.body){m("Chat event listener: failed to connect",{status:n.status}),t++,s();return}l("Chat event listener connected"),t=0;const a=n.body.getReader(),i=new TextDecoder;let p="";for(;;){const{done:A,value:O}=await a.read();if(A)break;p+=i.decode(O,{stream:!0});const E=p.split(`
73
+ `);p=E.pop()??"";let y="",L="";for(const S of E)S.startsWith("event: ")?y=S.slice(7).trim():S.startsWith("data: ")?L=S.slice(6):S===""&&y&&L&&(y==="aerostack_event"&&at(L),y="",L="")}l("Chat event listener: stream ended, reconnecting"),s()}catch(n){if(n?.name==="AbortError")return;m("Chat event listener error",{error:n?.message}),t++,s()}},s=()=>{if(G||e)return;const r=Math.min(2e3*Math.pow(2,t),3e4);l(`Chat event listener: reconnecting in ${r/1e3}s (failures: ${t})`),e=setTimeout(()=>{e=null,o()},r)};o()}function rt(){G=!0,F?.abort()}async function at(e){try{const t=JSON.parse(e);if(t.type!=="chat_message")return;let o=[];if(t.metadata_json)try{const f=JSON.parse(t.metadata_json);f?.v===1&&Array.isArray(f.attachments)&&(o=f.attachments)}catch{}const s=!!t.content&&t.content_type!=="media",r=o.length>0;if(!s&&!r)return;const n=t.session_id;l("Chat message received from admin",{sender:t.sender_name,sessionId:n,contentType:t.content_type,attachments:o.length});const a=/^[a-zA-Z0-9_-]{1,128}$/.test(t.message_id??"")?t.message_id:null,i=o.map(f=>{const D=Math.round(f.size/1024),V=D>=1024?`${(D/1024).toFixed(1)} MB`:`${D} KB`,J=w((f.filename||"file").slice(0,200)),Y=w((f.mime_type||"application/octet-stream").slice(0,100)),X=a?`${d}/chat/media/${a}/${encodeURIComponent(f.filename)}`:"(unavailable)";return`[Attached: ${J} (${Y}, ${V}) \u2014 ${X}]`}),p=i.length>0?`
74
74
  `+i.join(`
75
- `):"",y=t?s.content+(p?`
75
+ `):"",A=s?t.content+(p?`
76
76
  `+i.map(f=>f).join(`
77
77
  `):""):i.join(`
78
- `);n&&(x=n,H(n,"user",y)),K.length>=We&&K.shift(),K.push({sender_name:s.sender_name??"Admin",content:t?s.content:i.join(", "),created_at:Date.now(),session_id:n}),$=Date.now();const T=E(s.sender_name??"Admin"),_=t?E(s.content):"",w=n||x||"_default",Q=`aerostack-chat-${/^[a-zA-Z0-9_-]{1,64}$/.test(w)?w:"_default"}`,b=`turn-${Date.now()}-${Math.random().toString(36).slice(2,8)}`,N=[];z.set(b,N),I=b;try{const{execFile:f}=await import("child_process");u("Spawning full agent turn for chat",{sender:T,session:Q});const L=_?_+p:p.trim(),B=`[Aerostack Dashboard \u2014 ${T}]: ${L}`;f("openclaw",["agent","--agent","main","--json","--session-id",Q,"-m",B],{timeout:12e4},async(F,P)=>{let k=[];if(!F&&P)try{const c=JSON.parse(P.trim()),g=c?.result?.payloads;Array.isArray(g)&&g.length>0?k=g:(c.text||c.reply||c.message)&&(k=[{text:c.text||c.reply||c.message}])}catch{const c=P.trim().split(`
79
- `).pop()||P.trim();c&&(k=[{text:c}])}k.length===0&&(k=[{text:`Message received. I'll process "${_.slice(0,100)}" shortly.`}]),await new Promise(c=>setTimeout(c,500));for(const c of N)c.status==="running"&&(c.status="timeout",c.ended_at=Date.now(),c.duration_ms=c.ended_at-c.started_at);if(N.length>0){const c=N.reduce((m,C)=>m+(C.duration_ms||0),0),g=N.length;try{await S(`${d}/chat/messages`,{method:"POST",headers:{Authorization:`Bearer ${l}`,"Content-Type":"application/json"},body:JSON.stringify({content:`Used ${g} tool${g>1?"s":""}`,content_type:"tool_activity",session_id:n,metadata:JSON.stringify({v:1,tools:N.map(m=>({name:m.name,category:m.category,duration_ms:m.duration_ms,status:m.status,args_summary:m.args_summary?.slice(0,500),error:m.error?.slice(0,500)})),total_duration_ms:c})})})}catch{}}z.delete(b),I===b&&(I=null);for(const c of k){const g=c.text?.trim()||"";if(c.mediaUrl){let m=!1;try{m=new URL(c.mediaUrl).protocol==="https:"}catch{}if(m)try{const C=await S(`${d}/chat/media`,{method:"POST",headers:{Authorization:`Bearer ${l}`,"Content-Type":"application/json"},body:JSON.stringify({media_url:c.mediaUrl,content:g||void 0,session_id:n})});C.ok?u("Chat media sent to workspace",{mediaUrl:c.mediaUrl}):h("Chat media upload failed",{status:C.status})}catch(C){h("Failed to send chat media",{error:C?.message})}else h("Skipping invalid media URL",{mediaUrl:c.mediaUrl});H(w,"agent",g||`[media: ${c.mediaUrl}]`)}else if(g&&g.length>=2){H(w,"agent",g);try{const m=await S(`${d}/chat/messages`,{method:"POST",headers:{Authorization:`Bearer ${l}`,"Content-Type":"application/json"},body:JSON.stringify({content:g.slice(0,1e4),session_id:n})});m.ok?u("Chat reply sent to workspace"):h("Chat reply failed",{status:m.status})}catch(m){h("Failed to send chat reply",{error:m?.message})}}}})}catch(f){h("Failed to spawn agent turn",{error:f?.message}),z.delete(b),I===b&&(I=null)}}catch{}}function ue(){W?.abort(),v?.stop(),Re(),process.exit(0)}process.on("SIGTERM",()=>{ue()}),process.on("SIGINT",()=>{ue()}),process.argv.includes("--check")?Xe():Ze().catch(e=>{ve("Fatal error",{error:e instanceof Error?e.message:String(e)}),process.exit(1)});
78
+ `);n&&(x=n,H(n,"user",A)),M.length>=Fe&&M.shift(),M.push({sender_name:t.sender_name??"Admin",content:s?t.content:i.join(", "),created_at:Date.now(),session_id:n}),$=Date.now();const O=w(t.sender_name??"Admin"),E=s?w(t.content):"",y=n||x||"_default",S=`aerostack-chat-${/^[a-zA-Z0-9_-]{1,64}$/.test(y)?y:"_default"}`,b=`turn-${Date.now()}-${Math.random().toString(36).slice(2,8)}`,N=[];q.set(b,N),I=b;try{const{execFile:f}=await import("child_process");l("Spawning full agent turn for chat",{sender:O,session:S});const D=E?E+p:p.trim(),V=se(y),J=`You are chatting with ${O} via the Aerostack admin dashboard. Address them by name and be helpful.
79
+
80
+ `,Y=`${V}${J}[${O}]: ${D}`;f("openclaw",["agent","--agent","main","--json","--session-id",S,"-m",Y],{timeout:12e4},async(X,z)=>{let P=[];if(!X&&z)try{const c=JSON.parse(z.trim()),g=c?.result?.payloads;Array.isArray(g)&&g.length>0?P=g:(c.text||c.reply||c.message)&&(P=[{text:c.text||c.reply||c.message}])}catch{const c=z.trim().split(`
81
+ `).pop()||z.trim();c&&(P=[{text:c}])}P.length===0&&(P=[{text:`Message received. I'll process "${E.slice(0,100)}" shortly.`}]),await new Promise(c=>setTimeout(c,500));for(const c of N)c.status==="running"&&(c.status="timeout",c.ended_at=Date.now(),c.duration_ms=c.ended_at-c.started_at);if(N.length>0){const c=N.reduce((h,C)=>h+(C.duration_ms||0),0),g=N.length;try{await T(`${d}/chat/messages`,{method:"POST",headers:{Authorization:`Bearer ${u}`,"Content-Type":"application/json"},body:JSON.stringify({content:`Used ${g} tool${g>1?"s":""}`,content_type:"tool_activity",session_id:n,metadata:JSON.stringify({v:1,tools:N.map(h=>({name:h.name,category:h.category,duration_ms:h.duration_ms,status:h.status,args_summary:h.args_summary?.slice(0,500),error:h.error?.slice(0,500)})),total_duration_ms:c})})})}catch{}}q.delete(b),I===b&&(I=null);for(const c of P){const g=c.text?.trim()||"";if(c.mediaUrl){let h=!1;try{h=new URL(c.mediaUrl).protocol==="https:"}catch{}if(h)try{const C=await T(`${d}/chat/media`,{method:"POST",headers:{Authorization:`Bearer ${u}`,"Content-Type":"application/json"},body:JSON.stringify({media_url:c.mediaUrl,content:g||void 0,session_id:n})});C.ok?l("Chat media sent to workspace",{mediaUrl:c.mediaUrl}):m("Chat media upload failed",{status:C.status})}catch(C){m("Failed to send chat media",{error:C?.message})}else m("Skipping invalid media URL",{mediaUrl:c.mediaUrl});H(y,"agent",g||`[media: ${c.mediaUrl}]`)}else if(g&&g.length>=2){H(y,"agent",g);try{const h=await T(`${d}/chat/messages`,{method:"POST",headers:{Authorization:`Bearer ${u}`,"Content-Type":"application/json"},body:JSON.stringify({content:g.slice(0,1e4),session_id:n})});h.ok?l("Chat reply sent to workspace"):m("Chat reply failed",{status:h.status})}catch(h){m("Failed to send chat reply",{error:h?.message})}}}})}catch(f){m("Failed to spawn agent turn",{error:f?.message}),q.delete(b),I===b&&(I=null)}}catch{}}function me(){rt(),v?.stop(),$e(),process.exit(0)}process.on("SIGTERM",()=>{me()}),process.on("SIGINT",()=>{me()}),process.argv.includes("--check")?tt():ot().catch(e=>{Pe("Fatal error",{error:e instanceof Error?e.message:String(e)}),process.exit(1)});
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aerostack/gateway",
3
- "version": "0.15.26",
3
+ "version": "0.15.28",
4
4
  "description": "stdio-to-HTTP bridge connecting any MCP client to Aerostack Workspaces",
5
5
  "author": "Aerostack",
6
6
  "license": "MIT",