@aerostack/gateway 0.15.29 → 0.15.30
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,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
import{join as p}from"node:path";import{homedir as f}from"node:os";import{readFile as u}from"node:fs/promises";import{info as i,warn as l,error as S}from"./logger.js";import{startExecApprovalServer as x}from"./exec-approval-server.js";const d=process.env.AEROSTACK_WORKSPACE_URL,v=process.env.AEROSTACK_TOKEN;(!d||!v)&&(process.stderr.write(`ERROR: AEROSTACK_WORKSPACE_URL and AEROSTACK_TOKEN are required
|
|
4
|
-
`),process.exit(1));const m=d.replace(/\/+$/,"");async function O(t,a){const n={jsonrpc:"2.0",id:Date.now(),method:t,params:a??{}};try{const e=await fetch(m,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${v}`,"User-Agent":"aerostack-exec-daemon/0.15.
|
|
4
|
+
`),process.exit(1));const m=d.replace(/\/+$/,"");async function O(t,a){const n={jsonrpc:"2.0",id:Date.now(),method:t,params:a??{}};try{const e=await fetch(m,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${v}`,"User-Agent":"aerostack-exec-daemon/0.15.29","X-Agent-Id":"aerostack-exec-daemon"},body:JSON.stringify(n),signal:AbortSignal.timeout(15e3)});if((e.headers.get("content-type")??"").includes("text/event-stream")){const o=(await e.text()).split(`
|
|
5
5
|
`).reverse().find(s=>s.startsWith("data: "));return o?JSON.parse(o.slice(6)):null}return await e.json()}catch(e){return l("rpcCall failed",{method:t,error:e instanceof Error?e.message:String(e)}),null}}async function g(){i("Aerostack exec approval daemon starting",{workspace:m});const t=p(f(),".openclaw","exec-approvals.json");let a={};try{a=JSON.parse(await u(t,"utf-8"))}catch{l("exec-approvals.json not found, will retry on interval",{path:t})}const n=async()=>{try{const w=await u(t,"utf-8");a=JSON.parse(w)}catch{return null}const o=a?.socket?.path??p(f(),".openclaw","exec-approvals.sock"),s=a?.socket?.token;return s?(i("Starting exec approval socket server",{path:o}),x({socketPath:o,token:s,rpcCall:O})):(l("No socket token in exec-approvals.json, waiting for OpenClaw to write it"),null)};let e=await n(),r=null;e||(r=setInterval(async()=>{e=await n(),e&&r&&(clearInterval(r),r=null)},3e4)),i("Exec approval daemon ready \u2014 waiting for OpenClaw approval requests");const c=()=>{r&&(clearInterval(r),r=null),e?.stop(),process.exit(0)};process.on("SIGTERM",c),process.on("SIGINT",c)}g().catch(t=>{S("Fatal",{error:t instanceof Error?t.message:String(t)}),process.exit(1)});
|
package/dist/index.js
CHANGED
|
@@ -4,7 +4,7 @@ import{Server as he}from"@modelcontextprotocol/sdk/server/index.js";import{Stdio
|
|
|
4
4
|
`),process.exit(1)),d||(process.stderr.write(`ERROR: AEROSTACK_TOKEN is required
|
|
5
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
|
|
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.
|
|
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
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
9
|
${s.slice(-le).map(n=>`[${n.role==="user"?"Admin":"You (Agent)"}] ${n.content}`).join(`
|
|
10
10
|
`)}
|
|
@@ -42,9 +42,9 @@ 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.
|
|
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}
|
|
46
46
|
|
|
47
|
-
${q}`:q}}}const I=new he({name:"aerostack-gateway",version:"0.15.
|
|
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+`
|
|
48
48
|
`);e(`
|
|
49
49
|
Aerostack Connection Check
|
|
50
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(`
|
|
@@ -58,7 +58,7 @@ ${q}`:q}}}const I=new he({name:"aerostack-gateway",version:"0.15.14"},{capabilit
|
|
|
58
58
|
Fix: Check AEROSTACK_WORKSPACE_URL and your network connection
|
|
59
59
|
`)):(e(` \u274C Connection failed (${s?.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.
|
|
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
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
63
|
`+i.join(`
|
|
64
64
|
`):"",R=t?s.content+(c?`
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{readFile as m}from"node:fs/promises";import{join as f}from"node:path";import{homedir as w}from"node:os";import{info as u,warn as c,debug as d}from"./logger.js";import{addToBatch as p,detectCategory as b,summarizeToolInput as y}from"./hook-server.js";function S(l){return l.replace(/^(?:\w+__)*aerostack__/,"").replace(/__/g,":")||l}async function N(){try{const l=f(w(),".openclaw","openclaw.json"),e=await m(l,"utf-8");return JSON.parse(e)?.gateway?.auth?.token??null}catch{return null}}async function R(){try{const l=f(w(),".openclaw","exec-approvals.json"),e=await m(l,"utf-8");return JSON.parse(e)?.socket?.token??null}catch{return null}}const k=1e3,_=3e4;class A{opts;ws=null;destroyed=!1;reconnectMs=k;reconnectTimer=null;requestId=0;seenSessions=new Set;pendingRequests=new Map;lastActiveSession=null;pendingChatTurns=new Map;constructor(e){this.opts=e}async connect(){if(this.destroyed)return!1;const e=await this.getWebSocket(),s=`ws://127.0.0.1:${this.opts.port}`;return new Promise(t=>{try{const n=new e(s);this.ws=n;const o=setTimeout(()=>{d("OpenClaw connect timeout");try{n.close()}catch{}t(!1)},1e4);n.onopen=()=>{clearTimeout(o),d("OpenClaw WS connected, sending handshake"),this.sendHandshake()},n.onmessage=i=>{try{const r=JSON.parse(String(i.data));this.handleFrame(r,t)}catch{}},n.onerror=()=>{clearTimeout(o),t(!1)},n.onclose=()=>{clearTimeout(o),this.ws=null,this.destroyed||this.scheduleReconnect()}}catch{t(!1)}})}getLastActiveSession(){return this.lastActiveSession}async sendToSession(e,s){if(!this.ws||!this.connected)return!1;const t=await this.sendRequest("sessions.send",{key:e,message:s,idempotencyKey:`aerostack-${Date.now()}`});return t.ok||c("sendToSession failed",{key:e,error:t.error?.message}),t.ok??!1}async sendChatMessage(e,s=12e4){if(!this.ws||!this.connected)return null;const t="agent:main:main";this.subscribeToSessionMessages(t);const n=new Promise(i=>{const r=setTimeout(()=>{this.pendingChatTurns.delete(t),i(null)},s);this.pendingChatTurns.set(t,a=>{clearTimeout(r),i(a)})}),o=await this.sendRequest("sessions.send",{key:t,message:e,idempotencyKey:`aerostack-${Date.now()}-${Math.random().toString(36).slice(2,8)}`});return o.ok?n:(this.pendingChatTurns.delete(t),c("sendChatMessage: sessions.send rejected",{error:o.error?.message}),null)}stop(){if(this.destroyed=!0,this.reconnectTimer&&(clearTimeout(this.reconnectTimer),this.reconnectTimer=null),this.ws){try{this.ws.close(1e3)}catch{}this.ws=null}this.pendingRequests.clear(),this.pendingChatTurns.clear()}sendHandshake(){this.send({type:"req",id:String(++this.requestId),method:"connect",params:{minProtocol:3,maxProtocol:3,client:{id:"openclaw-tui",displayName:"Aerostack Guardian",version:"0.15.
|
|
1
|
+
import{readFile as m}from"node:fs/promises";import{join as f}from"node:path";import{homedir as w}from"node:os";import{info as u,warn as c,debug as d}from"./logger.js";import{addToBatch as p,detectCategory as b,summarizeToolInput as y}from"./hook-server.js";function S(l){return l.replace(/^(?:\w+__)*aerostack__/,"").replace(/__/g,":")||l}async function N(){try{const l=f(w(),".openclaw","openclaw.json"),e=await m(l,"utf-8");return JSON.parse(e)?.gateway?.auth?.token??null}catch{return null}}async function R(){try{const l=f(w(),".openclaw","exec-approvals.json"),e=await m(l,"utf-8");return JSON.parse(e)?.socket?.token??null}catch{return null}}const k=1e3,_=3e4;class A{opts;ws=null;destroyed=!1;reconnectMs=k;reconnectTimer=null;requestId=0;seenSessions=new Set;pendingRequests=new Map;lastActiveSession=null;pendingChatTurns=new Map;constructor(e){this.opts=e}async connect(){if(this.destroyed)return!1;const e=await this.getWebSocket(),s=`ws://127.0.0.1:${this.opts.port}`;return new Promise(t=>{try{const n=new e(s);this.ws=n;const o=setTimeout(()=>{d("OpenClaw connect timeout");try{n.close()}catch{}t(!1)},1e4);n.onopen=()=>{clearTimeout(o),d("OpenClaw WS connected, sending handshake"),this.sendHandshake()},n.onmessage=i=>{try{const r=JSON.parse(String(i.data));this.handleFrame(r,t)}catch{}},n.onerror=()=>{clearTimeout(o),t(!1)},n.onclose=()=>{clearTimeout(o),this.ws=null,this.destroyed||this.scheduleReconnect()}}catch{t(!1)}})}getLastActiveSession(){return this.lastActiveSession}async sendToSession(e,s){if(!this.ws||!this.connected)return!1;const t=await this.sendRequest("sessions.send",{key:e,message:s,idempotencyKey:`aerostack-${Date.now()}`});return t.ok||c("sendToSession failed",{key:e,error:t.error?.message}),t.ok??!1}async sendChatMessage(e,s=12e4){if(!this.ws||!this.connected)return null;const t="agent:main:main";this.subscribeToSessionMessages(t);const n=new Promise(i=>{const r=setTimeout(()=>{this.pendingChatTurns.delete(t),i(null)},s);this.pendingChatTurns.set(t,a=>{clearTimeout(r),i(a)})}),o=await this.sendRequest("sessions.send",{key:t,message:e,idempotencyKey:`aerostack-${Date.now()}-${Math.random().toString(36).slice(2,8)}`});return o.ok?n:(this.pendingChatTurns.delete(t),c("sendChatMessage: sessions.send rejected",{error:o.error?.message}),null)}stop(){if(this.destroyed=!0,this.reconnectTimer&&(clearTimeout(this.reconnectTimer),this.reconnectTimer=null),this.ws){try{this.ws.close(1e3)}catch{}this.ws=null}this.pendingRequests.clear(),this.pendingChatTurns.clear()}sendHandshake(){this.send({type:"req",id:String(++this.requestId),method:"connect",params:{minProtocol:3,maxProtocol:3,client:{id:"openclaw-tui",displayName:"Aerostack Guardian",version:"0.15.29",platform:process.platform,mode:"cli"},auth:{token:this.opts.token},scopes:["operator.read","operator.write"],caps:["tool-events"]}})}connected=!1;subscribedSessions=new Set;handleFrame(e,s){if(e.type==="res"&&!this.connected){e.ok?(this.connected=!0,this.reconnectMs=k,u("OpenClaw connector connected",{port:this.opts.port}),s?.(!0),this.subscribeAllSessions().catch(t=>c("subscribeAllSessions failed",{error:t?.message}))):(c("OpenClaw connect rejected",{error:e.error?.message}),s?.(!1));return}if(e.type==="res"&&e.id&&this.pendingRequests.has(e.id)){const t=this.pendingRequests.get(e.id);this.pendingRequests.delete(e.id),t(e);return}if(e.type==="res"&&e.ok===!1&&e.error){c("OpenClaw RPC error",{error:e.error.message,code:e.error.code});return}e.type==="event"&&this.handleEvent(e)}subscribeToSessionMessages(e){if(this.subscribedSessions.has(e))return;this.subscribedSessions.add(e);const s=String(++this.requestId);this.pendingRequests.set(s,t=>{t.ok?d("OpenClaw subscribed to session messages",{key:e}):c("OpenClaw messages.subscribe failed",{key:e,error:t.error?.message})}),this.send({type:"req",id:s,method:"sessions.messages.subscribe",params:{key:e}}),setTimeout(()=>this.pendingRequests.delete(s),1e4)}async subscribeAllSessions(){const e=await this.sendRequest("sessions.subscribe");e.ok?u("OpenClaw subscribed to all session events (tool + lifecycle)"):c("OpenClaw sessions.subscribe failed",{error:e.error?.message});const s=await this.sendRequest("sessions.list",{limit:50});if(!s.ok){c("OpenClaw sessions.list failed",{error:s.error?.message});return}const t=s.payload?.sessions,n=Array.isArray(t)?t:[];u("OpenClaw active sessions",{count:n.length});for(const o of n)o.key&&this.subscribeToSessionMessages(o.key)}sendRequest(e,s){return new Promise(t=>{const n=String(++this.requestId),o=setTimeout(()=>{this.pendingRequests.delete(n),t({type:"res",id:n,ok:!1,error:{code:"TIMEOUT",message:"request timeout"}})},1e4);this.pendingRequests.set(n,i=>{clearTimeout(o),t(i)}),this.send({type:"req",id:n,method:e,params:s})})}handleEvent(e){const s=e.event,t=e.payload??{};s==="agent"?this.handleAgentEvent(t):s==="session.tool"?this.handleToolEvent(t):s==="session.message"?this.handleMessageEvent(t):s==="sessions.changed"?this.handleSessionChanged(t):(s==="exec.approval"||s==="exec.request"||s==="tool.approval")&&this.handleExecApprovalEvent(t)}handleAgentEvent(e){const s=e.stream,t=e.data,n=e.sessionKey??"";if(n&&this.seenSessions.add(n),s==="tool"){const o=t?.name??t?.toolName??e.toolName??"unknown",i=t?.phase??"",r=t?.args??e.args??{};if(i==="start"){const{category:a,risk:h}=b(o,r),g=y(o,r);p({action:`OpenClaw ${S(o)}: ${g}`.slice(0,500),category:a,risk_level:h,details:JSON.stringify({tool:o,session:n,...r}).slice(0,500),agent_name:"OpenClaw"}),d("OpenClaw agent tool event",{tool:o,category:a,risk:h})}}if(s==="lifecycle"&&t){const o=t.phase??"";(o==="start"||o==="end")&&d("OpenClaw agent lifecycle",{phase:o,session:n})}}handleToolEvent(e){const s=e.data,t=s?.name??s?.toolName??e.toolName??"unknown",n=s?.phase??"",o=s?.args??e.args??{},i=e.sessionKey??"";if(n!=="start"&&n!=="end")return;const{category:r,risk:a}=b(t,o),h=y(t,o);if(n==="start"&&p({action:`OpenClaw ${S(t)}: ${h}`.slice(0,500),category:r,risk_level:a,details:JSON.stringify({tool:t,session:i,...o}).slice(0,500),agent_name:"OpenClaw"}),d("OpenClaw tool event",{tool:t,phase:n,category:r,risk:a,session:i}),!new Set(["aerostack__guardian_report","aerostack__chat_check","aerostack__chat_reply","aerostack__check_approval","aerostack__local_guardian","aerostack__guardian_check"]).has(t)){const T=s?.error??e.error??void 0;this.opts.onToolEvent?.({toolName:t,phase:n,category:r,summary:h,error:T})}i&&(this.seenSessions.add(i),this.lastActiveSession=i)}handleExecApprovalEvent(e){const s=e.command??e.cmd??"",t=e.sessionKey??"",n=e.agentId??"";if(!s)return;const o=s.toLowerCase();let i="medium",r="exec_command";/\brm\s+-rf?\b|\bdrop\b|\bdelete\b|\btruncate\b/.test(o)?(i="critical",r="file_delete"):/\brm\b|\bgit\s+push\b|\bgit\s+reset\b|\bdeploy\b|\bkill\b/.test(o)?(i="high",r="deploy"):/\binstall\b|\bpip\b|\bnpm\b|\bcurl\b|\bwget\b/.test(o)&&(i="medium",r="package_install"),p({action:`[approval requested] exec: ${s}`.slice(0,500),category:r,risk_level:i,details:JSON.stringify({command:s,agent:n,session:t,source:"exec.approval"}).slice(0,500),agent_name:"OpenClaw"}),this.opts.rpcCall("tools/call",{name:"aerostack__guardian_report",arguments:{action:`OpenClaw exec approval: ${s}`.slice(0,500),category:r,risk_level:i,details:JSON.stringify({command:s,agent:n,session:t}).slice(0,500)}}).catch(()=>{}),u("OpenClaw exec approval event",{command:s.slice(0,100),risk:i})}handleMessageEvent(e){const s=e.sessionKey??"";s&&(this.seenSessions.add(s),this.lastActiveSession=s);const t=this.pendingChatTurns.get(s);if(!t)return;const n=e.message,o=typeof n=="object"?n?.role:void 0;let i="";if(typeof n=="string")i=n;else if(n){const r=n.content;typeof r=="string"?i=r:Array.isArray(r)&&(i=r.map(a=>typeof a=="string"?a:a?.text??"").join(""))}i&&o!=="user"&&(this.pendingChatTurns.delete(s),t(i))}handleSessionChanged(e){const s=e.sessionKey??"";s&&(this.seenSessions.add(s),this.subscribeToSessionMessages(s))}send(e){if(this.ws)try{this.ws.send(JSON.stringify(e))}catch{}}scheduleReconnect(){this.destroyed||this.reconnectTimer||(d("OpenClaw reconnecting in",{ms:this.reconnectMs}),this.reconnectTimer=setTimeout(async()=>{this.reconnectTimer=null,this.connected=!1,this.seenSessions.clear(),this.subscribedSessions.clear(),this.pendingRequests.clear(),await this.connect()},this.reconnectMs),this.reconnectMs=Math.min(this.reconnectMs*2,_))}async getWebSocket(){return typeof globalThis.WebSocket<"u"?globalThis.WebSocket:(await import("ws")).default}}export{A as OpenClawConnector,R as resolveExecApprovalToken,N as resolveOpenClawToken};
|
package/dist/resolution.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{info as y,warn as _,debug as h}from"./logger.js";function J(e,o){const{approvalId:n}=e;let a=!1,s=null,r=null,g=null;const v=()=>{if(a=!0,r&&(clearInterval(r),r=null),g&&(clearTimeout(g),g=null),s){try{s.close(1e3)}catch{}s=null}},c=f=>{a||(o.resolve(n,f.status,f.reviewer_note),y("Background resolver: approval resolved",{approvalId:n,status:f.status}),v())};return(async()=>{if(e.wsUrl)try{const d=await O(),l=new d(e.wsUrl);s=l,l.onmessage=S=>{try{const i=typeof S.data=="string"?JSON.parse(S.data):JSON.parse(String(S.data)),p=i?.status;(p==="executed"||p==="approved"||p==="rejected"||p==="expired"||p==="changes_requested")&&c({status:p,reviewer_note:i?.reviewer_note})}catch{}},l.onerror=()=>{h("Background resolver WS error",{approvalId:n})},l.onclose=()=>{s=null},typeof l.on=="function"&&l.on("unexpected-response",async(S,i)=>{try{const p=[];for await(const w of i)p.push(w);const k=Buffer.concat(p).toString(),t=JSON.parse(k);t?.status&&t.status!=="pending"&&c({status:t.status,reviewer_note:t?.reviewer_note})}catch{}})}catch{h("Background resolver WS connect failed, using polling only",{approvalId:n})}r=setInterval(async()=>{if(!a)try{const d=await W(e.pollUrl);d&&c(d)}catch{}},3e4);try{const d=await W(e.pollUrl);if(d){c(d);return}}catch{}const m=e.timeoutMs??2*60*60*1e3;g=setTimeout(()=>{a||(o.resolve(n,"expired"),v())},m)})().catch(()=>{}),{cancel:v}}async function N(e){const{approvalId:o,wsUrl:n,pollUrl:a,pollIntervalMs:s,timeoutMs:r,token:g}=e,v=Date.now()+r;if(n)try{return await L(o,n,a,v,g)}catch(c){const f=c instanceof Error?c.message:"Unknown WS error";_("WebSocket resolution failed, falling back to polling",{approvalId:o,error:f})}return P(o,a,s,v,g)}async function L(e,o,n,a,s){const r=await O();return new Promise((g,v)=>{let c=!1,f=null,m=null;const d=()=>{c=!0,f&&clearInterval(f),m&&clearTimeout(m)},l=t=>{c||(d(),g(t))},S=t=>{c||(d(),v(t))};h("Connecting WebSocket",{approvalId:e,wsUrl:o});const i=new r(o);i.onopen=()=>{y("WebSocket connected, waiting for resolution",{approvalId:e})},i.onmessage=t=>{try{const w=typeof t.data=="string"?JSON.parse(t.data):JSON.parse(String(t.data)),u=w?.status;if(h("WebSocket message received",{approvalId:e,status:u}),u==="executed"||u==="approved"||u==="rejected"||u==="expired"||u==="changes_requested"){y("Approval resolved via WebSocket",{approvalId:e,status:u}),l({status:u,reviewer_note:w?.reviewer_note});try{i.close(1e3)}catch{}}}catch{_("Failed to parse WebSocket message",{approvalId:e})}},i.onerror=t=>{const w=t instanceof Error?t.message:"WebSocket error";h("WebSocket error",{approvalId:e,error:w}),S(new Error(`WebSocket error: ${w}`))},i.onclose=t=>{c||(h("WebSocket closed without resolution",{approvalId:e,code:t.code}),S(new Error(`WebSocket closed unexpectedly (code ${t.code})`)))},typeof i.on=="function"&&i.on("unexpected-response",async(t,w)=>{try{const u=[];for await(const A of w)u.push(A);const T=Buffer.concat(u).toString(),x=JSON.parse(T),b=x?.status;if(b&&b!=="pending"){y("Approval already resolved (WS endpoint returned JSON)",{approvalId:e,status:b}),l({status:b,reviewer_note:x?.reviewer_note});return}}catch{}S(new Error("WebSocket upgrade rejected by server"))}),f=setInterval(async()=>{if(!c)try{const t=await W(n,s);if(t){y("Approval resolved via safety-net poll",{approvalId:e,status:t.status}),l(t);try{i.close(1e3)}catch{}}}catch{}},3e4);const k=a-Date.now();if(k<=0){S(new Error("Approval timeout"));return}m=setTimeout(()=>{if(!c){_("Approval timed out",{approvalId:e}),l({status:"expired"});try{i.close(1e3)}catch{}}},k)})}async function P(e,o,n,a,s){for(y("Polling for approval resolution",{approvalId:e,intervalMs:n});Date.now()<a;){await B(n);try{const r=await W(o,s);if(r)return y("Approval resolved via polling",{approvalId:e,status:r.status}),r}catch{}}return _("Approval polling timed out",{approvalId:e}),{status:"expired"}}async function W(e,o){const n={"User-Agent":"aerostack-gateway/0.15.
|
|
1
|
+
import{info as y,warn as _,debug as h}from"./logger.js";function J(e,o){const{approvalId:n}=e;let a=!1,s=null,r=null,g=null;const v=()=>{if(a=!0,r&&(clearInterval(r),r=null),g&&(clearTimeout(g),g=null),s){try{s.close(1e3)}catch{}s=null}},c=f=>{a||(o.resolve(n,f.status,f.reviewer_note),y("Background resolver: approval resolved",{approvalId:n,status:f.status}),v())};return(async()=>{if(e.wsUrl)try{const d=await O(),l=new d(e.wsUrl);s=l,l.onmessage=S=>{try{const i=typeof S.data=="string"?JSON.parse(S.data):JSON.parse(String(S.data)),p=i?.status;(p==="executed"||p==="approved"||p==="rejected"||p==="expired"||p==="changes_requested")&&c({status:p,reviewer_note:i?.reviewer_note})}catch{}},l.onerror=()=>{h("Background resolver WS error",{approvalId:n})},l.onclose=()=>{s=null},typeof l.on=="function"&&l.on("unexpected-response",async(S,i)=>{try{const p=[];for await(const w of i)p.push(w);const k=Buffer.concat(p).toString(),t=JSON.parse(k);t?.status&&t.status!=="pending"&&c({status:t.status,reviewer_note:t?.reviewer_note})}catch{}})}catch{h("Background resolver WS connect failed, using polling only",{approvalId:n})}r=setInterval(async()=>{if(!a)try{const d=await W(e.pollUrl);d&&c(d)}catch{}},3e4);try{const d=await W(e.pollUrl);if(d){c(d);return}}catch{}const m=e.timeoutMs??2*60*60*1e3;g=setTimeout(()=>{a||(o.resolve(n,"expired"),v())},m)})().catch(()=>{}),{cancel:v}}async function N(e){const{approvalId:o,wsUrl:n,pollUrl:a,pollIntervalMs:s,timeoutMs:r,token:g}=e,v=Date.now()+r;if(n)try{return await L(o,n,a,v,g)}catch(c){const f=c instanceof Error?c.message:"Unknown WS error";_("WebSocket resolution failed, falling back to polling",{approvalId:o,error:f})}return P(o,a,s,v,g)}async function L(e,o,n,a,s){const r=await O();return new Promise((g,v)=>{let c=!1,f=null,m=null;const d=()=>{c=!0,f&&clearInterval(f),m&&clearTimeout(m)},l=t=>{c||(d(),g(t))},S=t=>{c||(d(),v(t))};h("Connecting WebSocket",{approvalId:e,wsUrl:o});const i=new r(o);i.onopen=()=>{y("WebSocket connected, waiting for resolution",{approvalId:e})},i.onmessage=t=>{try{const w=typeof t.data=="string"?JSON.parse(t.data):JSON.parse(String(t.data)),u=w?.status;if(h("WebSocket message received",{approvalId:e,status:u}),u==="executed"||u==="approved"||u==="rejected"||u==="expired"||u==="changes_requested"){y("Approval resolved via WebSocket",{approvalId:e,status:u}),l({status:u,reviewer_note:w?.reviewer_note});try{i.close(1e3)}catch{}}}catch{_("Failed to parse WebSocket message",{approvalId:e})}},i.onerror=t=>{const w=t instanceof Error?t.message:"WebSocket error";h("WebSocket error",{approvalId:e,error:w}),S(new Error(`WebSocket error: ${w}`))},i.onclose=t=>{c||(h("WebSocket closed without resolution",{approvalId:e,code:t.code}),S(new Error(`WebSocket closed unexpectedly (code ${t.code})`)))},typeof i.on=="function"&&i.on("unexpected-response",async(t,w)=>{try{const u=[];for await(const A of w)u.push(A);const T=Buffer.concat(u).toString(),x=JSON.parse(T),b=x?.status;if(b&&b!=="pending"){y("Approval already resolved (WS endpoint returned JSON)",{approvalId:e,status:b}),l({status:b,reviewer_note:x?.reviewer_note});return}}catch{}S(new Error("WebSocket upgrade rejected by server"))}),f=setInterval(async()=>{if(!c)try{const t=await W(n,s);if(t){y("Approval resolved via safety-net poll",{approvalId:e,status:t.status}),l(t);try{i.close(1e3)}catch{}}}catch{}},3e4);const k=a-Date.now();if(k<=0){S(new Error("Approval timeout"));return}m=setTimeout(()=>{if(!c){_("Approval timed out",{approvalId:e}),l({status:"expired"});try{i.close(1e3)}catch{}}},k)})}async function P(e,o,n,a,s){for(y("Polling for approval resolution",{approvalId:e,intervalMs:n});Date.now()<a;){await B(n);try{const r=await W(o,s);if(r)return y("Approval resolved via polling",{approvalId:e,status:r.status}),r}catch{}}return _("Approval polling timed out",{approvalId:e}),{status:"expired"}}async function W(e,o){const n={"User-Agent":"aerostack-gateway/0.15.29"};o&&(n.Authorization=`Bearer ${o}`);const a=await fetch(e,{headers:n});if(!a.ok)return null;const s=await a.json(),r=s.status;return r==="executed"||r==="approved"?{status:r,reviewer_note:s.reviewer_note}:r==="rejected"?{status:"rejected",reviewer_note:s.reviewer_note}:r==="changes_requested"?{status:"changes_requested",reviewer_note:s.reviewer_note}:r==="expired"?{status:"expired"}:null}async function O(){if(typeof globalThis.WebSocket<"u")return globalThis.WebSocket;try{return(await import("ws")).default}catch{throw new Error('WebSocket not available. Install the "ws" package: npm install ws')}}function B(e){return new Promise(o=>setTimeout(o,e))}export{N as resolveApproval,J as startBackgroundResolver};
|