@aerostack/gateway 0.15.3 → 0.15.5

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,3 +1,3 @@
1
- import{createServer as f}from"node:net";import{unlink as u,mkdir as g}from"node:fs/promises";import{dirname as h}from"node:path";import{info as p,warn as m,debug as b,error as d}from"./logger.js";import{addToBatch as y}from"./hook-server.js";function v(e){const t=e.toLowerCase();return/\brm\s+-rf?\b|\bdrop\s|\bdelete\s|\btruncate\s|\bformat\b/.test(t)?"critical":/\brm\b|\bgit\s+push\b|\bgit\s+reset\b|\bdeploy\b|\bkill\b/.test(t)?"high":/\binstall\b|\bpip\b|\bnpm\b|\bcurl\b|\bwget\b/.test(t)?"medium":"low"}function E(e){const t=e.toLowerCase();return/\brm\b|\bunlink\b|\brmdir\b/.test(t)?"file_delete":/\bgit\s+push\b|\bdeploy\b|\bwrangler\b/.test(t)?"deploy":/\binstall\b|\bpip\b|\bnpm\b|\byarn\b/.test(t)?"package_install":/\bcurl\b|\bwget\b|\bfetch\b/.test(t)?"api_call":/\bwrite\b|\btee\b|\b>\b|\bcat\s.*>/.test(t)?"file_write":"exec_command"}function D(e){let t=null,a=!1;return(async()=>{await g(h(e.socketPath),{recursive:!0}).catch(()=>{}),await u(e.socketPath).catch(()=>{}),t=f(r=>{k(r,e)}),t.on("error",r=>{r.code==="EADDRINUSE"?m("Exec approval socket in use, skipping",{path:e.socketPath}):d("Exec approval server error",{error:r.message})}),t.listen(e.socketPath,()=>{p("Exec approval server started",{path:e.socketPath})})})().catch(r=>{m("Exec approval server failed to start",{error:r instanceof Error?r.message:String(r)})}),{stop:()=>{a||(a=!0,t&&(t.close(),t=null),u(e.socketPath).catch(()=>{}))}}}function k(e,t){let a="";const n=setTimeout(()=>{b("Exec approval timeout, denying"),o(e,"deny")},14e3);e.on("data",r=>{a+=r.toString("utf-8");const i=a.indexOf(`
2
- `);if(i===-1)return;const c=a.slice(0,i).trim();a=a.slice(i+1),clearTimeout(n);try{const s=JSON.parse(c);if(s.token!==t.token){b("Exec approval token mismatch"),o(e,"deny");return}w(s,e,t).catch(l=>{d("Exec approval processing error",{error:l instanceof Error?l.message:String(l)}),o(e,"deny")})}catch{o(e,"deny")}}),e.on("error",()=>{clearTimeout(n)})}async function w(e,t,a){const n=e.request.command??e.request.args?.join(" ")??"unknown",r=v(n),i=E(n),c=e.request.sessionKey??"",s=e.request.agentId??"";y({action:`[exec-approval] ${n}`.slice(0,500),category:i,risk_level:r,details:JSON.stringify({command:n,host:e.request.host,agent:s,session:c,security:e.request.security}).slice(0,500)}),a.rpcCall("tools/call",{name:"aerostack__guardian_report",arguments:{action:`OpenClaw exec: ${n}`.slice(0,500),category:i,risk_level:r,details:JSON.stringify({command:n,agent:s,session:c}).slice(0,500)}}).catch(()=>{}),r==="critical"||r==="high"?(p("Exec approval DENIED (high risk)",{command:n.slice(0,100),risk:r}),a.rpcCall("tools/call",{name:"aerostack__local_guardian",arguments:{action:n.slice(0,500),category:i,risk_level:r,details:`OpenClaw agent ${s} wants to execute: ${n}`}}).catch(()=>{}),o(t,"deny")):(b("Exec approval ALLOWED",{command:n.slice(0,100),risk:r}),o(t,"allow-once"))}function o(e,t){try{e.write(JSON.stringify({type:"decision",decision:t})+`
1
+ import{createServer as f}from"node:net";import{unlink as u,mkdir as g}from"node:fs/promises";import{dirname as h}from"node:path";import{info as p,warn as m,debug as b,error as d}from"./logger.js";import{addToBatch as y}from"./hook-server.js";function v(e){const t=e.toLowerCase();return/\brm\s+-rf?\b|\bdrop\s|\bdelete\s|\btruncate\s|\bformat\b/.test(t)?"critical":/\brm\b|\bgit\s+push\b|\bgit\s+reset\b|\bdeploy\b|\bkill\b/.test(t)?"high":/\binstall\b|\bpip\b|\bnpm\b|\bcurl\b|\bwget\b/.test(t)?"medium":"low"}function w(e){const t=e.toLowerCase();return/\brm\b|\bunlink\b|\brmdir\b/.test(t)?"file_delete":/\bgit\s+push\b|\bdeploy\b|\bwrangler\b/.test(t)?"deploy":/\binstall\b|\bpip\b|\bnpm\b|\byarn\b/.test(t)?"package_install":/\bcurl\b|\bwget\b|\bfetch\b/.test(t)?"api_call":/\bwrite\b|\btee\b|\b>\b|\bcat\s.*>/.test(t)?"file_write":"exec_command"}function D(e){let t=null,a=!1;return(async()=>{await g(h(e.socketPath),{recursive:!0}).catch(()=>{}),await u(e.socketPath).catch(()=>{}),t=f(r=>{E(r,e)}),t.on("error",r=>{r.code==="EADDRINUSE"?m("Exec approval socket in use, skipping",{path:e.socketPath}):d("Exec approval server error",{error:r.message})}),t.listen(e.socketPath,()=>{p("Exec approval server started",{path:e.socketPath})})})().catch(r=>{m("Exec approval server failed to start",{error:r instanceof Error?r.message:String(r)})}),{stop:()=>{a||(a=!0,t&&(t.close(),t=null),u(e.socketPath).catch(()=>{}))}}}function E(e,t){let a="";const n=setTimeout(()=>{b("Exec approval timeout, denying"),o(e,"deny")},14e3);e.on("data",r=>{a+=r.toString("utf-8");const i=a.indexOf(`
2
+ `);if(i===-1)return;const c=a.slice(0,i).trim();a=a.slice(i+1),clearTimeout(n);try{const s=JSON.parse(c);if(s.token!==t.token){b("Exec approval token mismatch"),o(e,"deny");return}k(s,e,t).catch(l=>{d("Exec approval processing error",{error:l instanceof Error?l.message:String(l)}),o(e,"deny")})}catch{o(e,"deny")}}),e.on("error",()=>{clearTimeout(n)})}async function k(e,t,a){const n=e.request.command??e.request.args?.join(" ")??"unknown",r=v(n),i=w(n),c=e.request.sessionKey??"",s=e.request.agentId??"";y({action:`[exec-approval] ${n}`.slice(0,500),category:i,risk_level:r,details:JSON.stringify({command:n,host:e.request.host,agent:s,session:c,security:e.request.security}).slice(0,500),agent_name:"OpenClaw"}),a.rpcCall("tools/call",{name:"aerostack__guardian_report",arguments:{action:`OpenClaw exec: ${n}`.slice(0,500),category:i,risk_level:r,details:JSON.stringify({command:n,agent:s,session:c}).slice(0,500)}}).catch(()=>{}),r==="critical"||r==="high"?(p("Exec approval DENIED (high risk)",{command:n.slice(0,100),risk:r}),a.rpcCall("tools/call",{name:"aerostack__local_guardian",arguments:{action:n.slice(0,500),category:i,risk_level:r,details:`OpenClaw agent ${s} wants to execute: ${n}`}}).catch(()=>{}),o(t,"deny")):(b("Exec approval ALLOWED",{command:n.slice(0,100),risk:r}),o(t,"allow-once"))}function o(e,t){try{e.write(JSON.stringify({type:"decision",decision:t})+`
3
3
  `),e.end()}catch{}}export{D as startExecApprovalServer};
@@ -1,5 +1,5 @@
1
- import{createServer as A}from"node:http";import{readFile as w,writeFile as _,appendFile as L,stat as I}from"node:fs/promises";import{watch as R}from"node:fs";import{homedir as E}from"node:os";import{join as b}from"node:path";import{info as l,warn as p,debug as S}from"./logger.js";const W=18321,H=3e4,j=200,C=new Set(["Bash","Write","Edit","NotebookEdit"]),B=new Set(["Read","Glob","Grep","LS","WebSearch","WebFetch","Agent","AskUserQuestion","TodoRead","TaskList","TaskGet"]);function N(n,e){if(n==="Bash"){const t=(e.command||"").toLowerCase();return t.includes("rm ")||t.includes("rm ")||t.includes("rmdir")||t.includes("unlink")?{category:"file_delete",risk:"high"}:t.includes("git push")||t.includes("git reset")?{category:"deploy",risk:"high"}:t.includes("npm install")||t.includes("pip install")||t.includes("yarn add")?{category:"package_install",risk:"medium"}:t.includes("curl ")||t.includes("wget ")||t.includes("fetch")?{category:"api_call",risk:"low"}:t.includes("deploy")||t.includes("wrangler")?{category:"deploy",risk:"high"}:{category:"exec_command",risk:"low"}}return n==="Write"?{category:"file_write",risk:"medium"}:n==="Edit"||n==="NotebookEdit"?{category:"file_write",risk:"low"}:{category:"other",risk:"low"}}function F(n,e){if(n==="Bash")return e.command?.slice(0,200)||"";if(n==="Write"||n==="Edit")return e.file_path||"";const o=Object.keys(e);if(o.length===0)return"";const t=e[o[0]];return typeof t=="string"?t.slice(0,200):JSON.stringify(e).slice(0,200)}let k=[],d=null,T=null,h={enabled:!0,tools:["Bash","Write","Edit"],batch_interval_seconds:30,categories:["exec_command","file_write","file_delete","deploy","config_change"]},v=H;function Y(){return h}function P(n){h.enabled&&(k.length>=j&&k.shift(),k.push(n))}async function O(){if(k.length===0||!T)return;const n=k.splice(0);S(`Flushing ${n.length} hook events`);const e=await T(n);if(e){const o=JSON.stringify(h)!==JSON.stringify(e);h=e,o&&(l("Bridge config updated from gateway",{enabled:e.enabled,tools:e.tools}),e.batch_interval_seconds*1e3!==v&&(v=e.batch_interval_seconds*1e3,d&&(clearInterval(d),d=setInterval(()=>{O().catch(()=>{})},v),l("Batch interval updated",{seconds:e.batch_interval_seconds}))))}l(`Flushed ${n.length} hook events to gateway`)}let f=null,J=null;function z(n,e){if(n.method!=="POST"||!n.url?.startsWith("/hook")){e.writeHead(404),e.end();return}const o=[];n.on("data",t=>o.push(t)),n.on("end",()=>{try{const t=JSON.parse(Buffer.concat(o).toString()),s=t.tool_name??"";if(!h.tools.includes(s)&&!C.has(s)){e.writeHead(200,{"Content-Type":"application/json"}),e.end('{"status":"skipped"}');return}if(B.has(s)&&!h.tools.includes(s)){e.writeHead(200,{"Content-Type":"application/json"}),e.end('{"status":"skipped"}');return}const r=t.tool_input??{},{category:a,risk:i}=N(s,r),c=F(s,r);P({action:`${s}: ${c}`.slice(0,500),category:a,risk_level:i,details:JSON.stringify({tool:s,...r}).slice(0,500)}),S(`Hook received: ${s}`,{category:a,risk:i}),e.writeHead(200,{"Content-Type":"application/json"}),e.end('{"status":"queued"}')}catch{e.writeHead(400),e.end('{"error":"invalid JSON"}')}})}let m=null,y=0;async function U(){try{const n=await I(u).catch(()=>null);if(!n||n.size<=y)return;const e=await w(u,"utf-8"),o=e.split(`
2
- `).filter(Boolean),t=e.slice(y);y=n.size;const s=t.split(`
3
- `).filter(Boolean);for(const r of s)try{const a=JSON.parse(r),i=a.tool_name??"";if(B.has(i)||!C.has(i)&&!h.tools.includes(i))continue;const c=a.tool_input??{},{category:g,risk:$}=N(i,c),x=F(i,c);P({action:`${i}: ${x}`.slice(0,500),category:g,risk_level:$,details:JSON.stringify({tool:i,...c}).slice(0,500)}),S(`File hook event: ${i}`,{category:g})}catch{}n.size>1048576&&(await _(u,"","utf-8"),y=0)}catch{}}async function D(n,e=W){T=n;const o=await new Promise((t,s)=>{f=A(z),f.on("error",r=>{r.code==="EADDRINUSE"?(l(`Port ${e} in use, trying ${e+1}`),f.close(),D(n,e+1).then(t).catch(s)):(p("HTTP hook server failed, using file-based hooks only",{error:r.message}),t(0))}),f.listen(e,"127.0.0.1",()=>{J=e,l(`Hook server listening on http://127.0.0.1:${e}/hook`),t(e)})});try{await L(u,""),m=R(u,()=>{U().catch(()=>{})}),l("File watcher started",{path:u})}catch(t){p("File watcher failed",{error:t instanceof Error?t.message:String(t)})}return d=setInterval(()=>{U().catch(()=>{}),O().catch(t=>{p("Batch flush failed",{error:t instanceof Error?t.message:String(t)})})},H),o}function Z(){d&&(clearInterval(d),d=null),O().catch(()=>{}),m&&(m.close(),m=null),f&&(f.close(),f=null),J=null}const q="/* aerostack-guardian-hook */",u="/tmp/aerostack-guardian-events.jsonl";async function ee(n){const e=b(E(),".claude","settings.json");try{let o={};try{const i=await w(e,"utf-8");o=JSON.parse(i)}catch{}const t=o.hooks??{},r=(t.PreToolUse??[]).filter(i=>!(i.hooks??[]).some(g=>g.url?.includes("127.0.0.1")&&g.url?.includes("/hook")||g.command?.includes("aerostack-guardian"))),a={matcher:"Bash|Write|Edit",hooks:[{type:"command",command:`cat >> ${u}`}]};return r.push(a),t.PreToolUse=r,o.hooks=t,await _(e,JSON.stringify(o,null,2)+`
4
- `,"utf-8"),l("Installed Claude Code hook (file-based)",{eventsFile:u,path:e}),!0}catch(o){return p("Failed to install Claude Code hook",{error:o instanceof Error?o.message:String(o)}),!1}}async function te(){const n=b(E(),".claude","settings.json");try{const e=await w(n,"utf-8"),o=JSON.parse(e),t=o.hooks??{},s=t.PreToolUse??[],r=s.filter(a=>!(a.hooks??[]).some(c=>c.url?.includes("127.0.0.1")&&c.url?.includes("/hook")));return r.length===s.length?!1:(t.PreToolUse=r,o.hooks=t,await _(n,JSON.stringify(o,null,2)+`
5
- `,"utf-8"),l("Uninstalled Claude Code hook"),!0)}catch{return!1}}export{u as HOOK_EVENTS_FILE,P as addToBatch,N as detectCategory,Y as getBridgeConfig,ee as installClaudeHook,D as startHookServer,Z as stopHookServer,F as summarizeToolInput,te as uninstallClaudeHook};
1
+ import{createServer as $}from"node:http";import{readFile as y,writeFile as w,appendFile as j,stat as W}from"node:fs/promises";import{watch as R}from"node:fs";import{homedir as O}from"node:os";import{join as C}from"node:path";import{info as c,warn as m,debug as b}from"./logger.js";const z=18321,E=3e4,D=200,H=new Set(["Bash","Write","Edit","NotebookEdit"]),B=new Set(["Read","Glob","Grep","LS","WebSearch","WebFetch","Agent","AskUserQuestion","TodoRead","TaskList","TaskGet"]);function A(i){const t=i.toLowerCase();return t.includes("rm ")||t.includes("rm ")||t.includes("rmdir")||t.includes("unlink")?{category:"file_delete",risk:"high"}:t.includes("git push")||t.includes("git reset")?{category:"deploy",risk:"high"}:t.includes("npm install")||t.includes("pip install")||t.includes("yarn add")?{category:"package_install",risk:"medium"}:t.includes("curl ")||t.includes("wget ")||t.includes("fetch")?{category:"api_call",risk:"low"}:t.includes("deploy")||t.includes("wrangler")?{category:"deploy",risk:"high"}:{category:"exec_command",risk:"low"}}function F(i,t){const e=i.toLowerCase();if(i==="Bash")return A(t.command||"");if(i==="Write")return{category:"file_write",risk:"medium"};if(i==="Edit"||i==="NotebookEdit")return{category:"file_write",risk:"low"};if(e==="exec"||e==="run_command"||e==="shell"||e==="bash"||e==="terminal"){const r=t.command??t.cmd??(Array.isArray(t.args)?t.args.join(" "):"")??"";return A(r)}return e==="write_file"||e==="create_file"||e==="save_file"?{category:"file_write",risk:"medium"}:e==="edit_file"||e==="str_replace_based_edit_tool"||e==="replace_in_file"||e==="patch_file"||e==="apply_patch"?{category:"file_write",risk:"low"}:e==="delete_file"||e==="remove_file"||e==="unlink_file"?{category:"file_delete",risk:"high"}:e==="read_file"||e==="view_file"||e==="cat_file"||e==="search_files"||e==="find_files"||e==="list_dir"?{category:"data_access",risk:"low"}:e==="web_search"||e==="search_web"||e==="browser_search"?{category:"api_call",risk:"low"}:e==="web_fetch"||e==="http_request"||e==="fetch_url"||e==="browser_navigate"?{category:"api_call",risk:"low"}:e.includes("install")||e==="pip_install"||e==="npm_install"||e==="package_install"?{category:"package_install",risk:"medium"}:e==="git_push"||e==="git_reset"||e==="deploy"||e==="publish"||e==="release"?{category:"deploy",risk:"high"}:e.startsWith("git_")||e==="git"?{category:"exec_command",risk:"low"}:e==="send_message"||e==="notify"||e.includes("telegram")||e.includes("slack")||e.includes("email")||e.includes("sms")?{category:"message_send",risk:"low"}:{category:"other",risk:"low"}}function N(i,t){const e=i.toLowerCase();if(i==="Bash"||e==="exec"||e==="run_command"||e==="shell"||e==="bash"||e==="terminal")return((t.command??t.cmd??(Array.isArray(t.args)?t.args.join(" "):""))||"").slice(0,200);if(e==="write"||e==="edit"||i==="Write"||i==="Edit"||e==="write_file"||e==="edit_file"||e==="create_file"||e==="read_file"||e==="delete_file"||e==="view_file")return t.file_path??t.path??t.filepath??"";if(e==="web_search"||e==="search_web")return t.query?.slice(0,200)||"";if(e==="web_fetch"||e==="fetch_url"||e==="browser_navigate")return t.url?.slice(0,200)||"";const r=Object.keys(t);if(r.length===0)return"";const n=t[r[0]];return typeof n=="string"?n.slice(0,200):JSON.stringify(t).slice(0,200)}let _=[],u=null,v=null,f={enabled:!0,tools:["Bash","Write","Edit"],batch_interval_seconds:30,categories:["exec_command","file_write","file_delete","deploy","config_change"]},S=E;function Z(){return f}function x(i){f.enabled&&(_.length>=D&&_.shift(),_.push(i))}async function T(){if(_.length===0||!v)return;const i=_.splice(0);b(`Flushing ${i.length} hook events`);const t=await v(i);if(t){const e=JSON.stringify(f)!==JSON.stringify(t);f=t,e&&(c("Bridge config updated from gateway",{enabled:t.enabled,tools:t.tools}),t.batch_interval_seconds*1e3!==S&&(S=t.batch_interval_seconds*1e3,u&&(clearInterval(u),u=setInterval(()=>{T().catch(()=>{})},S),c("Batch interval updated",{seconds:t.batch_interval_seconds}))))}c(`Flushed ${i.length} hook events to gateway`)}let h=null,P=null;function M(i,t){if(i.method!=="POST"||!i.url?.startsWith("/hook")){t.writeHead(404),t.end();return}const e=[];i.on("data",r=>e.push(r)),i.on("end",()=>{try{const r=JSON.parse(Buffer.concat(e).toString()),n=r.tool_name??"";if(!f.tools.includes(n)&&!H.has(n)){t.writeHead(200,{"Content-Type":"application/json"}),t.end('{"status":"skipped"}');return}if(B.has(n)&&!f.tools.includes(n)){t.writeHead(200,{"Content-Type":"application/json"}),t.end('{"status":"skipped"}');return}const o=r.tool_input??{},{category:a,risk:s}=F(n,o),l=N(n,o);x({action:`${n}: ${l}`.slice(0,500),category:a,risk_level:s,details:JSON.stringify({tool:n,...o}).slice(0,500)}),b(`Hook received: ${n}`,{category:a,risk:s}),t.writeHead(200,{"Content-Type":"application/json"}),t.end('{"status":"queued"}')}catch{t.writeHead(400),t.end('{"error":"invalid JSON"}')}})}let k=null,p=0;async function J(){try{const i=await W(d).catch(()=>null);if(!i||i.size<=p)return;const t=await y(d,"utf-8"),e=t.split(`
2
+ `).filter(Boolean),r=t.slice(p);p=i.size;const n=r.split(`
3
+ `).filter(Boolean);for(const o of n)try{const a=JSON.parse(o),s=a.tool_name??"";if(B.has(s)||!H.has(s)&&!f.tools.includes(s))continue;const l=a.tool_input??{},{category:g,risk:L}=F(s,l),U=N(s,l);x({action:`${s}: ${U}`.slice(0,500),category:g,risk_level:L,details:JSON.stringify({tool:s,...l}).slice(0,500)}),b(`File hook event: ${s}`,{category:g})}catch{}i.size>1048576&&(await w(d,"","utf-8"),p=0)}catch{}}async function G(i,t=z){v=i;const e=await new Promise((r,n)=>{h=$(M),h.on("error",o=>{o.code==="EADDRINUSE"?(c(`Port ${t} in use, trying ${t+1}`),h.close(),G(i,t+1).then(r).catch(n)):(m("HTTP hook server failed, using file-based hooks only",{error:o.message}),r(0))}),h.listen(t,"127.0.0.1",()=>{P=t,c(`Hook server listening on http://127.0.0.1:${t}/hook`),r(t)})});try{await j(d,""),k=R(d,()=>{J().catch(()=>{})}),c("File watcher started",{path:d})}catch(r){m("File watcher failed",{error:r instanceof Error?r.message:String(r)})}return u=setInterval(()=>{J().catch(()=>{}),T().catch(r=>{m("Batch flush failed",{error:r instanceof Error?r.message:String(r)})})},E),e}function I(){u&&(clearInterval(u),u=null),T().catch(()=>{}),k&&(k.close(),k=null),h&&(h.close(),h=null),P=null}const ee="/* aerostack-guardian-hook */",d="/tmp/aerostack-guardian-events.jsonl";async function te(i){const t=C(O(),".claude","settings.json");try{let e={};try{const s=await y(t,"utf-8");e=JSON.parse(s)}catch{}const r=e.hooks??{},o=(r.PreToolUse??[]).filter(s=>!(s.hooks??[]).some(g=>g.url?.includes("127.0.0.1")&&g.url?.includes("/hook")||g.command?.includes("aerostack-guardian"))),a={matcher:"Bash|Write|Edit",hooks:[{type:"command",command:`cat >> ${d}`}]};return o.push(a),r.PreToolUse=o,e.hooks=r,await w(t,JSON.stringify(e,null,2)+`
4
+ `,"utf-8"),c("Installed Claude Code hook (file-based)",{eventsFile:d,path:t}),!0}catch(e){return m("Failed to install Claude Code hook",{error:e instanceof Error?e.message:String(e)}),!1}}async function ie(){const i=C(O(),".claude","settings.json");try{const t=await y(i,"utf-8"),e=JSON.parse(t),r=e.hooks??{},n=r.PreToolUse??[],o=n.filter(a=>!(a.hooks??[]).some(l=>l.url?.includes("127.0.0.1")&&l.url?.includes("/hook")));return o.length===n.length?!1:(r.PreToolUse=o,e.hooks=r,await w(i,JSON.stringify(e,null,2)+`
5
+ `,"utf-8"),c("Uninstalled Claude Code hook"),!0)}catch{return!1}}export{d as HOOK_EVENTS_FILE,x as addToBatch,F as detectCategory,Z as getBridgeConfig,te as installClaudeHook,G as startHookServer,I as stopHookServer,N as summarizeToolInput,ie as uninstallClaudeHook};
package/dist/index.js CHANGED
@@ -4,7 +4,7 @@ import{Server as x}from"@modelcontextprotocol/sdk/server/index.js";import{StdioS
4
4
  `),process.exit(1)),g||(process.stderr.write(`ERROR: AEROSTACK_TOKEN is required
5
5
  `),process.exit(1));let w;try{if(w=new URL(T),w.protocol!=="https:"&&w.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)}w.protocol==="http:"&&!w.hostname.match(/^(localhost|127\.0\.0\.1)$/)&&process.stderr.write(`WARNING: Using HTTP (not HTTPS) \u2014 token will be sent in plaintext
7
- `);const u=T.replace(/\/+$/,""),S=crypto.randomUUID(),ne=process.env.AEROSTACK_AGENT_TYPE||"unknown",l=new W;let A=null,L=null;async function p(t,r){const s={jsonrpc:"2.0",id:Date.now(),method:t,params:r??{}},e=new AbortController,n=setTimeout(()=>e.abort(),Z);try{const o=await fetch(u,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${g}`,"User-Agent":"aerostack-gateway/0.15.0","X-Agent-Id":"aerostack-gateway","X-Bridge-Id":S,"X-Agent-Type":ne},body:JSON.stringify(s),signal:e.signal});if(clearTimeout(n),(o.headers.get("content-type")??"").includes("text/event-stream")){const i=await o.text();return ae(i,s.id)}return await o.json()}catch(o){clearTimeout(n);const a=o instanceof Error?o.message:"Unknown error";return o instanceof Error&&o.name==="AbortError"?{jsonrpc:"2.0",id:s.id,error:{code:-32603,message:"Request timed out"}}:{jsonrpc:"2.0",id:s.id,error:{code:-32603,message:`HTTP error: ${a}`}}}}function ae(t,r){const s=t.split(`
7
+ `);const u=T.replace(/\/+$/,""),S=crypto.randomUUID(),ne=process.env.AEROSTACK_AGENT_TYPE||"unknown",l=new W;let A=null,L=null;async function p(t,r){const s={jsonrpc:"2.0",id:Date.now(),method:t,params:r??{}},e=new AbortController,n=setTimeout(()=>e.abort(),Z);try{const o=await fetch(u,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${g}`,"User-Agent":"aerostack-gateway/0.15.5","X-Agent-Id":"aerostack-gateway","X-Bridge-Id":S,"X-Agent-Type":ne},body:JSON.stringify(s),signal:e.signal});if(clearTimeout(n),(o.headers.get("content-type")??"").includes("text/event-stream")){const i=await o.text();return ae(i,s.id)}return await o.json()}catch(o){clearTimeout(n);const a=o instanceof Error?o.message:"Unknown error";return o instanceof Error&&o.name==="AbortError"?{jsonrpc:"2.0",id:s.id,error:{code:-32603,message:"Request timed out"}}:{jsonrpc:"2.0",id:s.id,error:{code:-32603,message:`HTTP error: ${a}`}}}}function ae(t,r){const s=t.split(`
8
8
  `);let e=null;for(const n of s)if(n.startsWith("data: "))try{e=JSON.parse(n.slice(6))}catch{}return e??{jsonrpc:"2.0",id:r,error:{code:-32603,message:"Empty SSE response"}}}const ie=new Set(["aerostack__guardian_report","aerostack__check_approval","aerostack__guardian_check"]);function ce(t,r){if(ie.has(t))return;let s="other";const e=t.toLowerCase();e.includes("exec")||e.includes("bash")||e.includes("shell")||e.includes("command")||e.includes("run")?s="exec_command":e.includes("write")||e.includes("edit")||e.includes("create")||e.includes("patch")?s="file_write":e.includes("delete")||e.includes("remove")||e.includes("trash")||e.includes("unlink")?s="file_delete":e.includes("fetch")||e.includes("http")||e.includes("request")||e.includes("api")||e.includes("get")||e.includes("post")?s="api_call":e.includes("install")||e.includes("package")||e.includes("npm")||e.includes("pip")?s="package_install":e.includes("config")||e.includes("setting")||e.includes("env")?s="config_change":e.includes("deploy")||e.includes("publish")||e.includes("release")?s="deploy":e.includes("send")||e.includes("message")||e.includes("email")||e.includes("notify")||e.includes("slack")||e.includes("telegram")?s="message_send":(e.includes("read")||e.includes("query")||e.includes("search")||e.includes("list")||e.includes("get"))&&(s="data_access");let n;try{const o=JSON.stringify(r);n=o.length>500?o.slice(0,500)+"...":o}catch{n="(unable to serialize)"}p("tools/call",{name:"aerostack__guardian_report",arguments:{action:`${t}(${Object.keys(r).join(", ")})`,category:s,risk_level:"low",details:n}}).catch(()=>{})}function I(t,r){return fetch(`${u}/approval-delivery-status`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${g}`,"X-Bridge-Id":S},body:JSON.stringify({approvals:t.map(s=>({id:s,delivery_status:r,delivery_channel:"bridge_check_approval"}))})}).then(()=>{}).catch(()=>{})}const le=/^[a-zA-Z0-9_][a-zA-Z0-9_-]{0,63}$/;function U(t){return t&&le.test(t)?t:"aerostack__check_approval"}async function pe(t,r){if(ce(t,r),h==="async"&&t==="aerostack__check_approval"){const o=r.approval_id;if(o){const a=l.get(o);if(a&&a.status!=="pending")return l.markDelivered(o),I([o],"delivered"),{jsonrpc:"2.0",id:Date.now(),result:{content:[{type:"text",text:JSON.stringify({approval_id:o,status:a.status,reviewer_note:a.reviewerNote??null})}]}}}}const s=await p("tools/call",{name:t,arguments:r});if(s.error?.code===-32050){const o=s.error.data,a=o?.approval_id;if(!a||!/^[a-zA-Z0-9_-]{4,128}$/.test(a))return{jsonrpc:"2.0",id:s.id,error:{code:-32603,message:"Approval required but no approval_id returned"}};if(h==="async"){c("Tool gate (async): returning pending to LLM",{approvalId:a}),l.set(a,{approvalId:a,toolName:t,toolArgs:r,status:"pending",createdAt:Date.now()});const E=o?.polling_url??`${u}/approval-status/${a}`,O=k({approvalId:a,wsUrl:o?.ws_url,pollUrl:E,pollIntervalMs:m},l);l.setCancelHandle(a,O.cancel);const R=U(o?.check_tool);return{jsonrpc:"2.0",id:s.id,result:{content:[{type:"text",text:`APPROVAL REQUIRED \u2014 This action needs human approval.
9
9
  Approval ID: ${a}
10
10
  Status: pending
@@ -18,4 +18,4 @@ Approval ID: ${o}
18
18
  Status: pending
19
19
 
20
20
  Call ${R}({ "approval_id": "${o}" }) to check status.
21
- You MUST NOT proceed with this action until approved.`}]}}}c("Permission gate: waiting for approval",{approvalId:o,transport:n.ws_url?"ws":"poll"});const a=n.polling_url??`${u}/approval-status/${o}`,d=await y({approvalId:o,wsUrl:n.ws_url,pollUrl:a,pollIntervalMs:m,timeoutMs:P});let i;return d.status==="approved"||d.status==="executed"?i="APPROVED \u2014 Your request has been approved. You may proceed with the action.":d.status==="rejected"?i=`REJECTED \u2014 Your request was denied. Reason: ${d.reviewer_note??"No reason given."}. Do NOT proceed.`:d.status==="changes_requested"?i=`CHANGES REQUESTED \u2014 ${d.reviewer_note??"No details given."}. Revise and resubmit your request.`:i="EXPIRED \u2014 Your approval request timed out. Submit a new request if needed.",{jsonrpc:"2.0",id:s.id,result:{content:[{type:"text",text:i}]}}}return s}let $=null;async function f(){if($)return;const t=await p("initialize",{protocolVersion:"2024-11-05",capabilities:{},clientInfo:{name:"aerostack-gateway",version:"0.15.0"}});if(t.result){const r=t.result;$={protocolVersion:r.protocolVersion??"2024-11-05",instructions:r.instructions}}}const _=new x({name:"aerostack-gateway",version:"0.15.0"},{capabilities:{tools:{},resources:{},prompts:{}}});_.setRequestHandler(j,async()=>{await f();const t=await p("tools/list");if(t.error)throw new Error(t.error.message);return{tools:t.result.tools??[]}}),_.setRequestHandler(q,async t=>{await f();const{name:r,arguments:s}=t.params,e=await pe(r,s??{});return e.error?{content:[{type:"text",text:`Error: ${e.error.message}`}],isError:!0}:{content:e.result.content??[{type:"text",text:JSON.stringify(e.result)}]}}),_.setRequestHandler(D,async()=>{await f();const t=await p("resources/list");if(t.error)throw new Error(t.error.message);return{resources:t.result.resources??[]}}),_.setRequestHandler(H,async t=>{await f();const r=await p("resources/read",{uri:t.params.uri});if(r.error)throw new Error(r.error.message);return{contents:r.result.contents??[]}}),_.setRequestHandler(M,async()=>{await f();const t=await p("prompts/list");if(t.error)throw new Error(t.error.message);return{prompts:t.result.prompts??[]}}),_.setRequestHandler(V,async t=>{await f();const r=await p("prompts/get",{name:t.params.name,arguments:t.params.arguments});if(r.error)throw new Error(r.error.message);return{messages:r.result.messages??[]}});async function ue(){try{const t=await fetch(`${u}/undelivered-approvals`,{headers:{Authorization:`Bearer ${g}`,"X-Bridge-Id":S}});if(!t.ok)return;const r=await t.json();if(!r.approvals?.length)return;for(const s of r.approvals)l.set(s.id,{approvalId:s.id,toolName:s.tool_name,toolArgs:{},status:s.status,reviewerNote:s.reviewer_note??void 0,resolvedAt:s.resolved_at??void 0,createdAt:s.resolved_at??Date.now()});c(`Loaded ${r.approvals.length} undelivered approvals from server`)}catch{}}async function de(){c("Connecting to workspace",{url:u});const t=new b;if(await _.connect(t),c("Ready",{url:u}),h==="async"&&ue().catch(()=>{}),ee)try{const s=await B(async e=>{try{const n=await fetch(`${u}/guardian-batch`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${g}`,"User-Agent":"aerostack-gateway/0.15.0","X-Agent-Id":"aerostack-gateway"},body:JSON.stringify({events:e})});return n.ok?(await n.json()).config?.hook_tracking??null:null}catch{return null}},te);re&&await z(s)&&c("Claude Code hook auto-installed",{port:s})}catch(r){C("Hook server failed to start (non-fatal)",{error:r instanceof Error?r.message:String(r)})}if(se)try{const r=oe??await Y();if(r)if(A=new J({port:N,token:r,rpcCall:p}),await A.connect()){c("OpenClaw connector started",{port:N});const e=await X();if(e){const{join:n}=await import("node:path"),{homedir:o}=await import("node:os");L=F({socketPath:n(o(),".openclaw","exec-approvals.sock"),token:e,rpcCall:p})}}else c("OpenClaw gateway not reachable, skipping connector"),A=null;else c("OpenClaw integration skipped (no token found)")}catch(r){C("OpenClaw connector failed (non-fatal)",{error:r instanceof Error?r.message:String(r)})}}async function K(){const t=l.getUndeliveredIds();t.length>0&&await I(t,"agent_disconnected"),A?.stop(),L?.stop(),l.destroy(),G(),process.exit(0)}process.on("SIGTERM",()=>{K()}),process.on("SIGINT",()=>{K()}),de().catch(t=>{Q("Fatal error",{error:t instanceof Error?t.message:String(t)}),process.exit(1)});
21
+ You MUST NOT proceed with this action until approved.`}]}}}c("Permission gate: waiting for approval",{approvalId:o,transport:n.ws_url?"ws":"poll"});const a=n.polling_url??`${u}/approval-status/${o}`,d=await y({approvalId:o,wsUrl:n.ws_url,pollUrl:a,pollIntervalMs:m,timeoutMs:P});let i;return d.status==="approved"||d.status==="executed"?i="APPROVED \u2014 Your request has been approved. You may proceed with the action.":d.status==="rejected"?i=`REJECTED \u2014 Your request was denied. Reason: ${d.reviewer_note??"No reason given."}. Do NOT proceed.`:d.status==="changes_requested"?i=`CHANGES REQUESTED \u2014 ${d.reviewer_note??"No details given."}. Revise and resubmit your request.`:i="EXPIRED \u2014 Your approval request timed out. Submit a new request if needed.",{jsonrpc:"2.0",id:s.id,result:{content:[{type:"text",text:i}]}}}return s}let $=null;async function f(){if($)return;const t=await p("initialize",{protocolVersion:"2024-11-05",capabilities:{},clientInfo:{name:"aerostack-gateway",version:"0.15.5"}});if(t.result){const r=t.result;$={protocolVersion:r.protocolVersion??"2024-11-05",instructions:r.instructions}}}const _=new x({name:"aerostack-gateway",version:"0.15.5"},{capabilities:{tools:{},resources:{},prompts:{}}});_.setRequestHandler(j,async()=>{await f();const t=await p("tools/list");if(t.error)throw new Error(t.error.message);return{tools:t.result.tools??[]}}),_.setRequestHandler(q,async t=>{await f();const{name:r,arguments:s}=t.params,e=await pe(r,s??{});return e.error?{content:[{type:"text",text:`Error: ${e.error.message}`}],isError:!0}:{content:e.result.content??[{type:"text",text:JSON.stringify(e.result)}]}}),_.setRequestHandler(D,async()=>{await f();const t=await p("resources/list");if(t.error)throw new Error(t.error.message);return{resources:t.result.resources??[]}}),_.setRequestHandler(H,async t=>{await f();const r=await p("resources/read",{uri:t.params.uri});if(r.error)throw new Error(r.error.message);return{contents:r.result.contents??[]}}),_.setRequestHandler(M,async()=>{await f();const t=await p("prompts/list");if(t.error)throw new Error(t.error.message);return{prompts:t.result.prompts??[]}}),_.setRequestHandler(V,async t=>{await f();const r=await p("prompts/get",{name:t.params.name,arguments:t.params.arguments});if(r.error)throw new Error(r.error.message);return{messages:r.result.messages??[]}});async function ue(){try{const t=await fetch(`${u}/undelivered-approvals`,{headers:{Authorization:`Bearer ${g}`,"X-Bridge-Id":S}});if(!t.ok)return;const r=await t.json();if(!r.approvals?.length)return;for(const s of r.approvals)l.set(s.id,{approvalId:s.id,toolName:s.tool_name,toolArgs:{},status:s.status,reviewerNote:s.reviewer_note??void 0,resolvedAt:s.resolved_at??void 0,createdAt:s.resolved_at??Date.now()});c(`Loaded ${r.approvals.length} undelivered approvals from server`)}catch{}}async function de(){c("Connecting to workspace",{url:u});const t=new b;if(await _.connect(t),c("Ready",{url:u}),h==="async"&&ue().catch(()=>{}),ee)try{const s=await B(async e=>{try{const n=await fetch(`${u}/guardian-batch`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${g}`,"User-Agent":"aerostack-gateway/0.15.5","X-Agent-Id":"aerostack-gateway"},body:JSON.stringify({events:e})});return n.ok?(await n.json()).config?.hook_tracking??null:null}catch{return null}},te);re&&await z(s)&&c("Claude Code hook auto-installed",{port:s})}catch(r){C("Hook server failed to start (non-fatal)",{error:r instanceof Error?r.message:String(r)})}if(se)try{const r=oe??await Y();if(r)if(A=new J({port:N,token:r,rpcCall:p}),await A.connect()){c("OpenClaw connector started",{port:N});const e=await X();if(e){const{join:n}=await import("node:path"),{homedir:o}=await import("node:os");L=F({socketPath:n(o(),".openclaw","exec-approvals.sock"),token:e,rpcCall:p})}}else c("OpenClaw gateway not reachable, skipping connector"),A=null;else c("OpenClaw integration skipped (no token found)")}catch(r){C("OpenClaw connector failed (non-fatal)",{error:r instanceof Error?r.message:String(r)})}}async function K(){const t=l.getUndeliveredIds();t.length>0&&await I(t,"agent_disconnected"),A?.stop(),L?.stop(),l.destroy(),G(),process.exit(0)}process.on("SIGTERM",()=>{K()}),process.on("SIGINT",()=>{K()}),de().catch(t=>{Q("Fatal error",{error:t instanceof Error?t.message:String(t)}),process.exit(1)});
@@ -1 +1 @@
1
- import{readFile as p}from"node:fs/promises";import{join as m}from"node:path";import{homedir as g}from"node:os";import{info as u,warn as a,debug as c}from"./logger.js";import{addToBatch as f,detectCategory as w,summarizeToolInput as y}from"./hook-server.js";async function R(){try{const d=m(g(),".openclaw","openclaw.json"),e=await p(d,"utf-8");return JSON.parse(e)?.gateway?.auth?.token??null}catch{return null}}async function M(){try{const d=m(g(),".openclaw","exec-approvals.json"),e=await p(d,"utf-8");return JSON.parse(e)?.socket?.token??null}catch{return null}}const b=1e3,T=3e4;class N{opts;ws=null;destroyed=!1;reconnectMs=b;reconnectTimer=null;requestId=0;seenSessions=new Set;pendingRequests=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(()=>{c("OpenClaw connect timeout");try{n.close()}catch{}t(!1)},1e4);n.onopen=()=>{clearTimeout(o),c("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)}})}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()}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.0",platform:process.platform,mode:"cli"},auth:{token:this.opts.token},scopes:["operator.read"],caps:["tool-events"]}})}connected=!1;subscribedSessions=new Set;handleFrame(e,s){if(e.type==="res"&&!this.connected){e.ok?(this.connected=!0,this.reconnectMs=b,u("OpenClaw connector connected",{port:this.opts.port}),s?.(!0),this.subscribeAllSessions().catch(t=>a("subscribeAllSessions failed",{error:t?.message}))):(a("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){a("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?c("OpenClaw subscribed to session messages",{key:e}):a("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)"):a("OpenClaw sessions.subscribe failed",{error:e.error?.message});const s=await this.sendRequest("sessions.list",{limit:50});if(!s.ok){a("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)}handleAgentEvent(e){const s=e.stream,t=e.data,n=e.sessionKey??"";if(n&&this.seenSessions.add(n),s==="tool"){const o=e.toolName??t?.toolName??t?.name??"unknown",i=t?.phase??"",r=e.args??t?.input??{};if(i==="start"){const{category:l,risk:h}=w(o,r),S=y(o,r);f({action:`${o}: ${S}`.slice(0,500),category:l,risk_level:h,details:JSON.stringify({tool:o,session:n,...r}).slice(0,500)}),c("OpenClaw agent tool event",{tool:o,category:l,risk:h})}}if(s==="lifecycle"&&t){const o=t.phase??"";(o==="start"||o==="end")&&c("OpenClaw agent lifecycle",{phase:o,session:n})}}handleToolEvent(e){const s=e.data,t=e.toolName??s?.toolName??s?.name??"unknown",n=s?.phase??"",o=e.args??s?.input??{},i=e.sessionKey??"";if(n!=="start"&&n!=="end"||n==="end")return;const{category:r,risk:l}=w(t,o),h=y(t,o);f({action:`${t}: ${h}`.slice(0,500),category:r,risk_level:l,details:JSON.stringify({tool:t,session:i,...o}).slice(0,500)}),c("OpenClaw tool event",{tool:t,category:r,risk:l,session:i}),i&&this.seenSessions.add(i)}handleMessageEvent(e){const s=e.sessionKey??"";s&&this.seenSessions.add(s)}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||(c("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,T))}async getWebSocket(){return typeof globalThis.WebSocket<"u"?globalThis.WebSocket:(await import("ws")).default}}export{N as OpenClawConnector,M as resolveExecApprovalToken,R as resolveOpenClawToken};
1
+ import{readFile as m}from"node:fs/promises";import{join as g}from"node:path";import{homedir as b}from"node:os";import{info as p,warn as a,debug as c}from"./logger.js";import{addToBatch as u,detectCategory as f,summarizeToolInput as w}from"./hook-server.js";async function _(){try{const d=g(b(),".openclaw","openclaw.json"),e=await m(d,"utf-8");return JSON.parse(e)?.gateway?.auth?.token??null}catch{return null}}async function N(){try{const d=g(b(),".openclaw","exec-approvals.json"),e=await m(d,"utf-8");return JSON.parse(e)?.socket?.token??null}catch{return null}}const y=1e3,C=3e4;class R{opts;ws=null;destroyed=!1;reconnectMs=y;reconnectTimer=null;requestId=0;seenSessions=new Set;pendingRequests=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(()=>{c("OpenClaw connect timeout");try{n.close()}catch{}t(!1)},1e4);n.onopen=()=>{clearTimeout(o),c("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)}})}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()}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.5",platform:process.platform,mode:"cli"},auth:{token:this.opts.token},scopes:["operator.read"],caps:["tool-events"]}})}connected=!1;subscribedSessions=new Set;handleFrame(e,s){if(e.type==="res"&&!this.connected){e.ok?(this.connected=!0,this.reconnectMs=y,p("OpenClaw connector connected",{port:this.opts.port}),s?.(!0),this.subscribeAllSessions().catch(t=>a("subscribeAllSessions failed",{error:t?.message}))):(a("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){a("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?c("OpenClaw subscribed to session messages",{key:e}):a("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?p("OpenClaw subscribed to all session events (tool + lifecycle)"):a("OpenClaw sessions.subscribe failed",{error:e.error?.message});const s=await this.sendRequest("sessions.list",{limit:50});if(!s.ok){a("OpenClaw sessions.list failed",{error:s.error?.message});return}const t=s.payload?.sessions,n=Array.isArray(t)?t:[];p("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:l,risk:h}=f(o,r),S=w(o,r);u({action:`${o}: ${S}`.slice(0,500),category:l,risk_level:h,details:JSON.stringify({tool:o,session:n,...r}).slice(0,500),agent_name:"OpenClaw"}),c("OpenClaw agent tool event",{tool:o,category:l,risk:h})}}if(s==="lifecycle"&&t){const o=t.phase??"";(o==="start"||o==="end")&&c("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"||n==="end")return;const{category:r,risk:l}=f(t,o),h=w(t,o);u({action:`${t}: ${h}`.slice(0,500),category:r,risk_level:l,details:JSON.stringify({tool:t,session:i,...o}).slice(0,500),agent_name:"OpenClaw"}),c("OpenClaw tool event",{tool:t,category:r,risk:l,session:i}),i&&this.seenSessions.add(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"),u({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(()=>{}),p("OpenClaw exec approval event",{command:s.slice(0,100),risk:i})}handleMessageEvent(e){const s=e.sessionKey??"";s&&this.seenSessions.add(s)}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||(c("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,C))}async getWebSocket(){return typeof globalThis.WebSocket<"u"?globalThis.WebSocket:(await import("ws")).default}}export{R as OpenClawConnector,N as resolveExecApprovalToken,_ as resolveOpenClawToken};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aerostack/gateway",
3
- "version": "0.15.3",
3
+ "version": "0.15.5",
4
4
  "description": "stdio-to-HTTP bridge connecting any MCP client to Aerostack Workspaces",
5
5
  "author": "Aerostack",
6
6
  "license": "MIT",