@aerostack/gateway 0.17.1 → 0.17.3
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/README.md +40 -4
- package/dist/index.js +18 -18
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -116,12 +116,22 @@ Token: mwt_your_token_here (Authorization: Bearer header)
|
|
|
116
116
|
|
|
117
117
|
### 3. Verify your connection
|
|
118
118
|
|
|
119
|
-
Run the built-in diagnostic
|
|
119
|
+
Run the built-in diagnostic to verify everything works end-to-end:
|
|
120
120
|
|
|
121
121
|
```bash
|
|
122
|
+
# Option A: if npx works on your system
|
|
122
123
|
AEROSTACK_WORKSPACE_URL="https://mcp.aerostack.dev/ws/your-workspace" \
|
|
123
124
|
AEROSTACK_TOKEN="mwt_your_token_here" \
|
|
124
|
-
npx @aerostack/gateway --check
|
|
125
|
+
npx -y @aerostack/gateway --check
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
```bash
|
|
129
|
+
# Option B: install globally first (recommended for servers/VMs)
|
|
130
|
+
npm install -g @aerostack/gateway
|
|
131
|
+
|
|
132
|
+
export AEROSTACK_WORKSPACE_URL="https://mcp.aerostack.dev/ws/your-workspace"
|
|
133
|
+
export AEROSTACK_TOKEN="mwt_your_token_here"
|
|
134
|
+
aerostack-gateway --check
|
|
125
135
|
```
|
|
126
136
|
|
|
127
137
|
You should see:
|
|
@@ -141,6 +151,8 @@ You should see:
|
|
|
141
151
|
|
|
142
152
|
If any step fails, the output tells you exactly what to fix.
|
|
143
153
|
|
|
154
|
+
> **Tip:** After global install, you can simplify your MCP config to use `"command": "aerostack-gateway"` instead of `"command": "npx"` — faster startup, no network fetch.
|
|
155
|
+
|
|
144
156
|
### 4. Use it
|
|
145
157
|
|
|
146
158
|
```
|
|
@@ -152,14 +164,38 @@ Tools are namespaced as `{server}__{tool}` (e.g., `github__create_issue`, `slack
|
|
|
152
164
|
|
|
153
165
|
## Troubleshooting
|
|
154
166
|
|
|
167
|
+
**Step-by-step debug:**
|
|
168
|
+
|
|
169
|
+
```bash
|
|
170
|
+
# 1. Can the gateway reach your workspace?
|
|
171
|
+
aerostack-gateway --check
|
|
172
|
+
|
|
173
|
+
# 2. If using OpenClaw, is the gateway running?
|
|
174
|
+
openclaw gateway status
|
|
175
|
+
|
|
176
|
+
# 3. Is the MCP server configured?
|
|
177
|
+
openclaw mcp show aerostack
|
|
178
|
+
|
|
179
|
+
# 4. Start the gateway if not running
|
|
180
|
+
openclaw gateway start
|
|
181
|
+
|
|
182
|
+
# 5. Run OpenClaw's built-in diagnostics
|
|
183
|
+
openclaw doctor
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
**Common issues:**
|
|
187
|
+
|
|
155
188
|
| Problem | Solution |
|
|
156
189
|
|---------|----------|
|
|
157
|
-
| `npx` hangs or fails | Run `npm install -g @aerostack/gateway` instead, then use `"command": "aerostack-gateway"` |
|
|
190
|
+
| `npx` hangs or fails | Run `npm install -g @aerostack/gateway` instead, then use `"command": "aerostack-gateway"` in your MCP config |
|
|
191
|
+
| `AEROSTACK_WORKSPACE_URL is required` | The env vars aren't being passed. Use `export` before running `--check`, or verify your MCP config has the `env` block |
|
|
192
|
+
| `npx` error: "could not determine executable" | Install globally: `npm install -g @aerostack/gateway`, then run `aerostack-gateway --check` |
|
|
158
193
|
| "Token invalid" in `--check` | Regenerate token at dashboard → Workspace → Tokens |
|
|
159
|
-
| "0 tools found" | Add MCP servers to your workspace in the dashboard first |
|
|
194
|
+
| "0 tools found" | Add MCP servers or functions to your workspace in the dashboard first |
|
|
160
195
|
| OpenClaw: tools not showing | Send a message via Telegram or another channel to initialize the session |
|
|
161
196
|
| Dashboard shows "No agents" | Run `--check` to verify, or wait 2 minutes for the heartbeat to register |
|
|
162
197
|
| "Workspace unreachable" | Check the URL format: `https://mcp.aerostack.dev/ws/your-workspace` |
|
|
198
|
+
| Multiple phantom agents in dashboard | Update to latest version (`npm install -g @aerostack/gateway@latest`) — fixed in 0.17.2 |
|
|
163
199
|
|
|
164
200
|
## How It Works
|
|
165
201
|
|
package/dist/index.js
CHANGED
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
import{Server as we}from"@modelcontextprotocol/sdk/server/index.js";import{StdioServerTransport as Ae}from"@modelcontextprotocol/sdk/server/stdio.js";import{ListToolsRequestSchema as Se,CallToolRequestSchema as Te,ListResourcesRequestSchema as Ee,ReadResourceRequestSchema as ke,ListPromptsRequestSchema as Re,GetPromptRequestSchema as Oe}from"@modelcontextprotocol/sdk/types.js";import{readFile as Ce,writeFile as ve}from"node:fs/promises";import{join as $e}from"node:path";import{homedir as be}from"node:os";import{resolveApproval as
|
|
3
|
+
import{Server as we}from"@modelcontextprotocol/sdk/server/index.js";import{StdioServerTransport as Ae}from"@modelcontextprotocol/sdk/server/stdio.js";import{ListToolsRequestSchema as Se,CallToolRequestSchema as Te,ListResourcesRequestSchema as Ee,ReadResourceRequestSchema as ke,ListPromptsRequestSchema as Re,GetPromptRequestSchema as Oe}from"@modelcontextprotocol/sdk/types.js";import{readFile as Ce,writeFile as ve}from"node:fs/promises";import{join as $e}from"node:path";import{homedir as be}from"node:os";import{resolveApproval as Z}from"./resolution.js";import{startHookServer as Ne,installClaudeHook as Pe,stopHookServer as Ie}from"./hook-server.js";import{OpenClawConnector as xe,resolveOpenClawToken as Ue}from"./openclaw-connector.js";import{info as l,warn as y,error as Le}from"./logger.js";const ce=$e(be(),".openclaw","pre-authorized.json");async function De(e){try{let t={};try{t=JSON.parse(await Ce(ce,"utf-8"))}catch{}t[e]=Date.now(),await ve(ce,JSON.stringify(t))}catch{}}const ee=process.env.AEROSTACK_WORKSPACE_URL,h=process.env.AEROSTACK_TOKEN;function W(e,t,n){const s=parseInt(e??String(t),10);return Number.isFinite(s)&&s>=n?s:t}const te=W(process.env.AEROSTACK_APPROVAL_POLL_MS,3e3,500),se=W(process.env.AEROSTACK_APPROVAL_TIMEOUT_MS,864e5,5e3),le=W(process.env.AEROSTACK_REQUEST_TIMEOUT_MS,3e4,1e3),Me=process.env.AEROSTACK_HOOK_SERVER!=="false",je=W(process.env.AEROSTACK_HOOK_PORT,18321,1024),Ke=process.env.AEROSTACK_HOOK_AUTO_INSTALL!=="false",He=process.env.AEROSTACK_OPENCLAW_ENABLED!=="false",ue=W(process.env.AEROSTACK_OPENCLAW_PORT,18789,1024),We=process.env.AEROSTACK_OPENCLAW_TOKEN;ee||(process.stderr.write(`ERROR: AEROSTACK_WORKSPACE_URL is required
|
|
4
4
|
`),process.exit(1)),h||(process.stderr.write(`ERROR: AEROSTACK_TOKEN is required
|
|
5
|
-
`),process.exit(1));let B;try{if(B=new URL(
|
|
5
|
+
`),process.exit(1));let B;try{if(B=new URL(ee),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 m=
|
|
8
|
-
`);let s=null;for(const
|
|
9
|
-
${t.slice(-
|
|
7
|
+
`);const m=ee.replace(/\/+$/,""),ne=crypto.randomUUID();function Be(){return process.env.AEROSTACK_AGENT_TYPE?process.env.AEROSTACK_AGENT_TYPE:process.env.CLAUDECODE||process.env.CLAUDE_CODE?"claude-code":process.env.MCP_TRANSPORT==="stdio"?"mcp-stdio":"unknown"}const de=Be();let $=null,L=Date.now();const ze=3e4;async function O(e,t){const n={jsonrpc:"2.0",id:Date.now(),method:e,params:t??{}},s=new AbortController,o=setTimeout(()=>s.abort(),le);try{const a=await fetch(m,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${h}`,"User-Agent":"aerostack-gateway/0.15.29","X-Agent-Id":"aerostack-gateway","X-Bridge-Id":ne,"X-Agent-Type":de},body:JSON.stringify(n),signal:s.signal});if(clearTimeout(o),(a.headers.get("content-type")??"").includes("text/event-stream")){const c=await a.text();return Fe(c,n.id)}return await a.json()}catch(a){clearTimeout(o);const r=a instanceof Error?a.message:"Unknown error";return a instanceof Error&&a.name==="AbortError"?{jsonrpc:"2.0",id:n.id,error:{code:-32603,message:"Request timed out"}}:{jsonrpc:"2.0",id:n.id,error:{code:-32603,message:`HTTP error: ${r}`}}}}function Fe(e,t){const n=e.split(`
|
|
8
|
+
`);let s=null;for(const o of n)if(o.startsWith("data: "))try{s=JSON.parse(o.slice(6))}catch{}return s??{jsonrpc:"2.0",id:t,error:{code:-32603,message:"Empty SSE response"}}}const qe=new Set(["aerostack__guardian_report","aerostack__check_approval","aerostack__guardian_check"]);function Je(e,t){if(qe.has(e))return;let n="other";const s=e.toLowerCase();s.includes("exec")||s.includes("bash")||s.includes("shell")||s.includes("command")||s.includes("run")?n="exec_command":s.includes("write")||s.includes("edit")||s.includes("create")||s.includes("patch")?n="file_write":s.includes("delete")||s.includes("remove")||s.includes("trash")||s.includes("unlink")?n="file_delete":s.includes("fetch")||s.includes("http")||s.includes("request")||s.includes("api")||s.includes("get")||s.includes("post")?n="api_call":s.includes("install")||s.includes("package")||s.includes("npm")||s.includes("pip")?n="package_install":s.includes("config")||s.includes("setting")||s.includes("env")?n="config_change":s.includes("deploy")||s.includes("publish")||s.includes("release")?n="deploy":s.includes("send")||s.includes("message")||s.includes("email")||s.includes("notify")||s.includes("slack")||s.includes("telegram")?n="message_send":(s.includes("read")||s.includes("query")||s.includes("search")||s.includes("list")||s.includes("get"))&&(n="data_access");let o;try{const a=JSON.stringify(t);o=a.length>500?a.slice(0,500)+"...":a}catch{o="(unable to serialize)"}O("tools/call",{name:"aerostack__guardian_report",arguments:{action:`${e}(${Object.keys(t).join(", ")})`,category:n,risk_level:"low",details:o}}).catch(()=>{})}const Ge=new Set(["aerostack__check_approval"]),pe=[{name:"aerostack__chat_check",description:"Check for new messages from the workspace owner. Call this periodically or when asked to check for instructions.",inputSchema:{type:"object",properties:{},required:[]}},{name:"aerostack__chat_reply",description:"Send a reply message to the workspace owner via Agent Chat.",inputSchema:{type:"object",properties:{message:{type:"string",description:"Your reply message to the workspace owner"}},required:["message"]}}],Ve=new Set(pe.map(e=>e.name));function w(e){return e.replace(/[\r\n]+/g," ").replace(/\[/g,"(").replace(/\]/g,")").replace(/---/g,"\u2014")}const Ye=new Set(["image/jpeg","image/png","image/gif","image/webp"]),Xe=2*1024*1024;async function oe(e,t,n,s,o){if(n!=="media"&&n!=="rich")return[{type:"text",text:`${w(o)}: ${w(s)}`}];let a=[];if(t)try{const c=JSON.parse(t);c?.v===1&&Array.isArray(c.attachments)&&(a=c.attachments)}catch{}if(a.length===0)return[{type:"text",text:`${w(o)}: ${w(s)}`}];const r=[];n==="rich"&&s?r.push({type:"text",text:`${w(o)}: ${w(s)}`}):a.length>0&&r.push({type:"text",text:`${w(o)} sent ${a.length} file(s):`});const i=e&&/^[a-zA-Z0-9_-]{1,128}$/.test(e)?e:null;for(const c of a){if(Ye.has(c.mime_type)&&c.size<=Xe&&i)try{const H=`${m}/chat/media/${i}/${encodeURIComponent(c.filename)}`,R=await T(H,{headers:{Authorization:`Bearer ${h}`}});if(R.ok){const g=Buffer.from(await R.arrayBuffer()).toString("base64");r.push({type:"image",data:g,mimeType:c.mime_type});continue}}catch{}const b=Math.round(c.size/1024),D=b>=1024?`${(b/1024).toFixed(1)} MB`:`${b} KB`,k=w(c.filename.slice(0,200)),C=w(c.mime_type.slice(0,100)),ie=i?`${m}/chat/media/${i}/${encodeURIComponent(c.filename)}`:"(unavailable)";r.push({type:"text",text:`Attached: ${k} (${C}, ${D}) \u2014 ${ie}`})}return r}const z=[],Qe=100,N=new Map,he=50,Ze=100,me=10;let P=null;const fe=new Set,F=new Map;let I=null;function j(e,t,n){let s=N.get(e);if(!s){if(N.size>=Ze){const o=N.keys().next().value;o&&N.delete(o)}s=[],N.set(e,s)}s.push({role:t,content:n,ts:Date.now()}),s.length>he&&s.splice(0,s.length-he)}async function et(e){if(!fe.has(e))try{const t=await T(`${m}/chat/history?limit=${me}`,{headers:{Authorization:`Bearer ${h}`}});if(!t.ok)return;const n=await t.json();n.session_id&&(P=n.session_id);for(const s of n.messages??[]){const o=s.sender_type==="agent"?"agent":"user",a=n.session_id||e;let r=N.get(a);r||(r=[],N.set(a,r));const i=s.content_type==="media"||s.content_type==="rich"?`${s.content} [has media attachment]`:s.content;r.push({role:o,content:i,ts:s.created_at,content_type:s.content_type})}fe.add(e),l("Thread history seeded from D1",{sessionId:n.session_id,count:n.messages?.length??0})}catch{}}function ye(e){const t=N.get(e);return!t||t.length===0?"":`Previous conversation:
|
|
9
|
+
${t.slice(-me).map(o=>`[${o.role==="user"?"Admin":"You (Agent)"}] ${o.content}`).join(`
|
|
10
10
|
`)}
|
|
11
11
|
|
|
12
|
-
`}async function
|
|
13
|
-
`):["[APPROVAL RECEIVED] Your request has been approved by the workspace owner.",
|
|
14
|
-
`);case"changes_requested":return["[CHANGES REQUESTED] The workspace owner reviewed your action and requested changes.","",`Original action: ${s}`,`Reviewer feedback: "${
|
|
15
|
-
`);case"rejected":return["[REJECTED] The workspace owner denied this action.","",`Action: ${s}`,
|
|
12
|
+
`}async function T(e,t){const n=new AbortController,s=setTimeout(()=>n.abort(),le);try{return await fetch(e,{...t,signal:n.signal})}finally{clearTimeout(s)}}async function tt(){const e=P||"_default";if(await et(e),z.length>0){const t=z.splice(0);L=Date.now();const n=ye(e),s=[];n&&s.push({type:"text",text:n}),s.push({type:"text",text:"New messages from workspace owner:"});for(const o of t){const a=await oe(o.message_id??null,o.metadata_json??null,o.content_type,o.content,o.sender_name);s.push(...a)}return s}try{const n=await T(`${m}/chat/pending?since=${L}`,{headers:{Authorization:`Bearer ${h}`}});if(!n.ok)return[{type:"text",text:"No new messages."}];const o=(await n.json()).messages??[];if(L=Date.now(),o.length===0)return[{type:"text",text:"No new messages from the workspace owner."}];for(const i of o)j(e,"user",i.content);const a=ye(e),r=[];a&&r.push({type:"text",text:a}),r.push({type:"text",text:"New messages from workspace owner:"});for(const i of o){const c=await oe(i.id??null,i.metadata_json??null,i.content_type,i.content,i.sender_name);r.push(...c)}return r}catch{return[{type:"text",text:"Failed to check messages."}]}}async function st(e){const t=typeof e.message=="string"?e.message.trim().slice(0,1e4):"";if(!t)return[{type:"text",text:"Error: message is required."}];try{const n=await T(`${m}/chat/messages`,{method:"POST",headers:{Authorization:`Bearer ${h}`,"Content-Type":"application/json"},body:JSON.stringify({content:t,session_id:P})});return n.ok?(j(P||"_default","agent",t),[{type:"text",text:"Reply sent to workspace owner."}]):[{type:"text",text:`Failed to send reply (HTTP ${n.status}).`}]}catch{return[{type:"text",text:"Failed to send reply."}]}}async function nt(e,t){Je(e,t);const n=await O("tools/call",{name:e,arguments:t});if(n.error?.code===-32050){const a=n.error.data,r=a?.approval_id;if(!r||!/^[a-zA-Z0-9_-]{4,128}$/.test(r))return{jsonrpc:"2.0",id:n.id,error:{code:-32603,message:"Approval required but no approval_id returned"}};l("Tool gate: blocking until approval resolves",{tool:e,approvalId:r});const i=await Z({approvalId:r,wsUrl:a?.ws_url,pollUrl:a?.polling_url??`${m}/approval-status/${r}`,pollIntervalMs:te,timeoutMs:se,token:h});l("Tool gate resolved",{tool:e,status:i.status}),q({approvalId:r,toolName:e,toolArgs:t,sessionKey:null,gate:"tool_gate",pollUrl:"",authToken:h},i.status,!0);const c=ae({approvalId:r,toolName:e,toolArgs:t,sessionKey:null,gate:"tool_gate",pollUrl:"",authToken:h},i);return{jsonrpc:"2.0",id:n.id,result:{content:[{type:"text",text:c}]}}}const o=n.result?._meta;if(o?.approval_id&&o?.status==="pending"){const a=o.approval_id;if(!/^[a-zA-Z0-9_-]{4,128}$/.test(a))return n;l("Permission gate: blocking until approval resolves",{tool:e,approvalId:a});const r=await Z({approvalId:a,wsUrl:o.ws_url,pollUrl:o.polling_url??`${m}/approval-status/${a}`,pollIntervalMs:te,timeoutMs:se,token:h});l("Permission gate resolved",{tool:e,status:r.status}),q({approvalId:a,toolName:e,toolArgs:t,sessionKey:null,gate:"permission_gate",pollUrl:"",authToken:h},r.status,!0);const i=ae({approvalId:a,toolName:e,toolArgs:t,sessionKey:null,gate:"permission_gate",pollUrl:"",authToken:h},r);return{jsonrpc:"2.0",id:n.id,result:{content:[{type:"text",text:i}]}}}return n}function kt(e){Z({approvalId:e.approvalId,wsUrl:e.wsUrl,pollUrl:e.pollUrl,pollIntervalMs:te,timeoutMs:se,token:e.authToken}).then(async t=>{l("Approval resolved",{tool:e.toolName,status:t.status,session:e.sessionKey});const n=ae(e,t),s=e.sessionKey??$?.getLastActiveSession()??null;if((t.status==="approved"||t.status==="executed")&&s&&De(s).catch(()=>{}),s&&$){if(await $.sendToSession(s,n)){l("Agent resumed via sessions.send",{session:s,status:t.status}),q(e,t.status,!0);return}y("sessions.send failed, falling back to channel notification",{session:s})}try{await x.notification({method:"notifications/claude/channel",params:{content:n,meta:{tool_name:e.toolName,approval_id:e.approvalId,status:t.status,gate:e.gate}}}),l("Agent resumed via channel notification",{tool:e.toolName,status:t.status}),q(e,t.status,!0)}catch(o){y("Channel notification failed",{error:o instanceof Error?o.message:String(o)}),q(e,t.status,!1)}}).catch(t=>{y("Background approval resolver error",{error:t instanceof Error?t.message:String(t)})})}function q(e,t,n){const s=typeof e.toolArgs.action=="string"?e.toolArgs.action:null,o=s??e.toolName.replace(/^(?:\w+__)*aerostack__/,"")??e.toolName,a=s?`${o}`:`${o}(${Object.keys(e.toolArgs).join(", ")})`,r=n?"resume sent to agent":"session unavailable \u2014 resume not delivered";let i;t==="approved"||t==="executed"?i=n?`[RESUMED] ${a} \u2014 ${r}`:`[RESUME FAILED] ${a} \u2014 ${r}`:t==="rejected"?i=`[REJECTED] ${a} \u2014 rejection sent to agent`:t==="changes_requested"?i=`[CHANGES REQUESTED] ${a} \u2014 feedback sent to agent`:i=`[EXPIRED] ${a} \u2014 approval timed out, expiry sent to agent`,O("tools/call",{name:"aerostack__guardian_report",arguments:{action:i.slice(0,500),category:"approval",risk_level:"low",details:JSON.stringify({tool:e.toolName,approvalId:e.approvalId,status:t,delivered:n,session:e.sessionKey}).slice(0,500)}}).catch(()=>{})}function ae(e,t){const n=t.reviewer_note?.trim()||null,s=`${e.toolName}(${Object.keys(e.toolArgs).join(", ")})`;switch(t.status){case"approved":case"executed":return e.gate==="tool_gate"?["[APPROVAL RECEIVED] Your pending action has been approved.","",`Action: ${s}`,n?`Reviewer note: "${n}"`:null,"",`Please now execute this action: call ${e.toolName} with the same parameters as before.`].filter(o=>o!==null).join(`
|
|
13
|
+
`):["[APPROVAL RECEIVED] Your request has been approved by the workspace owner.",n?`Reviewer note: "${n}"`:null,"","You may now proceed with the action you requested approval for."].filter(o=>o!==null).join(`
|
|
14
|
+
`);case"changes_requested":return["[CHANGES REQUESTED] The workspace owner reviewed your action and requested changes.","",`Original action: ${s}`,`Reviewer feedback: "${n??"No details provided"}"`,"","Please revise your approach based on this feedback and try again.","Do NOT proceed with the original plan."].join(`
|
|
15
|
+
`);case"rejected":return["[REJECTED] The workspace owner denied this action.","",`Action: ${s}`,n?`Reason: "${n}"`:"No reason provided.","","Do not retry this action. If you believe this was a mistake, inform the user."].join(`
|
|
16
16
|
`);case"expired":return[`[EXPIRED] The approval request for "${s}" timed out \u2014 no decision was made.`,"","You may resubmit the request if the action is still needed."].join(`
|
|
17
17
|
`);default:return`[APPROVAL UPDATE] Status for "${s}": ${t.status}.`}}const V=`
|
|
18
18
|
=== AEROSTACK GUARDIAN POLICY (MANDATORY) ===
|
|
@@ -42,26 +42,26 @@ or before starting a new one, call aerostack__chat_check to see if the owner sen
|
|
|
42
42
|
If there are messages, read them carefully and respond using aerostack__chat_reply. This keeps
|
|
43
43
|
the owner informed and allows them to steer your work in real time.
|
|
44
44
|
=== END WORKSPACE CHAT ===
|
|
45
|
-
`.trim(),
|
|
45
|
+
`.trim(),ot=["delete","remove","drop","truncate","destroy","wipe","reset","deploy","publish","release","push","exec","shell","bash","run","command","terminal","install","uninstall","send","email","notify","webhook"];function at(e,t){const n=e.toLowerCase(),s=ot.some(a=>n.includes(a)),o=t??"";return s?`[REQUIRES GUARDIAN APPROVAL] ${o}`.trim():o}let ge=null;async function K(){if(ge)return;const e=await O("initialize",{protocolVersion:"2024-11-05",capabilities:{},clientInfo:{name:"aerostack-gateway",version:"0.15.29"}});if(e.result){const t=e.result,n=t.instructions??"";ge={protocolVersion:t.protocolVersion??"2024-11-05",instructions:n?`${n}
|
|
46
46
|
|
|
47
|
-
${V}`:V}}}const x=new we({name:"aerostack-gateway",version:"0.15.29"},{capabilities:{tools:{},resources:{},prompts:{},experimental:{"claude/channel":{}}},instructions:V});x.setRequestHandler(Se,async()=>{await K();const e=await O("tools/list");if(e.error)throw new Error(e.error.message);const
|
|
47
|
+
${V}`:V}}}const x=new we({name:"aerostack-gateway",version:"0.15.29"},{capabilities:{tools:{},resources:{},prompts:{},experimental:{"claude/channel":{}}},instructions:V});x.setRequestHandler(Se,async()=>{await K();const e=await O("tools/list");if(e.error)throw new Error(e.error.message);const n=(e.result.tools??[]).filter(s=>!Ge.has(s.name)).map(s=>({...s,description:at(s.name,s.description)}));return n.push(...pe),{tools:n}}),x.setRequestHandler(Te,async e=>{await K();const{name:t,arguments:n}=e.params;if(Ve.has(t))return{content:t==="aerostack__chat_check"?await tt():await st(n??{})};const s=await nt(t,n??{});if(s.error)return{content:[{type:"text",text:`Error: ${s.error.message}`}],isError:!0};const a=s.result.content??[{type:"text",text:JSON.stringify(s.result)}];if(Date.now()-L>ze)try{const i=await T(`${m}/chat/pending?since=${L}`,{headers:{Authorization:`Bearer ${h}`}});if(i.ok){const _=(await i.json()).messages??[];if(L=Date.now(),_.length>0){const b=[];for(const k of _){const C=await oe(k.id??null,k.metadata_json??null,k.content_type,k.content,k.sender_name);b.push(...C)}const D=[{type:"text",text:"--- WORKSPACE OWNER MESSAGE ---"},...b,{type:"text",text:"--- END MESSAGE \u2014 please acknowledge ---"}];a.unshift(...D)}}}catch{}return{content:a}});const rt={uri:"aerostack://guardian/policy",name:"Aerostack Guardian Policy",description:"Mandatory workspace monitoring policy. Read this to understand required approval workflows.",mimeType:"text/plain"};x.setRequestHandler(Ee,async()=>{await K();const e=await O("resources/list");if(e.error)throw new Error(e.error.message);const t=e.result;return{resources:[rt,...t.resources??[]]}}),x.setRequestHandler(ke,async e=>{if(await K(),e.params.uri==="aerostack://guardian/policy")return{contents:[{uri:e.params.uri,text:V,mimeType:"text/plain"}]};const t=await O("resources/read",{uri:e.params.uri});if(t.error)throw new Error(t.error.message);return{contents:t.result.contents??[]}}),x.setRequestHandler(Re,async()=>{await K();const e=await O("prompts/list");if(e.error)throw new Error(e.error.message);return{prompts:e.result.prompts??[]}}),x.setRequestHandler(Oe,async e=>{await K();const t=await O("prompts/get",{name:e.params.name,arguments:e.params.arguments});if(t.error)throw new Error(t.error.message);return{messages:t.result.messages??[]}});async function it(){const e=t=>process.stderr.write(t+`
|
|
48
48
|
`);e(`
|
|
49
49
|
Aerostack Connection Check
|
|
50
50
|
`);try{const t=await fetch(`${m}/chat/connection-test`,{headers:{Authorization:`Bearer ${h}`},signal:AbortSignal.timeout(1e4)});t.ok||(t.status===401&&(e(" \u2705 Workspace URL reachable"),e(" \u274C Token invalid (HTTP 401)"),e(`
|
|
51
51
|
Fix: Regenerate token at your dashboard \u2192 Workspace \u2192 Tokens
|
|
52
52
|
`),process.exit(1)),e(` \u274C Workspace unreachable (HTTP ${t.status})`),e(`
|
|
53
53
|
Fix: Check AEROSTACK_WORKSPACE_URL is correct
|
|
54
|
-
`),process.exit(1));const
|
|
55
|
-
All checks passed. Your agent is connected.`),e(` Dashboard: ${
|
|
56
|
-
`),
|
|
54
|
+
`),process.exit(1));const n=await t.json();e(` \u2705 Workspace URL reachable (${m})`),e(` \u2705 Token valid (${n.token_name}, role: ${n.token_role})`),e(` \u2705 Workspace connected ("${n.workspace}")`),e(` \u2705 Tools available (${n.mcp_servers} tools)`);try{const s=await fetch(`${m}/chat/heartbeat`,{method:"POST",headers:{Authorization:`Bearer ${h}`,"Content-Type":"application/json"},body:JSON.stringify({agent_type:"check",user_agent:"aerostack-gateway/check",bridge_id:"check"}),signal:AbortSignal.timeout(1e4)});e(s.ok?" \u2705 Dashboard registration (bridge visible in admin)":` \u26A0\uFE0F Dashboard registration (returned ${s.status})`)}catch{e(" \u26A0\uFE0F Dashboard registration (heartbeat failed \u2014 non-fatal)")}e(`
|
|
55
|
+
All checks passed. Your agent is connected.`),e(` Dashboard: ${n.dashboard_url||"https://app.aerostack.dev"}
|
|
56
|
+
`),n.mcp_servers===0&&e(` Note: 0 tools found. Add MCP servers or functions to your workspace first.
|
|
57
57
|
`),process.exit(0)}catch(t){t?.name==="TimeoutError"||t?.name==="AbortError"?(e(" \u274C Workspace unreachable (timeout after 10s)"),e(`
|
|
58
58
|
Fix: Check AEROSTACK_WORKSPACE_URL and your network connection
|
|
59
59
|
`)):(e(` \u274C Connection failed (${t?.message??"unknown error"})`),e(`
|
|
60
60
|
Fix: Check AEROSTACK_WORKSPACE_URL is correct
|
|
61
|
-
`)),process.exit(1)}}async function ct(){try{const e=await fetch(`${m}/chat/connection-test`,{headers:{Authorization:`Bearer ${h}`},signal:AbortSignal.timeout(5e3)});if(e.ok){const t=await e.json();
|
|
61
|
+
`)),process.exit(1)}}async function ct(){try{const e=await fetch(`${m}/chat/connection-test`,{headers:{Authorization:`Bearer ${h}`},signal:AbortSignal.timeout(5e3)});if(e.ok){const t=await e.json();l(`Connected to "${t.workspace}" (${t.mcp_servers} tools)`,{dashboard:"https://app.aerostack.dev"})}else e.status===401?y("Token invalid \u2014 regenerate at dashboard \u2192 Workspace \u2192 Tokens"):y("Connection check failed",{status:e.status})}catch{y("Could not verify connection (non-fatal)")}}async function lt(){l("Connecting to workspace",{url:m});const e=new Ae;await x.connect(e),process.stdin.on("end",()=>{l("stdin closed \u2014 MCP session ended, shutting down"),Y()}),process.stdin.on("close",()=>{l("stdin closed \u2014 MCP session ended, shutting down"),Y()}),l("Ready",{url:m}),ct().catch(()=>{});const t=()=>{T(`${m}/chat/heartbeat`,{method:"POST",headers:{Authorization:`Bearer ${h}`,"Content-Type":"application/json"},body:JSON.stringify({agent_type:de,user_agent:"aerostack-gateway/0.15.29",bridge_id:ne})}).catch(()=>{})};t();const n=setInterval(t,12e4);if(process.on("exit",()=>clearInterval(n)),Me)try{const o=await Ne(async a=>{try{const r=await fetch(`${m}/guardian-batch`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${h}`,"User-Agent":"aerostack-gateway/0.15.29","X-Agent-Id":"aerostack-gateway"},body:JSON.stringify({events:a})});return r.ok?(await r.json()).config?.hook_tracking??null:null}catch{return null}},je);Ke&&await Pe(o)&&l("Claude Code hook auto-installed",{port:o})}catch(s){y("Hook server failed to start (non-fatal)",{error:s instanceof Error?s.message:String(s)})}if(He)try{const s=We??await Ue();s?($=new xe({port:ue,token:s,rpcCall:O,onToolEvent:a=>{const r=I;if(!r)return;const i=F.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(_=>_.name===a.toolName&&_.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))}T(`${m}/chat/tool-status`,{method:"POST",headers:{Authorization:`Bearer ${h}`,"Content-Type":"application/json"},body:JSON.stringify({tool_name:a.toolName,phase:a.phase,category:a.category})}).catch(()=>{})}}}),await $.connect()?l("OpenClaw connector started",{port:ue}):(l("OpenClaw gateway not reachable, skipping connector"),$=null)):l("OpenClaw integration skipped (no token found)")}catch(s){y("OpenClaw connector failed (non-fatal)",{error:s instanceof Error?s.message:String(s)})}process.env.AEROSTACK_CHAT_LISTENER!=="false"&&dt()}let E=null,J=!1;async function ut(){return typeof globalThis.WebSocket<"u"?globalThis.WebSocket:(await import("ws")).default}function dt(){J=!1;let e=null,t=0;const n=async()=>{if(!J){if(E){try{E.close()}catch{}E=null}try{const o=await ut(),a=m.replace(/^http/,"ws")+`/chat-relay?token=${encodeURIComponent(h||"")}&client_type=bridge&client_id=${encodeURIComponent(ne)}`,r=new o(a);E=r,r.onopen=()=>{l("[chat-relay] WebSocket connected to workspace"),t=0},r.onmessage=i=>{const c=typeof i.data=="string"?i.data:"";if(c)try{const _=JSON.parse(c);if(_.type==="pong")return;if(_.type==="ping"){try{r.send(JSON.stringify({type:"pong",ts:Date.now()}))}catch{}return}l("[chat-relay] WS frame received",{dataLength:c.length}),ht(c)}catch{}},r.onclose=i=>{l("[chat-relay] WebSocket closed",{code:i.code,reason:i.reason}),E=null,J||s()},r.onerror=()=>{y("[chat-relay] WebSocket error"),t++}}catch(o){y("[chat-relay] Connection failed",{error:o?.message}),t++,s()}}},s=()=>{if(J||e)return;const o=Math.min(2e3*Math.pow(2,t),3e4);l(`[chat-relay] Reconnecting in ${o/1e3}s (failures: ${t})`),e=setTimeout(()=>{e=null,n()},o)};n()}function _e(e,t){if(!E)return!1;try{return E.send(JSON.stringify({type:"chat_reply",content:e.slice(0,1e4),session_id:t||P,sender_name:"Agent",content_type:"text"})),!0}catch{return!1}}function re(e,t=!0){if(E)try{E.send(JSON.stringify({type:"typing",session_id:e||P,is_typing:t}))}catch{}}function pt(){if(J=!0,E){try{E.close()}catch{}E=null}}async function ht(e){try{const t=JSON.parse(e);if(t.type!=="chat_message"){l("[chat-trace] Event ignored (not chat_message)",{type:t.type});return}const n=Date.now();l("[chat-trace] Chat event received",{type:t.type,sender:t.sender_name,contentType:t.content_type,hasContent:!!t.content,sessionId:t.session_id,messageId:t.message_id});let s=[];if(t.metadata_json)try{const g=JSON.parse(t.metadata_json);g?.v===1&&Array.isArray(g.attachments)&&(s=g.attachments)}catch{}const o=!!t.content&&t.content_type!=="media",a=s.length>0;if(!o&&!a)return;const r=t.session_id;l("Chat message received from admin",{sender:t.sender_name,sessionId:r,contentType:t.content_type,attachments:s.length});const i=/^[a-zA-Z0-9_-]{1,128}$/.test(t.message_id??"")?t.message_id:null,c=s.map(g=>{const U=Math.round(g.size/1024),X=U>=1024?`${(U/1024).toFixed(1)} MB`:`${U} KB`,Q=w((g.filename||"file").slice(0,200)),M=w((g.mime_type||"application/octet-stream").slice(0,100)),v=i?`${m}/chat/media/${i}/${encodeURIComponent(g.filename)}`:"(unavailable)";return`[Attached: ${Q} (${M}, ${X}) \u2014 ${v}]`}),_=c.length>0?`
|
|
62
62
|
`+c.join(`
|
|
63
|
-
`):"",b=
|
|
63
|
+
`):"",b=o?t.content+(_?`
|
|
64
64
|
`+c.map(g=>g).join(`
|
|
65
65
|
`):""):c.join(`
|
|
66
|
-
`);r&&(P=r,j(r,"user",b)),z.length>=Qe&&z.shift(),z.push({sender_name:t.sender_name??"Admin",content:
|
|
67
|
-
`).pop()||v.trim();
|
|
66
|
+
`);r&&(P=r,j(r,"user",b)),z.length>=Qe&&z.shift(),z.push({sender_name:t.sender_name??"Admin",content:o?t.content:c.join(", "),created_at:Date.now(),session_id:r,content_type:t.content_type,metadata_json:t.metadata_json,message_id:t.message_id}),L=Date.now();const D=w(t.sender_name??"Admin"),k=o?w(t.content):"",C=r||P||"_default",H=`aerostack-chat-${/^[a-zA-Z0-9_-]{1,64}$/.test(C)?C:"_default"}`,R=`turn-${Date.now()}-${Math.random().toString(36).slice(2,8)}`,A=[];F.set(R,A),I=R;try{const g=k?k+_:_.trim(),U=`[${D} via Aerostack Dashboard]: ${g}`;if(re(r,!0),$){const M=Date.now();l("[chat-trace] WS fast path starting",{session:H,promptLength:U.length,elapsedMs:M-n});const v=await $.sendChatMessage(U);if(v){const S=v.trim(),u=Date.now();l("[chat-trace] WS fast path got response",{responseLength:S.length,llmMs:u-M,totalMs:u-n}),await new Promise(d=>setTimeout(d,500));for(const d of A)d.status==="running"&&(d.status="timeout",d.ended_at=Date.now(),d.duration_ms=d.ended_at-d.started_at);if(A.length>0){const d=A.reduce((p,G)=>p+(G.duration_ms||0),0),f=A.length;try{await T(`${m}/chat/messages`,{method:"POST",headers:{Authorization:`Bearer ${h}`,"Content-Type":"application/json"},body:JSON.stringify({content:`Used ${f} tool${f>1?"s":""}`,content_type:"tool_activity",session_id:r,metadata:JSON.stringify({v:1,tools:A.map(p=>({name:p.name,category:p.category,duration_ms:p.duration_ms,status:p.status,args_summary:p.args_summary?.slice(0,500),error:p.error?.slice(0,500)})),total_duration_ms:d})})})}catch{}}if(F.delete(R),I===R&&(I=null),re(r,!1),S.length>=2)if(j(C,"agent",S),_e(S,r))l("[chat-trace] Reply sent via WS upstream fast path (no HTTP POST needed)",{totalMs:Date.now()-n});else{const f=Date.now();try{const p=await T(`${m}/chat/messages`,{method:"POST",headers:{Authorization:`Bearer ${h}`,"Content-Type":"application/json"},body:JSON.stringify({content:S.slice(0,1e4),session_id:r})}),G=Date.now()-f;p.ok?l("[chat-trace] Reply sent (HTTP fallback)",{replyMs:G,totalMs:Date.now()-n}):y("[chat-trace] Reply failed (HTTP fallback)",{status:p.status,replyMs:G})}catch(p){y("[chat-trace] Reply error (HTTP fallback)",{error:p?.message,replyMs:Date.now()-f})}}return}l("[chat-trace] WS fast path failed, falling back to CLI",{elapsedMs:Date.now()-n})}const{execFile:X}=await import("child_process"),Q=Date.now();l("[chat-trace] CLI fallback starting",{sender:D,session:H,elapsedMs:Q-n}),X("openclaw",["agent","--agent","main","--json","--session-id",H,"-m",U],{timeout:12e4},async(M,v)=>{let S=[];if(!M&&v)try{const u=JSON.parse(v.trim()),d=u?.result?.payloads;Array.isArray(d)&&d.length>0?S=d:(u.text||u.reply||u.message)&&(S=[{text:u.text||u.reply||u.message}])}catch{const u=v.trim().split(`
|
|
67
|
+
`).pop()||v.trim();u&&(S=[{text:u}])}S.length===0&&(S=[{text:`Message received. I'll process "${k.slice(0,100)}" shortly.`}]),await new Promise(u=>setTimeout(u,500));for(const u of A)u.status==="running"&&(u.status="timeout",u.ended_at=Date.now(),u.duration_ms=u.ended_at-u.started_at);if(A.length>0){const u=A.reduce((f,p)=>f+(p.duration_ms||0),0),d=A.length;try{await T(`${m}/chat/messages`,{method:"POST",headers:{Authorization:`Bearer ${h}`,"Content-Type":"application/json"},body:JSON.stringify({content:`Used ${d} tool${d>1?"s":""}`,content_type:"tool_activity",session_id:r,metadata:JSON.stringify({v:1,tools:A.map(f=>({name:f.name,category:f.category,duration_ms:f.duration_ms,status:f.status,args_summary:f.args_summary?.slice(0,500),error:f.error?.slice(0,500)})),total_duration_ms:u})})})}catch{}}F.delete(R),I===R&&(I=null),re(r,!1);for(const u of S){const d=u.text?.trim()||"";if(u.mediaUrl){let f=!1;try{f=new URL(u.mediaUrl).protocol==="https:"}catch{}if(f)try{const p=await T(`${m}/chat/media`,{method:"POST",headers:{Authorization:`Bearer ${h}`,"Content-Type":"application/json"},body:JSON.stringify({media_url:u.mediaUrl,content:d||void 0,session_id:r})});p.ok?l("Chat media sent to workspace",{mediaUrl:u.mediaUrl}):y("Chat media upload failed",{status:p.status})}catch(p){y("Failed to send chat media",{error:p?.message})}else y("Skipping invalid media URL",{mediaUrl:u.mediaUrl});j(C,"agent",d||`[media: ${u.mediaUrl}]`)}else if(d&&d.length>=2)if(j(C,"agent",d),_e(d,r))l("Chat reply sent via WS upstream (no HTTP POST needed)");else try{const p=await T(`${m}/chat/messages`,{method:"POST",headers:{Authorization:`Bearer ${h}`,"Content-Type":"application/json"},body:JSON.stringify({content:d.slice(0,1e4),session_id:r})});p.ok?l("Chat reply sent to workspace"):y("Chat reply failed",{status:p.status})}catch(p){y("Failed to send chat reply",{error:p?.message})}}})}catch(g){y("Failed to spawn agent turn",{error:g?.message}),F.delete(R),I===R&&(I=null)}}catch{}}function Y(){pt(),$?.stop(),Ie(),process.exit(0)}process.on("SIGTERM",()=>{Y()}),process.on("SIGINT",()=>{Y()}),process.argv.includes("--check")?it():lt().catch(e=>{Le("Fatal error",{error:e instanceof Error?e.message:String(e)}),process.exit(1)});
|