@aerostack/gateway 0.15.9 → 0.15.11

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.
package/dist/index.js CHANGED
@@ -1,22 +1,22 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- import{Server as S}from"@modelcontextprotocol/sdk/server/index.js";import{StdioServerTransport as k}from"@modelcontextprotocol/sdk/server/stdio.js";import{ListToolsRequestSchema as P,CallToolRequestSchema as C,ListResourcesRequestSchema as I,ReadResourceRequestSchema as N,ListPromptsRequestSchema as b,GetPromptRequestSchema as L}from"@modelcontextprotocol/sdk/types.js";import{resolveApproval as U}from"./resolution.js";import{startHookServer as K,installClaudeHook as $,stopHookServer as D}from"./hook-server.js";import{OpenClawConnector as x,resolveOpenClawToken as j}from"./openclaw-connector.js";import{info as i,warn as m,error as q}from"./logger.js";const R=process.env.AEROSTACK_WORKSPACE_URL,w=process.env.AEROSTACK_TOKEN;function h(t,s,r){const e=parseInt(t??String(s),10);return Number.isFinite(e)&&e>=r?e:s}const H=h(process.env.AEROSTACK_APPROVAL_POLL_MS,3e3,500),V=h(process.env.AEROSTACK_APPROVAL_TIMEOUT_MS,864e5,5e3),M=h(process.env.AEROSTACK_REQUEST_TIMEOUT_MS,3e4,1e3),G=process.env.AEROSTACK_HOOK_SERVER!=="false",W=h(process.env.AEROSTACK_HOOK_PORT,18321,1024),Y=process.env.AEROSTACK_HOOK_AUTO_INSTALL!=="false",B=process.env.AEROSTACK_OPENCLAW_ENABLED!=="false",_=h(process.env.AEROSTACK_OPENCLAW_PORT,18789,1024),J=process.env.AEROSTACK_OPENCLAW_TOKEN;R||(process.stderr.write(`ERROR: AEROSTACK_WORKSPACE_URL is required
3
+ import{Server as S}from"@modelcontextprotocol/sdk/server/index.js";import{StdioServerTransport as k}from"@modelcontextprotocol/sdk/server/stdio.js";import{ListToolsRequestSchema as P,CallToolRequestSchema as C,ListResourcesRequestSchema as I,ReadResourceRequestSchema as N,ListPromptsRequestSchema as b,GetPromptRequestSchema as L}from"@modelcontextprotocol/sdk/types.js";import{resolveApproval as U}from"./resolution.js";import{startHookServer as $,installClaudeHook as K,stopHookServer as D}from"./hook-server.js";import{OpenClawConnector as x,resolveOpenClawToken as j}from"./openclaw-connector.js";import{info as i,warn as m,error as q}from"./logger.js";const R=process.env.AEROSTACK_WORKSPACE_URL,w=process.env.AEROSTACK_TOKEN;function h(t,s,r){const e=parseInt(t??String(s),10);return Number.isFinite(e)&&e>=r?e:s}const H=h(process.env.AEROSTACK_APPROVAL_POLL_MS,3e3,500),V=h(process.env.AEROSTACK_APPROVAL_TIMEOUT_MS,864e5,5e3),M=h(process.env.AEROSTACK_REQUEST_TIMEOUT_MS,3e4,1e3),G=process.env.AEROSTACK_HOOK_SERVER!=="false",W=h(process.env.AEROSTACK_HOOK_PORT,18321,1024),Y=process.env.AEROSTACK_HOOK_AUTO_INSTALL!=="false",B=process.env.AEROSTACK_OPENCLAW_ENABLED!=="false",_=h(process.env.AEROSTACK_OPENCLAW_PORT,18789,1024),J=process.env.AEROSTACK_OPENCLAW_TOKEN;R||(process.stderr.write(`ERROR: AEROSTACK_WORKSPACE_URL is required
4
4
  `),process.exit(1)),w||(process.stderr.write(`ERROR: AEROSTACK_TOKEN is required
5
5
  `),process.exit(1));let E;try{if(E=new URL(R),E.protocol!=="https:"&&E.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)}E.protocol==="http:"&&!E.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=R.replace(/\/+$/,""),z=crypto.randomUUID(),F=process.env.AEROSTACK_AGENT_TYPE||"unknown";let l=null;async function c(t,s){const r={jsonrpc:"2.0",id:Date.now(),method:t,params:s??{}},e=new AbortController,n=setTimeout(()=>e.abort(),M);try{const o=await fetch(p,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${w}`,"User-Agent":"aerostack-gateway/0.15.8","X-Agent-Id":"aerostack-gateway","X-Bridge-Id":z,"X-Agent-Type":F},body:JSON.stringify(r),signal:e.signal});if(clearTimeout(n),(o.headers.get("content-type")??"").includes("text/event-stream")){const v=await o.text();return Q(v,r.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:r.id,error:{code:-32603,message:"Request timed out"}}:{jsonrpc:"2.0",id:r.id,error:{code:-32603,message:`HTTP error: ${a}`}}}}function Q(t,s){const r=t.split(`
8
- `);let e=null;for(const n of r)if(n.startsWith("data: "))try{e=JSON.parse(n.slice(6))}catch{}return e??{jsonrpc:"2.0",id:s,error:{code:-32603,message:"Empty SSE response"}}}const X=new Set(["aerostack__guardian_report","aerostack__check_approval","aerostack__guardian_check"]);function Z(t,s){if(X.has(t))return;let r="other";const e=t.toLowerCase();e.includes("exec")||e.includes("bash")||e.includes("shell")||e.includes("command")||e.includes("run")?r="exec_command":e.includes("write")||e.includes("edit")||e.includes("create")||e.includes("patch")?r="file_write":e.includes("delete")||e.includes("remove")||e.includes("trash")||e.includes("unlink")?r="file_delete":e.includes("fetch")||e.includes("http")||e.includes("request")||e.includes("api")||e.includes("get")||e.includes("post")?r="api_call":e.includes("install")||e.includes("package")||e.includes("npm")||e.includes("pip")?r="package_install":e.includes("config")||e.includes("setting")||e.includes("env")?r="config_change":e.includes("deploy")||e.includes("publish")||e.includes("release")?r="deploy":e.includes("send")||e.includes("message")||e.includes("email")||e.includes("notify")||e.includes("slack")||e.includes("telegram")?r="message_send":(e.includes("read")||e.includes("query")||e.includes("search")||e.includes("list")||e.includes("get"))&&(r="data_access");let n;try{const o=JSON.stringify(s);n=o.length>500?o.slice(0,500)+"...":o}catch{n="(unable to serialize)"}c("tools/call",{name:"aerostack__guardian_report",arguments:{action:`${t}(${Object.keys(s).join(", ")})`,category:r,risk_level:"low",details:n}}).catch(()=>{})}const ee=new Set(["aerostack__check_approval"]);async function te(t,s){Z(t,s);const r=await c("tools/call",{name:t,arguments:s});if(r.error?.code===-32050){const o=r.error.data,a=o?.approval_id;if(!a||!/^[a-zA-Z0-9_-]{4,128}$/.test(a))return{jsonrpc:"2.0",id:r.id,error:{code:-32603,message:"Approval required but no approval_id returned"}};const A=l?.getLastActiveSession()??null;return i("Tool gate: returning pending, background resolver started",{tool:t,approvalId:a,sessionKey:A}),y({approvalId:a,toolName:t,toolArgs:s,sessionKey:A,gate:"tool_gate",wsUrl:o?.ws_url,pollUrl:o?.polling_url??`${p}/approval-status/${a}`,authToken:w}),{jsonrpc:"2.0",id:r.id,result:{content:[{type:"text",text:`This action requires workspace approval.
7
+ `);const p=R.replace(/\/+$/,""),z=crypto.randomUUID(),F=process.env.AEROSTACK_AGENT_TYPE||"unknown";let l=null;async function c(t,s){const r={jsonrpc:"2.0",id:Date.now(),method:t,params:s??{}},e=new AbortController,o=setTimeout(()=>e.abort(),M);try{const n=await fetch(p,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${w}`,"User-Agent":"aerostack-gateway/0.15.8","X-Agent-Id":"aerostack-gateway","X-Bridge-Id":z,"X-Agent-Type":F},body:JSON.stringify(r),signal:e.signal});if(clearTimeout(o),(n.headers.get("content-type")??"").includes("text/event-stream")){const v=await n.text();return Q(v,r.id)}return await n.json()}catch(n){clearTimeout(o);const a=n instanceof Error?n.message:"Unknown error";return n instanceof Error&&n.name==="AbortError"?{jsonrpc:"2.0",id:r.id,error:{code:-32603,message:"Request timed out"}}:{jsonrpc:"2.0",id:r.id,error:{code:-32603,message:`HTTP error: ${a}`}}}}function Q(t,s){const r=t.split(`
8
+ `);let e=null;for(const o of r)if(o.startsWith("data: "))try{e=JSON.parse(o.slice(6))}catch{}return e??{jsonrpc:"2.0",id:s,error:{code:-32603,message:"Empty SSE response"}}}const X=new Set(["aerostack__guardian_report","aerostack__check_approval","aerostack__guardian_check"]);function Z(t,s){if(X.has(t))return;let r="other";const e=t.toLowerCase();e.includes("exec")||e.includes("bash")||e.includes("shell")||e.includes("command")||e.includes("run")?r="exec_command":e.includes("write")||e.includes("edit")||e.includes("create")||e.includes("patch")?r="file_write":e.includes("delete")||e.includes("remove")||e.includes("trash")||e.includes("unlink")?r="file_delete":e.includes("fetch")||e.includes("http")||e.includes("request")||e.includes("api")||e.includes("get")||e.includes("post")?r="api_call":e.includes("install")||e.includes("package")||e.includes("npm")||e.includes("pip")?r="package_install":e.includes("config")||e.includes("setting")||e.includes("env")?r="config_change":e.includes("deploy")||e.includes("publish")||e.includes("release")?r="deploy":e.includes("send")||e.includes("message")||e.includes("email")||e.includes("notify")||e.includes("slack")||e.includes("telegram")?r="message_send":(e.includes("read")||e.includes("query")||e.includes("search")||e.includes("list")||e.includes("get"))&&(r="data_access");let o;try{const n=JSON.stringify(s);o=n.length>500?n.slice(0,500)+"...":n}catch{o="(unable to serialize)"}c("tools/call",{name:"aerostack__guardian_report",arguments:{action:`${t}(${Object.keys(s).join(", ")})`,category:r,risk_level:"low",details:o}}).catch(()=>{})}const ee=new Set(["aerostack__check_approval"]);async function te(t,s){Z(t,s);const r=await c("tools/call",{name:t,arguments:s});if(r.error?.code===-32050){const n=r.error.data,a=n?.approval_id;if(!a||!/^[a-zA-Z0-9_-]{4,128}$/.test(a))return{jsonrpc:"2.0",id:r.id,error:{code:-32603,message:"Approval required but no approval_id returned"}};const A=l?.getLastActiveSession()??null;return i("Tool gate: returning pending, background resolver started",{tool:t,approvalId:a,sessionKey:A}),y({approvalId:a,toolName:t,toolArgs:s,sessionKey:A,gate:"tool_gate",wsUrl:n?.ws_url,pollUrl:n?.polling_url??`${p}/approval-status/${a}`,authToken:w}),{jsonrpc:"2.0",id:r.id,result:{content:[{type:"text",text:`This action requires workspace approval.
9
9
 
10
10
  Tool: ${t}
11
11
  Status: Pending review by workspace owner
12
12
 
13
- The gateway will resume this task automatically once a decision is made. You may continue with other tasks in the meantime.`}]}}}const n=r.result?._meta;if(n?.approval_id&&n?.status==="pending"){const o=n.approval_id;if(!/^[a-zA-Z0-9_-]{4,128}$/.test(o))return r;const a=l?.getLastActiveSession()??null;return i("Permission gate: returning pending, background resolver started",{tool:t,approvalId:o,sessionKey:a}),y({approvalId:o,toolName:t,toolArgs:s,sessionKey:a,gate:"permission_gate",wsUrl:n.ws_url,pollUrl:n.polling_url??`${p}/approval-status/${o}`,authToken:w}),{jsonrpc:"2.0",id:r.id,result:{content:[{type:"text",text:`Permission request submitted for workspace review.
13
+ The gateway will resume this task automatically once a decision is made. You may continue with other tasks in the meantime.`}]}}}const o=r.result?._meta;if(o?.approval_id&&o?.status==="pending"){const n=o.approval_id;if(!/^[a-zA-Z0-9_-]{4,128}$/.test(n))return r;const a=l?.getLastActiveSession()??null;return i("Permission gate: returning pending, background resolver started",{tool:t,approvalId:n,sessionKey:a}),y({approvalId:n,toolName:t,toolArgs:s,sessionKey:a,gate:"permission_gate",wsUrl:o.ws_url,pollUrl:o.polling_url??`${p}/approval-status/${n}`,authToken:w}),{jsonrpc:"2.0",id:r.id,result:{content:[{type:"text",text:`Permission request submitted for workspace review.
14
14
 
15
15
  Action: ${t}
16
16
  Status: Pending
17
17
 
18
- I'll be notified when the workspace owner decides. You may continue with other tasks.`}]}}}return r}function y(t){U({approvalId:t.approvalId,wsUrl:t.wsUrl,pollUrl:t.pollUrl,pollIntervalMs:H,timeoutMs:V,token:t.authToken}).then(async s=>{i("Approval resolved",{tool:t.toolName,status:s.status,session:t.sessionKey});const r=se(t,s);if(t.sessionKey&&l){if(await l.sendToSession(t.sessionKey,r)){i("Agent resumed via sessions.send",{session:t.sessionKey,status:s.status}),g(t,s.status,!0);return}m("sessions.send failed, session may have ended",{session:t.sessionKey}),g(t,s.status,!1)}else m("No session key available for resume",{tool:t.toolName,approvalId:t.approvalId}),g(t,s.status,!1)}).catch(s=>{m("Background approval resolver error",{error:s instanceof Error?s.message:String(s)})})}function g(t,s,r){const e=`${t.toolName}(${Object.keys(t.toolArgs).join(", ")})`,n=r?"resume sent to agent":"session unavailable \u2014 resume not delivered";let o;s==="approved"||s==="executed"?o=r?`[RESUMED] ${e} \u2014 ${n}`:`[RESUME FAILED] ${e} \u2014 ${n}`:s==="rejected"?o=`[REJECTED] ${e} \u2014 rejection sent to agent`:s==="changes_requested"?o=`[CHANGES REQUESTED] ${e} \u2014 feedback sent to agent`:o=`[EXPIRED] ${e} \u2014 approval timed out, expiry sent to agent`,c("tools/call",{name:"aerostack__guardian_report",arguments:{action:o.slice(0,500),category:"approval",risk_level:"low",details:JSON.stringify({tool:t.toolName,approvalId:t.approvalId,status:s,delivered:r,session:t.sessionKey}).slice(0,500)}}).catch(()=>{})}function se(t,s){const r=s.reviewer_note?.trim()||null,e=`${t.toolName}(${Object.keys(t.toolArgs).join(", ")})`;switch(s.status){case"approved":case"executed":return t.gate==="tool_gate"?["[APPROVAL RECEIVED] Your pending action has been approved.","",`Action: ${e}`,r?`Reviewer note: "${r}"`:null,"",`Please now execute this action: call ${t.toolName} with the same parameters as before.`].filter(n=>n!==null).join(`
19
- `):["[APPROVAL RECEIVED] Your request has been approved by the workspace owner.",r?`Reviewer note: "${r}"`:null,"","You may now proceed with the action you requested approval for."].filter(n=>n!==null).join(`
18
+ I'll be notified when the workspace owner decides. You may continue with other tasks.`}]}}}return r}function y(t){U({approvalId:t.approvalId,wsUrl:t.wsUrl,pollUrl:t.pollUrl,pollIntervalMs:H,timeoutMs:V,token:t.authToken}).then(async s=>{i("Approval resolved",{tool:t.toolName,status:s.status,session:t.sessionKey});const r=se(t,s),e=t.sessionKey??l?.getLastActiveSession()??null;if(e&&l){if(await l.sendToSession(e,r)){i("Agent resumed via sessions.send",{session:e,status:s.status}),g(t,s.status,!0);return}m("sessions.send failed, session may have ended",{session:e}),g(t,s.status,!1)}else m("No session key available for resume",{tool:t.toolName,approvalId:t.approvalId}),g(t,s.status,!1)}).catch(s=>{m("Background approval resolver error",{error:s instanceof Error?s.message:String(s)})})}function g(t,s,r){const e=`${t.toolName}(${Object.keys(t.toolArgs).join(", ")})`,o=r?"resume sent to agent":"session unavailable \u2014 resume not delivered";let n;s==="approved"||s==="executed"?n=r?`[RESUMED] ${e} \u2014 ${o}`:`[RESUME FAILED] ${e} \u2014 ${o}`:s==="rejected"?n=`[REJECTED] ${e} \u2014 rejection sent to agent`:s==="changes_requested"?n=`[CHANGES REQUESTED] ${e} \u2014 feedback sent to agent`:n=`[EXPIRED] ${e} \u2014 approval timed out, expiry sent to agent`,c("tools/call",{name:"aerostack__guardian_report",arguments:{action:n.slice(0,500),category:"approval",risk_level:"low",details:JSON.stringify({tool:t.toolName,approvalId:t.approvalId,status:s,delivered:r,session:t.sessionKey}).slice(0,500)}}).catch(()=>{})}function se(t,s){const r=s.reviewer_note?.trim()||null,e=`${t.toolName}(${Object.keys(t.toolArgs).join(", ")})`;switch(s.status){case"approved":case"executed":return t.gate==="tool_gate"?["[APPROVAL RECEIVED] Your pending action has been approved.","",`Action: ${e}`,r?`Reviewer note: "${r}"`:null,"",`Please now execute this action: call ${t.toolName} with the same parameters as before.`].filter(o=>o!==null).join(`
19
+ `):["[APPROVAL RECEIVED] Your request has been approved by the workspace owner.",r?`Reviewer note: "${r}"`:null,"","You may now proceed with the action you requested approval for."].filter(o=>o!==null).join(`
20
20
  `);case"changes_requested":return["[CHANGES REQUESTED] The workspace owner reviewed your action and requested changes.","",`Original action: ${e}`,`Reviewer feedback: "${r??"No details provided"}"`,"","Please revise your approach based on this feedback and try again.","Do NOT proceed with the original plan."].join(`
21
21
  `);case"rejected":return["[REJECTED] The workspace owner denied this action.","",`Action: ${e}`,r?`Reason: "${r}"`:"No reason provided.","","Do not retry this action. If you believe this was a mistake, inform the user."].join(`
22
22
  `);case"expired":return[`[EXPIRED] The approval request for "${e}" timed out \u2014 no decision was made.`,"","You may resubmit the request if the action is still needed."].join(`
@@ -41,6 +41,6 @@ the result once the workspace owner approves. Simply wait; do not retry or proce
41
41
  These rules exist to protect the workspace owner. Violating them may result in your session
42
42
  being terminated. Compliance is non-negotiable.
43
43
  === END GUARDIAN POLICY ===
44
- `.trim(),re=["delete","remove","drop","truncate","destroy","wipe","reset","deploy","publish","release","push","exec","shell","bash","run","command","terminal","install","uninstall","send","email","notify","webhook"];function oe(t,s){const r=t.toLowerCase(),e=re.some(o=>r.includes(o)),n=s??"";return e?`[REQUIRES GUARDIAN APPROVAL] ${n}`.trim():n}let O=null;async function d(){if(O)return;const t=await c("initialize",{protocolVersion:"2024-11-05",capabilities:{},clientInfo:{name:"aerostack-gateway",version:"0.15.8"}});if(t.result){const s=t.result,r=s.instructions??"";O={protocolVersion:s.protocolVersion??"2024-11-05",instructions:r?`${r}
44
+ `.trim(),re=["delete","remove","drop","truncate","destroy","wipe","reset","deploy","publish","release","push","exec","shell","bash","run","command","terminal","install","uninstall","send","email","notify","webhook"];function oe(t,s){const r=t.toLowerCase(),e=re.some(n=>r.includes(n)),o=s??"";return e?`[REQUIRES GUARDIAN APPROVAL] ${o}`.trim():o}let O=null;async function d(){if(O)return;const t=await c("initialize",{protocolVersion:"2024-11-05",capabilities:{},clientInfo:{name:"aerostack-gateway",version:"0.15.8"}});if(t.result){const s=t.result,r=s.instructions??"";O={protocolVersion:s.protocolVersion??"2024-11-05",instructions:r?`${r}
45
45
 
46
- ${f}`:f}}}const u=new S({name:"aerostack-gateway",version:"0.15.8"},{capabilities:{tools:{},resources:{},prompts:{}},instructions:f});u.setRequestHandler(P,async()=>{await d();const t=await c("tools/list");if(t.error)throw new Error(t.error.message);return{tools:(t.result.tools??[]).filter(e=>!ee.has(e.name)).map(e=>({...e,description:oe(e.name,e.description)}))}}),u.setRequestHandler(C,async t=>{await d();const{name:s,arguments:r}=t.params,e=await te(s,r??{});return e.error?{content:[{type:"text",text:`Error: ${e.error.message}`}],isError:!0}:{content:e.result.content??[{type:"text",text:JSON.stringify(e.result)}]}});const ne={uri:"aerostack://guardian/policy",name:"Aerostack Guardian Policy",description:"Mandatory workspace monitoring policy. Read this to understand required approval workflows.",mimeType:"text/plain"};u.setRequestHandler(I,async()=>{await d();const t=await c("resources/list");if(t.error)throw new Error(t.error.message);const s=t.result;return{resources:[ne,...s.resources??[]]}}),u.setRequestHandler(N,async t=>{if(await d(),t.params.uri==="aerostack://guardian/policy")return{contents:[{uri:t.params.uri,text:f,mimeType:"text/plain"}]};const s=await c("resources/read",{uri:t.params.uri});if(s.error)throw new Error(s.error.message);return{contents:s.result.contents??[]}}),u.setRequestHandler(b,async()=>{await d();const t=await c("prompts/list");if(t.error)throw new Error(t.error.message);return{prompts:t.result.prompts??[]}}),u.setRequestHandler(L,async t=>{await d();const s=await c("prompts/get",{name:t.params.name,arguments:t.params.arguments});if(s.error)throw new Error(s.error.message);return{messages:s.result.messages??[]}});async function ae(){i("Connecting to workspace",{url:p});const t=new k;if(await u.connect(t),i("Ready",{url:p}),G)try{const r=await K(async e=>{try{const n=await fetch(`${p}/guardian-batch`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${w}`,"User-Agent":"aerostack-gateway/0.15.8","X-Agent-Id":"aerostack-gateway"},body:JSON.stringify({events:e})});return n.ok?(await n.json()).config?.hook_tracking??null:null}catch{return null}},W);Y&&await $(r)&&i("Claude Code hook auto-installed",{port:r})}catch(s){m("Hook server failed to start (non-fatal)",{error:s instanceof Error?s.message:String(s)})}if(B)try{const s=J??await j();s?(l=new x({port:_,token:s,rpcCall:c}),await l.connect()?i("OpenClaw connector started",{port:_}):(i("OpenClaw gateway not reachable, skipping connector"),l=null)):i("OpenClaw integration skipped (no token found)")}catch(s){m("OpenClaw connector failed (non-fatal)",{error:s instanceof Error?s.message:String(s)})}}function T(){l?.stop(),D(),process.exit(0)}process.on("SIGTERM",()=>{T()}),process.on("SIGINT",()=>{T()}),ae().catch(t=>{q("Fatal error",{error:t instanceof Error?t.message:String(t)}),process.exit(1)});
46
+ ${f}`:f}}}const u=new S({name:"aerostack-gateway",version:"0.15.8"},{capabilities:{tools:{},resources:{},prompts:{}},instructions:f});u.setRequestHandler(P,async()=>{await d();const t=await c("tools/list");if(t.error)throw new Error(t.error.message);return{tools:(t.result.tools??[]).filter(e=>!ee.has(e.name)).map(e=>({...e,description:oe(e.name,e.description)}))}}),u.setRequestHandler(C,async t=>{await d();const{name:s,arguments:r}=t.params,e=await te(s,r??{});return e.error?{content:[{type:"text",text:`Error: ${e.error.message}`}],isError:!0}:{content:e.result.content??[{type:"text",text:JSON.stringify(e.result)}]}});const ne={uri:"aerostack://guardian/policy",name:"Aerostack Guardian Policy",description:"Mandatory workspace monitoring policy. Read this to understand required approval workflows.",mimeType:"text/plain"};u.setRequestHandler(I,async()=>{await d();const t=await c("resources/list");if(t.error)throw new Error(t.error.message);const s=t.result;return{resources:[ne,...s.resources??[]]}}),u.setRequestHandler(N,async t=>{if(await d(),t.params.uri==="aerostack://guardian/policy")return{contents:[{uri:t.params.uri,text:f,mimeType:"text/plain"}]};const s=await c("resources/read",{uri:t.params.uri});if(s.error)throw new Error(s.error.message);return{contents:s.result.contents??[]}}),u.setRequestHandler(b,async()=>{await d();const t=await c("prompts/list");if(t.error)throw new Error(t.error.message);return{prompts:t.result.prompts??[]}}),u.setRequestHandler(L,async t=>{await d();const s=await c("prompts/get",{name:t.params.name,arguments:t.params.arguments});if(s.error)throw new Error(s.error.message);return{messages:s.result.messages??[]}});async function ae(){i("Connecting to workspace",{url:p});const t=new k;if(await u.connect(t),i("Ready",{url:p}),G)try{const r=await $(async e=>{try{const o=await fetch(`${p}/guardian-batch`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${w}`,"User-Agent":"aerostack-gateway/0.15.8","X-Agent-Id":"aerostack-gateway"},body:JSON.stringify({events:e})});return o.ok?(await o.json()).config?.hook_tracking??null:null}catch{return null}},W);Y&&await K(r)&&i("Claude Code hook auto-installed",{port:r})}catch(s){m("Hook server failed to start (non-fatal)",{error:s instanceof Error?s.message:String(s)})}if(B)try{const s=J??await j();s?(l=new x({port:_,token:s,rpcCall:c}),await l.connect()?i("OpenClaw connector started",{port:_}):(i("OpenClaw gateway not reachable, skipping connector"),l=null)):i("OpenClaw integration skipped (no token found)")}catch(s){m("OpenClaw connector failed (non-fatal)",{error:s instanceof Error?s.message:String(s)})}}function T(){l?.stop(),D(),process.exit(0)}process.on("SIGTERM",()=>{T()}),process.on("SIGINT",()=>{T()}),ae().catch(t=>{q("Fatal error",{error:t instanceof Error?t.message:String(t)}),process.exit(1)});
@@ -1 +1 @@
1
- import{readFile as m}from"node:fs/promises";import{join as g}from"node:path";import{homedir as f}from"node:os";import{info as u,warn as a,debug as c}from"./logger.js";import{addToBatch as p,detectCategory as b,summarizeToolInput as w}from"./hook-server.js";async function _(){try{const d=g(f(),".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(f(),".openclaw","exec-approvals.json"),e=await m(d,"utf-8");return JSON.parse(e)?.socket?.token??null}catch{return null}}const y=1e3,k=3e4;class R{opts;ws=null;destroyed=!1;reconnectMs=y;reconnectTimer=null;requestId=0;seenSessions=new Set;pendingRequests=new Map;lastActiveSession=null;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)}})}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||a("sendToSession failed",{key:e,error:t.error?.message}),t.ok??!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.8",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=y,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):(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}=b(o,r),S=w(o,r);p({action:`OpenClaw ${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}=b(t,o),h=w(t,o);p({action:`OpenClaw ${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),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)}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,k))}async getWebSocket(){return typeof globalThis.WebSocket<"u"?globalThis.WebSocket:(await import("ws")).default}}export{R as OpenClawConnector,N as resolveExecApprovalToken,_ as resolveOpenClawToken};
1
+ import{readFile as m}from"node:fs/promises";import{join as g}from"node:path";import{homedir as f}from"node:os";import{info as u,warn as c,debug as l}from"./logger.js";import{addToBatch as p,detectCategory as b,summarizeToolInput as w}from"./hook-server.js";function y(a){return a.replace(/^(?:\w+__)*aerostack__/,"").replace(/__/g,":")||a}async function R(){try{const a=g(f(),".openclaw","openclaw.json"),e=await m(a,"utf-8");return JSON.parse(e)?.gateway?.auth?.token??null}catch{return null}}async function N(){try{const a=g(f(),".openclaw","exec-approvals.json"),e=await m(a,"utf-8");return JSON.parse(e)?.socket?.token??null}catch{return null}}const S=1e3,v=3e4;class E{opts;ws=null;destroyed=!1;reconnectMs=S;reconnectTimer=null;requestId=0;seenSessions=new Set;pendingRequests=new Map;lastActiveSession=null;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(()=>{l("OpenClaw connect timeout");try{n.close()}catch{}t(!1)},1e4);n.onopen=()=>{clearTimeout(o),l("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}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.8",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=S,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?l("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:d,risk:h}=b(o,r),k=w(o,r);p({action:`OpenClaw ${y(o)}: ${k}`.slice(0,500),category:d,risk_level:h,details:JSON.stringify({tool:o,session:n,...r}).slice(0,500),agent_name:"OpenClaw"}),l("OpenClaw agent tool event",{tool:o,category:d,risk:h})}}if(s==="lifecycle"&&t){const o=t.phase??"";(o==="start"||o==="end")&&l("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:d}=b(t,o),h=w(t,o);p({action:`OpenClaw ${y(t)}: ${h}`.slice(0,500),category:r,risk_level:d,details:JSON.stringify({tool:t,session:i,...o}).slice(0,500),agent_name:"OpenClaw"}),l("OpenClaw tool event",{tool:t,category:r,risk:d,session:i}),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)}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||(l("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,v))}async getWebSocket(){return typeof globalThis.WebSocket<"u"?globalThis.WebSocket:(await import("ws")).default}}export{E as OpenClawConnector,N as resolveExecApprovalToken,R as resolveOpenClawToken};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aerostack/gateway",
3
- "version": "0.15.9",
3
+ "version": "0.15.11",
4
4
  "description": "stdio-to-HTTP bridge connecting any MCP client to Aerostack Workspaces",
5
5
  "author": "Aerostack",
6
6
  "license": "MIT",