@aerostack/gateway 0.15.30 → 0.17.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,18 +1,14 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- import{homedir as Y}from"node:os";import{readFile as V}from"node:fs/promises";import{join as Q}from"node:path";import{execFile as H}from"node:child_process";const Z=process.env.AEROSTACK_WORKSPACE_URL||"https://api.aerostack.dev/api/gateway/ws/notification",m=process.env.AEROSTACK_TOKEN||"",v=parseInt(process.env.AEROSTACK_OPENCLAW_PORT||"18789",10),ee=6e4,te=1e3,ne=3e4,se=3e4,L=12e4,x=`aerostack-connector-${Date.now().toString(36)}`,h=Z.replace(/\/+$/,"");function o(e,t){const n=new Date().toISOString(),s=t?` ${JSON.stringify(t)}`:"";process.stderr.write(`[${n}] [INFO] ${e}${s}
4
- `)}function g(e,t){const n=new Date().toISOString(),s=t?` ${JSON.stringify(t)}`:"";process.stderr.write(`[${n}] [WARN] ${e}${s}
5
- `)}function p(e,t){const n=new Date().toISOString(),s=t?` ${JSON.stringify(t)}`:"";process.stderr.write(`[${n}] [ERROR] ${e}${s}
6
- `)}async function w(e,t={},n=se){const s=new AbortController,a=setTimeout(()=>s.abort(),n);try{return await fetch(e,{...t,signal:s.signal})}finally{clearTimeout(a)}}let C=null,S=!1,$=null,E=0,D=0;const _=new Map,ae=100,oe=2e3,re=3e4,ie=3e4;let M=null;async function ce(){try{const e=Q(Y(),".openclaw","openclaw.json"),t=await V(e,"utf-8");return JSON.parse(t)?.gateway?.auth?.token??null}catch{return null}}async function b(){const e=await ce();if(!e)return g("No OpenClaw gateway token found, skipping WS control"),!1;const t=`ws://127.0.0.1:${v}`;return new Promise(n=>{try{const s=new WebSocket(t);C=s;const a=setTimeout(()=>{o("Gateway WS connect timeout");try{s.close()}catch{}n(!1)},1e4);s.onopen=()=>{clearTimeout(a),o("Gateway WS connected, sending handshake"),K({type:"req",id:String(++D),method:"connect",params:{minProtocol:3,maxProtocol:3,client:{id:"openclaw-tui",displayName:"Aerostack Connector",version:"1.0.0",platform:process.platform,mode:"cli"},auth:{token:e},scopes:["operator.read","operator.write","operator.admin"],caps:["tool-events","sessions"]}})},s.onmessage=c=>{try{const i=JSON.parse(String(c.data));le(i,n)}catch{}},s.onerror=()=>{clearTimeout(a),n(!1)},s.onclose=()=>{clearTimeout(a),C=null,S=!1,I||ge()}}catch{n(!1)}})}function K(e){try{C?.send(JSON.stringify(e))}catch{}}function le(e,t){if(e.type==="res"&&!S){e.ok?(S=!0,E=0,o("Gateway WS authenticated",{port:v}),t?.(!0),N("sessions.subscribe").then(n=>{n.ok&&o("Subscribed to gateway session events")})):(g("Gateway WS auth rejected",{error:e.error?.message}),t?.(!1));return}if(e.type==="res"&&e.id&&_.has(e.id)){const n=_.get(e.id);_.delete(e.id),n(e);return}e.type==="event"&&ue(e)}const T=new Map;function ue(e){const t=e.event,n=e.payload??{};if(t==="session.tool"){const s=n.data,a=s?.name??n.toolName??"unknown",c=s?.phase??"";(c==="start"||c==="end")&&me(a,c,"tool_call")}else if(t==="session.message"){const s=n.sessionKey??"",a=n.message,c=typeof a=="object"?a?.role:void 0;let i="";if(typeof a=="string")i=a;else if(a){const u=a.content;typeof u=="string"?i=u:Array.isArray(u)&&(i=u.map(f=>typeof f=="string"?f:f?.text??"").join("")),i||(i=a.text??n.text??"")}o("session.message",{sessionKey:s,role:c,textLength:i?.length??0});const r=T.get(s);r&&i&&c!=="user"&&(o("WS fast path: got agent response",{sessionKey:s,textLength:i.length}),r({text:i}))}else t==="sessions.changed"&&o("Gateway session changed",{key:n.sessionKey})}function N(e,t){return fe(e,t,1e4)}function fe(e,t,n=1e4){return new Promise(s=>{const a=String(++D),c=setTimeout(()=>{_.delete(a),s({type:"res",id:a,ok:!1,error:{code:"TIMEOUT",message:"timeout"}})},n);_.set(a,i=>{clearTimeout(c),s(i)}),K({type:"req",id:a,method:e,params:t})})}async function de(){if(!S){W("disconnected");return}const e=await N("health");e.ok?W("healthy",e.payload):W("unhealthy",{error:e.error?.message})}async function W(e,t){try{await w(`${h}/chat/heartbeat`,{method:"POST",headers:{Authorization:`Bearer ${m}`,"Content-Type":"application/json"},body:JSON.stringify({agent_type:"openclaw-connector",user_agent:"aerostack-connector/1.0.0",bridge_id:x,gateway_status:e,gateway_data:t})})}catch{}}function ge(){if(I||$||E>=ae)return;const e=Math.min(oe*Math.pow(2,Math.min(E,5)),re);E++,o(`Gateway WS reconnecting in ${e/1e3}s`,{attempt:E}),$=setTimeout(async()=>{$=null,S=!1,_.clear(),await b()},e)}function ye(){return new Promise(e=>{o("Restarting OpenClaw gateway"),H("openclaw",["gateway","restart"],{timeout:3e4},t=>{t?(p("Gateway restart failed",{error:t.message}),e(!1)):(o("Gateway restart triggered"),setTimeout(()=>{b()},5e3),e(!0))})})}async function he(e,t){const n=Date.now();o("Agent turn starting",{sessionKey:e,messageLength:t.length});const s="agent:main:main";if(S)try{const a=`aerostack-${Date.now()}-${Math.random().toString(36).slice(2,8)}`,c=new Promise(r=>{const u=setTimeout(()=>{T.delete(s),r(null)},L);T.set(s,f=>{clearTimeout(u),T.delete(s),r(f)})});N("sessions.messages.subscribe",{key:s});const i=await N("sessions.send",{key:s,message:t,idempotencyKey:a});if(!i.ok)T.delete(s),g("Gateway WS sessions.send rejected, falling back to CLI",{error:i.error?.message});else{const r=await c,u=Date.now()-n;if(r?.text)return o("Agent turn completed (WS fast path)",{sessionKey:e,durationMs:u}),[{text:r.text}];g("No response received via WS events, falling back to CLI",{durationMs:u})}}catch(a){T.delete(s),g("Gateway WS fast path error, falling back to CLI",{error:a?.message})}return S||(g("Gateway disconnected, attempting reconnect before agent turn"),await b()||(g("Gateway unreachable, attempting restart"),await ye(),await new Promise(c=>setTimeout(c,8e3)))),new Promise((a,c)=>{H("openclaw",["agent","--agent","main","--json","--session-id",e,"-m",t],{timeout:L},(r,u)=>{const f=Date.now()-n;if(r){p("Agent turn failed",{sessionKey:e,durationMs:f,error:r.message}),c(r);return}let l=[];if(u)try{const d=JSON.parse(u.trim()),y=d?.result?.payloads;Array.isArray(y)&&y.length>0?l=y:(d.text||d.reply||d.message)&&(l=[{text:d.text||d.reply||d.message}])}catch{const d=u.trim().split(`
7
- `).pop()||"";try{const y=JSON.parse(d);l=[{text:y.text||y.reply||y.message||d}]}catch{d&&(l=[{text:d}])}}l.length===0&&(l=[{text:`Processed your message (${f}ms).`}]),o("Agent turn completed (CLI fallback)",{sessionKey:e,durationMs:f,payloads:l.length}),a(l)})})}let I=!1,G=null;async function J(){try{const e=await w(`${h}/chat/heartbeat`,{method:"POST",headers:{Authorization:`Bearer ${m}`,"Content-Type":"application/json"},body:JSON.stringify({agent_type:"openclaw-connector",user_agent:"aerostack-connector/1.0.0",bridge_id:x})});e.ok?o("Heartbeat OK"):g("Heartbeat failed",{status:e.status})}catch(e){g("Heartbeat error",{error:e?.message})}}async function z(e,t){try{const n=await w(`${h}/chat/messages`,{method:"POST",headers:{Authorization:`Bearer ${m}`,"Content-Type":"application/json"},body:JSON.stringify({content:e.slice(0,1e4),session_id:t||G})});n.ok?o("Reply sent",{length:e.length,sessionId:t}):g("Reply failed",{status:n.status})}catch(n){p("Reply error",{error:n?.message})}}async function me(e,t,n){try{await w(`${h}/chat/tool-status`,{method:"POST",headers:{Authorization:`Bearer ${m}`,"Content-Type":"application/json"},body:JSON.stringify({tool_name:e,phase:t,category:n})})}catch{}}const P=new Map,U=50,F=10;function j(e,t,n){let s=P.get(e);s||(s=[],P.set(e,s)),s.push({role:t,content:n,ts:Date.now()}),s.length>U&&s.splice(0,s.length-U)}function pe(e){const t=P.get(e);return!t||t.length===0?"":`Previous conversation:
8
- `+t.slice(-F).map(n=>`[${n.role==="user"?"Admin":"You (Agent)"}] ${n.content}`).join(`
3
+ import{homedir as J}from"node:os";import{readFile as V}from"node:fs/promises";import{join as x}from"node:path";import{execFile as L}from"node:child_process";const ue=process.env.AEROSTACK_WORKSPACE_URL||"https://api.aerostack.dev/api/gateway/ws/notification",S=process.env.AEROSTACK_TOKEN||"",K=parseInt(process.env.AEROSTACK_OPENCLAW_PORT||"18789",10),Y=6e4,ge=1e3,de=3e4,ye=3e4,Q=12e4,I=`aerostack-connector-${Date.now().toString(36)}`,w=ue.replace(/\/+$/,"");function r(e,t){const n=new Date().toISOString(),s=t?` ${JSON.stringify(t)}`:"";process.stderr.write(`[${n}] [INFO] ${e}${s}
4
+ `)}function u(e,t){const n=new Date().toISOString(),s=t?` ${JSON.stringify(t)}`:"";process.stderr.write(`[${n}] [WARN] ${e}${s}
5
+ `)}function m(e,t){const n=new Date().toISOString(),s=t?` ${JSON.stringify(t)}`:"";process.stderr.write(`[${n}] [ERROR] ${e}${s}
6
+ `)}async function C(e,t={},n=ye){const s=new AbortController,a=setTimeout(()=>s.abort(),n);try{return await fetch(e,{...t,signal:s.signal})}finally{clearTimeout(a)}}let D=null,_=!1,$=null,P=0,Z=0;const R=new Map,pe=100,fe=2e3,he=3e4,we=3e4;let B=null;async function me(){try{const e=x(J(),".openclaw","openclaw.json"),t=await V(e,"utf-8");return JSON.parse(t)?.gateway?.auth?.token??null}catch{return null}}async function E(){const e=await me();if(!e)return u("No OpenClaw gateway token found, skipping WS control"),!1;const t=`ws://127.0.0.1:${K}`;return new Promise(n=>{try{const s=new WebSocket(t);D=s;const a=setTimeout(()=>{r("Gateway WS connect timeout");try{s.close()}catch{}n(!1)},1e4);s.onopen=()=>{clearTimeout(a),r("Gateway WS connected, sending handshake"),ee({type:"req",id:String(++Z),method:"connect",params:{minProtocol:3,maxProtocol:3,client:{id:"openclaw-tui",displayName:"Aerostack Connector",version:"1.0.0",platform:process.platform,mode:"cli"},auth:{token:e},scopes:["operator.read","operator.write","operator.admin"],caps:["tool-events","sessions"]}})},s.onmessage=o=>{try{const i=JSON.parse(String(o.data));_e(i,n)}catch{}},s.onerror=()=>{clearTimeout(a),n(!1)},s.onclose=()=>{clearTimeout(a),D=null,_=!1,j||Te()}}catch{n(!1)}})}function ee(e){try{D?.send(JSON.stringify(e))}catch{}}function _e(e,t){if(e.type==="res"&&!_){e.ok?(_=!0,P=0,r("Gateway WS authenticated",{port:K}),t?.(!0),N("sessions.subscribe").then(n=>{n.ok&&r("Subscribed to gateway session events")})):(u("Gateway WS auth rejected",{error:e.error?.message}),t?.(!1));return}if(e.type==="res"&&e.id&&R.has(e.id)){const n=R.get(e.id);R.delete(e.id),n(e);return}e.type==="event"&&Se(e)}const b=new Map;function Se(e){const t=e.event,n=e.payload??{};if(t==="session.tool"){const s=n.data,a=s?.name??n.toolName??"unknown",o=s?.phase??"";(o==="start"||o==="end")&&Oe(a,o,"tool_call")}else if(t==="session.message"){const s=n.sessionKey??"",a=n.message,o=typeof a=="object"?a?.role:void 0;let i="";if(typeof a=="string")i=a;else if(a){const c=a.content;typeof c=="string"?i=c:Array.isArray(c)&&(i=c.map(l=>typeof l=="string"?l:l?.text??"").join("")),i||(i=a.text??n.text??"")}r("session.message",{sessionKey:s,role:o,textLength:i?.length??0});const g=b.get(s);g&&i&&o!=="user"&&(r("WS fast path: got agent response",{sessionKey:s,textLength:i.length}),g({text:i}))}else t==="sessions.changed"&&r("Gateway session changed",{key:n.sessionKey})}function N(e,t){return G(e,t,1e4)}function G(e,t,n=1e4){return new Promise(s=>{const a=String(++Z),o=setTimeout(()=>{R.delete(a),s({type:"res",id:a,ok:!1,error:{code:"TIMEOUT",message:"timeout"}})},n);R.set(a,i=>{clearTimeout(o),s(i)}),ee({type:"req",id:a,method:e,params:t})})}async function ke(){if(!_){U("disconnected");return}const e=await N("health");e.ok?U("healthy",e.payload):U("unhealthy",{error:e.error?.message})}async function U(e,t){try{await C(`${w}/chat/heartbeat`,{method:"POST",headers:{Authorization:`Bearer ${S}`,"Content-Type":"application/json"},body:JSON.stringify({agent_type:"openclaw-connector",user_agent:"aerostack-connector/1.0.0",bridge_id:I,gateway_status:e,gateway_data:t})})}catch{}}function Te(){if(j||$||P>=pe)return;const e=Math.min(fe*Math.pow(2,Math.min(P,5)),he);P++,r(`Gateway WS reconnecting in ${e/1e3}s`,{attempt:P}),$=setTimeout(async()=>{$=null,_=!1,R.clear(),await E()},e)}function Ce(){return new Promise(e=>{r("Restarting OpenClaw gateway"),L("openclaw",["gateway","restart"],{timeout:3e4},t=>{t?(m("Gateway restart failed",{error:t.message}),e(!1)):(r("Gateway restart triggered"),setTimeout(()=>{E()},5e3),e(!0))})})}function te(){return new Promise(e=>{r("Diagnose: restarting OpenClaw gateway (fast)"),L("openclaw",["gateway","restart"],{timeout:1e4},t=>{t?(m("Diagnose: gateway restart failed",{error:t.message}),e(!1)):(r("Diagnose: gateway restart triggered"),setTimeout(()=>{E()},3e3),e(!0))})})}async function Ae(e,t){const n=Date.now();r("Agent turn starting",{sessionKey:e,messageLength:t.length});const s=e;if(_)try{const a=`aerostack-${Date.now()}-${Math.random().toString(36).slice(2,8)}`,o=`agent:main:${s}`,i=new Promise(c=>{const l=setTimeout(()=>{b.delete(o),c(null)},Q);b.set(o,f=>{clearTimeout(l),b.delete(o),c(f)})});N("sessions.messages.subscribe",{key:o});let g=await N("sessions.send",{key:o,message:t,idempotencyKey:a});if(!g.ok&&g.error?.message?.includes("session not found")&&(r("Creating new gateway session for thread",{key:s}),(await N("sessions.create",{key:s,agentId:"main"})).ok&&(g=await N("sessions.send",{key:o,message:t,idempotencyKey:a}))),!g.ok)b.delete(o),u("Gateway WS sessions.send rejected, falling back to CLI",{error:g.error?.message});else{const c=await i,l=Date.now()-n;if(c?.text)return r("Agent turn completed (WS fast path)",{sessionKey:o,durationMs:l}),[{text:c.text}];u("No response received via WS events, falling back to CLI",{durationMs:l})}}catch(a){b.delete(`agent:main:${s}`),u("Gateway WS fast path error, falling back to CLI",{error:a?.message})}return _||(u("Gateway disconnected, attempting reconnect before agent turn"),await E()||(u("Gateway unreachable, attempting restart"),await Ce(),await new Promise(o=>setTimeout(o,8e3)))),new Promise((a,o)=>{L("openclaw",["agent","--agent","main","--json","--session-id",e,"-m",t],{timeout:Q},(g,c)=>{const l=Date.now()-n;if(g){m("Agent turn failed",{sessionKey:e,durationMs:l,error:g.message}),o(g);return}let f=[];if(c)try{const y=JSON.parse(c.trim()),h=y?.result?.payloads;Array.isArray(h)&&h.length>0?f=h:(y.text||y.reply||y.message)&&(f=[{text:y.text||y.reply||y.message}])}catch{const y=c.trim().split(`
7
+ `).pop()||"";try{const h=JSON.parse(y);f=[{text:h.text||h.reply||h.message||y}]}catch{y&&(f=[{text:y}])}}f.length===0&&(f=[{text:`Processed your message (${l}ms).`}]),r("Agent turn completed (CLI fallback)",{sessionKey:e,durationMs:l,payloads:f.length}),a(f)})})}let j=!1,v=null;async function ne(){try{const e=await C(`${w}/chat/heartbeat`,{method:"POST",headers:{Authorization:`Bearer ${S}`,"Content-Type":"application/json"},body:JSON.stringify({agent_type:"openclaw-connector",user_agent:"aerostack-connector/1.0.0",bridge_id:I})});e.ok?r("Heartbeat OK"):u("Heartbeat failed",{status:e.status})}catch(e){u("Heartbeat error",{error:e?.message})}}async function z(e,t){try{const n=await C(`${w}/chat/messages`,{method:"POST",headers:{Authorization:`Bearer ${S}`,"Content-Type":"application/json"},body:JSON.stringify({content:e.slice(0,1e4),session_id:t||v})});n.ok?r("Reply sent",{length:e.length,sessionId:t}):u("Reply failed",{status:n.status})}catch(n){m("Reply error",{error:n?.message})}}async function Oe(e,t,n){try{await C(`${w}/chat/tool-status`,{method:"POST",headers:{Authorization:`Bearer ${S}`,"Content-Type":"application/json"},body:JSON.stringify({tool_name:e,phase:t,category:n})})}catch{}}const F=new Map,se=50,ae=10;function q(e,t,n){let s=F.get(e);s||(s=[],F.set(e,s)),s.push({role:t,content:n,ts:Date.now()}),s.length>se&&s.splice(0,s.length-se)}function Re(e){const t=F.get(e);return!t||t.length===0?"":`Previous conversation:
8
+ `+t.slice(-ae).map(n=>`[${n.role==="user"?"Admin":"You (Agent)"}] ${n.content}`).join(`
9
9
  `)+`
10
10
 
11
- `}async function we(e){try{const t=await w(`${h}/chat/history?limit=${F}`,{headers:{Authorization:`Bearer ${m}`}});if(!t.ok)return;const n=await t.json();n.session_id&&(G=n.session_id);for(const s of n.messages??[]){const a=s.sender_type==="agent"?"agent":"user";j(n.session_id||e,a,s.content)}o("History seeded",{sessionId:n.session_id,count:n.messages?.length??0})}catch{}}let R=null,k=0,O=null,A=!1;function Se(e){return(e||"").replace(/[\r\n]+/g," ").replace(/\[/g,"(").replace(/\]/g,")").replace(/---/g,"\u2014")}async function _e(e,t){try{const n=await w(`${h}/chat/media/${e}/${t.filename}`,{headers:{Authorization:`Bearer ${m}`}},15e3);if(!n.ok)return g("Media fetch failed",{messageId:e,filename:t.filename,status:n.status}),null;const s=Buffer.from(await n.arrayBuffer());return`data:${t.mime_type};base64,${s.toString("base64")}`}catch(n){return g("Media fetch error",{messageId:e,filename:t.filename,error:n?.message}),null}}function Te(e,t){return`
12
- [Attached files]
13
- ${e.map(s=>{const a=Math.round(s.size/1024),c=`${h}/chat/media/${t}/${s.filename}`;return`- ${s.filename} (${s.mime_type}, ${a}KB) \u2192 ${c}`}).join(`
14
- `)}`}async function Ae(e){try{const t=JSON.parse(e);if(t.type!=="chat_message")return;const n=t.session_id,s=Se(t.sender_name||"Admin"),a=t.content||"",c=t.content_type||"text",i=t.message_id;let r=[];if(t.metadata_json)try{const l=JSON.parse(t.metadata_json);l?.v===1&&Array.isArray(l.attachments)&&(r=l.attachments)}catch{}if(!a&&r.length===0)return;o("Chat message received",{sender:s,sessionId:n,contentType:c,textLength:a.length,attachments:r.length}),n&&(G=n);const u=`aerostack-chat-${n||"_default"}`;let f=`[${s} via Aerostack Dashboard]:`;if(a&&(f+=` ${a}`),r.length>0&&i){f+=Te(r,i);const l=r.filter(d=>d.mime_type.startsWith("image/"));if(l.length>0){const y=(await Promise.all(l.slice(0,3).map(X=>_e(i,X)))).filter(Boolean);y.length>0&&(f+=`
15
- [Image data (base64)]
16
- ${y.join(`
17
- `)}`)}}j(n||"_default","user",a||`[${r.map(l=>l.filename).join(", ")}]`);try{const l=await he(u,f);for(const d of l){const y=(typeof d.text=="string"?d.text:String(d.text??"")).trim();y&&y.length>=2&&(j(n||"_default","agent",y),await z(y,n))}}catch(l){p("Agent turn failed, sending fallback",{error:l?.message}),await z("I received your message but encountered an error processing it. Please try again.",n)}}catch(t){p("handleChatMessage error",{error:t?.message})}}async function q(){if(!A){A=!0,R?.abort(),R=new AbortController;try{o("SSE connecting",{url:h});const e=await fetch(h,{method:"GET",headers:{Authorization:`Bearer ${m}`,Accept:"text/event-stream"},signal:R.signal});if(!e.ok||!e.body){g("SSE connect failed",{status:e.status}),k++,A=!1,B();return}o("SSE connected"),k=0;const t=e.body.getReader(),n=new TextDecoder;let s="";for(;;){const{done:a,value:c}=await t.read();if(a)break;s+=n.decode(c,{stream:!0});const i=s.split(`
18
- `);s=i.pop()??"";let r="",u="";for(const f of i)f.startsWith("event: ")?r=f.slice(7).trim():f.startsWith("data: ")?u=f.slice(6):f===""&&r&&u&&(r==="aerostack_event"&&Ae(u),r="",u="")}o("SSE stream ended, reconnecting"),A=!1,B()}catch(e){if(e?.name==="AbortError"){A=!1;return}g("SSE error",{error:e?.message}),k++,A=!1,B()}}}function B(){if(O)return;const e=Math.min(te*Math.pow(2,k),ne);o(`SSE reconnecting in ${e/1e3}s`,{failures:k}),O=setTimeout(()=>{O=null,q()},e)}async function $e(){try{const e=await w(`${h}/chat/connection-test`,{headers:{Authorization:`Bearer ${m}`}},1e4);if(e.ok){const t=await e.json();return o(`Connected to "${t.workspace}"`,{tools:t.mcp_servers,token:t.token_name,role:t.token_role}),!0}else return e.status===401?(p("Token invalid \u2014 regenerate at dashboard \u2192 Workspace \u2192 Tokens"),!1):(g("Connection check failed",{status:e.status}),!1)}catch(e){return p("Connection check error",{error:e?.message}),!1}}async function Ee(){o("Aerostack Connector starting",{workspace:h,openclawPort:v,bridgeId:x}),m||(p("AEROSTACK_TOKEN is required"),process.exit(1)),await $e()||g("Connection verification failed, continuing anyway (will retry)"),await we("_default"),await b()?o("Gateway WS control layer active"):g("Gateway WS not reachable \u2014 will keep retrying"),M=setInterval(de,ie),await J();const n=setInterval(J,ee);q(),o("Connector ready \u2014 listening for dashboard chat messages");const s=()=>{I=!0,o("Shutting down"),clearInterval(n),M&&clearInterval(M),R?.abort(),O&&clearTimeout(O),$&&clearTimeout($);try{C?.close(1e3)}catch{}process.exit(0)};process.on("SIGTERM",s),process.on("SIGINT",s)}Ee().catch(e=>{p("Fatal error",{error:e?.message}),process.exit(1)});
11
+ `}async function be(e){try{const t=await C(`${w}/chat/history?limit=${ae}`,{headers:{Authorization:`Bearer ${S}`}});if(!t.ok)return;const n=await t.json();n.session_id&&(v=n.session_id);for(const s of n.messages??[]){const a=s.sender_type==="agent"?"agent":"user";q(n.session_id||e,a,s.content)}r("History seeded",{sessionId:n.session_id,count:n.messages?.length??0})}catch{}}let d=null,H=null,M=0,W=null;function Ne(e){return(e||"").replace(/[\r\n]+/g," ").replace(/\[/g,"(").replace(/\]/g,")").replace(/---/g,"\u2014")}import{writeFile as $e,mkdir as Pe}from"node:fs/promises";const oe=x(J(),".openclaw","media","inbound");async function Ee(e,t){try{const n=await C(`${w}/chat/media/${e}/${t.filename}`,{headers:{Authorization:`Bearer ${S}`}},15e3);if(!n.ok)return u("Media fetch failed",{messageId:e,filename:t.filename,status:n.status}),null;const s=Buffer.from(await n.arrayBuffer());await Pe(oe,{recursive:!0});const a=t.filename.split(".").pop()||"bin",o=x(oe,`file_0---${t.id||e}.${a}`);return await $e(o,s),r("Media saved locally",{localPath:o,size:s.length}),o}catch(n){return u("Media download error",{messageId:e,filename:t.filename,error:n?.message}),null}}function ve(e){return e.map(t=>`[media attached: ${t.path} (${t.mime}) | ${t.path}]`).join(`
12
+ `)}async function Me(e){const t=Date.now();try{const n=JSON.parse(e);if(n.type!=="chat_message")return;const s=n.session_id,a=n.session_title||null,o=Ne(n.sender_name||"Admin"),i=n.content||"",g=n.content_type||"text",c=n.message_id;let l=[];if(n.metadata_json)try{const p=JSON.parse(n.metadata_json);p?.v===1&&Array.isArray(p.attachments)&&(l=p.attachments)}catch{}if(!i&&l.length===0)return;r(">>> Chat message received via WebSocket (ChatRelayDO)",{transport:"websocket",wsDeliveryMs:Date.now()-t,sender:o,sessionId:s,sessionTitle:a,contentType:g,textLength:i.length,attachments:l.length}),s&&(v=s);const f=`aerostack-chat-${s||"_default"}`;let y=`[Style: concise, numbered lists, bold headers, short sub-bullets. No walls of text.]
13
+ [${o}]:`;if(i&&(y+=` ${i}`),l.length>0&&c){const p=[],A=await Promise.all(l.slice(0,5).map(k=>Ee(c,k).then(O=>O?{path:O,mime:k.mime_type}:null)));for(const k of A)k&&p.push(k);p.length>0&&(y=ve(p)+`
14
+ `+y)}q(s||"_default","user",i||`[${l.map(p=>p.filename).join(", ")}]`),X(s,!0);const h=Date.now();r(">>> Agent turn starting",{timeSinceWsReceived:`${h-t}ms`});try{const p=await Ae(f,y),A=Date.now();X(s,!1),r(">>> Agent turn completed \u2014 timing breakdown",{transport:"websocket",agentProcessingMs:A-h,totalFromWsReceiveMs:A-t});for(const O of p){let T=(typeof O.text=="string"?O.text:String(O.text??"")).trim();T=T.replace(/\[\[reply_to_current\]\]\s*/gi,"").trim(),T&&T.length>=2&&(q(s||"_default","agent",T),We(T,s)?r(">>> Reply sent via WS fast path (no HTTP POST needed)",{textLength:T.length}):await z(T,s))}const k=Date.now();r(">>> Full round-trip complete",{wsToAgentStartMs:h-t,agentProcessingMs:A-h,replyPostMs:k-A,totalRoundTripMs:k-t})}catch(p){X(s,!1),m("Agent turn failed, sending fallback",{error:p?.message}),await z("I received your message but encountered an error processing it. Please try again.",s)}}catch(n){m("handleChatMessage error",{error:n?.message})}}function We(e,t){if(!d||d.readyState!==WebSocket.OPEN)return!1;try{const n=t||v||null;return d.send(JSON.stringify({type:"chat_reply",content:e.slice(0,1e4),session_id:n,sender_name:"Agent",content_type:"text"})),H={content:e.slice(0,1e4),sessionId:n},!0}catch{return!1}}function X(e,t=!0){if(!(!d||d.readyState!==WebSocket.OPEN))try{d.send(JSON.stringify({type:"typing",session_id:e||v,is_typing:t}))}catch{}}async function xe(e){r(">>> Diagnose requested",{requestId:e});const t=[];t.push({step:"connector",status:"ok",message:"Connector running and connected"}),_?(await G("health",void 0,5e3)).ok?t.push({step:"gateway",status:"ok",message:"OpenClaw gateway healthy"}):(r(">>> Diagnose: gateway unhealthy, attempting restart"),await te()?(await new Promise(o=>setTimeout(o,5e3)),(await G("health",void 0,5e3)).ok?t.push({step:"gateway",status:"ok",message:"Gateway restarted and healthy"}):t.push({step:"gateway",status:"fail",message:"Gateway restarted but health check still fails. Run: openclaw gateway restart"})):t.push({step:"gateway",status:"fail",message:"Gateway restart failed. Run: openclaw gateway restart"})):(r(">>> Diagnose: gateway not connected, attempting restart"),await te()?(await new Promise(s=>setTimeout(s,5e3)),_?t.push({step:"gateway",status:"ok",message:"Gateway was down, restarted successfully"}):t.push({step:"gateway",status:"fail",message:"Gateway not reachable after restart. Run: openclaw gateway start"})):t.push({step:"gateway",status:"fail",message:"OpenClaw gateway not running. Run: openclaw gateway start"}));try{const n=x(J(),".openclaw","openclaw.json"),s=await V(n,"utf-8"),a=JSON.parse(s),o=a?.mcp?.servers||a?.mcpServers||{};Object.keys(o).some(g=>{const c=o[g];return g.toLowerCase().includes("aerostack")||g.toLowerCase().includes("notification")||typeof c?.url=="string"&&c.url.includes("aerostack")||Array.isArray(c?.args)&&c.args.some(l=>typeof l=="string"&&l.includes("aerostack"))})?t.push({step:"mcp_config",status:"ok",message:"Aerostack MCP server configured in OpenClaw"}):t.push({step:"mcp_config",status:"warn",message:"Aerostack MCP server not found in OpenClaw config",detail:{fix:"Add Aerostack MCP server via workspace Connect dialog"}})}catch(n){t.push({step:"mcp_config",status:"fail",message:"Cannot read OpenClaw config (~/.openclaw/openclaw.json)",detail:{error:n?.message}})}d&&d.readyState===WebSocket.OPEN?(d.send(JSON.stringify({type:"diagnose_result",request_id:e,steps:t})),t.push({step:"data_flow",status:"ok",message:"Diagnostic result sent via WebSocket"}),r(">>> Diagnose complete",{requestId:e,steps:t.length})):u("Cannot send diagnose result \u2014 ChatRelay WS not connected")}let re="";async function ie(){if(!(!_||!d||d.readyState!==WebSocket.OPEN))try{const e=await G("sessions.list",{limit:100},5e3);if(!e.ok||!e.payload?.sessions)return;const t=e.payload.sessions.map(s=>({key:String(s.key??""),inputTokens:Number(s.inputTokens)||0,outputTokens:Number(s.outputTokens)||0,totalTokens:Number(s.totalTokens)||0,model:s.model?String(s.model):void 0,contextTokens:Number(s.contextTokens)||0,runtimeMs:Number(s.runtimeMs)||0,updatedAt:Number(s.updatedAt)||0})),n=JSON.stringify(t);if(n===re)return;re=n,d.send(JSON.stringify({type:"token_analytics",sessions:t,timestamp:Date.now()}))}catch{}}function ce(){if(d){try{d.close()}catch{}d=null}try{const e=w.replace(/^http/,"ws")+`/chat-relay?token=${encodeURIComponent(S)}&client_type=connector&client_id=${encodeURIComponent(I)}`;r("Chat relay WS connecting",{url:w+"/chat-relay"});const t=new WebSocket(e);d=t,t.onopen=()=>{r("Chat relay WS connected"),M=0},t.onmessage=n=>{const s=typeof n.data=="string"?n.data:"";if(s)try{const a=JSON.parse(s);if(a.type==="pong")return;if(a.type==="chat_reply_nack"){r(">>> chat_reply_nack received, retrying last reply via HTTP",{reason:a.reason});const o=H;o&&(H=null,z(o.content,o.sessionId).catch(i=>{u("HTTP fallback after NACK failed",{error:i?.message})}));return}if(a.type==="chat_reply_ack"){H=null;return}if(a.type==="diagnose"){xe(a.request_id).catch(o=>{u("Diagnose handler error",{error:o?.message})});return}r(">>> WebSocket frame received from ChatRelayDO",{type:a.type,dataLength:s.length}),Me(s)}catch{}},t.onclose=()=>{d=null,j||le()},t.onerror=()=>{M++}}catch(e){u("Chat relay WS connect error",{error:e?.message}),M++,le()}}function le(){if(W)return;const e=Math.min(ge*Math.pow(2,M),de);r(`Chat relay WS reconnecting in ${e/1e3}s`,{failures:M}),W=setTimeout(()=>{W=null,ce()},e)}async function Ie(){try{const e=await C(`${w}/chat/connection-test`,{headers:{Authorization:`Bearer ${S}`}},1e4);if(e.ok){const t=await e.json();return r(`Connected to "${t.workspace}"`,{tools:t.mcp_servers,token:t.token_name,role:t.token_role}),!0}else return e.status===401?(m("Token invalid \u2014 regenerate at dashboard \u2192 Workspace \u2192 Tokens"),!1):(u("Connection check failed",{status:e.status}),!1)}catch(e){return m("Connection check error",{error:e?.message}),!1}}async function De(){r("Aerostack Connector starting",{workspace:w,openclawPort:K,bridgeId:I}),S||(m("AEROSTACK_TOKEN is required"),process.exit(1)),await Ie()||u("Connection verification failed, continuing anyway (will retry)"),await be("_default"),await E()?r("Gateway WS control layer active"):u("Gateway WS not reachable \u2014 will keep retrying"),B=setInterval(ke,we),await ne();const n=setInterval(ne,Y);ie().catch(()=>{});const s=setInterval(ie,Y);ce(),r("Connector ready \u2014 listening for dashboard chat messages");const a=()=>{j=!0,r("Shutting down"),clearInterval(n),clearInterval(s),B&&clearInterval(B);try{d?.close(1e3)}catch{}W&&clearTimeout(W),$&&clearTimeout($);try{D?.close(1e3)}catch{}process.exit(0)};process.on("SIGTERM",a),process.on("SIGINT",a)}De().catch(e=>{m("Fatal error",{error:e?.message}),process.exit(1)});
package/dist/index.js CHANGED
@@ -1,20 +1,20 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- import{Server as he}from"@modelcontextprotocol/sdk/server/index.js";import{StdioServerTransport as fe}from"@modelcontextprotocol/sdk/server/stdio.js";import{ListToolsRequestSchema as ge,CallToolRequestSchema as _e,ListResourcesRequestSchema as ye,ReadResourceRequestSchema as we,ListPromptsRequestSchema as Ae,GetPromptRequestSchema as Ee}from"@modelcontextprotocol/sdk/types.js";import{readFile as Te,writeFile as Se}from"node:fs/promises";import{join as ke}from"node:path";import{homedir as Re}from"node:os";import{resolveApproval as Y}from"./resolution.js";import{startHookServer as Oe,installClaudeHook as Ce,stopHookServer as ve}from"./hook-server.js";import{OpenClawConnector as $e,resolveOpenClawToken as be}from"./openclaw-connector.js";import{info as u,warn as _,error as Ne}from"./logger.js";const se=ke(Re(),".openclaw","pre-authorized.json");async function xe(e){try{let s={};try{s=JSON.parse(await Te(se,"utf-8"))}catch{}s[e]=Date.now(),await Se(se,JSON.stringify(s))}catch{}}const X=process.env.AEROSTACK_WORKSPACE_URL,d=process.env.AEROSTACK_TOKEN;function H(e,s,o){const t=parseInt(e??String(s),10);return Number.isFinite(t)&&t>=o?t:s}const Q=H(process.env.AEROSTACK_APPROVAL_POLL_MS,3e3,500),Z=H(process.env.AEROSTACK_APPROVAL_TIMEOUT_MS,864e5,5e3),oe=H(process.env.AEROSTACK_REQUEST_TIMEOUT_MS,3e4,1e3),Pe=process.env.AEROSTACK_HOOK_SERVER!=="false",Ie=H(process.env.AEROSTACK_HOOK_PORT,18321,1024),Ue=process.env.AEROSTACK_HOOK_AUTO_INSTALL!=="false",Le=process.env.AEROSTACK_OPENCLAW_ENABLED!=="false",ae=H(process.env.AEROSTACK_OPENCLAW_PORT,18789,1024),De=process.env.AEROSTACK_OPENCLAW_TOKEN;X||(process.stderr.write(`ERROR: AEROSTACK_WORKSPACE_URL is required
4
- `),process.exit(1)),d||(process.stderr.write(`ERROR: AEROSTACK_TOKEN is required
5
- `),process.exit(1));let B;try{if(B=new URL(X),B.protocol!=="https:"&&B.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
3
+ import{Server as we}from"@modelcontextprotocol/sdk/server/index.js";import{StdioServerTransport as Ae}from"@modelcontextprotocol/sdk/server/stdio.js";import{ListToolsRequestSchema as Se,CallToolRequestSchema as Te,ListResourcesRequestSchema as Ee,ReadResourceRequestSchema as ke,ListPromptsRequestSchema as Re,GetPromptRequestSchema as Oe}from"@modelcontextprotocol/sdk/types.js";import{readFile as Ce,writeFile as ve}from"node:fs/promises";import{join as $e}from"node:path";import{homedir as be}from"node:os";import{resolveApproval as Q}from"./resolution.js";import{startHookServer as Ne,installClaudeHook as Pe,stopHookServer as Ie}from"./hook-server.js";import{OpenClawConnector as xe,resolveOpenClawToken as Ue}from"./openclaw-connector.js";import{info as u,warn as y,error as Le}from"./logger.js";const ie=$e(be(),".openclaw","pre-authorized.json");async function De(e){try{let t={};try{t=JSON.parse(await Ce(ie,"utf-8"))}catch{}t[e]=Date.now(),await ve(ie,JSON.stringify(t))}catch{}}const Z=process.env.AEROSTACK_WORKSPACE_URL,h=process.env.AEROSTACK_TOKEN;function W(e,t,a){const s=parseInt(e??String(t),10);return Number.isFinite(s)&&s>=a?s:t}const ee=W(process.env.AEROSTACK_APPROVAL_POLL_MS,3e3,500),te=W(process.env.AEROSTACK_APPROVAL_TIMEOUT_MS,864e5,5e3),ce=W(process.env.AEROSTACK_REQUEST_TIMEOUT_MS,3e4,1e3),Me=process.env.AEROSTACK_HOOK_SERVER!=="false",je=W(process.env.AEROSTACK_HOOK_PORT,18321,1024),Ke=process.env.AEROSTACK_HOOK_AUTO_INSTALL!=="false",He=process.env.AEROSTACK_OPENCLAW_ENABLED!=="false",le=W(process.env.AEROSTACK_OPENCLAW_PORT,18789,1024),We=process.env.AEROSTACK_OPENCLAW_TOKEN;Z||(process.stderr.write(`ERROR: AEROSTACK_WORKSPACE_URL is required
4
+ `),process.exit(1)),h||(process.stderr.write(`ERROR: AEROSTACK_TOKEN is required
5
+ `),process.exit(1));let B;try{if(B=new URL(Z),B.protocol!=="https:"&&B.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
6
  `),process.exit(1)}B.protocol==="http:"&&!B.hostname.match(/^(localhost|127\.0\.0\.1)$/)&&process.stderr.write(`WARNING: Using HTTP (not HTTPS) \u2014 token will be sent in plaintext
7
- `);const p=X.replace(/\/+$/,""),ne=crypto.randomUUID();function je(){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 re=je();let b=null,L=Date.now();const Ke=3e4;async function v(e,s){const o={jsonrpc:"2.0",id:Date.now(),method:e,params:s??{}},t=new AbortController,n=setTimeout(()=>t.abort(),oe);try{const a=await fetch(p,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${d}`,"User-Agent":"aerostack-gateway/0.15.29","X-Agent-Id":"aerostack-gateway","X-Bridge-Id":ne,"X-Agent-Type":re},body:JSON.stringify(o),signal:t.signal});if(clearTimeout(n),(a.headers.get("content-type")??"").includes("text/event-stream")){const c=await a.text();return Me(c,o.id)}return await a.json()}catch(a){clearTimeout(n);const r=a instanceof Error?a.message:"Unknown error";return a instanceof Error&&a.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: ${r}`}}}}function Me(e,s){const o=e.split(`
8
- `);let t=null;for(const n of o)if(n.startsWith("data: "))try{t=JSON.parse(n.slice(6))}catch{}return t??{jsonrpc:"2.0",id:s,error:{code:-32603,message:"Empty SSE response"}}}const He=new Set(["aerostack__guardian_report","aerostack__check_approval","aerostack__guardian_check"]);function Be(e,s){if(He.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 n;try{const a=JSON.stringify(s);n=a.length>500?a.slice(0,500)+"...":a}catch{n="(unable to serialize)"}v("tools/call",{name:"aerostack__guardian_report",arguments:{action:`${e}(${Object.keys(s).join(", ")})`,category:o,risk_level:"low",details:n}}).catch(()=>{})}const We=new Set(["aerostack__check_approval"]),ie=[{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"]}}],ze=new Set(ie.map(e=>e.name));function A(e){return e.replace(/[\r\n]+/g," ").replace(/\[/g,"(").replace(/\]/g,")").replace(/---/g,"\u2014")}const Fe=new Set(["image/jpeg","image/png","image/gif","image/webp"]),qe=2*1024*1024;async function ee(e,s,o,t,n){if(o!=="media"&&o!=="rich")return[{type:"text",text:`${A(n)}: ${A(t)}`}];let a=[];if(s)try{const c=JSON.parse(s);c?.v===1&&Array.isArray(c.attachments)&&(a=c.attachments)}catch{}if(a.length===0)return[{type:"text",text:`${A(n)}: ${A(t)}`}];const r=[];o==="rich"&&t?r.push({type:"text",text:`${A(n)}: ${A(t)}`}):a.length>0&&r.push({type:"text",text:`${A(n)} sent ${a.length} file(s):`});const i=e&&/^[a-zA-Z0-9_-]{1,128}$/.test(e)?e:null;for(const c of a){if(Fe.has(c.mime_type)&&c.size<=qe&&i)try{const C=`${p}/chat/media/${i}/${encodeURIComponent(c.filename)}`,w=await E(C,{headers:{Authorization:`Bearer ${d}`}});if(w.ok){const N=Buffer.from(await w.arrayBuffer()).toString("base64");r.push({type:"image",data:N,mimeType:c.mime_type});continue}}catch{}const T=Math.round(c.size/1024),O=T>=1024?`${(T/1024).toFixed(1)} MB`:`${T} KB`,f=A(c.filename.slice(0,200)),$=A(c.mime_type.slice(0,100)),S=i?`${p}/chat/media/${i}/${encodeURIComponent(c.filename)}`:"(unavailable)";r.push({type:"text",text:`Attached: ${f} (${$}, ${O}) \u2014 ${S}`})}return r}const W=[],Ge=100,x=new Map,ce=50,Je=100,le=10;let j=null;const ue=new Set,z=new Map;let P=null;function K(e,s,o){let t=x.get(e);if(!t){if(x.size>=Je){const n=x.keys().next().value;n&&x.delete(n)}t=[],x.set(e,t)}t.push({role:s,content:o,ts:Date.now()}),t.length>ce&&t.splice(0,t.length-ce)}async function Ve(e){if(!ue.has(e))try{const s=await E(`${p}/chat/history?limit=${le}`,{headers:{Authorization:`Bearer ${d}`}});if(!s.ok)return;const o=await s.json();o.session_id&&(j=o.session_id);for(const t of o.messages??[]){const n=t.sender_type==="agent"?"agent":"user",a=o.session_id||e;let r=x.get(a);r||(r=[],x.set(a,r));const i=t.content_type==="media"||t.content_type==="rich"?`${t.content} [has media attachment]`:t.content;r.push({role:n,content:i,ts:t.created_at,content_type:t.content_type})}ue.add(e),u("Thread history seeded from D1",{sessionId:o.session_id,count:o.messages?.length??0})}catch{}}function de(e){const s=x.get(e);return!s||s.length===0?"":`Previous conversation:
9
- ${s.slice(-le).map(n=>`[${n.role==="user"?"Admin":"You (Agent)"}] ${n.content}`).join(`
7
+ `);const m=Z.replace(/\/+$/,""),se=crypto.randomUUID();function Be(){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 ue=Be();let $=null,L=Date.now();const ze=3e4;async function O(e,t){const a={jsonrpc:"2.0",id:Date.now(),method:e,params:t??{}},s=new AbortController,n=setTimeout(()=>s.abort(),ce);try{const o=await fetch(m,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${h}`,"User-Agent":"aerostack-gateway/0.15.29","X-Agent-Id":"aerostack-gateway","X-Bridge-Id":se,"X-Agent-Type":ue},body:JSON.stringify(a),signal:s.signal});if(clearTimeout(n),(o.headers.get("content-type")??"").includes("text/event-stream")){const c=await o.text();return Fe(c,a.id)}return await o.json()}catch(o){clearTimeout(n);const r=o instanceof Error?o.message:"Unknown error";return o instanceof Error&&o.name==="AbortError"?{jsonrpc:"2.0",id:a.id,error:{code:-32603,message:"Request timed out"}}:{jsonrpc:"2.0",id:a.id,error:{code:-32603,message:`HTTP error: ${r}`}}}}function Fe(e,t){const a=e.split(`
8
+ `);let s=null;for(const n of a)if(n.startsWith("data: "))try{s=JSON.parse(n.slice(6))}catch{}return s??{jsonrpc:"2.0",id:t,error:{code:-32603,message:"Empty SSE response"}}}const qe=new Set(["aerostack__guardian_report","aerostack__check_approval","aerostack__guardian_check"]);function Je(e,t){if(qe.has(e))return;let a="other";const s=e.toLowerCase();s.includes("exec")||s.includes("bash")||s.includes("shell")||s.includes("command")||s.includes("run")?a="exec_command":s.includes("write")||s.includes("edit")||s.includes("create")||s.includes("patch")?a="file_write":s.includes("delete")||s.includes("remove")||s.includes("trash")||s.includes("unlink")?a="file_delete":s.includes("fetch")||s.includes("http")||s.includes("request")||s.includes("api")||s.includes("get")||s.includes("post")?a="api_call":s.includes("install")||s.includes("package")||s.includes("npm")||s.includes("pip")?a="package_install":s.includes("config")||s.includes("setting")||s.includes("env")?a="config_change":s.includes("deploy")||s.includes("publish")||s.includes("release")?a="deploy":s.includes("send")||s.includes("message")||s.includes("email")||s.includes("notify")||s.includes("slack")||s.includes("telegram")?a="message_send":(s.includes("read")||s.includes("query")||s.includes("search")||s.includes("list")||s.includes("get"))&&(a="data_access");let n;try{const o=JSON.stringify(t);n=o.length>500?o.slice(0,500)+"...":o}catch{n="(unable to serialize)"}O("tools/call",{name:"aerostack__guardian_report",arguments:{action:`${e}(${Object.keys(t).join(", ")})`,category:a,risk_level:"low",details:n}}).catch(()=>{})}const Ge=new Set(["aerostack__check_approval"]),de=[{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"]}}],Ve=new Set(de.map(e=>e.name));function _(e){return e.replace(/[\r\n]+/g," ").replace(/\[/g,"(").replace(/\]/g,")").replace(/---/g,"\u2014")}const Ye=new Set(["image/jpeg","image/png","image/gif","image/webp"]),Xe=2*1024*1024;async function ae(e,t,a,s,n){if(a!=="media"&&a!=="rich")return[{type:"text",text:`${_(n)}: ${_(s)}`}];let o=[];if(t)try{const c=JSON.parse(t);c?.v===1&&Array.isArray(c.attachments)&&(o=c.attachments)}catch{}if(o.length===0)return[{type:"text",text:`${_(n)}: ${_(s)}`}];const r=[];a==="rich"&&s?r.push({type:"text",text:`${_(n)}: ${_(s)}`}):o.length>0&&r.push({type:"text",text:`${_(n)} sent ${o.length} file(s):`});const i=e&&/^[a-zA-Z0-9_-]{1,128}$/.test(e)?e:null;for(const c of o){if(Ye.has(c.mime_type)&&c.size<=Xe&&i)try{const H=`${m}/chat/media/${i}/${encodeURIComponent(c.filename)}`,R=await S(H,{headers:{Authorization:`Bearer ${h}`}});if(R.ok){const g=Buffer.from(await R.arrayBuffer()).toString("base64");r.push({type:"image",data:g,mimeType:c.mime_type});continue}}catch{}const b=Math.round(c.size/1024),D=b>=1024?`${(b/1024).toFixed(1)} MB`:`${b} KB`,k=_(c.filename.slice(0,200)),C=_(c.mime_type.slice(0,100)),re=i?`${m}/chat/media/${i}/${encodeURIComponent(c.filename)}`:"(unavailable)";r.push({type:"text",text:`Attached: ${k} (${C}, ${D}) \u2014 ${re}`})}return r}const z=[],Qe=100,N=new Map,pe=50,Ze=100,he=10;let P=null;const me=new Set,F=new Map;let I=null;function j(e,t,a){let s=N.get(e);if(!s){if(N.size>=Ze){const n=N.keys().next().value;n&&N.delete(n)}s=[],N.set(e,s)}s.push({role:t,content:a,ts:Date.now()}),s.length>pe&&s.splice(0,s.length-pe)}async function et(e){if(!me.has(e))try{const t=await S(`${m}/chat/history?limit=${he}`,{headers:{Authorization:`Bearer ${h}`}});if(!t.ok)return;const a=await t.json();a.session_id&&(P=a.session_id);for(const s of a.messages??[]){const n=s.sender_type==="agent"?"agent":"user",o=a.session_id||e;let r=N.get(o);r||(r=[],N.set(o,r));const i=s.content_type==="media"||s.content_type==="rich"?`${s.content} [has media attachment]`:s.content;r.push({role:n,content:i,ts:s.created_at,content_type:s.content_type})}me.add(e),u("Thread history seeded from D1",{sessionId:a.session_id,count:a.messages?.length??0})}catch{}}function fe(e){const t=N.get(e);return!t||t.length===0?"":`Previous conversation:
9
+ ${t.slice(-he).map(n=>`[${n.role==="user"?"Admin":"You (Agent)"}] ${n.content}`).join(`
10
10
  `)}
11
11
 
12
- `}async function E(e,s){const o=new AbortController,t=setTimeout(()=>o.abort(),oe);try{return await fetch(e,{...s,signal:o.signal})}finally{clearTimeout(t)}}async function Ye(){const e=j||"_default";if(await Ve(e),W.length>0){const s=W.splice(0);L=Date.now();const o=de(e),t=[];o&&t.push({type:"text",text:o}),t.push({type:"text",text:"New messages from workspace owner:"});for(const n of s){const a=await ee(n.message_id??null,n.metadata_json??null,n.content_type,n.content,n.sender_name);t.push(...a)}return t}try{const o=await E(`${p}/chat/pending?since=${L}`,{headers:{Authorization:`Bearer ${d}`}});if(!o.ok)return[{type:"text",text:"No new messages."}];const n=(await o.json()).messages??[];if(L=Date.now(),n.length===0)return[{type:"text",text:"No new messages from the workspace owner."}];for(const i of n)K(e,"user",i.content);const a=de(e),r=[];a&&r.push({type:"text",text:a}),r.push({type:"text",text:"New messages from workspace owner:"});for(const i of n){const c=await ee(i.id??null,i.metadata_json??null,i.content_type,i.content,i.sender_name);r.push(...c)}return r}catch{return[{type:"text",text:"Failed to check messages."}]}}async function Xe(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 E(`${p}/chat/messages`,{method:"POST",headers:{Authorization:`Bearer ${d}`,"Content-Type":"application/json"},body:JSON.stringify({content:s,session_id:j})});return o.ok?(K(j||"_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 Qe(e,s){Be(e,s);const o=await v("tools/call",{name:e,arguments:s});if(o.error?.code===-32050){const a=o.error.data,r=a?.approval_id;if(!r||!/^[a-zA-Z0-9_-]{4,128}$/.test(r))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:r});const i=await Y({approvalId:r,wsUrl:a?.ws_url,pollUrl:a?.polling_url??`${p}/approval-status/${r}`,pollIntervalMs:Q,timeoutMs:Z,token:d});u("Tool gate resolved",{tool:e,status:i.status}),F({approvalId:r,toolName:e,toolArgs:s,sessionKey:null,gate:"tool_gate",pollUrl:"",authToken:d},i.status,!0);const c=te({approvalId:r,toolName:e,toolArgs:s,sessionKey:null,gate:"tool_gate",pollUrl:"",authToken:d},i);return{jsonrpc:"2.0",id:o.id,result:{content:[{type:"text",text:c}]}}}const n=o.result?._meta;if(n?.approval_id&&n?.status==="pending"){const a=n.approval_id;if(!/^[a-zA-Z0-9_-]{4,128}$/.test(a))return o;u("Permission gate: blocking until approval resolves",{tool:e,approvalId:a});const r=await Y({approvalId:a,wsUrl:n.ws_url,pollUrl:n.polling_url??`${p}/approval-status/${a}`,pollIntervalMs:Q,timeoutMs:Z,token:d});u("Permission gate resolved",{tool:e,status:r.status}),F({approvalId:a,toolName:e,toolArgs:s,sessionKey:null,gate:"permission_gate",pollUrl:"",authToken:d},r.status,!0);const i=te({approvalId:a,toolName:e,toolArgs:s,sessionKey:null,gate:"permission_gate",pollUrl:"",authToken:d},r);return{jsonrpc:"2.0",id:o.id,result:{content:[{type:"text",text:i}]}}}return o}function yt(e){Y({approvalId:e.approvalId,wsUrl:e.wsUrl,pollUrl:e.pollUrl,pollIntervalMs:Q,timeoutMs:Z,token:e.authToken}).then(async s=>{u("Approval resolved",{tool:e.toolName,status:s.status,session:e.sessionKey});const o=te(e,s),t=e.sessionKey??b?.getLastActiveSession()??null;if((s.status==="approved"||s.status==="executed")&&t&&xe(t).catch(()=>{}),t&&b){if(await b.sendToSession(t,o)){u("Agent resumed via sessions.send",{session:t,status:s.status}),F(e,s.status,!0);return}_("sessions.send failed, falling back to channel notification",{session:t})}try{await I.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}),F(e,s.status,!0)}catch(n){_("Channel notification failed",{error:n instanceof Error?n.message:String(n)}),F(e,s.status,!1)}}).catch(s=>{_("Background approval resolver error",{error:s instanceof Error?s.message:String(s)})})}function F(e,s,o){const t=typeof e.toolArgs.action=="string"?e.toolArgs.action:null,n=t??e.toolName.replace(/^(?:\w+__)*aerostack__/,"")??e.toolName,a=t?`${n}`:`${n}(${Object.keys(e.toolArgs).join(", ")})`,r=o?"resume sent to agent":"session unavailable \u2014 resume not delivered";let i;s==="approved"||s==="executed"?i=o?`[RESUMED] ${a} \u2014 ${r}`:`[RESUME FAILED] ${a} \u2014 ${r}`:s==="rejected"?i=`[REJECTED] ${a} \u2014 rejection sent to agent`:s==="changes_requested"?i=`[CHANGES REQUESTED] ${a} \u2014 feedback sent to agent`:i=`[EXPIRED] ${a} \u2014 approval timed out, expiry sent to agent`,v("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 te(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(n=>n!==null).join(`
13
- `):["[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(n=>n!==null).join(`
14
- `);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(`
15
- `);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(`
16
- `);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(`
17
- `);default:return`[APPROVAL UPDATE] Status for "${t}": ${s.status}.`}}const q=`
12
+ `}async function S(e,t){const a=new AbortController,s=setTimeout(()=>a.abort(),ce);try{return await fetch(e,{...t,signal:a.signal})}finally{clearTimeout(s)}}async function tt(){const e=P||"_default";if(await et(e),z.length>0){const t=z.splice(0);L=Date.now();const a=fe(e),s=[];a&&s.push({type:"text",text:a}),s.push({type:"text",text:"New messages from workspace owner:"});for(const n of t){const o=await ae(n.message_id??null,n.metadata_json??null,n.content_type,n.content,n.sender_name);s.push(...o)}return s}try{const a=await S(`${m}/chat/pending?since=${L}`,{headers:{Authorization:`Bearer ${h}`}});if(!a.ok)return[{type:"text",text:"No new messages."}];const n=(await a.json()).messages??[];if(L=Date.now(),n.length===0)return[{type:"text",text:"No new messages from the workspace owner."}];for(const i of n)j(e,"user",i.content);const o=fe(e),r=[];o&&r.push({type:"text",text:o}),r.push({type:"text",text:"New messages from workspace owner:"});for(const i of n){const c=await ae(i.id??null,i.metadata_json??null,i.content_type,i.content,i.sender_name);r.push(...c)}return r}catch{return[{type:"text",text:"Failed to check messages."}]}}async function st(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 a=await S(`${m}/chat/messages`,{method:"POST",headers:{Authorization:`Bearer ${h}`,"Content-Type":"application/json"},body:JSON.stringify({content:t,session_id:P})});return a.ok?(j(P||"_default","agent",t),[{type:"text",text:"Reply sent to workspace owner."}]):[{type:"text",text:`Failed to send reply (HTTP ${a.status}).`}]}catch{return[{type:"text",text:"Failed to send reply."}]}}async function at(e,t){Je(e,t);const a=await O("tools/call",{name:e,arguments:t});if(a.error?.code===-32050){const o=a.error.data,r=o?.approval_id;if(!r||!/^[a-zA-Z0-9_-]{4,128}$/.test(r))return{jsonrpc:"2.0",id:a.id,error:{code:-32603,message:"Approval required but no approval_id returned"}};u("Tool gate: blocking until approval resolves",{tool:e,approvalId:r});const i=await Q({approvalId:r,wsUrl:o?.ws_url,pollUrl:o?.polling_url??`${m}/approval-status/${r}`,pollIntervalMs:ee,timeoutMs:te,token:h});u("Tool gate resolved",{tool:e,status:i.status}),q({approvalId:r,toolName:e,toolArgs:t,sessionKey:null,gate:"tool_gate",pollUrl:"",authToken:h},i.status,!0);const c=ne({approvalId:r,toolName:e,toolArgs:t,sessionKey:null,gate:"tool_gate",pollUrl:"",authToken:h},i);return{jsonrpc:"2.0",id:a.id,result:{content:[{type:"text",text:c}]}}}const n=a.result?._meta;if(n?.approval_id&&n?.status==="pending"){const o=n.approval_id;if(!/^[a-zA-Z0-9_-]{4,128}$/.test(o))return a;u("Permission gate: blocking until approval resolves",{tool:e,approvalId:o});const r=await Q({approvalId:o,wsUrl:n.ws_url,pollUrl:n.polling_url??`${m}/approval-status/${o}`,pollIntervalMs:ee,timeoutMs:te,token:h});u("Permission gate resolved",{tool:e,status:r.status}),q({approvalId:o,toolName:e,toolArgs:t,sessionKey:null,gate:"permission_gate",pollUrl:"",authToken:h},r.status,!0);const i=ne({approvalId:o,toolName:e,toolArgs:t,sessionKey:null,gate:"permission_gate",pollUrl:"",authToken:h},r);return{jsonrpc:"2.0",id:a.id,result:{content:[{type:"text",text:i}]}}}return a}function kt(e){Q({approvalId:e.approvalId,wsUrl:e.wsUrl,pollUrl:e.pollUrl,pollIntervalMs:ee,timeoutMs:te,token:e.authToken}).then(async t=>{u("Approval resolved",{tool:e.toolName,status:t.status,session:e.sessionKey});const a=ne(e,t),s=e.sessionKey??$?.getLastActiveSession()??null;if((t.status==="approved"||t.status==="executed")&&s&&De(s).catch(()=>{}),s&&$){if(await $.sendToSession(s,a)){u("Agent resumed via sessions.send",{session:s,status:t.status}),q(e,t.status,!0);return}y("sessions.send failed, falling back to channel notification",{session:s})}try{await x.notification({method:"notifications/claude/channel",params:{content:a,meta:{tool_name:e.toolName,approval_id:e.approvalId,status:t.status,gate:e.gate}}}),u("Agent resumed via channel notification",{tool:e.toolName,status:t.status}),q(e,t.status,!0)}catch(n){y("Channel notification failed",{error:n instanceof Error?n.message:String(n)}),q(e,t.status,!1)}}).catch(t=>{y("Background approval resolver error",{error:t instanceof Error?t.message:String(t)})})}function q(e,t,a){const s=typeof e.toolArgs.action=="string"?e.toolArgs.action:null,n=s??e.toolName.replace(/^(?:\w+__)*aerostack__/,"")??e.toolName,o=s?`${n}`:`${n}(${Object.keys(e.toolArgs).join(", ")})`,r=a?"resume sent to agent":"session unavailable \u2014 resume not delivered";let i;t==="approved"||t==="executed"?i=a?`[RESUMED] ${o} \u2014 ${r}`:`[RESUME FAILED] ${o} \u2014 ${r}`:t==="rejected"?i=`[REJECTED] ${o} \u2014 rejection sent to agent`:t==="changes_requested"?i=`[CHANGES REQUESTED] ${o} \u2014 feedback sent to agent`:i=`[EXPIRED] ${o} \u2014 approval timed out, expiry sent to agent`,O("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:a,session:e.sessionKey}).slice(0,500)}}).catch(()=>{})}function ne(e,t){const a=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}`,a?`Reviewer note: "${a}"`:null,"",`Please now execute this action: call ${e.toolName} with the same parameters as before.`].filter(n=>n!==null).join(`
13
+ `):["[APPROVAL RECEIVED] Your request has been approved by the workspace owner.",a?`Reviewer note: "${a}"`:null,"","You may now proceed with the action you requested approval for."].filter(n=>n!==null).join(`
14
+ `);case"changes_requested":return["[CHANGES REQUESTED] The workspace owner reviewed your action and requested changes.","",`Original action: ${s}`,`Reviewer feedback: "${a??"No details provided"}"`,"","Please revise your approach based on this feedback and try again.","Do NOT proceed with the original plan."].join(`
15
+ `);case"rejected":return["[REJECTED] The workspace owner denied this action.","",`Action: ${s}`,a?`Reason: "${a}"`:"No reason provided.","","Do not retry this action. If you believe this was a mistake, inform the user."].join(`
16
+ `);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(`
17
+ `);default:return`[APPROVAL UPDATE] Status for "${s}": ${t.status}.`}}const V=`
18
18
  === AEROSTACK GUARDIAN POLICY (MANDATORY) ===
19
19
 
20
20
  You are operating under Aerostack workspace monitoring. These rules apply to ALL actions:
@@ -42,27 +42,26 @@ or before starting a new one, call aerostack__chat_check to see if the owner sen
42
42
  If there are messages, read them carefully and respond using aerostack__chat_reply. This keeps
43
43
  the owner informed and allows them to steer your work in real time.
44
44
  === END WORKSPACE CHAT ===
45
- `.trim(),Ze=["delete","remove","drop","truncate","destroy","wipe","reset","deploy","publish","release","push","exec","shell","bash","run","command","terminal","install","uninstall","send","email","notify","webhook"];function et(e,s){const o=e.toLowerCase(),t=Ze.some(a=>o.includes(a)),n=s??"";return t?`[REQUIRES GUARDIAN APPROVAL] ${n}`.trim():n}let pe=null;async function M(){if(pe)return;const e=await v("initialize",{protocolVersion:"2024-11-05",capabilities:{},clientInfo:{name:"aerostack-gateway",version:"0.15.29"}});if(e.result){const s=e.result,o=s.instructions??"";pe={protocolVersion:s.protocolVersion??"2024-11-05",instructions:o?`${o}
45
+ `.trim(),nt=["delete","remove","drop","truncate","destroy","wipe","reset","deploy","publish","release","push","exec","shell","bash","run","command","terminal","install","uninstall","send","email","notify","webhook"];function ot(e,t){const a=e.toLowerCase(),s=nt.some(o=>a.includes(o)),n=t??"";return s?`[REQUIRES GUARDIAN APPROVAL] ${n}`.trim():n}let ye=null;async function K(){if(ye)return;const e=await O("initialize",{protocolVersion:"2024-11-05",capabilities:{},clientInfo:{name:"aerostack-gateway",version:"0.15.29"}});if(e.result){const t=e.result,a=t.instructions??"";ye={protocolVersion:t.protocolVersion??"2024-11-05",instructions:a?`${a}
46
46
 
47
- ${q}`:q}}}const I=new he({name:"aerostack-gateway",version:"0.15.29"},{capabilities:{tools:{},resources:{},prompts:{},experimental:{"claude/channel":{}}},instructions:q});I.setRequestHandler(ge,async()=>{await M();const e=await v("tools/list");if(e.error)throw new Error(e.error.message);const o=(e.result.tools??[]).filter(t=>!We.has(t.name)).map(t=>({...t,description:et(t.name,t.description)}));return o.push(...ie),{tools:o}}),I.setRequestHandler(_e,async e=>{await M();const{name:s,arguments:o}=e.params;if(ze.has(s))return{content:s==="aerostack__chat_check"?await Ye():await Xe(o??{})};const t=await Qe(s,o??{});if(t.error)return{content:[{type:"text",text:`Error: ${t.error.message}`}],isError:!0};const a=t.result.content??[{type:"text",text:JSON.stringify(t.result)}];if(Date.now()-L>Ke)try{const i=await E(`${p}/chat/pending?since=${L}`,{headers:{Authorization:`Bearer ${d}`}});if(i.ok){const R=(await i.json()).messages??[];if(L=Date.now(),R.length>0){const T=[];for(const f of R){const $=await ee(f.id??null,f.metadata_json??null,f.content_type,f.content,f.sender_name);T.push(...$)}const O=[{type:"text",text:"--- WORKSPACE OWNER MESSAGE ---"},...T,{type:"text",text:"--- END MESSAGE \u2014 please acknowledge ---"}];a.unshift(...O)}}}catch{}return{content:a}});const tt={uri:"aerostack://guardian/policy",name:"Aerostack Guardian Policy",description:"Mandatory workspace monitoring policy. Read this to understand required approval workflows.",mimeType:"text/plain"};I.setRequestHandler(ye,async()=>{await M();const e=await v("resources/list");if(e.error)throw new Error(e.error.message);const s=e.result;return{resources:[tt,...s.resources??[]]}}),I.setRequestHandler(we,async e=>{if(await M(),e.params.uri==="aerostack://guardian/policy")return{contents:[{uri:e.params.uri,text:q,mimeType:"text/plain"}]};const s=await v("resources/read",{uri:e.params.uri});if(s.error)throw new Error(s.error.message);return{contents:s.result.contents??[]}}),I.setRequestHandler(Ae,async()=>{await M();const e=await v("prompts/list");if(e.error)throw new Error(e.error.message);return{prompts:e.result.prompts??[]}}),I.setRequestHandler(Ee,async e=>{await M();const s=await v("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 st(){const e=s=>process.stderr.write(s+`
47
+ ${V}`:V}}}const x=new we({name:"aerostack-gateway",version:"0.15.29"},{capabilities:{tools:{},resources:{},prompts:{},experimental:{"claude/channel":{}}},instructions:V});x.setRequestHandler(Se,async()=>{await K();const e=await O("tools/list");if(e.error)throw new Error(e.error.message);const a=(e.result.tools??[]).filter(s=>!Ge.has(s.name)).map(s=>({...s,description:ot(s.name,s.description)}));return a.push(...de),{tools:a}}),x.setRequestHandler(Te,async e=>{await K();const{name:t,arguments:a}=e.params;if(Ve.has(t))return{content:t==="aerostack__chat_check"?await tt():await st(a??{})};const s=await at(t,a??{});if(s.error)return{content:[{type:"text",text:`Error: ${s.error.message}`}],isError:!0};const o=s.result.content??[{type:"text",text:JSON.stringify(s.result)}];if(Date.now()-L>ze)try{const i=await S(`${m}/chat/pending?since=${L}`,{headers:{Authorization:`Bearer ${h}`}});if(i.ok){const E=(await i.json()).messages??[];if(L=Date.now(),E.length>0){const b=[];for(const k of E){const C=await ae(k.id??null,k.metadata_json??null,k.content_type,k.content,k.sender_name);b.push(...C)}const D=[{type:"text",text:"--- WORKSPACE OWNER MESSAGE ---"},...b,{type:"text",text:"--- END MESSAGE \u2014 please acknowledge ---"}];o.unshift(...D)}}}catch{}return{content:o}});const rt={uri:"aerostack://guardian/policy",name:"Aerostack Guardian Policy",description:"Mandatory workspace monitoring policy. Read this to understand required approval workflows.",mimeType:"text/plain"};x.setRequestHandler(Ee,async()=>{await K();const e=await O("resources/list");if(e.error)throw new Error(e.error.message);const t=e.result;return{resources:[rt,...t.resources??[]]}}),x.setRequestHandler(ke,async e=>{if(await K(),e.params.uri==="aerostack://guardian/policy")return{contents:[{uri:e.params.uri,text:V,mimeType:"text/plain"}]};const t=await O("resources/read",{uri:e.params.uri});if(t.error)throw new Error(t.error.message);return{contents:t.result.contents??[]}}),x.setRequestHandler(Re,async()=>{await K();const e=await O("prompts/list");if(e.error)throw new Error(e.error.message);return{prompts:e.result.prompts??[]}}),x.setRequestHandler(Oe,async e=>{await K();const t=await O("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 it(){const e=t=>process.stderr.write(t+`
48
48
  `);e(`
49
49
  Aerostack Connection Check
50
- `);try{const s=await fetch(`${p}/chat/connection-test`,{headers:{Authorization:`Bearer ${d}`},signal:AbortSignal.timeout(1e4)});s.ok||(s.status===401&&(e(" \u2705 Workspace URL reachable"),e(" \u274C Token invalid (HTTP 401)"),e(`
50
+ `);try{const t=await fetch(`${m}/chat/connection-test`,{headers:{Authorization:`Bearer ${h}`},signal:AbortSignal.timeout(1e4)});t.ok||(t.status===401&&(e(" \u2705 Workspace URL reachable"),e(" \u274C Token invalid (HTTP 401)"),e(`
51
51
  Fix: Regenerate token at your dashboard \u2192 Workspace \u2192 Tokens
52
- `),process.exit(1)),e(` \u274C Workspace unreachable (HTTP ${s.status})`),e(`
52
+ `),process.exit(1)),e(` \u274C Workspace unreachable (HTTP ${t.status})`),e(`
53
53
  Fix: Check AEROSTACK_WORKSPACE_URL is correct
54
- `),process.exit(1));const o=await s.json();e(` \u2705 Workspace URL reachable (${p})`),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(`${p}/chat/heartbeat`,{method:"POST",headers:{Authorization:`Bearer ${d}`,"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(`
55
- All checks passed. Your agent is connected.`),e(` Dashboard: ${o.dashboard_url||"https://app.aerostack.dev"}
56
- `),o.mcp_servers===0&&e(` Note: 0 tools found. Add MCP servers or functions to your workspace first.
57
- `),process.exit(0)}catch(s){s?.name==="TimeoutError"||s?.name==="AbortError"?(e(" \u274C Workspace unreachable (timeout after 10s)"),e(`
54
+ `),process.exit(1));const a=await t.json();e(` \u2705 Workspace URL reachable (${m})`),e(` \u2705 Token valid (${a.token_name}, role: ${a.token_role})`),e(` \u2705 Workspace connected ("${a.workspace}")`),e(` \u2705 Tools available (${a.mcp_servers} tools)`);try{const s=await fetch(`${m}/chat/heartbeat`,{method:"POST",headers:{Authorization:`Bearer ${h}`,"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(`
55
+ All checks passed. Your agent is connected.`),e(` Dashboard: ${a.dashboard_url||"https://app.aerostack.dev"}
56
+ `),a.mcp_servers===0&&e(` Note: 0 tools found. Add MCP servers or functions to your workspace first.
57
+ `),process.exit(0)}catch(t){t?.name==="TimeoutError"||t?.name==="AbortError"?(e(" \u274C Workspace unreachable (timeout after 10s)"),e(`
58
58
  Fix: Check AEROSTACK_WORKSPACE_URL and your network connection
59
- `)):(e(` \u274C Connection failed (${s?.message??"unknown error"})`),e(`
59
+ `)):(e(` \u274C Connection failed (${t?.message??"unknown error"})`),e(`
60
60
  Fix: Check AEROSTACK_WORKSPACE_URL is correct
61
- `)),process.exit(1)}}async function ot(){try{const e=await fetch(`${p}/chat/connection-test`,{headers:{Authorization:`Bearer ${d}`},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?_("Token invalid \u2014 regenerate at dashboard \u2192 Workspace \u2192 Tokens"):_("Connection check failed",{status:e.status})}catch{_("Could not verify connection (non-fatal)")}}async function at(){u("Connecting to workspace",{url:p});const e=new fe;await I.connect(e),u("Ready",{url:p}),ot().catch(()=>{});const s=()=>{E(`${p}/chat/heartbeat`,{method:"POST",headers:{Authorization:`Bearer ${d}`,"Content-Type":"application/json"},body:JSON.stringify({agent_type:re,user_agent:"aerostack-gateway/0.15.29",bridge_id:ne})}).catch(()=>{})};s();const o=setInterval(s,12e4);if(process.on("exit",()=>clearInterval(o)),Pe)try{const n=await Oe(async a=>{try{const r=await fetch(`${p}/guardian-batch`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${d}`,"User-Agent":"aerostack-gateway/0.15.29","X-Agent-Id":"aerostack-gateway"},body:JSON.stringify({events:a})});return r.ok?(await r.json()).config?.hook_tracking??null:null}catch{return null}},Ie);Ue&&await Ce(n)&&u("Claude Code hook auto-installed",{port:n})}catch(t){_("Hook server failed to start (non-fatal)",{error:t instanceof Error?t.message:String(t)})}if(Le)try{const t=De??await be();t?(b=new $e({port:ae,token:t,rpcCall:v,onToolEvent:a=>{const r=P;if(!r)return;const i=z.get(r);if(i){if(a.phase==="start")i.push({name:a.toolName,category:a.category,started_at:Date.now(),ended_at:null,duration_ms:null,status:"running",args_summary:a.summary});else if(a.phase==="end"){const c=[...i].reverse().find(R=>R.name===a.toolName&&R.status==="running");c&&(c.ended_at=Date.now(),c.duration_ms=c.ended_at-c.started_at,c.status=a.error?"error":"success",a.error&&(c.error=a.error))}E(`${p}/chat/tool-status`,{method:"POST",headers:{Authorization:`Bearer ${d}`,"Content-Type":"application/json"},body:JSON.stringify({tool_name:a.toolName,phase:a.phase,category:a.category})}).catch(()=>{})}}}),await b.connect()?u("OpenClaw connector started",{port:ae}):(u("OpenClaw gateway not reachable, skipping connector"),b=null)):u("OpenClaw integration skipped (no token found)")}catch(t){_("OpenClaw connector failed (non-fatal)",{error:t instanceof Error?t.message:String(t)})}process.env.AEROSTACK_CHAT_LISTENER!=="false"&&nt()}let G=null,J=!1;function nt(){J=!1,G&&G.abort();let e=null,s=0;const o=async()=>{if(J)return;const n=new AbortController;G=n;try{const a=await fetch(p,{method:"GET",headers:{Authorization:`Bearer ${d}`,Accept:"text/event-stream"},signal:n.signal});if(!a.ok||!a.body){_("Chat event listener: failed to connect",{status:a.status}),s++,t();return}u("Chat event listener connected"),s=0;const r=a.body.getReader(),i=new TextDecoder;let c="";for(;;){const{done:R,value:T}=await r.read();if(R)break;c+=i.decode(T,{stream:!0});const O=c.split(`
62
- `);c=O.pop()??"";let f="",$="";for(const S of O)S.startsWith("event: ")?f=S.slice(7).trim():S.startsWith("data: ")?$=S.slice(6):S===""&&f&&$&&(f==="aerostack_event"&&it($),f="",$="")}u("Chat event listener: stream ended, reconnecting"),t()}catch(a){if(a?.name==="AbortError")return;_("Chat event listener error",{error:a?.message}),s++,t()}},t=()=>{if(J||e)return;const n=Math.min(2e3*Math.pow(2,s),3e4);u(`Chat event listener: reconnecting in ${n/1e3}s (failures: ${s})`),e=setTimeout(()=>{e=null,o()},n)};o()}function rt(){J=!0,G?.abort()}async function it(e){try{const s=JSON.parse(e);if(s.type!=="chat_message")return;let o=[];if(s.metadata_json)try{const y=JSON.parse(s.metadata_json);y?.v===1&&Array.isArray(y.attachments)&&(o=y.attachments)}catch{}const t=!!s.content&&s.content_type!=="media",n=o.length>0;if(!t&&!n)return;const a=s.session_id;u("Chat message received from admin",{sender:s.sender_name,sessionId:a,contentType:s.content_type,attachments:o.length});const r=/^[a-zA-Z0-9_-]{1,128}$/.test(s.message_id??"")?s.message_id:null,i=o.map(y=>{const N=Math.round(y.size/1024),V=N>=1024?`${(N/1024).toFixed(1)} MB`:`${N} KB`,D=A((y.filename||"file").slice(0,200)),k=A((y.mime_type||"application/octet-stream").slice(0,100)),m=r?`${p}/chat/media/${r}/${encodeURIComponent(y.filename)}`:"(unavailable)";return`[Attached: ${D} (${k}, ${V}) \u2014 ${m}]`}),c=i.length>0?`
63
- `+i.join(`
64
- `):"",R=t?s.content+(c?`
65
- `+i.map(y=>y).join(`
66
- `):""):i.join(`
67
- `);a&&(j=a,K(a,"user",R)),W.length>=Ge&&W.shift(),W.push({sender_name:s.sender_name??"Admin",content:t?s.content:i.join(", "),created_at:Date.now(),session_id:a,content_type:s.content_type,metadata_json:s.metadata_json,message_id:s.message_id}),L=Date.now();const T=A(s.sender_name??"Admin"),O=t?A(s.content):"",f=a||j||"_default",S=`aerostack-chat-${/^[a-zA-Z0-9_-]{1,64}$/.test(f)?f:"_default"}`,C=`turn-${Date.now()}-${Math.random().toString(36).slice(2,8)}`,w=[];z.set(C,w),P=C;try{const y=O?O+c:c.trim(),N=`[${T} via Aerostack Dashboard]: ${y}`;if(b){u("Trying WS fast path for chat",{session:S});const D=await b.sendChatMessage(N);if(D){const k=D.trim();u("WS fast path succeeded",{responseLength:k.length}),await new Promise(m=>setTimeout(m,500));for(const m of w)m.status==="running"&&(m.status="timeout",m.ended_at=Date.now(),m.duration_ms=m.ended_at-m.started_at);if(w.length>0){const m=w.reduce((h,g)=>h+(g.duration_ms||0),0),l=w.length;try{await E(`${p}/chat/messages`,{method:"POST",headers:{Authorization:`Bearer ${d}`,"Content-Type":"application/json"},body:JSON.stringify({content:`Used ${l} tool${l>1?"s":""}`,content_type:"tool_activity",session_id:a,metadata:JSON.stringify({v:1,tools:w.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:m})})})}catch{}}if(z.delete(C),P===C&&(P=null),k.length>=2){K(f,"agent",k);try{const m=await E(`${p}/chat/messages`,{method:"POST",headers:{Authorization:`Bearer ${d}`,"Content-Type":"application/json"},body:JSON.stringify({content:k.slice(0,1e4),session_id:a})});m.ok?u("Chat reply sent via WS fast path"):_("Chat reply failed (WS fast path)",{status:m.status})}catch(m){_("Failed to send chat reply (WS fast path)",{error:m?.message})}}return}u("WS fast path failed, falling back to CLI subprocess")}const{execFile:V}=await import("child_process");u("Spawning agent turn via CLI for chat",{sender:T,session:S}),V("openclaw",["agent","--agent","main","--json","--session-id",S,"-m",N],{timeout:12e4},async(D,k)=>{let m=[];if(!D&&k)try{const l=JSON.parse(k.trim()),h=l?.result?.payloads;Array.isArray(h)&&h.length>0?m=h:(l.text||l.reply||l.message)&&(m=[{text:l.text||l.reply||l.message}])}catch{const l=k.trim().split(`
68
- `).pop()||k.trim();l&&(m=[{text:l}])}m.length===0&&(m=[{text:`Message received. I'll process "${O.slice(0,100)}" shortly.`}]),await new Promise(l=>setTimeout(l,500));for(const l of w)l.status==="running"&&(l.status="timeout",l.ended_at=Date.now(),l.duration_ms=l.ended_at-l.started_at);if(w.length>0){const l=w.reduce((g,U)=>g+(U.duration_ms||0),0),h=w.length;try{await E(`${p}/chat/messages`,{method:"POST",headers:{Authorization:`Bearer ${d}`,"Content-Type":"application/json"},body:JSON.stringify({content:`Used ${h} tool${h>1?"s":""}`,content_type:"tool_activity",session_id:a,metadata:JSON.stringify({v:1,tools:w.map(g=>({name:g.name,category:g.category,duration_ms:g.duration_ms,status:g.status,args_summary:g.args_summary?.slice(0,500),error:g.error?.slice(0,500)})),total_duration_ms:l})})})}catch{}}z.delete(C),P===C&&(P=null);for(const l of m){const h=l.text?.trim()||"";if(l.mediaUrl){let g=!1;try{g=new URL(l.mediaUrl).protocol==="https:"}catch{}if(g)try{const U=await E(`${p}/chat/media`,{method:"POST",headers:{Authorization:`Bearer ${d}`,"Content-Type":"application/json"},body:JSON.stringify({media_url:l.mediaUrl,content:h||void 0,session_id:a})});U.ok?u("Chat media sent to workspace",{mediaUrl:l.mediaUrl}):_("Chat media upload failed",{status:U.status})}catch(U){_("Failed to send chat media",{error:U?.message})}else _("Skipping invalid media URL",{mediaUrl:l.mediaUrl});K(f,"agent",h||`[media: ${l.mediaUrl}]`)}else if(h&&h.length>=2){K(f,"agent",h);try{const g=await E(`${p}/chat/messages`,{method:"POST",headers:{Authorization:`Bearer ${d}`,"Content-Type":"application/json"},body:JSON.stringify({content:h.slice(0,1e4),session_id:a})});g.ok?u("Chat reply sent to workspace"):_("Chat reply failed",{status:g.status})}catch(g){_("Failed to send chat reply",{error:g?.message})}}}})}catch(y){_("Failed to spawn agent turn",{error:y?.message}),z.delete(C),P===C&&(P=null)}}catch{}}function me(){rt(),b?.stop(),ve(),process.exit(0)}process.on("SIGTERM",()=>{me()}),process.on("SIGINT",()=>{me()}),process.argv.includes("--check")?st():at().catch(e=>{Ne("Fatal error",{error:e instanceof Error?e.message:String(e)}),process.exit(1)});
61
+ `)),process.exit(1)}}async function ct(){try{const e=await fetch(`${m}/chat/connection-test`,{headers:{Authorization:`Bearer ${h}`},signal:AbortSignal.timeout(5e3)});if(e.ok){const t=await e.json();u(`Connected to "${t.workspace}" (${t.mcp_servers} tools)`,{dashboard:"https://app.aerostack.dev"})}else e.status===401?y("Token invalid \u2014 regenerate at dashboard \u2192 Workspace \u2192 Tokens"):y("Connection check failed",{status:e.status})}catch{y("Could not verify connection (non-fatal)")}}async function lt(){u("Connecting to workspace",{url:m});const e=new Ae;await x.connect(e),u("Ready",{url:m}),ct().catch(()=>{});const t=()=>{S(`${m}/chat/heartbeat`,{method:"POST",headers:{Authorization:`Bearer ${h}`,"Content-Type":"application/json"},body:JSON.stringify({agent_type:ue,user_agent:"aerostack-gateway/0.15.29",bridge_id:se})}).catch(()=>{})};t();const a=setInterval(t,12e4);if(process.on("exit",()=>clearInterval(a)),Me)try{const n=await Ne(async o=>{try{const r=await fetch(`${m}/guardian-batch`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${h}`,"User-Agent":"aerostack-gateway/0.15.29","X-Agent-Id":"aerostack-gateway"},body:JSON.stringify({events:o})});return r.ok?(await r.json()).config?.hook_tracking??null:null}catch{return null}},je);Ke&&await Pe(n)&&u("Claude Code hook auto-installed",{port:n})}catch(s){y("Hook server failed to start (non-fatal)",{error:s instanceof Error?s.message:String(s)})}if(He)try{const s=We??await Ue();s?($=new xe({port:le,token:s,rpcCall:O,onToolEvent:o=>{const r=I;if(!r)return;const i=F.get(r);if(i){if(o.phase==="start")i.push({name:o.toolName,category:o.category,started_at:Date.now(),ended_at:null,duration_ms:null,status:"running",args_summary:o.summary});else if(o.phase==="end"){const c=[...i].reverse().find(E=>E.name===o.toolName&&E.status==="running");c&&(c.ended_at=Date.now(),c.duration_ms=c.ended_at-c.started_at,c.status=o.error?"error":"success",o.error&&(c.error=o.error))}S(`${m}/chat/tool-status`,{method:"POST",headers:{Authorization:`Bearer ${h}`,"Content-Type":"application/json"},body:JSON.stringify({tool_name:o.toolName,phase:o.phase,category:o.category})}).catch(()=>{})}}}),await $.connect()?u("OpenClaw connector started",{port:le}):(u("OpenClaw gateway not reachable, skipping connector"),$=null)):u("OpenClaw integration skipped (no token found)")}catch(s){y("OpenClaw connector failed (non-fatal)",{error:s instanceof Error?s.message:String(s)})}process.env.AEROSTACK_CHAT_LISTENER!=="false"&&dt()}let T=null,J=!1;async function ut(){return typeof globalThis.WebSocket<"u"?globalThis.WebSocket:(await import("ws")).default}function dt(){J=!1;let e=null,t=0;const a=async()=>{if(!J){if(T){try{T.close()}catch{}T=null}try{const n=await ut(),o=m.replace(/^http/,"ws")+`/chat-relay?token=${encodeURIComponent(h||"")}&client_type=bridge&client_id=${encodeURIComponent(se)}`,r=new n(o);T=r,r.onopen=()=>{u("[chat-relay] WebSocket connected to workspace"),t=0},r.onmessage=i=>{const c=typeof i.data=="string"?i.data:"";if(c)try{if(JSON.parse(c).type==="pong")return;u("[chat-relay] WS frame received",{dataLength:c.length}),ht(c)}catch{}},r.onclose=i=>{u("[chat-relay] WebSocket closed",{code:i.code,reason:i.reason}),T=null,J||s()},r.onerror=()=>{y("[chat-relay] WebSocket error"),t++}}catch(n){y("[chat-relay] Connection failed",{error:n?.message}),t++,s()}}},s=()=>{if(J||e)return;const n=Math.min(2e3*Math.pow(2,t),3e4);u(`[chat-relay] Reconnecting in ${n/1e3}s (failures: ${t})`),e=setTimeout(()=>{e=null,a()},n)};a()}function ge(e,t){if(!T)return!1;try{return T.send(JSON.stringify({type:"chat_reply",content:e.slice(0,1e4),session_id:t||P,sender_name:"Agent",content_type:"text"})),!0}catch{return!1}}function oe(e,t=!0){if(T)try{T.send(JSON.stringify({type:"typing",session_id:e||P,is_typing:t}))}catch{}}function pt(){if(J=!0,T){try{T.close()}catch{}T=null}}async function ht(e){try{const t=JSON.parse(e);if(t.type!=="chat_message"){u("[chat-trace] Event ignored (not chat_message)",{type:t.type});return}const a=Date.now();u("[chat-trace] Chat event received",{type:t.type,sender:t.sender_name,contentType:t.content_type,hasContent:!!t.content,sessionId:t.session_id,messageId:t.message_id});let s=[];if(t.metadata_json)try{const g=JSON.parse(t.metadata_json);g?.v===1&&Array.isArray(g.attachments)&&(s=g.attachments)}catch{}const n=!!t.content&&t.content_type!=="media",o=s.length>0;if(!n&&!o)return;const r=t.session_id;u("Chat message received from admin",{sender:t.sender_name,sessionId:r,contentType:t.content_type,attachments:s.length});const i=/^[a-zA-Z0-9_-]{1,128}$/.test(t.message_id??"")?t.message_id:null,c=s.map(g=>{const U=Math.round(g.size/1024),Y=U>=1024?`${(U/1024).toFixed(1)} MB`:`${U} KB`,X=_((g.filename||"file").slice(0,200)),M=_((g.mime_type||"application/octet-stream").slice(0,100)),v=i?`${m}/chat/media/${i}/${encodeURIComponent(g.filename)}`:"(unavailable)";return`[Attached: ${X} (${M}, ${Y}) \u2014 ${v}]`}),E=c.length>0?`
62
+ `+c.join(`
63
+ `):"",b=n?t.content+(E?`
64
+ `+c.map(g=>g).join(`
65
+ `):""):c.join(`
66
+ `);r&&(P=r,j(r,"user",b)),z.length>=Qe&&z.shift(),z.push({sender_name:t.sender_name??"Admin",content:n?t.content:c.join(", "),created_at:Date.now(),session_id:r,content_type:t.content_type,metadata_json:t.metadata_json,message_id:t.message_id}),L=Date.now();const D=_(t.sender_name??"Admin"),k=n?_(t.content):"",C=r||P||"_default",H=`aerostack-chat-${/^[a-zA-Z0-9_-]{1,64}$/.test(C)?C:"_default"}`,R=`turn-${Date.now()}-${Math.random().toString(36).slice(2,8)}`,w=[];F.set(R,w),I=R;try{const g=k?k+E:E.trim(),U=`[${D} via Aerostack Dashboard]: ${g}`;if(oe(r,!0),$){const M=Date.now();u("[chat-trace] WS fast path starting",{session:H,promptLength:U.length,elapsedMs:M-a});const v=await $.sendChatMessage(U);if(v){const A=v.trim(),l=Date.now();u("[chat-trace] WS fast path got response",{responseLength:A.length,llmMs:l-M,totalMs:l-a}),await new Promise(d=>setTimeout(d,500));for(const d of w)d.status==="running"&&(d.status="timeout",d.ended_at=Date.now(),d.duration_ms=d.ended_at-d.started_at);if(w.length>0){const d=w.reduce((p,G)=>p+(G.duration_ms||0),0),f=w.length;try{await S(`${m}/chat/messages`,{method:"POST",headers:{Authorization:`Bearer ${h}`,"Content-Type":"application/json"},body:JSON.stringify({content:`Used ${f} tool${f>1?"s":""}`,content_type:"tool_activity",session_id:r,metadata:JSON.stringify({v:1,tools:w.map(p=>({name:p.name,category:p.category,duration_ms:p.duration_ms,status:p.status,args_summary:p.args_summary?.slice(0,500),error:p.error?.slice(0,500)})),total_duration_ms:d})})})}catch{}}if(F.delete(R),I===R&&(I=null),oe(r,!1),A.length>=2)if(j(C,"agent",A),ge(A,r))u("[chat-trace] Reply sent via WS upstream fast path (no HTTP POST needed)",{totalMs:Date.now()-a});else{const f=Date.now();try{const p=await S(`${m}/chat/messages`,{method:"POST",headers:{Authorization:`Bearer ${h}`,"Content-Type":"application/json"},body:JSON.stringify({content:A.slice(0,1e4),session_id:r})}),G=Date.now()-f;p.ok?u("[chat-trace] Reply sent (HTTP fallback)",{replyMs:G,totalMs:Date.now()-a}):y("[chat-trace] Reply failed (HTTP fallback)",{status:p.status,replyMs:G})}catch(p){y("[chat-trace] Reply error (HTTP fallback)",{error:p?.message,replyMs:Date.now()-f})}}return}u("[chat-trace] WS fast path failed, falling back to CLI",{elapsedMs:Date.now()-a})}const{execFile:Y}=await import("child_process"),X=Date.now();u("[chat-trace] CLI fallback starting",{sender:D,session:H,elapsedMs:X-a}),Y("openclaw",["agent","--agent","main","--json","--session-id",H,"-m",U],{timeout:12e4},async(M,v)=>{let A=[];if(!M&&v)try{const l=JSON.parse(v.trim()),d=l?.result?.payloads;Array.isArray(d)&&d.length>0?A=d:(l.text||l.reply||l.message)&&(A=[{text:l.text||l.reply||l.message}])}catch{const l=v.trim().split(`
67
+ `).pop()||v.trim();l&&(A=[{text:l}])}A.length===0&&(A=[{text:`Message received. I'll process "${k.slice(0,100)}" shortly.`}]),await new Promise(l=>setTimeout(l,500));for(const l of w)l.status==="running"&&(l.status="timeout",l.ended_at=Date.now(),l.duration_ms=l.ended_at-l.started_at);if(w.length>0){const l=w.reduce((f,p)=>f+(p.duration_ms||0),0),d=w.length;try{await S(`${m}/chat/messages`,{method:"POST",headers:{Authorization:`Bearer ${h}`,"Content-Type":"application/json"},body:JSON.stringify({content:`Used ${d} tool${d>1?"s":""}`,content_type:"tool_activity",session_id:r,metadata:JSON.stringify({v:1,tools:w.map(f=>({name:f.name,category:f.category,duration_ms:f.duration_ms,status:f.status,args_summary:f.args_summary?.slice(0,500),error:f.error?.slice(0,500)})),total_duration_ms:l})})})}catch{}}F.delete(R),I===R&&(I=null),oe(r,!1);for(const l of A){const d=l.text?.trim()||"";if(l.mediaUrl){let f=!1;try{f=new URL(l.mediaUrl).protocol==="https:"}catch{}if(f)try{const p=await S(`${m}/chat/media`,{method:"POST",headers:{Authorization:`Bearer ${h}`,"Content-Type":"application/json"},body:JSON.stringify({media_url:l.mediaUrl,content:d||void 0,session_id:r})});p.ok?u("Chat media sent to workspace",{mediaUrl:l.mediaUrl}):y("Chat media upload failed",{status:p.status})}catch(p){y("Failed to send chat media",{error:p?.message})}else y("Skipping invalid media URL",{mediaUrl:l.mediaUrl});j(C,"agent",d||`[media: ${l.mediaUrl}]`)}else if(d&&d.length>=2)if(j(C,"agent",d),ge(d,r))u("Chat reply sent via WS upstream (no HTTP POST needed)");else try{const p=await S(`${m}/chat/messages`,{method:"POST",headers:{Authorization:`Bearer ${h}`,"Content-Type":"application/json"},body:JSON.stringify({content:d.slice(0,1e4),session_id:r})});p.ok?u("Chat reply sent to workspace"):y("Chat reply failed",{status:p.status})}catch(p){y("Failed to send chat reply",{error:p?.message})}}})}catch(g){y("Failed to spawn agent turn",{error:g?.message}),F.delete(R),I===R&&(I=null)}}catch{}}function _e(){pt(),$?.stop(),Ie(),process.exit(0)}process.on("SIGTERM",()=>{_e()}),process.on("SIGINT",()=>{_e()}),process.argv.includes("--check")?it():lt().catch(e=>{Le("Fatal error",{error:e instanceof Error?e.message:String(e)}),process.exit(1)});
package/dist/logger.js CHANGED
@@ -1,2 +1,2 @@
1
- const t={debug:0,info:1,warn:2,error:3},i=(()=>{const r=(process.env.AEROSTACK_LOG_LEVEL??"info").toLowerCase();return r in t?r:"info"})();function n(r,o,e){if(t[r]<t[i])return;const s={ts:new Date().toISOString(),level:r,msg:o,...e};process.stderr.write(JSON.stringify(s)+`
2
- `)}const c=(r,o)=>n("debug",r,o),f=(r,o)=>n("info",r,o),g=(r,o)=>n("warn",r,o),p=(r,o)=>n("error",r,o);export{c as debug,p as error,f as info,n as log,g as warn};
1
+ import{appendFileSync as p,mkdirSync as u}from"node:fs";import{join as i}from"node:path";import{homedir as a}from"node:os";const n={debug:0,info:1,warn:2,error:3},g=(()=>{const r=(process.env.AEROSTACK_LOG_LEVEL??"info").toLowerCase();return r in n?r:"info"})(),c=(()=>{const r=process.env.AEROSTACK_LOG_FILE;if(r)return r;try{const o=i(a(),".aerostack","logs");return u(o,{recursive:!0}),i(o,"bridge.log")}catch{return null}})();function t(r,o,s){if(n[r]<n[g])return;const f={ts:new Date().toISOString(),level:r,msg:o,...s},e=JSON.stringify(f)+`
2
+ `;if(process.stderr.write(e),c)try{p(c,e)}catch{}}const L=(r,o)=>t("debug",r,o),S=(r,o)=>t("info",r,o),w=(r,o)=>t("warn",r,o),E=(r,o)=>t("error",r,o);export{L as debug,E as error,S as info,t as log,w as warn};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aerostack/gateway",
3
- "version": "0.15.30",
3
+ "version": "0.17.0",
4
4
  "description": "stdio-to-HTTP bridge connecting any MCP client to Aerostack Workspaces",
5
5
  "author": "Aerostack",
6
6
  "license": "MIT",