@aerostack/gateway 0.15.21 → 0.15.23

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.
Files changed (3) hide show
  1. package/README.md +109 -15
  2. package/dist/index.js +41 -21
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @aerostack/gateway
2
2
 
3
- Connect any MCP client (Claude Desktop, Cline, Cursor, Goose, Windsurf, and more) to an [Aerostack Workspace](https://aerostack.dev) — one URL, all your tools, with built-in human approval gates.
3
+ Connect any MCP client (Claude Code, Claude Desktop, Cline, Cursor, Goose, Windsurf, OpenClaw, and more) to an [Aerostack Workspace](https://aerostack.dev) — one URL, all your tools, with built-in human approval gates.
4
4
 
5
5
  [![License: MIT](https://img.shields.io/badge/LICENSE_//_MIT-3b5bdb?style=for-the-badge&labelColor=eff6ff)](https://opensource.org/licenses/MIT)
6
6
  [![npm](https://img.shields.io/npm/v/@aerostack/gateway?style=for-the-badge&color=3b5bdb&labelColor=eff6ff)](https://www.npmjs.com/package/@aerostack/gateway)
@@ -15,18 +15,45 @@ Instead of configuring dozens of separate MCP servers in your AI tool, point at
15
15
  - **Centralized OAuth** — 27+ providers managed by Aerostack, not your agent
16
16
  - **Human approval gates** — sensitive tools require your approval before executing
17
17
  - **Local Guardian** — approval gates for local operations (file delete, shell, git push, deploy)
18
+ - **Agent Chat** — message your agent from the dashboard with full conversation context
18
19
  - **Per-tool permissions** — workspace tokens scope exactly which tools are accessible
19
20
  - **Usage tracking & audit** — every tool call logged and metered
20
- - **Rate limiting** — per-token, plan-tiered
21
21
 
22
22
  ## Quick Start
23
23
 
24
24
  ### 1. Get a workspace token
25
25
 
26
- In your [Aerostack Admin Dashboard](https://app.aerostack.dev), open your workspace and generate a token (`mwt_...`).
26
+ In your [Aerostack Admin Dashboard](https://app.aerostack.dev), open your workspace and click **Connect**. Copy the workspace URL and token.
27
+
28
+ Your workspace URL looks like: `https://mcp.aerostack.dev/ws/your-workspace`
27
29
 
28
30
  ### 2. Configure your AI tool
29
31
 
32
+ **Claude Code** — run in your terminal:
33
+
34
+ ```bash
35
+ claude mcp add aerostack -- npx -y @aerostack/gateway \
36
+ --env AEROSTACK_WORKSPACE_URL=https://mcp.aerostack.dev/ws/your-workspace \
37
+ --env AEROSTACK_TOKEN=mwt_your_token_here
38
+ ```
39
+
40
+ Or add to `.mcp.json` in your project root:
41
+
42
+ ```json
43
+ {
44
+ "mcpServers": {
45
+ "aerostack": {
46
+ "command": "npx",
47
+ "args": ["-y", "@aerostack/gateway"],
48
+ "env": {
49
+ "AEROSTACK_WORKSPACE_URL": "https://mcp.aerostack.dev/ws/your-workspace",
50
+ "AEROSTACK_TOKEN": "mwt_your_token_here"
51
+ }
52
+ }
53
+ }
54
+ }
55
+ ```
56
+
30
57
  **Claude Desktop** — edit `~/Library/Application Support/Claude/claude_desktop_config.json`:
31
58
 
32
59
  ```json
@@ -36,7 +63,7 @@ In your [Aerostack Admin Dashboard](https://app.aerostack.dev), open your worksp
36
63
  "command": "npx",
37
64
  "args": ["-y", "@aerostack/gateway"],
38
65
  "env": {
39
- "AEROSTACK_WORKSPACE_URL": "https://api.aerostack.dev/api/gateway/ws/my-workspace",
66
+ "AEROSTACK_WORKSPACE_URL": "https://mcp.aerostack.dev/ws/your-workspace",
40
67
  "AEROSTACK_TOKEN": "mwt_your_token_here"
41
68
  }
42
69
  }
@@ -44,6 +71,27 @@ In your [Aerostack Admin Dashboard](https://app.aerostack.dev), open your worksp
44
71
  }
45
72
  ```
46
73
 
74
+ **OpenClaw** — add the MCP server:
75
+
76
+ ```bash
77
+ openclaw mcp set aerostack '{
78
+ "command": "npx",
79
+ "args": ["-y", "@aerostack/gateway"],
80
+ "env": {
81
+ "AEROSTACK_WORKSPACE_URL": "https://mcp.aerostack.dev/ws/your-workspace",
82
+ "AEROSTACK_TOKEN": "mwt_your_token_here"
83
+ }
84
+ }'
85
+ ```
86
+
87
+ Then restart the gateway:
88
+
89
+ ```bash
90
+ openclaw gateway restart
91
+ ```
92
+
93
+ > **Note:** OpenClaw loads MCP servers when a session starts. Send a message via Telegram (or another connected channel) to initialize the tools. After the first message, tools are available for all sessions.
94
+
47
95
  **Cline (VS Code)** — add to VS Code settings under `cline.mcpServers`:
48
96
 
49
97
  ```json
@@ -52,7 +100,7 @@ In your [Aerostack Admin Dashboard](https://app.aerostack.dev), open your worksp
52
100
  "command": "npx",
53
101
  "args": ["-y", "@aerostack/gateway"],
54
102
  "env": {
55
- "AEROSTACK_WORKSPACE_URL": "https://api.aerostack.dev/api/gateway/ws/my-workspace",
103
+ "AEROSTACK_WORKSPACE_URL": "https://mcp.aerostack.dev/ws/your-workspace",
56
104
  "AEROSTACK_TOKEN": "mwt_your_token_here"
57
105
  }
58
106
  }
@@ -62,11 +110,38 @@ In your [Aerostack Admin Dashboard](https://app.aerostack.dev), open your worksp
62
110
  **Cursor / Goose / HTTP-native clients** — use the workspace URL directly (no bridge needed):
63
111
 
64
112
  ```
65
- URL: https://api.aerostack.dev/api/gateway/ws/my-workspace
113
+ URL: https://mcp.aerostack.dev/ws/your-workspace
66
114
  Token: mwt_your_token_here (Authorization: Bearer header)
67
115
  ```
68
116
 
69
- ### 3. Use it
117
+ ### 3. Verify your connection
118
+
119
+ Run the built-in diagnostic check to make sure everything is working:
120
+
121
+ ```bash
122
+ AEROSTACK_WORKSPACE_URL="https://mcp.aerostack.dev/ws/your-workspace" \
123
+ AEROSTACK_TOKEN="mwt_your_token_here" \
124
+ npx @aerostack/gateway --check
125
+ ```
126
+
127
+ You should see:
128
+
129
+ ```
130
+ Aerostack Connection Check
131
+
132
+ ✅ Workspace URL reachable (https://mcp.aerostack.dev/ws/your-workspace)
133
+ ✅ Token valid (My Token, role: editor)
134
+ ✅ Workspace connected ("my-workspace")
135
+ ✅ Tools available (14 tools)
136
+ ✅ Dashboard registration (bridge visible in admin)
137
+
138
+ All checks passed. Your agent is connected.
139
+ Dashboard: https://app.aerostack.dev
140
+ ```
141
+
142
+ If any step fails, the output tells you exactly what to fix.
143
+
144
+ ### 4. Use it
70
145
 
71
146
  ```
72
147
  You: Create a GitHub issue for the login bug
@@ -75,16 +150,27 @@ AI: [calls github__create_issue via Aerostack workspace]
75
150
 
76
151
  Tools are namespaced as `{server}__{tool}` (e.g., `github__create_issue`, `slack__send_message`).
77
152
 
153
+ ## Troubleshooting
154
+
155
+ | Problem | Solution |
156
+ |---------|----------|
157
+ | `npx` hangs or fails | Run `npm install -g @aerostack/gateway` instead, then use `"command": "aerostack-gateway"` |
158
+ | "Token invalid" in `--check` | Regenerate token at dashboard → Workspace → Tokens |
159
+ | "0 tools found" | Add MCP servers to your workspace in the dashboard first |
160
+ | OpenClaw: tools not showing | Send a message via Telegram or another channel to initialize the session |
161
+ | Dashboard shows "No agents" | Run `--check` to verify, or wait 2 minutes for the heartbeat to register |
162
+ | "Workspace unreachable" | Check the URL format: `https://mcp.aerostack.dev/ws/your-workspace` |
163
+
78
164
  ## How It Works
79
165
 
80
166
  ```
81
167
  Your AI tool (stdio) → @aerostack/gateway → HTTPS → Aerostack Workspace
82
-
83
- ┌────────────────────────┐
84
- │ Fan-out to MCP servers │
85
- │ GitHub, Slack, Gmail, │
86
- │ custom Workers, etc. │
87
- └────────────────────────┘
168
+
169
+ ┌────────────────────────┐
170
+ │ Fan-out to MCP servers │
171
+ │ GitHub, Slack, Gmail, │
172
+ │ custom Workers, etc. │
173
+ └────────────────────────┘
88
174
  ```
89
175
 
90
176
  The bridge:
@@ -93,6 +179,7 @@ The bridge:
93
179
  2. Forwards all JSON-RPC requests to your workspace over HTTPS
94
180
  3. Handles SSE streaming responses transparently
95
181
  4. Manages the approval gate flow automatically
182
+ 5. Maintains conversation history for agent chat context
96
183
 
97
184
  ## Approval Gates
98
185
 
@@ -136,13 +223,20 @@ Agent wants to: rm -rf ./old-config/
136
223
 
137
224
  | Variable | Required | Default | Description |
138
225
  |----------|----------|---------|-------------|
139
- | `AEROSTACK_WORKSPACE_URL` | Yes | — | Full workspace endpoint URL |
226
+ | `AEROSTACK_WORKSPACE_URL` | Yes | — | Full workspace endpoint URL (`https://mcp.aerostack.dev/ws/...`) |
140
227
  | `AEROSTACK_TOKEN` | Yes | — | Workspace token (`mwt_...`) |
141
228
  | `AEROSTACK_APPROVAL_POLL_MS` | No | `3000` | Approval polling interval (ms) |
142
- | `AEROSTACK_APPROVAL_TIMEOUT_MS` | No | `300000` | Max approval wait time (5 min) |
229
+ | `AEROSTACK_APPROVAL_TIMEOUT_MS` | No | `86400000` | Max approval wait time (24h default) |
143
230
  | `AEROSTACK_REQUEST_TIMEOUT_MS` | No | `30000` | HTTP request timeout (30s) |
144
231
  | `AEROSTACK_LOCAL_GUARDIAN` | No | `true` | Set `false` to disable Local Guardian |
145
232
  | `AEROSTACK_LOG_LEVEL` | No | `info` | Log level: debug, info, warn, error |
233
+ | `AEROSTACK_APPROVAL_MODE` | No | `async` | Approval mode: `async` (non-blocking) or `sync` |
234
+
235
+ ## CLI Flags
236
+
237
+ | Flag | Description |
238
+ |------|-------------|
239
+ | `--check` | Run connection diagnostics and exit (no MCP server started) |
146
240
 
147
241
  ## Supported MCP Methods
148
242
 
package/dist/index.js CHANGED
@@ -1,24 +1,28 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- import{Server as W}from"@modelcontextprotocol/sdk/server/index.js";import{StdioServerTransport as G}from"@modelcontextprotocol/sdk/server/stdio.js";import{ListToolsRequestSchema as V,CallToolRequestSchema as z,ListResourcesRequestSchema as B,ReadResourceRequestSchema as F,ListPromptsRequestSchema as Y,GetPromptRequestSchema as J}from"@modelcontextprotocol/sdk/types.js";import{readFile as Q,writeFile as X}from"node:fs/promises";import{join as Z}from"node:path";import{homedir as ee}from"node:os";import{resolveApproval as b}from"./resolution.js";import{startHookServer as te,installClaudeHook as se,stopHookServer as re}from"./hook-server.js";import{OpenClawConnector as oe,resolveOpenClawToken as ne}from"./openclaw-connector.js";import{info as c,warn as m,error as ae}from"./logger.js";const L=Z(ee(),".openclaw","pre-authorized.json");async function ie(t){try{let s={};try{s=JSON.parse(await Q(L,"utf-8"))}catch{}s[t]=Date.now(),await X(L,JSON.stringify(s))}catch{}}const P=process.env.AEROSTACK_WORKSPACE_URL,u=process.env.AEROSTACK_TOKEN;function R(t,s,r){const e=parseInt(t??String(s),10);return Number.isFinite(e)&&e>=r?e:s}const $=R(process.env.AEROSTACK_APPROVAL_POLL_MS,3e3,500),I=R(process.env.AEROSTACK_APPROVAL_TIMEOUT_MS,864e5,5e3),U=R(process.env.AEROSTACK_REQUEST_TIMEOUT_MS,3e4,1e3),ce=process.env.AEROSTACK_HOOK_SERVER!=="false",le=R(process.env.AEROSTACK_HOOK_PORT,18321,1024),ue=process.env.AEROSTACK_HOOK_AUTO_INSTALL!=="false",pe=process.env.AEROSTACK_OPENCLAW_ENABLED!=="false",D=R(process.env.AEROSTACK_OPENCLAW_PORT,18789,1024),de=process.env.AEROSTACK_OPENCLAW_TOKEN;P||(process.stderr.write(`ERROR: AEROSTACK_WORKSPACE_URL is required
4
- `),process.exit(1)),u||(process.stderr.write(`ERROR: AEROSTACK_TOKEN is required
5
- `),process.exit(1));let O;try{if(O=new URL(P),O.protocol!=="https:"&&O.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
- `),process.exit(1)}O.protocol==="http:"&&!O.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=P.replace(/\/+$/,""),K=crypto.randomUUID();function me(){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 j=me();let h=null,_=Date.now();const he=3e4;async function f(t,s){const r={jsonrpc:"2.0",id:Date.now(),method:t,params:s??{}},e=new AbortController,o=setTimeout(()=>e.abort(),U);try{const n=await fetch(p,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${u}`,"User-Agent":"aerostack-gateway/0.15.14","X-Agent-Id":"aerostack-gateway","X-Bridge-Id":K,"X-Agent-Type":j},body:JSON.stringify(r),signal:e.signal});if(clearTimeout(o),(n.headers.get("content-type")??"").includes("text/event-stream")){const l=await n.text();return fe(l,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 fe(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 ge=new Set(["aerostack__guardian_report","aerostack__check_approval","aerostack__guardian_check"]);function we(t,s){if(ge.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)"}f("tools/call",{name:"aerostack__guardian_report",arguments:{action:`${t}(${Object.keys(s).join(", ")})`,category:r,risk_level:"low",details:o}}).catch(()=>{})}const ye=new Set(["aerostack__check_approval"]),H=[{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"]}}],_e=new Set(H.map(t=>t.name));function g(t){return t.replace(/\[/g,"(").replace(/\]/g,")")}const S=[],Ae=100;async function v(t,s){const r=new AbortController,e=setTimeout(()=>r.abort(),U);try{return await fetch(t,{...s,signal:r.signal})}finally{clearTimeout(e)}}async function Ee(){if(S.length>0){const t=S.splice(0);return _=Date.now(),[{type:"text",text:`Messages from workspace owner:
3
+ import{Server as Q}from"@modelcontextprotocol/sdk/server/index.js";import{StdioServerTransport as Z}from"@modelcontextprotocol/sdk/server/stdio.js";import{ListToolsRequestSchema as ee,CallToolRequestSchema as te,ListResourcesRequestSchema as se,ReadResourceRequestSchema as oe,ListPromptsRequestSchema as re,GetPromptRequestSchema as ne}from"@modelcontextprotocol/sdk/types.js";import{readFile as ae,writeFile as ie}from"node:fs/promises";import{join as ce}from"node:path";import{homedir as le}from"node:os";import{resolveApproval as I}from"./resolution.js";import{startHookServer as ue,installClaudeHook as pe,stopHookServer as de}from"./hook-server.js";import{OpenClawConnector as he,resolveOpenClawToken as me}from"./openclaw-connector.js";import{info as c,warn as h,error as fe}from"./logger.js";const H=ce(le(),".openclaw","pre-authorized.json");async function ge(e){try{let s={};try{s=JSON.parse(await ae(H,"utf-8"))}catch{}s[e]=Date.now(),await ie(H,JSON.stringify(s))}catch{}}const L=process.env.AEROSTACK_WORKSPACE_URL,l=process.env.AEROSTACK_TOKEN;function O(e,s,o){const t=parseInt(e??String(s),10);return Number.isFinite(t)&&t>=o?t:s}const D=O(process.env.AEROSTACK_APPROVAL_POLL_MS,3e3,500),U=O(process.env.AEROSTACK_APPROVAL_TIMEOUT_MS,864e5,5e3),W=O(process.env.AEROSTACK_REQUEST_TIMEOUT_MS,3e4,1e3),we=process.env.AEROSTACK_HOOK_SERVER!=="false",ye=O(process.env.AEROSTACK_HOOK_PORT,18321,1024),_e=process.env.AEROSTACK_HOOK_AUTO_INSTALL!=="false",Ae=process.env.AEROSTACK_OPENCLAW_ENABLED!=="false",M=O(process.env.AEROSTACK_OPENCLAW_PORT,18789,1024),Ee=process.env.AEROSTACK_OPENCLAW_TOKEN;L||(process.stderr.write(`ERROR: AEROSTACK_WORKSPACE_URL is required
4
+ `),process.exit(1)),l||(process.stderr.write(`ERROR: AEROSTACK_TOKEN is required
5
+ `),process.exit(1));let S;try{if(S=new URL(L),S.protocol!=="https:"&&S.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
+ `),process.exit(1)}S.protocol==="http:"&&!S.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=L.replace(/\/+$/,""),q=crypto.randomUUID();function Te(){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 F=Te();let f=null,E=Date.now();const ke=3e4;async function g(e,s){const o={jsonrpc:"2.0",id:Date.now(),method:e,params:s??{}},t=new AbortController,r=setTimeout(()=>t.abort(),W);try{const n=await fetch(u,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${l}`,"User-Agent":"aerostack-gateway/0.15.14","X-Agent-Id":"aerostack-gateway","X-Bridge-Id":q,"X-Agent-Type":F},body:JSON.stringify(o),signal:t.signal});if(clearTimeout(r),(n.headers.get("content-type")??"").includes("text/event-stream")){const w=await n.text();return Re(w,o.id)}return await n.json()}catch(n){clearTimeout(r);const a=n instanceof Error?n.message:"Unknown error";return n instanceof Error&&n.name==="AbortError"?{jsonrpc:"2.0",id:o.id,error:{code:-32603,message:"Request timed out"}}:{jsonrpc:"2.0",id:o.id,error:{code:-32603,message:`HTTP error: ${a}`}}}}function Re(e,s){const o=e.split(`
8
+ `);let t=null;for(const r of o)if(r.startsWith("data: "))try{t=JSON.parse(r.slice(6))}catch{}return t??{jsonrpc:"2.0",id:s,error:{code:-32603,message:"Empty SSE response"}}}const Oe=new Set(["aerostack__guardian_report","aerostack__check_approval","aerostack__guardian_check"]);function Se(e,s){if(Oe.has(e))return;let o="other";const t=e.toLowerCase();t.includes("exec")||t.includes("bash")||t.includes("shell")||t.includes("command")||t.includes("run")?o="exec_command":t.includes("write")||t.includes("edit")||t.includes("create")||t.includes("patch")?o="file_write":t.includes("delete")||t.includes("remove")||t.includes("trash")||t.includes("unlink")?o="file_delete":t.includes("fetch")||t.includes("http")||t.includes("request")||t.includes("api")||t.includes("get")||t.includes("post")?o="api_call":t.includes("install")||t.includes("package")||t.includes("npm")||t.includes("pip")?o="package_install":t.includes("config")||t.includes("setting")||t.includes("env")?o="config_change":t.includes("deploy")||t.includes("publish")||t.includes("release")?o="deploy":t.includes("send")||t.includes("message")||t.includes("email")||t.includes("notify")||t.includes("slack")||t.includes("telegram")?o="message_send":(t.includes("read")||t.includes("query")||t.includes("search")||t.includes("list")||t.includes("get"))&&(o="data_access");let r;try{const n=JSON.stringify(s);r=n.length>500?n.slice(0,500)+"...":n}catch{r="(unable to serialize)"}g("tools/call",{name:"aerostack__guardian_report",arguments:{action:`${e}(${Object.keys(s).join(", ")})`,category:o,risk_level:"low",details:r}}).catch(()=>{})}const ve=new Set(["aerostack__check_approval"]),G=[{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"]}}],Ce=new Set(G.map(e=>e.name));function y(e){return e.replace(/\[/g,"(").replace(/\]/g,")")}const v=[],be=100,T=new Map,V=50,B=10;let C=null,z=!1;function P(e,s,o){let t=T.get(e);t||(t=[],T.set(e,t)),t.push({role:s,content:o,ts:Date.now()}),t.length>V&&t.splice(0,t.length-V)}async function Y(e){if(!z)try{const s=await k(`${u}/chat/history?limit=${B}`,{headers:{Authorization:`Bearer ${l}`}});if(!s.ok)return;const o=await s.json();o.session_id&&(C=o.session_id);for(const t of o.messages??[]){const r=t.sender_type==="agent"?"agent":"user",n=o.session_id||e;let a=T.get(n);a||(a=[],T.set(n,a)),a.push({role:r,content:t.content,ts:t.created_at})}z=!0,c("Thread history seeded from D1",{sessionId:o.session_id,count:o.messages?.length??0})}catch{}}function K(e){const s=T.get(e);return!s||s.length===0?"":`Previous conversation:
9
+ ${s.slice(-B).map(r=>`[${r.role==="user"?"Admin":"You (Agent)"}] ${r.content}`).join(`
10
+ `)}
9
11
 
10
- ${t.map(r=>`${g(r.sender_name)}: ${g(r.content)}`).join(`
12
+ `}async function k(e,s){const o=new AbortController,t=setTimeout(()=>o.abort(),W);try{return await fetch(e,{...s,signal:o.signal})}finally{clearTimeout(t)}}async function $e(){const e=C||"_default";if(await Y(e),v.length>0){const s=v.splice(0);E=Date.now();const o=K(e),t=s.map(r=>`${y(r.sender_name)}: ${y(r.content)}`).join(`
11
13
 
12
- `)}`}]}try{const s=await v(`${p}/chat/pending?since=${_}`,{headers:{Authorization:`Bearer ${u}`}});if(!s.ok)return[{type:"text",text:"No new messages."}];const e=(await s.json()).messages??[];return _=Date.now(),e.length===0?[{type:"text",text:"No new messages from the workspace owner."}]:[{type:"text",text:`Messages from workspace owner:
14
+ `);return[{type:"text",text:`${o}New messages from workspace owner:
13
15
 
14
- ${e.map(n=>`${g(n.sender_name)}: ${g(n.content)}`).join(`
16
+ ${t}`}]}try{const o=await k(`${u}/chat/pending?since=${E}`,{headers:{Authorization:`Bearer ${l}`}});if(!o.ok)return[{type:"text",text:"No new messages."}];const r=(await o.json()).messages??[];if(E=Date.now(),r.length===0)return[{type:"text",text:"No new messages from the workspace owner."}];for(const i of r)P(e,"user",i.content);const n=K(e),a=r.map(i=>`${y(i.sender_name)}: ${y(i.content)}`).join(`
15
17
 
16
- `)}`}]}catch{return[{type:"text",text:"Failed to check messages."}]}}async function Te(t){const s=typeof t.message=="string"?t.message.trim().slice(0,1e4):"";if(!s)return[{type:"text",text:"Error: message is required."}];try{const r=await v(`${p}/chat/messages`,{method:"POST",headers:{Authorization:`Bearer ${u}`,"Content-Type":"application/json"},body:JSON.stringify({content:s})});return r.ok?[{type:"text",text:"Reply sent to workspace owner."}]:[{type:"text",text:`Failed to send reply: ${(await r.json().catch(()=>({error:"Unknown error"}))).error||r.statusText}`}]}catch{return[{type:"text",text:"Failed to send reply."}]}}async function Re(t,s){we(t,s);const r=await f("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"}};c("Tool gate: blocking until approval resolves",{tool:t,approvalId:a});const i=await b({approvalId:a,wsUrl:n?.ws_url,pollUrl:n?.polling_url??`${p}/approval-status/${a}`,pollIntervalMs:$,timeoutMs:I,token:u});c("Tool gate resolved",{tool:t,status:i.status}),k({approvalId:a,toolName:t,toolArgs:s,sessionKey:null,gate:"tool_gate",pollUrl:"",authToken:u},i.status,!0);const l=x({approvalId:a,toolName:t,toolArgs:s,sessionKey:null,gate:"tool_gate",pollUrl:"",authToken:u},i);return{jsonrpc:"2.0",id:r.id,result:{content:[{type:"text",text:l}]}}}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;c("Permission gate: blocking until approval resolves",{tool:t,approvalId:n});const a=await b({approvalId:n,wsUrl:o.ws_url,pollUrl:o.polling_url??`${p}/approval-status/${n}`,pollIntervalMs:$,timeoutMs:I,token:u});c("Permission gate resolved",{tool:t,status:a.status}),k({approvalId:n,toolName:t,toolArgs:s,sessionKey:null,gate:"permission_gate",pollUrl:"",authToken:u},a.status,!0);const i=x({approvalId:n,toolName:t,toolArgs:s,sessionKey:null,gate:"permission_gate",pollUrl:"",authToken:u},a);return{jsonrpc:"2.0",id:r.id,result:{content:[{type:"text",text:i}]}}}return r}function He(t){b({approvalId:t.approvalId,wsUrl:t.wsUrl,pollUrl:t.pollUrl,pollIntervalMs:$,timeoutMs:I,token:t.authToken}).then(async s=>{c("Approval resolved",{tool:t.toolName,status:s.status,session:t.sessionKey});const r=x(t,s),e=t.sessionKey??h?.getLastActiveSession()??null;if((s.status==="approved"||s.status==="executed")&&e&&ie(e).catch(()=>{}),e&&h){if(await h.sendToSession(e,r)){c("Agent resumed via sessions.send",{session:e,status:s.status}),k(t,s.status,!0);return}m("sessions.send failed, falling back to channel notification",{session:e})}try{await w.notification({method:"notifications/claude/channel",params:{content:r,meta:{tool_name:t.toolName,approval_id:t.approvalId,status:s.status,gate:t.gate}}}),c("Agent resumed via channel notification",{tool:t.toolName,status:s.status}),k(t,s.status,!0)}catch(o){m("Channel notification failed",{error:o instanceof Error?o.message:String(o)}),k(t,s.status,!1)}}).catch(s=>{m("Background approval resolver error",{error:s instanceof Error?s.message:String(s)})})}function k(t,s,r){const e=typeof t.toolArgs.action=="string"?t.toolArgs.action:null,o=e??t.toolName.replace(/^(?:\w+__)*aerostack__/,"")??t.toolName,n=e?`${o}`:`${o}(${Object.keys(t.toolArgs).join(", ")})`,a=r?"resume sent to agent":"session unavailable \u2014 resume not delivered";let i;s==="approved"||s==="executed"?i=r?`[RESUMED] ${n} \u2014 ${a}`:`[RESUME FAILED] ${n} \u2014 ${a}`:s==="rejected"?i=`[REJECTED] ${n} \u2014 rejection sent to agent`:s==="changes_requested"?i=`[CHANGES REQUESTED] ${n} \u2014 feedback sent to agent`:i=`[EXPIRED] ${n} \u2014 approval timed out, expiry sent to agent`,f("tools/call",{name:"aerostack__guardian_report",arguments:{action:i.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 x(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(`
17
- `):["[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(`
18
- `);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(`
19
- `);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(`
20
- `);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(`
21
- `);default:return`[APPROVAL UPDATE] Status for "${e}": ${s.status}.`}}const N=`
18
+ `);return[{type:"text",text:`${n}New messages from workspace owner:
19
+
20
+ ${a}`}]}catch{return[{type:"text",text:"Failed to check messages."}]}}async function Ne(e){const s=typeof e.message=="string"?e.message.trim().slice(0,1e4):"";if(!s)return[{type:"text",text:"Error: message is required."}];try{const o=await k(`${u}/chat/messages`,{method:"POST",headers:{Authorization:`Bearer ${l}`,"Content-Type":"application/json"},body:JSON.stringify({content:s})});return o.ok?(P(C||"_default","agent",s),[{type:"text",text:"Reply sent to workspace owner."}]):[{type:"text",text:`Failed to send reply: ${(await o.json().catch(()=>({error:"Unknown error"}))).error||o.statusText}`}]}catch{return[{type:"text",text:"Failed to send reply."}]}}async function Pe(e,s){Se(e,s);const o=await g("tools/call",{name:e,arguments:s});if(o.error?.code===-32050){const n=o.error.data,a=n?.approval_id;if(!a||!/^[a-zA-Z0-9_-]{4,128}$/.test(a))return{jsonrpc:"2.0",id:o.id,error:{code:-32603,message:"Approval required but no approval_id returned"}};c("Tool gate: blocking until approval resolves",{tool:e,approvalId:a});const i=await I({approvalId:a,wsUrl:n?.ws_url,pollUrl:n?.polling_url??`${u}/approval-status/${a}`,pollIntervalMs:D,timeoutMs:U,token:l});c("Tool gate resolved",{tool:e,status:i.status}),b({approvalId:a,toolName:e,toolArgs:s,sessionKey:null,gate:"tool_gate",pollUrl:"",authToken:l},i.status,!0);const w=j({approvalId:a,toolName:e,toolArgs:s,sessionKey:null,gate:"tool_gate",pollUrl:"",authToken:l},i);return{jsonrpc:"2.0",id:o.id,result:{content:[{type:"text",text:w}]}}}const r=o.result?._meta;if(r?.approval_id&&r?.status==="pending"){const n=r.approval_id;if(!/^[a-zA-Z0-9_-]{4,128}$/.test(n))return o;c("Permission gate: blocking until approval resolves",{tool:e,approvalId:n});const a=await I({approvalId:n,wsUrl:r.ws_url,pollUrl:r.polling_url??`${u}/approval-status/${n}`,pollIntervalMs:D,timeoutMs:U,token:l});c("Permission gate resolved",{tool:e,status:a.status}),b({approvalId:n,toolName:e,toolArgs:s,sessionKey:null,gate:"permission_gate",pollUrl:"",authToken:l},a.status,!0);const i=j({approvalId:n,toolName:e,toolArgs:s,sessionKey:null,gate:"permission_gate",pollUrl:"",authToken:l},a);return{jsonrpc:"2.0",id:o.id,result:{content:[{type:"text",text:i}]}}}return o}function Xe(e){I({approvalId:e.approvalId,wsUrl:e.wsUrl,pollUrl:e.pollUrl,pollIntervalMs:D,timeoutMs:U,token:e.authToken}).then(async s=>{c("Approval resolved",{tool:e.toolName,status:s.status,session:e.sessionKey});const o=j(e,s),t=e.sessionKey??f?.getLastActiveSession()??null;if((s.status==="approved"||s.status==="executed")&&t&&ge(t).catch(()=>{}),t&&f){if(await f.sendToSession(t,o)){c("Agent resumed via sessions.send",{session:t,status:s.status}),b(e,s.status,!0);return}h("sessions.send failed, falling back to channel notification",{session:t})}try{await _.notification({method:"notifications/claude/channel",params:{content:o,meta:{tool_name:e.toolName,approval_id:e.approvalId,status:s.status,gate:e.gate}}}),c("Agent resumed via channel notification",{tool:e.toolName,status:s.status}),b(e,s.status,!0)}catch(r){h("Channel notification failed",{error:r instanceof Error?r.message:String(r)}),b(e,s.status,!1)}}).catch(s=>{h("Background approval resolver error",{error:s instanceof Error?s.message:String(s)})})}function b(e,s,o){const t=typeof e.toolArgs.action=="string"?e.toolArgs.action:null,r=t??e.toolName.replace(/^(?:\w+__)*aerostack__/,"")??e.toolName,n=t?`${r}`:`${r}(${Object.keys(e.toolArgs).join(", ")})`,a=o?"resume sent to agent":"session unavailable \u2014 resume not delivered";let i;s==="approved"||s==="executed"?i=o?`[RESUMED] ${n} \u2014 ${a}`:`[RESUME FAILED] ${n} \u2014 ${a}`:s==="rejected"?i=`[REJECTED] ${n} \u2014 rejection sent to agent`:s==="changes_requested"?i=`[CHANGES REQUESTED] ${n} \u2014 feedback sent to agent`:i=`[EXPIRED] ${n} \u2014 approval timed out, expiry sent to agent`,g("tools/call",{name:"aerostack__guardian_report",arguments:{action:i.slice(0,500),category:"approval",risk_level:"low",details:JSON.stringify({tool:e.toolName,approvalId:e.approvalId,status:s,delivered:o,session:e.sessionKey}).slice(0,500)}}).catch(()=>{})}function j(e,s){const o=s.reviewer_note?.trim()||null,t=`${e.toolName}(${Object.keys(e.toolArgs).join(", ")})`;switch(s.status){case"approved":case"executed":return e.gate==="tool_gate"?["[APPROVAL RECEIVED] Your pending action has been approved.","",`Action: ${t}`,o?`Reviewer note: "${o}"`:null,"",`Please now execute this action: call ${e.toolName} with the same parameters as before.`].filter(r=>r!==null).join(`
21
+ `):["[APPROVAL RECEIVED] Your request has been approved by the workspace owner.",o?`Reviewer note: "${o}"`:null,"","You may now proceed with the action you requested approval for."].filter(r=>r!==null).join(`
22
+ `);case"changes_requested":return["[CHANGES REQUESTED] The workspace owner reviewed your action and requested changes.","",`Original action: ${t}`,`Reviewer feedback: "${o??"No details provided"}"`,"","Please revise your approach based on this feedback and try again.","Do NOT proceed with the original plan."].join(`
23
+ `);case"rejected":return["[REJECTED] The workspace owner denied this action.","",`Action: ${t}`,o?`Reason: "${o}"`:"No reason provided.","","Do not retry this action. If you believe this was a mistake, inform the user."].join(`
24
+ `);case"expired":return[`[EXPIRED] The approval request for "${t}" timed out \u2014 no decision was made.`,"","You may resubmit the request if the action is still needed."].join(`
25
+ `);default:return`[APPROVAL UPDATE] Status for "${t}": ${s.status}.`}}const x=`
22
26
  === AEROSTACK GUARDIAN POLICY (MANDATORY) ===
23
27
 
24
28
  You are operating under Aerostack workspace monitoring. These rules apply to ALL actions:
@@ -46,11 +50,27 @@ or before starting a new one, call aerostack__chat_check to see if the owner sen
46
50
  If there are messages, read them carefully and respond using aerostack__chat_reply. This keeps
47
51
  the owner informed and allows them to steer your work in real time.
48
52
  === END WORKSPACE CHAT ===
49
- `.trim(),Oe=["delete","remove","drop","truncate","destroy","wipe","reset","deploy","publish","release","push","exec","shell","bash","run","command","terminal","install","uninstall","send","email","notify","webhook"];function Se(t,s){const r=t.toLowerCase(),e=Oe.some(n=>r.includes(n)),o=s??"";return e?`[REQUIRES GUARDIAN APPROVAL] ${o}`.trim():o}let M=null;async function E(){if(M)return;const t=await f("initialize",{protocolVersion:"2024-11-05",capabilities:{},clientInfo:{name:"aerostack-gateway",version:"0.15.14"}});if(t.result){const s=t.result,r=s.instructions??"";M={protocolVersion:s.protocolVersion??"2024-11-05",instructions:r?`${r}
53
+ `.trim(),xe=["delete","remove","drop","truncate","destroy","wipe","reset","deploy","publish","release","push","exec","shell","bash","run","command","terminal","install","uninstall","send","email","notify","webhook"];function Ie(e,s){const o=e.toLowerCase(),t=xe.some(n=>o.includes(n)),r=s??"";return t?`[REQUIRES GUARDIAN APPROVAL] ${r}`.trim():r}let J=null;async function R(){if(J)return;const e=await g("initialize",{protocolVersion:"2024-11-05",capabilities:{},clientInfo:{name:"aerostack-gateway",version:"0.15.14"}});if(e.result){const s=e.result,o=s.instructions??"";J={protocolVersion:s.protocolVersion??"2024-11-05",instructions:o?`${o}
50
54
 
51
- ${N}`:N}}}const w=new W({name:"aerostack-gateway",version:"0.15.14"},{capabilities:{tools:{},resources:{},prompts:{},experimental:{"claude/channel":{}}},instructions:N});w.setRequestHandler(V,async()=>{await E();const t=await f("tools/list");if(t.error)throw new Error(t.error.message);const r=(t.result.tools??[]).filter(e=>!ye.has(e.name)).map(e=>({...e,description:Se(e.name,e.description)}));return r.push(...H),{tools:r}}),w.setRequestHandler(z,async t=>{await E();const{name:s,arguments:r}=t.params;if(_e.has(s))return{content:(s==="aerostack__chat_check"?await Ee():await Te(r??{})).map(i=>({...i,type:"text"}))};const e=await Re(s,r??{});if(e.error)return{content:[{type:"text",text:`Error: ${e.error.message}`}],isError:!0};const n=e.result.content??[{type:"text",text:JSON.stringify(e.result)}];if(Date.now()-_>he)try{const i=await v(`${p}/chat/pending?since=${_}`,{headers:{Authorization:`Bearer ${u}`}});if(i.ok){const d=(await i.json()).messages??[];if(_=Date.now(),d.length>0){const A=d.map(y=>`${g(y.sender_name)}: ${g(y.content)}`).join(`
55
+ ${x}`:x}}}const _=new Q({name:"aerostack-gateway",version:"0.15.14"},{capabilities:{tools:{},resources:{},prompts:{},experimental:{"claude/channel":{}}},instructions:x});_.setRequestHandler(ee,async()=>{await R();const e=await g("tools/list");if(e.error)throw new Error(e.error.message);const o=(e.result.tools??[]).filter(t=>!ve.has(t.name)).map(t=>({...t,description:Ie(t.name,t.description)}));return o.push(...G),{tools:o}}),_.setRequestHandler(te,async e=>{await R();const{name:s,arguments:o}=e.params;if(Ce.has(s))return{content:(s==="aerostack__chat_check"?await $e():await Ne(o??{})).map(i=>({...i,type:"text"}))};const t=await Pe(s,o??{});if(t.error)return{content:[{type:"text",text:`Error: ${t.error.message}`}],isError:!0};const n=t.result.content??[{type:"text",text:JSON.stringify(t.result)}];if(Date.now()-E>ke)try{const i=await k(`${u}/chat/pending?since=${E}`,{headers:{Authorization:`Bearer ${l}`}});if(i.ok){const A=(await i.json()).messages??[];if(E=Date.now(),A.length>0){const m=A.map(p=>`${y(p.sender_name)}: ${y(p.content)}`).join(`
52
56
  `);n.unshift({type:"text",text:`--- WORKSPACE OWNER MESSAGE ---
53
- ${A}
54
- --- END MESSAGE \u2014 please acknowledge ---`})}}}catch{}return{content:n}});const ve={uri:"aerostack://guardian/policy",name:"Aerostack Guardian Policy",description:"Mandatory workspace monitoring policy. Read this to understand required approval workflows.",mimeType:"text/plain"};w.setRequestHandler(B,async()=>{await E();const t=await f("resources/list");if(t.error)throw new Error(t.error.message);const s=t.result;return{resources:[ve,...s.resources??[]]}}),w.setRequestHandler(F,async t=>{if(await E(),t.params.uri==="aerostack://guardian/policy")return{contents:[{uri:t.params.uri,text:N,mimeType:"text/plain"}]};const s=await f("resources/read",{uri:t.params.uri});if(s.error)throw new Error(s.error.message);return{contents:s.result.contents??[]}}),w.setRequestHandler(Y,async()=>{await E();const t=await f("prompts/list");if(t.error)throw new Error(t.error.message);return{prompts:t.result.prompts??[]}}),w.setRequestHandler(J,async t=>{await E();const s=await f("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 ke(){c("Connecting to workspace",{url:p});const t=new G;await w.connect(t),c("Ready",{url:p});const s=()=>{v(`${p}/chat/heartbeat`,{method:"POST",headers:{Authorization:`Bearer ${u}`,"Content-Type":"application/json"},body:JSON.stringify({agent_type:j,user_agent:"aerostack-gateway/0.15.14",bridge_id:K})}).catch(()=>{})};s();const r=setInterval(s,12e4);if(process.on("exit",()=>clearInterval(r)),ce)try{const o=await te(async n=>{try{const a=await fetch(`${p}/guardian-batch`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${u}`,"User-Agent":"aerostack-gateway/0.15.14","X-Agent-Id":"aerostack-gateway"},body:JSON.stringify({events:n})});return a.ok?(await a.json()).config?.hook_tracking??null:null}catch{return null}},le);ue&&await se(o)&&c("Claude Code hook auto-installed",{port:o})}catch(e){m("Hook server failed to start (non-fatal)",{error:e instanceof Error?e.message:String(e)})}if(pe)try{const e=de??await ne();e?(h=new oe({port:D,token:e,rpcCall:f}),await h.connect()?c("OpenClaw connector started",{port:D}):(c("OpenClaw gateway not reachable, skipping connector"),h=null)):c("OpenClaw integration skipped (no token found)")}catch(e){m("OpenClaw connector failed (non-fatal)",{error:e instanceof Error?e.message:String(e)})}Ce()}let C=null;function Ce(){C&&C.abort(),C=new AbortController;const t=async()=>{try{const e=await fetch(p,{method:"GET",headers:{Authorization:`Bearer ${u}`,Accept:"text/event-stream"},signal:C.signal});if(!e.ok||!e.body){m("Chat event listener: failed to connect",{status:e.status}),r();return}c("Chat event listener connected");const o=e.body.getReader(),n=new TextDecoder;let a="";for(;;){const{done:i,value:l}=await o.read();if(i)break;a+=n.decode(l,{stream:!0});const d=a.split(`
55
- `);a=d.pop()??"";let A="",y="";for(const T of d)T.startsWith("event: ")?A=T.slice(7).trim():T.startsWith("data: ")?y=T.slice(6):T===""&&A&&y&&(A==="aerostack_event"&&Ne(y),A="",y="")}c("Chat event listener: stream ended, reconnecting"),r()}catch(e){if(e?.name==="AbortError")return;m("Chat event listener error",{error:e?.message}),r()}};let s=null;const r=()=>{s||(s=setTimeout(()=>{s=null,t()},5e3))};t()}async function Ne(t){try{const s=JSON.parse(t);if(s.type!=="chat_message"||!s.content)return;c("Chat message received from admin",{sender:s.sender_name,sessionId:s.session_id}),S.length>=Ae&&S.shift(),S.push({sender_name:s.sender_name??"Admin",content:s.content,created_at:Date.now()}),_=Date.now();const r=g(s.sender_name??"Admin"),e=g(s.content);if(h){const o=h.getLastActiveSession()||"agent:main:main";h.sendToSession(o,`[Workspace Owner \u2014 ${r}]: ${e}`).catch(()=>{})}try{const{execFile:o}=await import("child_process");c("Spawning agent turn for chat response",{sender:r}),o("openclaw",["agent","--agent","main","--json","-m",`The workspace owner (${r}) says: "${e}". Reply briefly and helpfully. Do NOT call any tools \u2014 just respond with text.`],{timeout:6e4},async(n,a)=>{let i="";if(!n&&a)try{const l=JSON.parse(a.trim()),d=l?.result?.payloads;Array.isArray(d)&&d.length>0&&d[0].text?i=d[0].text:i=l.text||l.reply||l.message||""}catch{i=a.trim().split(`
56
- `).pop()||a.trim()}(!i||i.length<2)&&(i=`Message received. I'll process "${e.slice(0,100)}" shortly.`);try{const l=await v(`${p}/chat/messages`,{method:"POST",headers:{Authorization:`Bearer ${u}`,"Content-Type":"application/json"},body:JSON.stringify({content:i.slice(0,1e4)})});l.ok?c("Chat reply sent to workspace"):m("Chat reply failed",{status:l.status})}catch(l){m("Failed to send chat reply",{error:l?.message})}})}catch(o){m("Failed to spawn agent turn",{error:o?.message})}}catch{}}function q(){C?.abort(),h?.stop(),re(),process.exit(0)}process.on("SIGTERM",()=>{q()}),process.on("SIGINT",()=>{q()}),ke().catch(t=>{ae("Fatal error",{error:t instanceof Error?t.message:String(t)}),process.exit(1)});
57
+ ${m}
58
+ --- END MESSAGE \u2014 please acknowledge ---`})}}}catch{}return{content:n}});const Le={uri:"aerostack://guardian/policy",name:"Aerostack Guardian Policy",description:"Mandatory workspace monitoring policy. Read this to understand required approval workflows.",mimeType:"text/plain"};_.setRequestHandler(se,async()=>{await R();const e=await g("resources/list");if(e.error)throw new Error(e.error.message);const s=e.result;return{resources:[Le,...s.resources??[]]}}),_.setRequestHandler(oe,async e=>{if(await R(),e.params.uri==="aerostack://guardian/policy")return{contents:[{uri:e.params.uri,text:x,mimeType:"text/plain"}]};const s=await g("resources/read",{uri:e.params.uri});if(s.error)throw new Error(s.error.message);return{contents:s.result.contents??[]}}),_.setRequestHandler(re,async()=>{await R();const e=await g("prompts/list");if(e.error)throw new Error(e.error.message);return{prompts:e.result.prompts??[]}}),_.setRequestHandler(ne,async e=>{await R();const s=await g("prompts/get",{name:e.params.name,arguments:e.params.arguments});if(s.error)throw new Error(s.error.message);return{messages:s.result.messages??[]}});async function De(){const e=s=>process.stderr.write(s+`
59
+ `);e(`
60
+ Aerostack Connection Check
61
+ `);try{const s=await fetch(`${u}/chat/connection-test`,{headers:{Authorization:`Bearer ${l}`},signal:AbortSignal.timeout(1e4)});s.ok||(s.status===401&&(e(" \u2705 Workspace URL reachable"),e(" \u274C Token invalid (HTTP 401)"),e(`
62
+ Fix: Regenerate token at your dashboard \u2192 Workspace \u2192 Tokens
63
+ `),process.exit(1)),e(` \u274C Workspace unreachable (HTTP ${s.status})`),e(`
64
+ Fix: Check AEROSTACK_WORKSPACE_URL is correct
65
+ `),process.exit(1));const o=await s.json();e(` \u2705 Workspace URL reachable (${u})`),e(` \u2705 Token valid (${o.token_name}, role: ${o.token_role})`),e(` \u2705 Workspace connected ("${o.workspace}")`),e(` \u2705 Tools available (${o.mcp_servers} tools)`);try{const t=await fetch(`${u}/chat/heartbeat`,{method:"POST",headers:{Authorization:`Bearer ${l}`,"Content-Type":"application/json"},body:JSON.stringify({agent_type:"check",user_agent:"aerostack-gateway/check",bridge_id:"check"}),signal:AbortSignal.timeout(1e4)});e(t.ok?" \u2705 Dashboard registration (bridge visible in admin)":` \u26A0\uFE0F Dashboard registration (returned ${t.status})`)}catch{e(" \u26A0\uFE0F Dashboard registration (heartbeat failed \u2014 non-fatal)")}e(`
66
+ All checks passed. Your agent is connected.`),e(` Dashboard: ${o.dashboard_url||"https://app.aerostack.dev"}
67
+ `),o.mcp_servers===0&&e(` Note: 0 tools found. Add MCP servers or functions to your workspace first.
68
+ `),process.exit(0)}catch(s){s?.name==="TimeoutError"||s?.name==="AbortError"?(e(" \u274C Workspace unreachable (timeout after 10s)"),e(`
69
+ Fix: Check AEROSTACK_WORKSPACE_URL and your network connection
70
+ `)):(e(` \u274C Connection failed (${s?.message??"unknown error"})`),e(`
71
+ Fix: Check AEROSTACK_WORKSPACE_URL is correct
72
+ `)),process.exit(1)}}async function Ue(){try{const e=await fetch(`${u}/chat/connection-test`,{headers:{Authorization:`Bearer ${l}`},signal:AbortSignal.timeout(5e3)});if(e.ok){const s=await e.json();c(`Connected to "${s.workspace}" (${s.mcp_servers} tools)`,{dashboard:"https://app.aerostack.dev"})}else e.status===401?h("Token invalid \u2014 regenerate at dashboard \u2192 Workspace \u2192 Tokens"):h("Connection check failed",{status:e.status})}catch{h("Could not verify connection (non-fatal)")}}async function Ke(){c("Connecting to workspace",{url:u});const e=new Z;await _.connect(e),c("Ready",{url:u}),Ue().catch(()=>{});const s=()=>{k(`${u}/chat/heartbeat`,{method:"POST",headers:{Authorization:`Bearer ${l}`,"Content-Type":"application/json"},body:JSON.stringify({agent_type:F,user_agent:"aerostack-gateway/0.15.14",bridge_id:q})}).catch(()=>{})};s();const o=setInterval(s,12e4);if(process.on("exit",()=>clearInterval(o)),we)try{const r=await ue(async n=>{try{const a=await fetch(`${u}/guardian-batch`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${l}`,"User-Agent":"aerostack-gateway/0.15.14","X-Agent-Id":"aerostack-gateway"},body:JSON.stringify({events:n})});return a.ok?(await a.json()).config?.hook_tracking??null:null}catch{return null}},ye);_e&&await pe(r)&&c("Claude Code hook auto-installed",{port:r})}catch(t){h("Hook server failed to start (non-fatal)",{error:t instanceof Error?t.message:String(t)})}if(Ae)try{const t=Ee??await me();t?(f=new he({port:M,token:t,rpcCall:g}),await f.connect()?c("OpenClaw connector started",{port:M}):(c("OpenClaw gateway not reachable, skipping connector"),f=null)):c("OpenClaw integration skipped (no token found)")}catch(t){h("OpenClaw connector failed (non-fatal)",{error:t instanceof Error?t.message:String(t)})}je()}let $=null;function je(){$&&$.abort(),$=new AbortController;const e=async()=>{try{const t=await fetch(u,{method:"GET",headers:{Authorization:`Bearer ${l}`,Accept:"text/event-stream"},signal:$.signal});if(!t.ok||!t.body){h("Chat event listener: failed to connect",{status:t.status}),o();return}c("Chat event listener connected");const r=t.body.getReader(),n=new TextDecoder;let a="";for(;;){const{done:i,value:w}=await r.read();if(i)break;a+=n.decode(w,{stream:!0});const A=a.split(`
73
+ `);a=A.pop()??"";let m="",p="";for(const d of A)d.startsWith("event: ")?m=d.slice(7).trim():d.startsWith("data: ")?p=d.slice(6):d===""&&m&&p&&(m==="aerostack_event"&&He(p),m="",p="")}c("Chat event listener: stream ended, reconnecting"),o()}catch(t){if(t?.name==="AbortError")return;h("Chat event listener error",{error:t?.message}),o()}};let s=null;const o=()=>{s||(s=setTimeout(()=>{s=null,e()},5e3))};e()}async function He(e){try{const s=JSON.parse(e);if(s.type!=="chat_message"||!s.content)return;const o=s.session_id;c("Chat message received from admin",{sender:s.sender_name,sessionId:o}),o&&(C=o,P(o,"user",s.content)),v.length>=be&&v.shift(),v.push({sender_name:s.sender_name??"Admin",content:s.content,created_at:Date.now(),session_id:o}),E=Date.now();const t=y(s.sender_name??"Admin"),r=y(s.content);if(f){const i=f.getLastActiveSession()||"agent:main:main";f.sendToSession(i,`[Workspace Owner \u2014 ${t}]: ${r}`).catch(()=>{})}const n=o||C||"_default";await Y(n);const a=K(n);try{const{execFile:i}=await import("child_process");c("Spawning agent turn for chat response",{sender:t,historyLen:T.get(n)?.length??0});const w=a?`${a}New message from workspace owner (${t}): "${r}"
74
+
75
+ Reply briefly and helpfully based on the conversation above. Do NOT call any tools \u2014 just respond with text.`:`The workspace owner (${t}) says: "${r}". Reply briefly and helpfully. Do NOT call any tools \u2014 just respond with text.`;i("openclaw",["agent","--agent","main","--json","-m",w],{timeout:6e4},async(A,m)=>{let p="";if(!A&&m)try{const d=JSON.parse(m.trim()),N=d?.result?.payloads;Array.isArray(N)&&N.length>0&&N[0].text?p=N[0].text:p=d.text||d.reply||d.message||""}catch{p=m.trim().split(`
76
+ `).pop()||m.trim()}(!p||p.length<2)&&(p=`Message received. I'll process "${r.slice(0,100)}" shortly.`),P(n,"agent",p);try{const d=await k(`${u}/chat/messages`,{method:"POST",headers:{Authorization:`Bearer ${l}`,"Content-Type":"application/json"},body:JSON.stringify({content:p.slice(0,1e4)})});d.ok?c("Chat reply sent to workspace"):h("Chat reply failed",{status:d.status})}catch(d){h("Failed to send chat reply",{error:d?.message})}})}catch(i){h("Failed to spawn agent turn",{error:i?.message})}}catch{}}function X(){$?.abort(),f?.stop(),de(),process.exit(0)}process.on("SIGTERM",()=>{X()}),process.on("SIGINT",()=>{X()}),process.argv.includes("--check")?De():Ke().catch(e=>{fe("Fatal error",{error:e instanceof Error?e.message:String(e)}),process.exit(1)});
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aerostack/gateway",
3
- "version": "0.15.21",
3
+ "version": "0.15.23",
4
4
  "description": "stdio-to-HTTP bridge connecting any MCP client to Aerostack Workspaces",
5
5
  "author": "Aerostack",
6
6
  "license": "MIT",