svamp-cli 0.1.69 → 0.1.71

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.
@@ -14,10 +14,12 @@ class TunnelClient {
14
14
  requestCount = 0;
15
15
  localWebSockets = /* @__PURE__ */ new Map();
16
16
  // request_id → local WS connection
17
+ pendingBinaryBody = null;
18
+ // awaiting binary body frame
17
19
  constructor(options) {
18
20
  this.options = {
19
21
  localHost: "localhost",
20
- requestTimeout: 3e4,
22
+ requestTimeout: 12e4,
21
23
  ...options
22
24
  };
23
25
  this.env = options.env || requireSandboxApiEnv();
@@ -40,7 +42,10 @@ class TunnelClient {
40
42
  const url = this.buildWsUrl();
41
43
  return new Promise((resolve, reject) => {
42
44
  try {
43
- this.ws = new WebSocket(url);
45
+ this.ws = new WebSocket(url, {
46
+ maxPayload: 100 * 1024 * 1024
47
+ // 100MB for large binary bodies
48
+ });
44
49
  } catch (err) {
45
50
  reject(new Error(`Failed to create WebSocket: ${err.message}`));
46
51
  return;
@@ -51,13 +56,18 @@ class TunnelClient {
51
56
  this.options.onConnect?.();
52
57
  resolve();
53
58
  });
54
- this.ws.on("message", (data) => {
55
- const raw = typeof data === "string" ? data : data.toString("utf8");
56
- this.handleMessage(raw);
59
+ this.ws.on("message", (data, isBinary) => {
60
+ if (isBinary) {
61
+ this.handleBinaryBody(data);
62
+ } else {
63
+ const raw = typeof data === "string" ? data : data.toString("utf8");
64
+ this.handleMessage(raw);
65
+ }
57
66
  });
58
67
  this.ws.on("close", () => {
59
68
  this.stopPingInterval();
60
69
  this.cleanupLocalWebSockets();
70
+ this.pendingBinaryBody = null;
61
71
  this.options.onDisconnect?.();
62
72
  if (!this.destroyed) {
63
73
  this.scheduleReconnect();
@@ -104,9 +114,13 @@ class TunnelClient {
104
114
  break;
105
115
  case "request":
106
116
  this.options.onRequest?.(msg);
107
- this.proxyRequest(msg).catch((err) => {
108
- this.options.onError?.(err);
109
- });
117
+ if (msg.has_body) {
118
+ this.pendingBinaryBody = msg;
119
+ } else {
120
+ this.proxyRequest(msg, Buffer.alloc(0)).catch((err) => {
121
+ this.options.onError?.(err);
122
+ });
123
+ }
110
124
  break;
111
125
  case "ws_open":
112
126
  this.handleWsOpen(msg).catch((err) => {
@@ -121,7 +135,15 @@ class TunnelClient {
121
135
  break;
122
136
  }
123
137
  }
124
- async proxyRequest(req) {
138
+ handleBinaryBody(data) {
139
+ const req = this.pendingBinaryBody;
140
+ this.pendingBinaryBody = null;
141
+ if (!req) return;
142
+ this.proxyRequest(req, data).catch((err) => {
143
+ this.options.onError?.(err);
144
+ });
145
+ }
146
+ async proxyRequest(req, bodyBuffer) {
125
147
  this.requestCount++;
126
148
  const port = req.port || this.options.ports[0];
127
149
  const url = `http://${this.options.localHost}:${port}${req.path}`;
@@ -135,14 +157,12 @@ class TunnelClient {
135
157
  headers: forwardHeaders,
136
158
  signal: controller.signal
137
159
  };
138
- if (req.body && !["GET", "HEAD"].includes(req.method.toUpperCase())) {
139
- init.body = Buffer.from(req.body, "base64");
160
+ if (bodyBuffer.byteLength > 0 && !["GET", "HEAD"].includes(req.method.toUpperCase())) {
161
+ init.body = bodyBuffer;
140
162
  }
141
163
  try {
142
164
  const res = await fetch(url, init);
143
165
  clearTimeout(timeout);
144
- const bodyBuffer = await res.arrayBuffer();
145
- const bodyBase64 = bodyBuffer.byteLength > 0 ? Buffer.from(bodyBuffer).toString("base64") : void 0;
146
166
  const headers = {};
147
167
  res.headers.forEach((value, key) => {
148
168
  headers[key] = value;
@@ -152,27 +172,36 @@ class TunnelClient {
152
172
  id: req.id,
153
173
  status: res.status,
154
174
  headers,
155
- body: bodyBase64
175
+ streaming: true
156
176
  });
177
+ if (res.body) {
178
+ const reader = res.body.getReader();
179
+ try {
180
+ while (true) {
181
+ const { done, value } = await reader.read();
182
+ if (done) break;
183
+ this.send({ type: "response_chunk", id: req.id });
184
+ this.sendBinary(Buffer.from(value));
185
+ }
186
+ } finally {
187
+ reader.releaseLock();
188
+ }
189
+ }
190
+ this.send({ type: "response_end", id: req.id });
157
191
  } catch (err) {
158
192
  clearTimeout(timeout);
159
- if (err.name === "AbortError") {
160
- this.send({
161
- type: "response",
162
- id: req.id,
163
- status: 504,
164
- headers: { "content-type": "text/plain" },
165
- body: Buffer.from("Tunnel: request timeout").toString("base64")
166
- });
167
- } else {
168
- this.send({
169
- type: "response",
170
- id: req.id,
171
- status: 502,
172
- headers: { "content-type": "text/plain" },
173
- body: Buffer.from(`Tunnel: local service error: ${err.message}`).toString("base64")
174
- });
175
- }
193
+ const status = err.name === "AbortError" ? 504 : 502;
194
+ const message = err.name === "AbortError" ? "Tunnel: request timeout" : `Tunnel: local service error: ${err.message}`;
195
+ this.send({
196
+ type: "response",
197
+ id: req.id,
198
+ status,
199
+ headers: { "content-type": "text/plain" },
200
+ streaming: true
201
+ });
202
+ this.send({ type: "response_chunk", id: req.id });
203
+ this.sendBinary(Buffer.from(message));
204
+ this.send({ type: "response_end", id: req.id });
176
205
  }
177
206
  }
178
207
  // ── WebSocket proxying ───────────────────────────────────────────────
@@ -238,6 +267,11 @@ class TunnelClient {
238
267
  this.ws.send(JSON.stringify(msg));
239
268
  }
240
269
  }
270
+ sendBinary(data) {
271
+ if (this.ws?.readyState === WebSocket.OPEN) {
272
+ this.ws.send(data);
273
+ }
274
+ }
241
275
  startPingInterval() {
242
276
  this.stopPingInterval();
243
277
  this.pingInterval = setInterval(() => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "svamp-cli",
3
- "version": "0.1.69",
3
+ "version": "0.1.71",
4
4
  "description": "Svamp CLI — AI workspace daemon on Hypha Cloud",
5
5
  "author": "Amun AI AB",
6
6
  "license": "SEE LICENSE IN LICENSE",
@@ -20,7 +20,7 @@
20
20
  "scripts": {
21
21
  "build": "rm -rf dist && tsc --noEmit && pkgroll",
22
22
  "typecheck": "tsc --noEmit",
23
- "test": "npx tsx test/test-authorize.mjs && npx tsx test/test-session-helpers.mjs && npx tsx test/test-cli-routing.mjs && npx tsx test/test-security-context.mjs && npx tsx test/test-ralph-loop.mjs && npx tsx test/test-message-helpers.mjs && npx tsx test/test-agent-config.mjs && npx tsx test/test-wrap-command.mjs && npx tsx test/test-credential-staging.mjs && npx tsx test/test-output-formatters.mjs && npx tsx test/test-agent-types.mjs && npx tsx test/test-transport.mjs && npx tsx test/test-session-update-handlers.mjs && npx tsx test/test-session-scanner.mjs && npx tsx test/test-hypha-client.mjs && npx tsx test/test-hook-settings.mjs && npx tsx test/test-session-service-logic.mjs && npx tsx test/test-daemon-persistence.mjs && npx tsx test/test-detect-isolation.mjs && npx tsx test/test-machine-service-logic.mjs && npx tsx test/test-interactive-helpers.mjs && npx tsx test/test-codex-backend.mjs && npx tsx test/test-acp-backend.mjs && npx tsx test/test-acp-bridge.mjs && npx tsx test/test-hook-server.mjs && npx tsx test/test-session-commands.mjs && npx tsx test/test-interactive-console.mjs && npx tsx test/test-session-messages.mjs && npx tsx test/test-skills.mjs && npx tsx test/test-agent-grouping.mjs && npx tsx test/test-ralph-loop-integration.mjs && npx tsx test/test-ralph-loop-modes.mjs && npx tsx test/test-machine-list-directory.mjs && npx tsx test/test-service-commands.mjs && npx tsx test/test-supervisor.mjs && npx tsx test/test-clear-detection.mjs",
23
+ "test": "npx tsx test/test-authorize.mjs && npx tsx test/test-session-helpers.mjs && npx tsx test/test-cli-routing.mjs && npx tsx test/test-security-context.mjs && npx tsx test/test-ralph-loop.mjs && npx tsx test/test-message-helpers.mjs && npx tsx test/test-agent-config.mjs && npx tsx test/test-wrap-command.mjs && npx tsx test/test-credential-staging.mjs && npx tsx test/test-output-formatters.mjs && npx tsx test/test-agent-types.mjs && npx tsx test/test-transport.mjs && npx tsx test/test-session-update-handlers.mjs && npx tsx test/test-session-scanner.mjs && npx tsx test/test-hypha-client.mjs && npx tsx test/test-hook-settings.mjs && npx tsx test/test-session-service-logic.mjs && npx tsx test/test-daemon-persistence.mjs && npx tsx test/test-detect-isolation.mjs && npx tsx test/test-machine-service-logic.mjs && npx tsx test/test-interactive-helpers.mjs && npx tsx test/test-codex-backend.mjs && npx tsx test/test-acp-backend.mjs && npx tsx test/test-acp-bridge.mjs && npx tsx test/test-hook-server.mjs && npx tsx test/test-session-commands.mjs && npx tsx test/test-interactive-console.mjs && npx tsx test/test-session-messages.mjs && npx tsx test/test-skills.mjs && npx tsx test/test-agent-grouping.mjs && npx tsx test/test-ralph-loop-integration.mjs && npx tsx test/test-ralph-loop-modes.mjs && npx tsx test/test-machine-list-directory.mjs && npx tsx test/test-service-commands.mjs && npx tsx test/test-supervisor.mjs && npx tsx test/test-clear-detection.mjs && npx tsx test/test-session-consolidation.mjs",
24
24
  "test:hypha": "node --no-warnings test/test-hypha-service.mjs",
25
25
  "dev": "tsx src/cli.ts",
26
26
  "dev:daemon": "tsx src/cli.ts daemon start-sync",