svamp-cli 0.1.63 → 0.1.65
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agentCommands-C6iGblcL.mjs +156 -0
- package/dist/{api-Cegey1dh.mjs → api-BRbsyqJ4.mjs} +9 -2
- package/dist/cli.mjs +73 -20
- package/dist/{commands-dm3PSNsk.mjs → commands-CF32XIau.mjs} +3 -3
- package/dist/{commands-3Zu9sCxa.mjs → commands-Dc_6JdE_.mjs} +2 -2
- package/dist/{commands-BRow9cGp.mjs → commands-FAWhkKtz.mjs} +1 -1
- package/dist/{commands-6EyqaoCp.mjs → commands-UFi0_ESV.mjs} +48 -24
- package/dist/index.mjs +1 -1
- package/dist/{package-7U32bRBY.mjs → package-CvCDrFPH.mjs} +2 -2
- package/dist/{run-BaMf-bAo.mjs → run-DPhSmbr1.mjs} +942 -387
- package/dist/{run-COqTRwXb.mjs → run-DTwMJ7dx.mjs} +80 -28
- package/dist/{tunnel-dl6vFKgd.mjs → tunnel-C3UsqTxi.mjs} +53 -65
- package/package.json +2 -2
|
@@ -2,7 +2,7 @@ import{createRequire as _pkgrollCR}from"node:module";const require=_pkgrollCR(im
|
|
|
2
2
|
import os from 'node:os';
|
|
3
3
|
import { join, resolve } from 'node:path';
|
|
4
4
|
import { mkdirSync, writeFileSync, existsSync, unlinkSync, readFileSync, watch } from 'node:fs';
|
|
5
|
-
import { c as connectToHypha, a as registerSessionService } from './run-
|
|
5
|
+
import { c as connectToHypha, a as registerSessionService } from './run-DPhSmbr1.mjs';
|
|
6
6
|
import { createServer } from 'node:http';
|
|
7
7
|
import { spawn } from 'node:child_process';
|
|
8
8
|
import { createInterface } from 'node:readline';
|
|
@@ -203,9 +203,9 @@ function createSessionScanner(opts) {
|
|
|
203
203
|
},
|
|
204
204
|
sync: readAndSync,
|
|
205
205
|
cleanup() {
|
|
206
|
-
stopped = true;
|
|
207
206
|
stopWatching();
|
|
208
207
|
if (currentSessionId) readAndSync();
|
|
208
|
+
stopped = true;
|
|
209
209
|
}
|
|
210
210
|
};
|
|
211
211
|
}
|
|
@@ -432,11 +432,18 @@ async function runClaudeTurn(opts) {
|
|
|
432
432
|
opts.onThinkingChange(true);
|
|
433
433
|
let currentText = "";
|
|
434
434
|
return new Promise((resolve) => {
|
|
435
|
+
let killTimer;
|
|
435
436
|
const abortHandler = () => {
|
|
436
437
|
try {
|
|
437
438
|
child.kill("SIGTERM");
|
|
438
439
|
} catch {
|
|
439
440
|
}
|
|
441
|
+
killTimer = setTimeout(() => {
|
|
442
|
+
try {
|
|
443
|
+
child.kill("SIGKILL");
|
|
444
|
+
} catch {
|
|
445
|
+
}
|
|
446
|
+
}, 5e3);
|
|
440
447
|
};
|
|
441
448
|
if (opts.signal.aborted) {
|
|
442
449
|
abortHandler();
|
|
@@ -458,18 +465,31 @@ async function runClaudeTurn(opts) {
|
|
|
458
465
|
currentText += text;
|
|
459
466
|
});
|
|
460
467
|
});
|
|
468
|
+
rl.on("error", (err) => {
|
|
469
|
+
opts.log(`[remote] readline error: ${err.message}`);
|
|
470
|
+
});
|
|
461
471
|
if (child.stderr) {
|
|
462
472
|
child.stderr.on("data", (data) => {
|
|
463
473
|
opts.log(`[remote:stderr] ${data.toString().trim()}`);
|
|
464
474
|
});
|
|
465
475
|
}
|
|
466
476
|
child.on("error", (err) => {
|
|
477
|
+
if (killTimer) {
|
|
478
|
+
clearTimeout(killTimer);
|
|
479
|
+
killTimer = void 0;
|
|
480
|
+
}
|
|
481
|
+
rl.close();
|
|
467
482
|
opts.log(`[remote] Error: ${err.message}`);
|
|
468
483
|
opts.onThinkingChange(false);
|
|
469
484
|
if (currentText && !currentText.endsWith("\n")) process.stdout.write("\n");
|
|
470
485
|
resolve("error");
|
|
471
486
|
});
|
|
472
487
|
child.on("exit", (code, signal) => {
|
|
488
|
+
if (killTimer) {
|
|
489
|
+
clearTimeout(killTimer);
|
|
490
|
+
killTimer = void 0;
|
|
491
|
+
}
|
|
492
|
+
rl.close();
|
|
473
493
|
opts.log(`[remote] Exit: code=${code}, signal=${signal}`);
|
|
474
494
|
opts.onThinkingChange(false);
|
|
475
495
|
if (currentText && !currentText.endsWith("\n")) process.stdout.write("\n");
|
|
@@ -570,18 +590,25 @@ async function loop(opts) {
|
|
|
570
590
|
abortController.abort();
|
|
571
591
|
}
|
|
572
592
|
}, 500);
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
593
|
+
let result;
|
|
594
|
+
try {
|
|
595
|
+
result = await runLocalMode({
|
|
596
|
+
cwd: opts.cwd,
|
|
597
|
+
claudeSessionId,
|
|
598
|
+
onSessionFound,
|
|
599
|
+
onMessage: opts.onMessage,
|
|
600
|
+
onThinkingChange: opts.onThinkingChange,
|
|
601
|
+
abort: abortController.signal,
|
|
602
|
+
hookSettingsPath: opts.hookSettingsPath,
|
|
603
|
+
claudeArgs: opts.claudeArgs,
|
|
604
|
+
log
|
|
605
|
+
});
|
|
606
|
+
} finally {
|
|
607
|
+
if (messageWatcher) {
|
|
608
|
+
clearInterval(messageWatcher);
|
|
609
|
+
messageWatcher = null;
|
|
610
|
+
}
|
|
611
|
+
}
|
|
585
612
|
if (result.type === "switch") {
|
|
586
613
|
mode = "remote";
|
|
587
614
|
opts.onModeChange(mode);
|
|
@@ -664,8 +691,8 @@ async function runInteractive(options) {
|
|
|
664
691
|
if (messageQueue.length > 0) {
|
|
665
692
|
return Promise.resolve(messageQueue.shift());
|
|
666
693
|
}
|
|
667
|
-
return new Promise((
|
|
668
|
-
messageWaiter = { resolve };
|
|
694
|
+
return new Promise((resolve2) => {
|
|
695
|
+
messageWaiter = { resolve: resolve2 };
|
|
669
696
|
});
|
|
670
697
|
}
|
|
671
698
|
function hasRemoteMessage() {
|
|
@@ -706,9 +733,9 @@ async function runInteractive(options) {
|
|
|
706
733
|
log("[hypha] Restart requested");
|
|
707
734
|
return { success: false, message: "Restart not supported in interactive mode" };
|
|
708
735
|
},
|
|
709
|
-
onKillSession: () => {
|
|
736
|
+
onKillSession: async () => {
|
|
710
737
|
log("[hypha] Kill requested");
|
|
711
|
-
cleanup();
|
|
738
|
+
await cleanup();
|
|
712
739
|
process.exit(0);
|
|
713
740
|
},
|
|
714
741
|
// File system operations — run locally since we have direct access
|
|
@@ -728,28 +755,43 @@ async function runInteractive(options) {
|
|
|
728
755
|
return { output: err.stdout || "", error: err.message, exitCode: err.status || 1 };
|
|
729
756
|
}
|
|
730
757
|
},
|
|
731
|
-
onReadFile: async (
|
|
758
|
+
onReadFile: async (filePath) => {
|
|
759
|
+
const resolvedPath = resolve(cwd, filePath);
|
|
760
|
+
if (resolvedPath !== resolve(cwd) && !resolvedPath.startsWith(resolve(cwd) + "/")) {
|
|
761
|
+
throw new Error("Path outside working directory");
|
|
762
|
+
}
|
|
732
763
|
try {
|
|
733
764
|
const { readFileSync: readFileSync2 } = await import('fs');
|
|
734
|
-
|
|
765
|
+
const buf = readFileSync2(resolvedPath);
|
|
766
|
+
return buf.toString("base64");
|
|
735
767
|
} catch (err) {
|
|
736
768
|
throw new Error(`Cannot read file: ${err.message}`);
|
|
737
769
|
}
|
|
738
770
|
},
|
|
739
|
-
onWriteFile: async (
|
|
771
|
+
onWriteFile: async (filePath, content) => {
|
|
772
|
+
const resolvedPath = resolve(cwd, filePath);
|
|
773
|
+
if (resolvedPath !== resolve(cwd) && !resolvedPath.startsWith(resolve(cwd) + "/")) {
|
|
774
|
+
throw new Error("Path outside working directory");
|
|
775
|
+
}
|
|
740
776
|
try {
|
|
741
|
-
const { writeFileSync } = await import('fs');
|
|
742
|
-
|
|
777
|
+
const { writeFileSync, mkdirSync } = await import('fs');
|
|
778
|
+
const { dirname: dir } = await import('path');
|
|
779
|
+
mkdirSync(dir(resolvedPath), { recursive: true });
|
|
780
|
+
writeFileSync(resolvedPath, Buffer.from(content, "base64"));
|
|
743
781
|
} catch (err) {
|
|
744
782
|
throw new Error(`Cannot write file: ${err.message}`);
|
|
745
783
|
}
|
|
746
784
|
},
|
|
747
|
-
onListDirectory: async (
|
|
785
|
+
onListDirectory: async (dirPath) => {
|
|
786
|
+
const resolvedDir = resolve(cwd, dirPath || ".");
|
|
787
|
+
if (resolvedDir !== resolve(cwd) && !resolvedDir.startsWith(resolve(cwd) + "/")) {
|
|
788
|
+
throw new Error("Path outside working directory");
|
|
789
|
+
}
|
|
748
790
|
const { readdirSync, statSync } = await import('fs');
|
|
749
791
|
const { join: joinPath } = await import('path');
|
|
750
|
-
return readdirSync(
|
|
792
|
+
return readdirSync(resolvedDir).map((name) => {
|
|
751
793
|
try {
|
|
752
|
-
const st = statSync(joinPath(
|
|
794
|
+
const st = statSync(joinPath(resolvedDir, name));
|
|
753
795
|
return { name, type: st.isDirectory() ? "directory" : "file", size: st.size };
|
|
754
796
|
} catch {
|
|
755
797
|
return { name, type: "unknown", size: 0 };
|
|
@@ -803,7 +845,10 @@ async function runInteractive(options) {
|
|
|
803
845
|
let keepAliveInterval = null;
|
|
804
846
|
if (sessionService) {
|
|
805
847
|
keepAliveInterval = setInterval(() => {
|
|
806
|
-
|
|
848
|
+
try {
|
|
849
|
+
sessionService.sendKeepAlive(false);
|
|
850
|
+
} catch {
|
|
851
|
+
}
|
|
807
852
|
}, 3e4);
|
|
808
853
|
}
|
|
809
854
|
const cleanup = async () => {
|
|
@@ -870,11 +915,18 @@ async function runInteractive(options) {
|
|
|
870
915
|
onModeChange: (mode) => {
|
|
871
916
|
log(`Mode changed: ${mode}`);
|
|
872
917
|
currentMode = mode;
|
|
918
|
+
if (mode === "local" && messageWaiter) {
|
|
919
|
+
messageWaiter = null;
|
|
920
|
+
}
|
|
873
921
|
if (sessionService) {
|
|
874
922
|
sessionService.updateAgentState({
|
|
875
923
|
controlledByUser: mode === "local"
|
|
876
924
|
});
|
|
877
|
-
sessionService.updateMetadata({
|
|
925
|
+
sessionService.updateMetadata({
|
|
926
|
+
...metadata,
|
|
927
|
+
claudeSessionId: claudeSessionId || void 0,
|
|
928
|
+
lifecycleState: "running"
|
|
929
|
+
});
|
|
878
930
|
sessionService.sendKeepAlive(false, mode);
|
|
879
931
|
}
|
|
880
932
|
},
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import * as os from 'os';
|
|
2
|
-
import
|
|
3
|
-
import { requireSandboxApiEnv } from './api-Cegey1dh.mjs';
|
|
2
|
+
import { requireSandboxApiEnv } from './api-BRbsyqJ4.mjs';
|
|
4
3
|
import { WebSocket } from 'ws';
|
|
5
4
|
|
|
6
5
|
class TunnelClient {
|
|
@@ -98,6 +97,7 @@ class TunnelClient {
|
|
|
98
97
|
} catch {
|
|
99
98
|
return;
|
|
100
99
|
}
|
|
100
|
+
if (!msg || typeof msg !== "object") return;
|
|
101
101
|
switch (msg.type) {
|
|
102
102
|
case "ping":
|
|
103
103
|
this.send({ type: "pong" });
|
|
@@ -124,72 +124,56 @@ class TunnelClient {
|
|
|
124
124
|
async proxyRequest(req) {
|
|
125
125
|
this.requestCount++;
|
|
126
126
|
const port = req.port || this.options.ports[0];
|
|
127
|
+
const url = `http://${this.options.localHost}:${port}${req.path}`;
|
|
128
|
+
const controller = new AbortController();
|
|
129
|
+
const timeout = setTimeout(() => controller.abort(), this.options.requestTimeout);
|
|
127
130
|
const forwardHeaders = { ...req.headers };
|
|
128
|
-
forwardHeaders["accept-encoding"]
|
|
129
|
-
|
|
130
|
-
const
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
} else if (value !== void 0) {
|
|
147
|
-
status_holder.headers[key] = value;
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
res.on("data", (chunk) => chunks.push(chunk));
|
|
151
|
-
res.on("end", () => {
|
|
152
|
-
clearTimeout(timeout);
|
|
153
|
-
resolve();
|
|
154
|
-
});
|
|
155
|
-
res.on("error", (e) => {
|
|
156
|
-
clearTimeout(timeout);
|
|
157
|
-
reject(e);
|
|
158
|
-
});
|
|
159
|
-
});
|
|
160
|
-
httpReq.on("error", (e) => {
|
|
161
|
-
clearTimeout(timeout);
|
|
162
|
-
reject(e);
|
|
131
|
+
delete forwardHeaders["accept-encoding"];
|
|
132
|
+
delete forwardHeaders["Accept-Encoding"];
|
|
133
|
+
const init = {
|
|
134
|
+
method: req.method,
|
|
135
|
+
headers: forwardHeaders,
|
|
136
|
+
signal: controller.signal
|
|
137
|
+
};
|
|
138
|
+
if (req.body && !["GET", "HEAD"].includes(req.method.toUpperCase())) {
|
|
139
|
+
init.body = Buffer.from(req.body, "base64");
|
|
140
|
+
}
|
|
141
|
+
try {
|
|
142
|
+
const res = await fetch(url, init);
|
|
143
|
+
clearTimeout(timeout);
|
|
144
|
+
const bodyBuffer = await res.arrayBuffer();
|
|
145
|
+
const bodyBase64 = bodyBuffer.byteLength > 0 ? Buffer.from(bodyBuffer).toString("base64") : void 0;
|
|
146
|
+
const headers = {};
|
|
147
|
+
res.headers.forEach((value, key) => {
|
|
148
|
+
headers[key] = value;
|
|
163
149
|
});
|
|
164
|
-
if (req.body && !["GET", "HEAD"].includes(req.method.toUpperCase())) {
|
|
165
|
-
httpReq.write(Buffer.from(req.body, "base64"));
|
|
166
|
-
}
|
|
167
|
-
httpReq.end();
|
|
168
|
-
}).then(() => {
|
|
169
|
-
const bodyBuffer = Buffer.concat(chunks);
|
|
170
|
-
const bodyBase64 = bodyBuffer.length > 0 ? bodyBuffer.toString("base64") : void 0;
|
|
171
|
-
for (const h of ["connection", "keep-alive", "transfer-encoding"]) {
|
|
172
|
-
delete status_holder.headers[h];
|
|
173
|
-
}
|
|
174
150
|
this.send({
|
|
175
151
|
type: "response",
|
|
176
152
|
id: req.id,
|
|
177
|
-
status:
|
|
178
|
-
headers
|
|
153
|
+
status: res.status,
|
|
154
|
+
headers,
|
|
179
155
|
body: bodyBase64
|
|
180
156
|
});
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
)
|
|
191
|
-
}
|
|
192
|
-
|
|
157
|
+
} catch (err) {
|
|
158
|
+
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
|
+
}
|
|
176
|
+
}
|
|
193
177
|
}
|
|
194
178
|
// ── WebSocket proxying ───────────────────────────────────────────────
|
|
195
179
|
async handleWsOpen(msg) {
|
|
@@ -225,10 +209,14 @@ class TunnelClient {
|
|
|
225
209
|
const localWs = this.localWebSockets.get(msg.id);
|
|
226
210
|
if (!localWs || localWs.readyState !== WebSocket.OPEN) return;
|
|
227
211
|
const buf = Buffer.from(msg.data, "base64");
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
212
|
+
try {
|
|
213
|
+
if (msg.is_text) {
|
|
214
|
+
localWs.send(buf.toString("utf8"));
|
|
215
|
+
} else {
|
|
216
|
+
localWs.send(buf);
|
|
217
|
+
}
|
|
218
|
+
} catch (err) {
|
|
219
|
+
this.options.onError?.(err);
|
|
232
220
|
}
|
|
233
221
|
}
|
|
234
222
|
handleWsClose(msg) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "svamp-cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.65",
|
|
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
|
|
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",
|
|
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",
|