svamp-cli 0.2.128 → 0.2.130
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/bin/skills/crew/SKILL.md +11 -10
- package/bin/skills/loop/bin/checklist.mjs +3 -2
- package/bin/skills/loop/bin/loop-init.mjs +2 -2
- package/dist/{agentCommands-jGCEdEYn.mjs → agentCommands-DOUG625_.mjs} +4 -4
- package/dist/{auth-DVa-sVa9.mjs → auth-UNUDBJKU.mjs} +1 -1
- package/dist/cli.mjs +85 -95
- package/dist/{commands-CHUE-0Mz.mjs → commands-B8vUCW50.mjs} +1 -1
- package/dist/{commands-deNTJ9jb.mjs → commands-BFGR6-V-.mjs} +9 -9
- package/dist/{commands-BLYvHcrD.mjs → commands-BeOI6l78.mjs} +19 -8
- package/dist/{commands-Dif088xw.mjs → commands-D830hGXM.mjs} +2 -2
- package/dist/{commands-CO-lf8m_.mjs → commands-MdYMcyaZ.mjs} +2 -2
- package/dist/{commands-BVx72l2K.mjs → commands-QGaI-ukW.mjs} +12 -46
- package/dist/{fleet-D3L05h5k.mjs → fleet-nj6bMyhh.mjs} +1 -1
- package/dist/{frpc-CWyoLax7.mjs → frpc-DrfDPPux.mjs} +147 -8
- package/dist/{headlessCli-CB9HN7zY.mjs → headlessCli-D8x-uGEN.mjs} +2 -2
- package/dist/index.mjs +1 -1
- package/dist/{package-C5owhm4c.mjs → package-BK6btwnG.mjs} +2 -2
- package/dist/{run-CkPzZuKK.mjs → run---5cgexR.mjs} +1 -1
- package/dist/{run-C23-A9KM.mjs → run-DMahGhJP.mjs} +166 -102
- package/dist/{serveCommands-BBIKhjxn.mjs → serveCommands-C8iIs7jb.mjs} +5 -5
- package/dist/{serveManager-CxbgXYEo.mjs → serveManager-Csqa6icR.mjs} +2 -2
- package/dist/{sideband-D5F6XGss.mjs → sideband-Bk7iN3dp.mjs} +1 -1
- package/package.json +2 -2
|
@@ -58,7 +58,7 @@ async function serviceExpose(args) {
|
|
|
58
58
|
process.exit(1);
|
|
59
59
|
}
|
|
60
60
|
if (foreground) {
|
|
61
|
-
const { runFrpcTunnel } = await import('./frpc-
|
|
61
|
+
const { runFrpcTunnel } = await import('./frpc-DrfDPPux.mjs');
|
|
62
62
|
await runFrpcTunnel(name, ports, void 0, {
|
|
63
63
|
group,
|
|
64
64
|
groupKey,
|
|
@@ -68,7 +68,7 @@ async function serviceExpose(args) {
|
|
|
68
68
|
});
|
|
69
69
|
return;
|
|
70
70
|
}
|
|
71
|
-
const { connectAndGetMachine } = await import('./commands-
|
|
71
|
+
const { connectAndGetMachine } = await import('./commands-QGaI-ukW.mjs');
|
|
72
72
|
const { server, machine } = await connectAndGetMachine();
|
|
73
73
|
try {
|
|
74
74
|
const status = await machine.tunnelStart({
|
|
@@ -123,7 +123,7 @@ async function serviceServe(args) {
|
|
|
123
123
|
};
|
|
124
124
|
process.on("SIGINT", cleanup);
|
|
125
125
|
process.on("SIGTERM", cleanup);
|
|
126
|
-
const { runFrpcTunnel } = await import('./frpc-
|
|
126
|
+
const { runFrpcTunnel } = await import('./frpc-DrfDPPux.mjs');
|
|
127
127
|
await runFrpcTunnel(name, [caddyPort]);
|
|
128
128
|
} catch (err) {
|
|
129
129
|
console.error(`Error serving directory: ${err.message}`);
|
|
@@ -132,18 +132,29 @@ async function serviceServe(args) {
|
|
|
132
132
|
}
|
|
133
133
|
async function serviceList(_args) {
|
|
134
134
|
try {
|
|
135
|
-
const { connectAndGetMachine } = await import('./commands-
|
|
135
|
+
const { connectAndGetMachine } = await import('./commands-QGaI-ukW.mjs');
|
|
136
136
|
const { server, machine } = await connectAndGetMachine();
|
|
137
137
|
try {
|
|
138
138
|
const tunnels = await machine.tunnelList({});
|
|
139
139
|
if (!tunnels || tunnels.length === 0) {
|
|
140
|
-
console.log("No
|
|
140
|
+
console.log("No daemon-managed tunnels configured.");
|
|
141
141
|
console.log("Standalone foreground tunnels (from `svamp service expose`) are not listed here \u2014 check `pgrep -af frpc`.");
|
|
142
142
|
return;
|
|
143
143
|
}
|
|
144
|
-
|
|
144
|
+
const label = (t) => typeof t.state === "string" ? t.state : t.connected ? "connected" : "disconnected";
|
|
145
|
+
const icon = { connected: "\u2713", reconnecting: "\u21BB", failed: "\u2717", disconnected: "\u2717" };
|
|
146
|
+
console.log("Daemon-managed tunnels:");
|
|
145
147
|
for (const t of tunnels) {
|
|
146
|
-
|
|
148
|
+
const st = label(t);
|
|
149
|
+
const ports = Array.isArray(t.ports) && t.ports.length ? ` :${t.ports.join(",")}` : "";
|
|
150
|
+
let extra = "";
|
|
151
|
+
if (st !== "connected") {
|
|
152
|
+
const bits = [];
|
|
153
|
+
if (t.restartAttempts) bits.push(`${t.restartAttempts} restarts`);
|
|
154
|
+
if (t.probe && !t.probe.ok && t.probe.stalenessMs) bits.push(`probe stale ${Math.round(t.probe.stalenessMs / 1e3)}s`);
|
|
155
|
+
if (bits.length) extra = ` (${bits.join(", ")})`;
|
|
156
|
+
}
|
|
157
|
+
console.log(` ${icon[st] ?? "\u2022"} ${t.name}${ports} \u2014 ${st}${extra}`);
|
|
147
158
|
}
|
|
148
159
|
} finally {
|
|
149
160
|
await server.disconnect();
|
|
@@ -161,7 +172,7 @@ async function serviceDelete(args) {
|
|
|
161
172
|
process.exit(1);
|
|
162
173
|
}
|
|
163
174
|
try {
|
|
164
|
-
const { connectAndGetMachine } = await import('./commands-
|
|
175
|
+
const { connectAndGetMachine } = await import('./commands-QGaI-ukW.mjs');
|
|
165
176
|
const { server, machine } = await connectAndGetMachine();
|
|
166
177
|
try {
|
|
167
178
|
await machine.tunnelStop({ name });
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { writeFileSync, readFileSync } from 'fs';
|
|
2
2
|
import { resolve } from 'path';
|
|
3
|
-
import { connectAndGetMachine } from './commands-
|
|
3
|
+
import { connectAndGetMachine } from './commands-QGaI-ukW.mjs';
|
|
4
4
|
import 'node:fs';
|
|
5
5
|
import 'node:child_process';
|
|
6
6
|
import 'node:path';
|
|
7
7
|
import 'node:os';
|
|
8
|
-
import './run-
|
|
8
|
+
import './run-DMahGhJP.mjs';
|
|
9
9
|
import 'os';
|
|
10
10
|
import 'fs/promises';
|
|
11
11
|
import 'url';
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { execSync, execFileSync } from 'node:child_process';
|
|
2
2
|
import { randomUUID } from 'node:crypto';
|
|
3
3
|
import { createServer } from 'node:http';
|
|
4
|
-
import { E as readChecklist, F as compileChecklist, G as RoutineStore, H as RoutineRunner } from './run-
|
|
4
|
+
import { E as readChecklist, F as compileChecklist, G as RoutineStore, H as RoutineRunner } from './run-DMahGhJP.mjs';
|
|
5
5
|
import 'os';
|
|
6
6
|
import 'fs/promises';
|
|
7
7
|
import 'fs';
|
|
@@ -104,7 +104,7 @@ Criteria: ${res.criteria || "(none)"}
|
|
|
104
104
|
urgency: "normal",
|
|
105
105
|
hopCount: 1
|
|
106
106
|
};
|
|
107
|
-
const { connectAndGetMachine } = await import('./commands-
|
|
107
|
+
const { connectAndGetMachine } = await import('./commands-QGaI-ukW.mjs');
|
|
108
108
|
const { server, machine } = await connectAndGetMachine();
|
|
109
109
|
try {
|
|
110
110
|
await machine.sessionRPC(reportTo, "sendInboxMessage", { message });
|
|
@@ -2,7 +2,7 @@ import { existsSync, readFileSync, writeFileSync } from 'node:fs';
|
|
|
2
2
|
import { execSync } from 'node:child_process';
|
|
3
3
|
import { basename, resolve, join, isAbsolute } from 'node:path';
|
|
4
4
|
import os from 'node:os';
|
|
5
|
-
import { I as formatHandle, J as normalizeAllowedUser, K as loadSecurityContextConfig, L as resolveSecurityContext, M as buildSecurityContextFromFlags, N as mergeSecurityContexts, c as connectToHypha, O as buildSessionShareUrl, P as computeOutboundHop, m as shortId, Q as buildMachineShareUrl, T as parseHandle, U as handleMatchesMetadata } from './run-
|
|
5
|
+
import { I as formatHandle, J as normalizeAllowedUser, K as loadSecurityContextConfig, L as resolveSecurityContext, M as buildSecurityContextFromFlags, N as mergeSecurityContexts, c as connectToHypha, O as buildSessionShareUrl, P as computeOutboundHop, m as shortId, Q as buildMachineShareUrl, T as parseHandle, U as handleMatchesMetadata } from './run-DMahGhJP.mjs';
|
|
6
6
|
import 'os';
|
|
7
7
|
import 'fs/promises';
|
|
8
8
|
import 'fs';
|
|
@@ -2384,23 +2384,27 @@ async function sessionLoopStart(sessionIdPartial, task, machineId, opts) {
|
|
|
2384
2384
|
const { server, machine, fullId } = await connectAndResolveSession(sessionIdPartial, machineId);
|
|
2385
2385
|
try {
|
|
2386
2386
|
const svc = getSessionProxy(machine, fullId);
|
|
2387
|
+
const until = opts?.until || opts?.criteria;
|
|
2387
2388
|
const maxIterations = opts?.maxIterations ?? 20;
|
|
2388
|
-
const evaluator = opts?.evaluator !== false;
|
|
2389
|
+
const evaluator = opts?.agent ? true : opts?.evaluator !== false;
|
|
2389
2390
|
await svc.updateConfig({
|
|
2390
2391
|
loop: {
|
|
2391
|
-
task,
|
|
2392
|
-
...
|
|
2392
|
+
...task ? { task } : {},
|
|
2393
|
+
...until ? { until } : {},
|
|
2393
2394
|
...opts?.oracle ? { oracle: opts.oracle } : {},
|
|
2395
|
+
...opts?.parent ? { parent: opts.parent } : {},
|
|
2394
2396
|
max_iterations: maxIterations,
|
|
2395
2397
|
evaluator
|
|
2396
2398
|
}
|
|
2397
2399
|
});
|
|
2398
|
-
console.log(
|
|
2399
|
-
console.log(` Task: ${task.slice(0, 100)}${task.length > 100 ? "..." : ""}`);
|
|
2400
|
-
if (
|
|
2400
|
+
console.log(`\u{1F501} Loop ${task ? "started" : "attached"} on session ${fullId.slice(0, 8)}`);
|
|
2401
|
+
if (task) console.log(` Task: ${task.slice(0, 100)}${task.length > 100 ? "..." : ""}`);
|
|
2402
|
+
if (until) console.log(` Until: ${until.slice(0, 100)}${until.length > 100 ? "..." : ""}`);
|
|
2401
2403
|
console.log(` Oracle: ${opts?.oracle || "(none)"}`);
|
|
2402
2404
|
console.log(` Evaluator: ${evaluator ? "on" : "off"}`);
|
|
2405
|
+
if (opts?.parent) console.log(` Parent review: ${opts.parent.slice(0, 8)}`);
|
|
2403
2406
|
console.log(` Max iterations: ${maxIterations}`);
|
|
2407
|
+
if (!task) console.log(` (hot-plug \u2014 gating the session's current work)`);
|
|
2404
2408
|
} finally {
|
|
2405
2409
|
await server.disconnect();
|
|
2406
2410
|
}
|
|
@@ -2456,44 +2460,6 @@ async function sessionLoopStatus(sessionIdPartial, machineId) {
|
|
|
2456
2460
|
await server.disconnect();
|
|
2457
2461
|
}
|
|
2458
2462
|
}
|
|
2459
|
-
async function sessionSupervise(sessionIdPartial, criteria, machineId, opts) {
|
|
2460
|
-
const { server, machine, fullId } = await connectAndResolveSession(sessionIdPartial, machineId);
|
|
2461
|
-
try {
|
|
2462
|
-
const svc = getSessionProxy(machine, fullId);
|
|
2463
|
-
const judges = [];
|
|
2464
|
-
if (opts?.oracle) judges.push({ type: "oracle", cmd: opts.oracle, on_fail: opts?.parent ? "escalate" : "reject" });
|
|
2465
|
-
if (opts?.parent) judges.push({ type: "parent", parent: opts.parent });
|
|
2466
|
-
if (opts?.agent || judges.length === 0) judges.push({ type: "agent" });
|
|
2467
|
-
const maxRounds = opts?.maxRounds ?? 20;
|
|
2468
|
-
await svc.updateConfig({
|
|
2469
|
-
supervisor: {
|
|
2470
|
-
criteria,
|
|
2471
|
-
judges,
|
|
2472
|
-
action: opts?.action || "block",
|
|
2473
|
-
max_rounds: maxRounds
|
|
2474
|
-
}
|
|
2475
|
-
});
|
|
2476
|
-
const judgeLabel = judges.map((j) => j.type).join("\u2192");
|
|
2477
|
-
console.log(`\u{1F441} Supervisor attached to session ${fullId.slice(0, 8)}`);
|
|
2478
|
-
console.log(` Criteria: ${criteria.slice(0, 100)}${criteria.length > 100 ? "..." : ""}`);
|
|
2479
|
-
console.log(` Judges: ${judgeLabel}`);
|
|
2480
|
-
if (opts?.oracle) console.log(` Oracle: ${opts.oracle}`);
|
|
2481
|
-
if (opts?.parent) console.log(` Parent review: ${opts.parent.slice(0, 8)} (async)`);
|
|
2482
|
-
console.log(` Max rounds: ${maxRounds}`);
|
|
2483
|
-
} finally {
|
|
2484
|
-
await server.disconnect();
|
|
2485
|
-
}
|
|
2486
|
-
}
|
|
2487
|
-
async function sessionUnsupervise(sessionIdPartial, machineId) {
|
|
2488
|
-
const { server, machine, fullId } = await connectAndResolveSession(sessionIdPartial, machineId);
|
|
2489
|
-
try {
|
|
2490
|
-
const svc = getSessionProxy(machine, fullId);
|
|
2491
|
-
await svc.updateConfig({ supervisor: null });
|
|
2492
|
-
console.log(`Supervisor detached from session ${fullId.slice(0, 8)}`);
|
|
2493
|
-
} finally {
|
|
2494
|
-
await server.disconnect();
|
|
2495
|
-
}
|
|
2496
|
-
}
|
|
2497
2463
|
async function sessionInboxSend(sessionIdPartial, body, machineId, opts) {
|
|
2498
2464
|
const { server, machine, fullId } = await connectAndResolveSession(sessionIdPartial, machineId);
|
|
2499
2465
|
try {
|
|
@@ -2634,4 +2600,4 @@ async function sessionInboxClear(sessionIdPartial, machineId, opts) {
|
|
|
2634
2600
|
}
|
|
2635
2601
|
}
|
|
2636
2602
|
|
|
2637
|
-
export { collectAssistantResponse, connectAndGetMachine, connectAndResolveSession, createWorktree, generateWorktreeName, machineExec, machineInfo, machineLs, machineShare, parseShareArg, queryCore, renderMessage, resolveSessionId, sendCore, sessionApprove, sessionArchive, sessionAttach, sessionDelete, sessionDeny, sessionEditMessage, sessionInboxClear, sessionInboxList, sessionInboxRead, sessionInboxReply, sessionInboxSend, sessionInfo, sessionList, sessionLoopCancel, sessionLoopStart, sessionLoopStatus, sessionMachines, sessionMessages, sessionQuery, sessionRefineLastReply, sessionResume, sessionSend, sessionShare, sessionSpawn,
|
|
2603
|
+
export { collectAssistantResponse, connectAndGetMachine, connectAndResolveSession, createWorktree, generateWorktreeName, machineExec, machineInfo, machineLs, machineShare, parseShareArg, queryCore, renderMessage, resolveSessionId, sendCore, sessionApprove, sessionArchive, sessionAttach, sessionDelete, sessionDeny, sessionEditMessage, sessionInboxClear, sessionInboxList, sessionInboxRead, sessionInboxReply, sessionInboxSend, sessionInfo, sessionList, sessionLoopCancel, sessionLoopStart, sessionLoopStatus, sessionMachines, sessionMessages, sessionQuery, sessionRefineLastReply, sessionResume, sessionSend, sessionShare, sessionSpawn, sessionUndoEdit, sessionWait, sessionWhoami, snapshotLatestSeq, validateSendOptions, wiseAskCli };
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { existsSync, readFileSync } from 'node:fs';
|
|
2
2
|
import { join } from 'node:path';
|
|
3
3
|
import os from 'node:os';
|
|
4
|
-
import { c as connectToHypha } from './run-
|
|
4
|
+
import { c as connectToHypha } from './run-DMahGhJP.mjs';
|
|
5
5
|
import { PINNED_CLAUDE_CODE_VERSION } from './pinnedClaudeCode-HydRNEt7.mjs';
|
|
6
6
|
import 'os';
|
|
7
7
|
import 'fs/promises';
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { spawn, execSync } from 'child_process';
|
|
2
|
+
import { createServer } from 'net';
|
|
2
3
|
import { mkdirSync, writeFileSync, unlinkSync, existsSync, chmodSync, readFileSync } from 'fs';
|
|
3
4
|
import { join } from 'path';
|
|
4
5
|
import { homedir, platform, arch } from 'os';
|
|
5
|
-
import {
|
|
6
|
-
import { h as getFrpsSubdomainHost, i as getFrpsServerPort, j as getFrpsServerAddr } from './run-
|
|
6
|
+
import { randomUUID, createHash } from 'crypto';
|
|
7
|
+
import { h as getFrpsSubdomainHost, i as getFrpsServerPort, j as getFrpsServerAddr } from './run-DMahGhJP.mjs';
|
|
7
8
|
import 'fs/promises';
|
|
8
9
|
import 'url';
|
|
9
10
|
import 'node:crypto';
|
|
@@ -124,7 +125,7 @@ async function ensureFrpc(log) {
|
|
|
124
125
|
}
|
|
125
126
|
return FRPC_BIN;
|
|
126
127
|
}
|
|
127
|
-
function generateFrpcConfig(config, proxies) {
|
|
128
|
+
function generateFrpcConfig(config, proxies, admin) {
|
|
128
129
|
const useWSS = config.serverPort === 443;
|
|
129
130
|
const lines = [
|
|
130
131
|
"# Auto-generated by svamp \u2014 do not edit",
|
|
@@ -154,6 +155,14 @@ function generateFrpcConfig(config, proxies) {
|
|
|
154
155
|
'log.level = "info"',
|
|
155
156
|
""
|
|
156
157
|
];
|
|
158
|
+
if (admin) {
|
|
159
|
+
lines.push("# Local admin/status API (loopback only \u2014 polled by the svamp daemon)");
|
|
160
|
+
lines.push('webServer.addr = "127.0.0.1"');
|
|
161
|
+
lines.push(`webServer.port = ${admin.port}`);
|
|
162
|
+
lines.push(`webServer.user = "${admin.user}"`);
|
|
163
|
+
lines.push(`webServer.password = "${admin.password}"`);
|
|
164
|
+
lines.push("");
|
|
165
|
+
}
|
|
157
166
|
for (const proxy of proxies) {
|
|
158
167
|
lines.push(`[[proxies]]`);
|
|
159
168
|
lines.push(`name = "${proxy.name}"`);
|
|
@@ -189,6 +198,42 @@ function generateFrpcConfig(config, proxies) {
|
|
|
189
198
|
}
|
|
190
199
|
return lines.join("\n");
|
|
191
200
|
}
|
|
201
|
+
function getFreePort() {
|
|
202
|
+
return new Promise((resolve, reject) => {
|
|
203
|
+
const srv = createServer();
|
|
204
|
+
srv.unref();
|
|
205
|
+
srv.on("error", reject);
|
|
206
|
+
srv.listen(0, "127.0.0.1", () => {
|
|
207
|
+
const addr = srv.address();
|
|
208
|
+
const port = typeof addr === "object" && addr ? addr.port : 0;
|
|
209
|
+
srv.close(() => port ? resolve(port) : reject(new Error("no port")));
|
|
210
|
+
});
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
function parseFrpcStatus(payload, proxyNames) {
|
|
214
|
+
const byName = /* @__PURE__ */ new Map();
|
|
215
|
+
if (payload && typeof payload === "object") {
|
|
216
|
+
for (const group of Object.values(payload)) {
|
|
217
|
+
if (!Array.isArray(group)) continue;
|
|
218
|
+
for (const p of group) {
|
|
219
|
+
if (p && typeof p.name === "string") {
|
|
220
|
+
byName.set(p.name, { status: String(p.status ?? ""), err: p.err ? String(p.err) : void 0 });
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
const missing = [];
|
|
226
|
+
const failed = [];
|
|
227
|
+
for (const name of proxyNames) {
|
|
228
|
+
const entry = byName.get(name);
|
|
229
|
+
if (!entry) {
|
|
230
|
+
missing.push(name);
|
|
231
|
+
continue;
|
|
232
|
+
}
|
|
233
|
+
if (entry.status !== "running") failed.push({ name, status: entry.status, err: entry.err });
|
|
234
|
+
}
|
|
235
|
+
return { allRunning: missing.length === 0 && failed.length === 0, missing, failed };
|
|
236
|
+
}
|
|
192
237
|
class FrpcTunnel {
|
|
193
238
|
process = null;
|
|
194
239
|
_connected = false;
|
|
@@ -211,6 +256,11 @@ class FrpcTunnel {
|
|
|
211
256
|
_lastProbeOkAt = 0;
|
|
212
257
|
_lastProbeFailAt = 0;
|
|
213
258
|
_probeOk = false;
|
|
259
|
+
// frpc admin/status API state (set when options.adminStatus is true).
|
|
260
|
+
_adminPort = 0;
|
|
261
|
+
_adminUser = "svamp";
|
|
262
|
+
_adminPassword = randomUUID();
|
|
263
|
+
_statusTimer = null;
|
|
214
264
|
constructor(options) {
|
|
215
265
|
this.options = options;
|
|
216
266
|
this.log = options.log || ((msg) => console.log(`[FRPC] ${msg}`));
|
|
@@ -255,7 +305,18 @@ class FrpcTunnel {
|
|
|
255
305
|
if (this._destroyed) return;
|
|
256
306
|
if (this.process) return;
|
|
257
307
|
const frpcPath = await ensureFrpc(this.log);
|
|
258
|
-
|
|
308
|
+
let admin;
|
|
309
|
+
if (this.options.adminStatus) {
|
|
310
|
+
if (!this._adminPort) {
|
|
311
|
+
try {
|
|
312
|
+
this._adminPort = await getFreePort();
|
|
313
|
+
} catch (err) {
|
|
314
|
+
this.log(`admin port alloc failed: ${err?.message ?? err}`);
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
if (this._adminPort) admin = { port: this._adminPort, user: this._adminUser, password: this._adminPassword };
|
|
318
|
+
}
|
|
319
|
+
const configContent = generateFrpcConfig(this.serverConfig, this.proxies, admin);
|
|
259
320
|
writeFileSync(this.configPath, configContent);
|
|
260
321
|
this.log(`Config written to ${this.configPath}`);
|
|
261
322
|
return new Promise((resolve, reject) => {
|
|
@@ -335,6 +396,8 @@ class FrpcTunnel {
|
|
|
335
396
|
}
|
|
336
397
|
}
|
|
337
398
|
});
|
|
399
|
+
} else if (this.options.adminStatus && this._adminPort) {
|
|
400
|
+
this.startAdminStatusLoop();
|
|
338
401
|
}
|
|
339
402
|
setTimeout(() => {
|
|
340
403
|
if (!resolved) {
|
|
@@ -433,11 +496,86 @@ class FrpcTunnel {
|
|
|
433
496
|
this._probeTimer = null;
|
|
434
497
|
}
|
|
435
498
|
}
|
|
499
|
+
/**
|
|
500
|
+
* Poll frpc's loopback admin API (`/api/status`) to track whether this
|
|
501
|
+
* tunnel's proxies are registered ("running"). Feeds the same `_probeOk` /
|
|
502
|
+
* staleness fields the daemon health loop watches, so a ghosted/stuck proxy
|
|
503
|
+
* is detected and recreated even with no HTTP health endpoint on the backend.
|
|
504
|
+
*
|
|
505
|
+
* Conservative semantics (critical infra — must not churn healthy tunnels):
|
|
506
|
+
* - all proxies "running" → ok (refresh lastProbeOkAt)
|
|
507
|
+
* - a proxy present but not running, or missing after a grace period
|
|
508
|
+
* → fail (sets lastProbeFailAt; daemon recreates after staleness)
|
|
509
|
+
* - admin API unreachable → INCONCLUSIVE: leave _probeOk untouched
|
|
510
|
+
* (a transient blip can't flip ok→fail)
|
|
511
|
+
*/
|
|
512
|
+
startAdminStatusLoop() {
|
|
513
|
+
this.stopAdminStatusLoop();
|
|
514
|
+
if (!this._adminPort) return;
|
|
515
|
+
const proxyNames = this.proxies.map((p) => p.name);
|
|
516
|
+
const intervalMs = this.options.probeIntervalMs ?? 3e4;
|
|
517
|
+
const startedAt = Date.now();
|
|
518
|
+
const auth = "Basic " + Buffer.from(`${this._adminUser}:${this._adminPassword}`).toString("base64");
|
|
519
|
+
this._probeOk = true;
|
|
520
|
+
this._lastProbeOkAt = Date.now();
|
|
521
|
+
let inFlight = false;
|
|
522
|
+
const poll = async () => {
|
|
523
|
+
if (this._destroyed || inFlight || !this.process) return;
|
|
524
|
+
inFlight = true;
|
|
525
|
+
try {
|
|
526
|
+
const ctrl = new AbortController();
|
|
527
|
+
const timer = setTimeout(() => ctrl.abort(), 5e3);
|
|
528
|
+
let payload;
|
|
529
|
+
try {
|
|
530
|
+
const resp = await fetch(`http://127.0.0.1:${this._adminPort}/api/status`, {
|
|
531
|
+
headers: { Authorization: auth },
|
|
532
|
+
signal: ctrl.signal
|
|
533
|
+
});
|
|
534
|
+
if (!resp.ok) throw new Error(`admin status ${resp.status}`);
|
|
535
|
+
payload = await resp.json();
|
|
536
|
+
} finally {
|
|
537
|
+
clearTimeout(timer);
|
|
538
|
+
}
|
|
539
|
+
const { allRunning, missing, failed } = parseFrpcStatus(payload, proxyNames);
|
|
540
|
+
const graceElapsed = Date.now() - startedAt > 2e4;
|
|
541
|
+
if (allRunning || !failed.length && !graceElapsed) {
|
|
542
|
+
const wasFailing = !this._probeOk;
|
|
543
|
+
this._probeOk = true;
|
|
544
|
+
this._lastProbeOkAt = Date.now();
|
|
545
|
+
if (wasFailing) this.log(`admin status ok: all proxies running`);
|
|
546
|
+
} else {
|
|
547
|
+
const wasOk = this._probeOk;
|
|
548
|
+
this._probeOk = false;
|
|
549
|
+
this._lastProbeFailAt = Date.now();
|
|
550
|
+
if (wasOk) {
|
|
551
|
+
const detail = [
|
|
552
|
+
...failed.map((f) => `${f.name}=${f.status}${f.err ? ` (${f.err})` : ""}`),
|
|
553
|
+
...missing.map((m) => `${m}=missing`)
|
|
554
|
+
].join(", ");
|
|
555
|
+
this.log(`admin status fail: ${detail}`);
|
|
556
|
+
this.options.onProbeFail?.(new Error(`frpc proxy not running: ${detail}`));
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
} catch {
|
|
560
|
+
} finally {
|
|
561
|
+
inFlight = false;
|
|
562
|
+
}
|
|
563
|
+
};
|
|
564
|
+
if (intervalMs > 0) this._statusTimer = setInterval(poll, intervalMs);
|
|
565
|
+
setTimeout(() => void poll(), 3e3);
|
|
566
|
+
}
|
|
567
|
+
stopAdminStatusLoop() {
|
|
568
|
+
if (this._statusTimer) {
|
|
569
|
+
clearInterval(this._statusTimer);
|
|
570
|
+
this._statusTimer = null;
|
|
571
|
+
}
|
|
572
|
+
}
|
|
436
573
|
/** Disconnect and stop the frpc process. */
|
|
437
574
|
destroy() {
|
|
438
575
|
this._destroyed = true;
|
|
439
576
|
this._connected = false;
|
|
440
577
|
this.stopProbeLoop();
|
|
578
|
+
this.stopAdminStatusLoop();
|
|
441
579
|
if (this.process) {
|
|
442
580
|
this.process.kill("SIGTERM");
|
|
443
581
|
const p = this.process;
|
|
@@ -473,8 +611,8 @@ class FrpcTunnel {
|
|
|
473
611
|
firstErrorAt: this._firstErrorAt,
|
|
474
612
|
restartAttempts: this._restartAttempts,
|
|
475
613
|
failingDurationMs: this._firstErrorAt > 0 ? Date.now() - this._firstErrorAt : 0,
|
|
476
|
-
probe: this.resolveProbeUrl() ? {
|
|
477
|
-
url: this.resolveProbeUrl()
|
|
614
|
+
probe: this.resolveProbeUrl() || this.options.adminStatus && this._adminPort ? {
|
|
615
|
+
url: this.resolveProbeUrl() || `frpc-admin:${this._adminPort}/api/status`,
|
|
478
616
|
ok: this._probeOk,
|
|
479
617
|
lastOkAt: this._lastProbeOkAt,
|
|
480
618
|
lastFailAt: this._lastProbeFailAt,
|
|
@@ -485,7 +623,8 @@ class FrpcTunnel {
|
|
|
485
623
|
/** Update the Hypha token. Rewrites config; takes effect on next frpc restart. */
|
|
486
624
|
updateToken(newToken) {
|
|
487
625
|
this.serverConfig.hyphaToken = newToken;
|
|
488
|
-
const
|
|
626
|
+
const admin = this.options.adminStatus && this._adminPort ? { port: this._adminPort, user: this._adminUser, password: this._adminPassword } : void 0;
|
|
627
|
+
const configContent = generateFrpcConfig(this.serverConfig, this.proxies, admin);
|
|
489
628
|
writeFileSync(this.configPath, configContent);
|
|
490
629
|
this.log("Config updated with fresh token");
|
|
491
630
|
}
|
|
@@ -539,4 +678,4 @@ async function runFrpcTunnel(name, ports, serverConfig, tunnelOptions) {
|
|
|
539
678
|
}
|
|
540
679
|
}
|
|
541
680
|
|
|
542
|
-
export { FrpcTunnel, ensureFrpc, generateFrpcConfig, runFrpcTunnel };
|
|
681
|
+
export { FrpcTunnel, ensureFrpc, generateFrpcConfig, getFreePort, parseFrpcStatus, runFrpcTunnel };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { D as resolveModel, V as describeMisconfiguration, W as buildMachineDeps } from './run-
|
|
2
|
-
import { handleRealtimeEvent, initMachineVoiceSession } from './sideband-
|
|
1
|
+
import { D as resolveModel, V as describeMisconfiguration, W as buildMachineDeps } from './run-DMahGhJP.mjs';
|
|
2
|
+
import { handleRealtimeEvent, initMachineVoiceSession } from './sideband-Bk7iN3dp.mjs';
|
|
3
3
|
import { WebSocket } from 'ws';
|
|
4
4
|
import { execSync, spawn } from 'child_process';
|
|
5
5
|
import 'os';
|
package/dist/index.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { c as connectToHypha, a as createSessionStore, d as daemonStatus, g as getHyphaServerUrl, r as registerMachineService, s as startDaemon, b as stopDaemon } from './run-
|
|
1
|
+
export { c as connectToHypha, a as createSessionStore, d as daemonStatus, g as getHyphaServerUrl, r as registerMachineService, s as startDaemon, b as stopDaemon } from './run-DMahGhJP.mjs';
|
|
2
2
|
import 'os';
|
|
3
3
|
import 'fs/promises';
|
|
4
4
|
import 'fs';
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
var name = "svamp-cli";
|
|
2
|
-
var version = "0.2.
|
|
2
|
+
var version = "0.2.130";
|
|
3
3
|
var description = "Svamp CLI — AI workspace daemon on Hypha Cloud";
|
|
4
4
|
var author = "Amun AI AB";
|
|
5
5
|
var license = "SEE LICENSE IN LICENSE";
|
|
@@ -19,7 +19,7 @@ var exports$1 = {
|
|
|
19
19
|
var scripts = {
|
|
20
20
|
build: "rm -rf dist bin/skills && mkdir -p bin/skills && cp -r ../../skills/artifact bin/skills/artifact && cp -r ../../skills/loop bin/skills/loop && cp -r ../../skills/crew bin/skills/crew && tsc --noEmit && pkgroll",
|
|
21
21
|
typecheck: "tsc --noEmit",
|
|
22
|
-
test: "npx tsx test/test-context-window.mjs && npx tsx test/test-instance-config.mjs && npx tsx test/test-authorize.mjs && npx tsx test/test-normalize-allowed-user.mjs && npx tsx test/test-share-url.mjs && npx tsx test/test-update-sharing-normalization.mjs && npx tsx test/test-staged-homes-sweep.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-isolation-decision.mjs && npx tsx test/test-loop-activation.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-claude-auth.mjs && npx tsx test/test-output-formatters.mjs && npx tsx test/test-inbox-guard.mjs && npx tsx test/test-auto-topic.mjs && npx tsx test/test-project-info.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-session-send-query.mjs && npx tsx test/test-skills.mjs && npx tsx test/test-agent-grouping.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-supervisor-lock.mjs && node test/test-supervisor-restart.mjs && npx tsx test/test-clear-detection.mjs && npx tsx test/test-session-consolidation.mjs && npx tsx test/test-inbox.mjs && npx tsx test/test-checklist.mjs && npx tsx test/test-short-id.mjs && npx tsx test/test-transcript-edit.mjs && npx tsx test/test-edit-history.mjs && npx tsx test/test-friendly-name.mjs && npx tsx test/test-session-rpc-dispatch.mjs && npx tsx test/test-sandbox-cli.mjs && npx tsx test/test-serve-manager.mjs && npx tsx test/test-serve-stability.mjs && npx tsx test/test-frpc-e2e.mjs --unit-only && node test/pinnedClaudeCode.test.mjs && node test/fleet.test.mjs && npx tsx test/test-routine.mjs && npx tsx test/test-routine-rpc.mjs && npx tsx test/test-checklist-watchdog.mjs && npx tsx test/test-session-file.mjs && npx tsx test/test-channel-rpc.mjs && npx tsx test/test-wise-agent.mjs && npx tsx test/test-channel-agent.mjs && npx tsx test/test-channels-service.mjs && npx tsx test/test-channel-async-reply.mjs && npx tsx test/test-channel-binding.mjs && npx tsx test/test-channel-identity.mjs && npx tsx test/test-wise-agent-auth.mjs && npx tsx test/test-channel-http.mjs && npx tsx test/test-wise-voice.mjs && npx tsx test/test-wise-headless.mjs && npx tsx test/test-wise-machine.mjs && npx tsx test/test-crew-merge.mjs",
|
|
22
|
+
test: "npx tsx test/test-context-window.mjs && npx tsx test/test-instance-config.mjs && npx tsx test/test-authorize.mjs && npx tsx test/test-normalize-allowed-user.mjs && npx tsx test/test-share-url.mjs && npx tsx test/test-update-sharing-normalization.mjs && npx tsx test/test-staged-homes-sweep.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-isolation-decision.mjs && npx tsx test/test-loop-activation.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-claude-auth.mjs && npx tsx test/test-output-formatters.mjs && npx tsx test/test-inbox-guard.mjs && npx tsx test/test-auto-topic.mjs && npx tsx test/test-project-info.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-session-send-query.mjs && npx tsx test/test-skills.mjs && npx tsx test/test-agent-grouping.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-supervisor-lock.mjs && node test/test-supervisor-restart.mjs && npx tsx test/test-clear-detection.mjs && npx tsx test/test-session-consolidation.mjs && npx tsx test/test-inbox.mjs && npx tsx test/test-checklist.mjs && npx tsx test/test-short-id.mjs && npx tsx test/test-transcript-edit.mjs && npx tsx test/test-edit-history.mjs && npx tsx test/test-friendly-name.mjs && npx tsx test/test-session-rpc-dispatch.mjs && npx tsx test/test-sandbox-cli.mjs && npx tsx test/test-serve-manager.mjs && npx tsx test/test-serve-stability.mjs && npx tsx test/test-frpc-e2e.mjs --unit-only && npx tsx test/test-frpc-status.mjs && node test/pinnedClaudeCode.test.mjs && node test/fleet.test.mjs && npx tsx test/test-routine.mjs && npx tsx test/test-routine-rpc.mjs && npx tsx test/test-checklist-watchdog.mjs && npx tsx test/test-session-file.mjs && npx tsx test/test-channel-rpc.mjs && npx tsx test/test-wise-agent.mjs && npx tsx test/test-channel-agent.mjs && npx tsx test/test-channels-service.mjs && npx tsx test/test-channel-async-reply.mjs && npx tsx test/test-channel-binding.mjs && npx tsx test/test-channel-identity.mjs && npx tsx test/test-wise-agent-auth.mjs && npx tsx test/test-channel-http.mjs && npx tsx test/test-wise-voice.mjs && npx tsx test/test-wise-headless.mjs && npx tsx test/test-wise-machine.mjs && npx tsx test/test-crew-merge.mjs",
|
|
23
23
|
"test:hypha": "node --no-warnings test/test-hypha-service.mjs",
|
|
24
24
|
dev: "tsx src/cli.ts",
|
|
25
25
|
"dev:daemon": "tsx src/cli.ts daemon start-sync",
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{createRequire as _pkgrollCR}from"node:module";const require=_pkgrollCR(import.meta.url);import { X as generateFriendlyName, m as shortId, c as connectToHypha, a as createSessionStore, r as registerMachineService, Y as generateHookSettings } from './run-
|
|
1
|
+
import{createRequire as _pkgrollCR}from"node:module";const require=_pkgrollCR(import.meta.url);import { X as generateFriendlyName, m as shortId, c as connectToHypha, a as createSessionStore, r as registerMachineService, Y as generateHookSettings } from './run-DMahGhJP.mjs';
|
|
2
2
|
import os from 'node:os';
|
|
3
3
|
import { resolve, join } from 'node:path';
|
|
4
4
|
import { existsSync, readFileSync, watch } from 'node:fs';
|