@lifeaitools/clauth 1.30.10 → 1.30.12
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/cli/commands/serve.js
CHANGED
|
@@ -30,7 +30,9 @@ import {
|
|
|
30
30
|
} from "../watchdog-registry.js";
|
|
31
31
|
import {
|
|
32
32
|
discoverPlugins,
|
|
33
|
+
getSupervisorDir,
|
|
33
34
|
getSupervisorPort,
|
|
35
|
+
loadSupervisorState,
|
|
34
36
|
listPlugins,
|
|
35
37
|
listRoutes,
|
|
36
38
|
listSurfaces,
|
|
@@ -587,6 +589,65 @@ export function isLoopbackAddress(remote) {
|
|
|
587
589
|
return remote === "127.0.0.1" || remote === "::1" || remote === "::ffff:127.0.0.1";
|
|
588
590
|
}
|
|
589
591
|
|
|
592
|
+
function summarizeSupervisorTarget(target) {
|
|
593
|
+
if (!target || typeof target !== "object") return null;
|
|
594
|
+
return {
|
|
595
|
+
plugin_id: typeof target.plugin_id === "string" ? target.plugin_id : undefined,
|
|
596
|
+
surface_id: typeof target.surface_id === "string" ? target.surface_id : undefined,
|
|
597
|
+
tunnel_id: typeof target.tunnel_id === "string" ? target.tunnel_id : undefined,
|
|
598
|
+
route_id: typeof target.route_id === "string" ? target.route_id : undefined,
|
|
599
|
+
};
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
function summarizeSupervisorState(value) {
|
|
603
|
+
if (!value || typeof value !== "object") return null;
|
|
604
|
+
return {
|
|
605
|
+
ok: typeof value.ok === "boolean" ? value.ok : undefined,
|
|
606
|
+
state: typeof value.state === "string" ? value.state : undefined,
|
|
607
|
+
reason: typeof value.reason === "string" ? value.reason : undefined,
|
|
608
|
+
status: typeof value.status === "number" ? value.status : undefined,
|
|
609
|
+
private: typeof value.private === "boolean" ? value.private : undefined,
|
|
610
|
+
public_route: typeof value.public_route === "boolean" ? value.public_route : undefined,
|
|
611
|
+
evidence: Array.isArray(value.evidence) ? value.evidence.map((item) => String(item).slice(0, 200)).slice(0, 5) : [],
|
|
612
|
+
};
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
export function supervisorLogDto(event) {
|
|
616
|
+
if (!event || typeof event !== "object") return { kind: "unknown" };
|
|
617
|
+
if (event.kind === "operation") {
|
|
618
|
+
return {
|
|
619
|
+
ts: event.ts || event.created_at,
|
|
620
|
+
kind: "operation",
|
|
621
|
+
operationId: event.operationId,
|
|
622
|
+
actor: event.actor,
|
|
623
|
+
action: event.action,
|
|
624
|
+
target: summarizeSupervisorTarget(event.target),
|
|
625
|
+
resulting_state: summarizeSupervisorState(event.resulting_state),
|
|
626
|
+
completed_at: event.completed_at,
|
|
627
|
+
};
|
|
628
|
+
}
|
|
629
|
+
return {
|
|
630
|
+
ts: event.ts,
|
|
631
|
+
kind: event.kind,
|
|
632
|
+
plugin_id: event.plugin_id,
|
|
633
|
+
source: event.source,
|
|
634
|
+
state: event.state,
|
|
635
|
+
};
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
function supervisorOperationDto(receipt) {
|
|
639
|
+
return {
|
|
640
|
+
operationId: receipt.operationId,
|
|
641
|
+
actor: receipt.actor,
|
|
642
|
+
action: receipt.action,
|
|
643
|
+
target: summarizeSupervisorTarget(receipt.target),
|
|
644
|
+
resulting_state: summarizeSupervisorState(receipt.resulting_state),
|
|
645
|
+
evidence: Array.isArray(receipt.evidence) ? receipt.evidence.map((item) => String(item).slice(0, 200)).slice(0, 5) : [],
|
|
646
|
+
created_at: receipt.created_at,
|
|
647
|
+
completed_at: receipt.completed_at,
|
|
648
|
+
};
|
|
649
|
+
}
|
|
650
|
+
|
|
590
651
|
export function supervisorRequiresWriteToken(port = getSupervisorPort(), env = process.env) {
|
|
591
652
|
if (port !== getSupervisorPort()) return true;
|
|
592
653
|
return env.CLAUTH_SUPERVISOR_REQUIRE_WRITE_TOKEN === "1";
|
|
@@ -905,8 +966,32 @@ function dashboardHtml(port, whitelist, isStaged = false, initWriteToken = null)
|
|
|
905
966
|
.wiz-link:hover{text-decoration:underline}
|
|
906
967
|
.wiz-test-result{padding:12px 14px;border-radius:8px;font-size:.85rem;margin-top:10px;display:none}
|
|
907
968
|
.wiz-test-result.ok{background:rgba(74,222,128,.08);border:1px solid rgba(74,222,128,.2);color:#4ade80}
|
|
908
|
-
.wiz-test-result.fail{background:rgba(248,113,113,.08);border:1px solid rgba(248,113,113,.2);color:#f87171}
|
|
909
|
-
|
|
969
|
+
.wiz-test-result.fail{background:rgba(248,113,113,.08);border:1px solid rgba(248,113,113,.2);color:#f87171}
|
|
970
|
+
.supervisor-panel{margin:14px 16px;border:1px solid #233249;background:linear-gradient(135deg,rgba(15,23,42,.96),rgba(10,15,26,.98));border-radius:12px;padding:14px}
|
|
971
|
+
.supervisor-head{display:flex;align-items:flex-start;justify-content:space-between;gap:12px;margin-bottom:12px}
|
|
972
|
+
.supervisor-title{font-size:.95rem;font-weight:700;color:#e2e8f0}
|
|
973
|
+
.supervisor-sub{font-size:.76rem;color:#94a3b8;margin-top:3px;line-height:1.35}
|
|
974
|
+
.supervisor-actions{display:flex;gap:8px;flex-wrap:wrap}
|
|
975
|
+
.supervisor-action{background:#0f172a;color:#cbd5e1;border:1px solid #334155;border-radius:6px;padding:6px 10px;font-size:.75rem;cursor:pointer}
|
|
976
|
+
.supervisor-action:hover{border-color:#38bdf8;color:#e0f2fe}
|
|
977
|
+
.supervisor-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(220px,1fr));gap:10px}
|
|
978
|
+
.supervisor-card{border:1px solid #1e293b;background:rgba(2,6,23,.52);border-radius:10px;padding:10px;min-width:0}
|
|
979
|
+
.supervisor-card h4{margin:0 0 8px;color:#cbd5e1;font-size:.75rem;letter-spacing:.08em;text-transform:uppercase}
|
|
980
|
+
.supervisor-kpi{font-size:1.35rem;font-weight:700;color:#f8fafc}
|
|
981
|
+
.supervisor-meta{font-size:.72rem;color:#94a3b8;font-family:'Courier New',monospace;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
|
982
|
+
.supervisor-pill{display:inline-flex;align-items:center;border-radius:999px;padding:2px 7px;margin:2px 4px 2px 0;font-size:.68rem;border:1px solid #334155;color:#cbd5e1;background:#0f172a}
|
|
983
|
+
.supervisor-pill.ok{border-color:rgba(74,222,128,.35);color:#86efac;background:rgba(34,197,94,.08)}
|
|
984
|
+
.supervisor-pill.warn{border-color:rgba(250,204,21,.35);color:#fde68a;background:rgba(250,204,21,.08)}
|
|
985
|
+
.supervisor-pill.bad{border-color:rgba(248,113,113,.35);color:#fecaca;background:rgba(248,113,113,.08)}
|
|
986
|
+
.supervisor-list{display:flex;flex-direction:column;gap:8px;max-height:320px;overflow:auto}
|
|
987
|
+
.supervisor-row{border:1px solid #1e293b;border-radius:8px;padding:8px;background:rgba(15,23,42,.45)}
|
|
988
|
+
.supervisor-row-top{display:flex;justify-content:space-between;gap:8px;align-items:center}
|
|
989
|
+
.supervisor-name{font-size:.82rem;color:#e2e8f0;font-weight:600;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
|
990
|
+
.supervisor-row-actions{display:flex;gap:5px;flex-wrap:wrap;margin-top:7px}
|
|
991
|
+
.supervisor-log{font-family:'Courier New',monospace;font-size:.7rem;color:#94a3b8;white-space:pre-wrap;word-break:break-word;max-height:280px;overflow:auto}
|
|
992
|
+
.supervisor-feedback{margin-top:8px;padding:7px 9px;border-radius:6px;font-family:'Courier New',monospace;font-size:.72rem;color:#86efac;background:rgba(34,197,94,.08);border:1px solid rgba(74,222,128,.22);display:none}
|
|
993
|
+
.supervisor-feedback.bad{color:#fecaca;background:rgba(248,113,113,.08);border-color:rgba(248,113,113,.25)}
|
|
994
|
+
</style>
|
|
910
995
|
</head>
|
|
911
996
|
<body>
|
|
912
997
|
|
|
@@ -1094,7 +1179,7 @@ function dashboardHtml(port, whitelist, isStaged = false, initWriteToken = null)
|
|
|
1094
1179
|
</div>
|
|
1095
1180
|
</div>
|
|
1096
1181
|
|
|
1097
|
-
<div class="monkey-panel" id="monkey-panel">
|
|
1182
|
+
<div class="monkey-panel" id="monkey-panel">
|
|
1098
1183
|
<div class="monkey-head">
|
|
1099
1184
|
<div>
|
|
1100
1185
|
<div class="monkey-title">Monkey local CLI dispatch</div>
|
|
@@ -1119,10 +1204,45 @@ function dashboardHtml(port, whitelist, isStaged = false, initWriteToken = null)
|
|
|
1119
1204
|
<span class="monkey-status" id="monkey-status">idle</span>
|
|
1120
1205
|
</div>
|
|
1121
1206
|
<div class="monkey-result" id="monkey-result"></div>
|
|
1122
|
-
</div>
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1207
|
+
</div>
|
|
1208
|
+
|
|
1209
|
+
<section class="supervisor-panel" id="supervisor-panel">
|
|
1210
|
+
<div class="supervisor-head">
|
|
1211
|
+
<div>
|
|
1212
|
+
<div class="supervisor-title">Local Software Factory Supervisor</div>
|
|
1213
|
+
<div class="supervisor-sub">clauth owns local LIFEAI plugin discovery, candidate tests, clauth-owned surface operations, receipts, and supervisor event logs. Dev Center should call this surface instead of PM2 directly.</div>
|
|
1214
|
+
</div>
|
|
1215
|
+
<div class="supervisor-actions">
|
|
1216
|
+
<button class="supervisor-action" onclick="loadSupervisorCockpit()">Refresh</button>
|
|
1217
|
+
<button class="supervisor-action" onclick="rescanSupervisorPlugins()">Rescan plugins</button>
|
|
1218
|
+
</div>
|
|
1219
|
+
</div>
|
|
1220
|
+
<div id="supervisor-status" class="supervisor-sub">Loading supervisor state…</div>
|
|
1221
|
+
<div id="supervisor-feedback" class="supervisor-feedback" role="status"></div>
|
|
1222
|
+
<div class="supervisor-grid" style="margin-top:10px">
|
|
1223
|
+
<div class="supervisor-card">
|
|
1224
|
+
<h4>Plugins</h4>
|
|
1225
|
+
<div class="supervisor-kpi" id="supervisor-plugin-count">—</div>
|
|
1226
|
+
<div class="supervisor-meta" id="supervisor-plugin-meta">awaiting data</div>
|
|
1227
|
+
<div class="supervisor-list" id="supervisor-plugins" style="margin-top:8px"></div>
|
|
1228
|
+
</div>
|
|
1229
|
+
<div class="supervisor-card">
|
|
1230
|
+
<h4>Surfaces</h4>
|
|
1231
|
+
<div class="supervisor-kpi" id="supervisor-surface-count">—</div>
|
|
1232
|
+
<div class="supervisor-meta" id="supervisor-surface-meta">destination + owner matrix</div>
|
|
1233
|
+
<div class="supervisor-list" id="supervisor-surfaces" style="margin-top:8px"></div>
|
|
1234
|
+
</div>
|
|
1235
|
+
<div class="supervisor-card">
|
|
1236
|
+
<h4>Operations + log</h4>
|
|
1237
|
+
<div class="supervisor-kpi" id="supervisor-operation-count">—</div>
|
|
1238
|
+
<div class="supervisor-meta" id="supervisor-log-path">events.jsonl</div>
|
|
1239
|
+
<div class="supervisor-log" id="supervisor-events" style="margin-top:8px">No events loaded.</div>
|
|
1240
|
+
</div>
|
|
1241
|
+
</div>
|
|
1242
|
+
</section>
|
|
1243
|
+
|
|
1244
|
+
|
|
1245
|
+
<div class="wizard-panel" id="wizard-panel">
|
|
1126
1246
|
<div class="wizard-header">
|
|
1127
1247
|
<div class="tunnel-dot off" id="wiz-dot" style="width:10px;height:10px;border-radius:50%"></div>
|
|
1128
1248
|
<div class="wizard-title">claude.ai Tunnel Setup</div>
|
|
@@ -1372,10 +1492,103 @@ function showMain(ping) {
|
|
|
1372
1492
|
document.getElementById("s-fails").textContent =
|
|
1373
1493
|
ping.failures + "/" + (ping.failures + ping.failures_remaining);
|
|
1374
1494
|
}
|
|
1375
|
-
pollTunnel();
|
|
1376
|
-
updateBuildStatus();
|
|
1377
|
-
|
|
1378
|
-
|
|
1495
|
+
pollTunnel();
|
|
1496
|
+
updateBuildStatus();
|
|
1497
|
+
loadSupervisorCockpit();
|
|
1498
|
+
refreshWriteLockUi();
|
|
1499
|
+
}
|
|
1500
|
+
|
|
1501
|
+
function supervisorBadge(text, kind) {
|
|
1502
|
+
return '<span class="supervisor-pill ' + (kind || '') + '">' + htmlEscape(text) + '</span>';
|
|
1503
|
+
}
|
|
1504
|
+
|
|
1505
|
+
async function supervisorJson(path, options) {
|
|
1506
|
+
const response = await fetch(BASE + path, { cache: "no-store", ...(options || {}) });
|
|
1507
|
+
const data = await response.json().catch(() => ({}));
|
|
1508
|
+
if (!response.ok || data.error) throw new Error(data.error || ("HTTP " + response.status));
|
|
1509
|
+
return data;
|
|
1510
|
+
}
|
|
1511
|
+
|
|
1512
|
+
function supervisorFeedback(text, bad) {
|
|
1513
|
+
const el = document.getElementById("supervisor-feedback");
|
|
1514
|
+
if (!el) return;
|
|
1515
|
+
el.textContent = text || "";
|
|
1516
|
+
el.className = "supervisor-feedback" + (bad ? " bad" : "");
|
|
1517
|
+
el.style.display = text ? "block" : "none";
|
|
1518
|
+
}
|
|
1519
|
+
|
|
1520
|
+
async function loadSupervisorCockpit() {
|
|
1521
|
+
const status = document.getElementById("supervisor-status");
|
|
1522
|
+
if (status) status.textContent = "Loading supervisor state…";
|
|
1523
|
+
try {
|
|
1524
|
+
const [health, plugins, surfaces, logs] = await Promise.all([
|
|
1525
|
+
supervisorJson("/health"),
|
|
1526
|
+
supervisorJson("/v1/plugins"),
|
|
1527
|
+
supervisorJson("/v1/surfaces"),
|
|
1528
|
+
supervisorJson("/v1/logs?limit=40"),
|
|
1529
|
+
]);
|
|
1530
|
+
const pluginRows = plugins.plugins || [];
|
|
1531
|
+
const surfaceRows = surfaces.surfaces || [];
|
|
1532
|
+
document.getElementById("supervisor-plugin-count").textContent = String(pluginRows.length);
|
|
1533
|
+
document.getElementById("supervisor-surface-count").textContent = String(surfaceRows.length);
|
|
1534
|
+
document.getElementById("supervisor-operation-count").textContent = String((logs.operations || []).length);
|
|
1535
|
+
document.getElementById("supervisor-plugin-meta").textContent = "current " + (pluginRows.filter(p => p.state === "current").length) + " · awaiting " + (pluginRows.filter(p => p.state === "awaiting_enable").length);
|
|
1536
|
+
document.getElementById("supervisor-surface-meta").textContent = "pm2 home: " + (health.pm2_home || "—");
|
|
1537
|
+
document.getElementById("supervisor-log-path").textContent = logs.log_path || "events.jsonl";
|
|
1538
|
+
document.getElementById("supervisor-plugins").innerHTML = pluginRows.length ? pluginRows.map(renderSupervisorPlugin).join("") : '<div class="supervisor-sub">No plugins discovered.</div>';
|
|
1539
|
+
document.getElementById("supervisor-surfaces").innerHTML = surfaceRows.length ? surfaceRows.map(renderSupervisorSurface).join("") : '<div class="supervisor-sub">No surfaces declared.</div>';
|
|
1540
|
+
document.getElementById("supervisor-events").textContent = (logs.events || []).slice(-20).reverse().map(e => {
|
|
1541
|
+
const label = e.kind === "operation" ? (e.action + " " + JSON.stringify(e.target || {})) : (e.kind + " " + (e.plugin_id || ""));
|
|
1542
|
+
return (e.ts || e.created_at || "") + " " + label;
|
|
1543
|
+
}).join("\\n") || "No events yet.";
|
|
1544
|
+
if (status) status.innerHTML = supervisorBadge("supervisor " + (health.status === "ok" ? "healthy" : (health.status || "unknown")), health.status === "ok" ? "ok" : "warn") + supervisorBadge("vault " + (health.vault_locked ? "locked" : "unlocked"), health.vault_locked ? "warn" : "ok") + supervisorBadge("v" + (health.clauth_version || "${VERSION}"), "");
|
|
1545
|
+
} catch (err) {
|
|
1546
|
+
if (status) status.innerHTML = supervisorBadge("supervisor unreachable", "bad") + " " + htmlEscape(err.message || err);
|
|
1547
|
+
supervisorFeedback("Supervisor unavailable — actions are disabled until the local control plane returns.", true);
|
|
1548
|
+
}
|
|
1549
|
+
}
|
|
1550
|
+
|
|
1551
|
+
function renderSupervisorPlugin(plugin) {
|
|
1552
|
+
const stateKind = plugin.state === "current" ? "ok" : (plugin.state === "manifest_invalid" || plugin.state === "missing" ? "bad" : "warn");
|
|
1553
|
+
return '<div class="supervisor-row"><div class="supervisor-row-top"><span class="supervisor-name">' + htmlEscape(plugin.id) + '</span>' + supervisorBadge(plugin.state || "unknown", stateKind) + '</div>' +
|
|
1554
|
+
'<div class="supervisor-meta">' + htmlEscape(plugin.publisher || "unknown") + ' · ' + htmlEscape(plugin.version || "—") + ' · ' + htmlEscape(plugin.source || "—") + '</div>' +
|
|
1555
|
+
'<div class="supervisor-meta">' + htmlEscape((plugin.manifest_hash || "").slice(0, 12)) + '</div>' +
|
|
1556
|
+
'<div class="supervisor-row-actions"><button class="supervisor-action" onclick="runSupervisorPlugin(' + jsArg(plugin.id) + ',' + jsArg(plugin.enabled ? "disable" : "enable") + ')">' + (plugin.enabled ? "Disable" : "Enable") + '</button><button class="supervisor-action" onclick="runSupervisorPlugin(' + jsArg(plugin.id) + ',' + jsArg("test") + ')">Test</button><button class="supervisor-action" onclick="runSupervisorPlugin(' + jsArg(plugin.id) + ',' + jsArg("promote") + ')">Promote</button></div></div>';
|
|
1557
|
+
}
|
|
1558
|
+
|
|
1559
|
+
function renderSupervisorSurface(surface) {
|
|
1560
|
+
const ownerKind = surface.lifecycle_owner === "clauth" ? "ok" : (surface.lifecycle_owner === "plugin" ? "warn" : "");
|
|
1561
|
+
const stateKind = surface.state === "current" || surface.status === "healthy" ? "ok" : (surface.state === "unavailable" ? "bad" : "warn");
|
|
1562
|
+
return '<div class="supervisor-row"><div class="supervisor-row-top"><span class="supervisor-name">' + htmlEscape(surface.plugin_id + ":" + surface.id) + '</span>' + supervisorBadge(surface.lifecycle_owner || "unknown", ownerKind) + supervisorBadge(surface.state || surface.status || "unknown", stateKind) + '</div>' +
|
|
1563
|
+
'<div class="supervisor-meta">' + htmlEscape(surface.destination || "—") + ' · port ' + htmlEscape(surface.port || "—") + '</div>' +
|
|
1564
|
+
'<div class="supervisor-meta">' + htmlEscape(surface.health || surface.health_url || "no health") + '</div>' +
|
|
1565
|
+
'<div class="supervisor-row-actions">' + ["start","stop","restart","reconcile","test","promote","rollback"].map(a => '<button class="supervisor-action" data-supervisor-action="' + a + '" onclick="runSupervisorSurface(' + jsArg(surface.id) + ',' + jsArg(a) + ')">' + a + '</button>').join("") + '</div></div>';
|
|
1566
|
+
}
|
|
1567
|
+
|
|
1568
|
+
async function rescanSupervisorPlugins() {
|
|
1569
|
+
try {
|
|
1570
|
+
await supervisorJson("/v1/plugins/rescan", { method: "POST", headers: writeHeaders() });
|
|
1571
|
+
supervisorFeedback("Plugin rescan completed.", false);
|
|
1572
|
+
await loadSupervisorCockpit();
|
|
1573
|
+
} catch (err) { supervisorFeedback("Plugin rescan failed: " + (err.message || err), true); }
|
|
1574
|
+
}
|
|
1575
|
+
|
|
1576
|
+
async function runSupervisorPlugin(id, action) {
|
|
1577
|
+
try {
|
|
1578
|
+
const receipt = await supervisorJson("/v1/plugins/" + encodeURIComponent(id) + "/" + encodeURIComponent(action), { method: "POST", headers: writeHeaders() });
|
|
1579
|
+
supervisorFeedback("Plugin " + action + " receipt " + (receipt.operationId || "recorded") + " · " + ((receipt.resulting_state && receipt.resulting_state.state) || "completed"), false);
|
|
1580
|
+
await loadSupervisorCockpit();
|
|
1581
|
+
} catch (err) { supervisorFeedback("Plugin " + action + " failed: " + (err.message || err), true); }
|
|
1582
|
+
}
|
|
1583
|
+
|
|
1584
|
+
async function runSupervisorSurface(id, action) {
|
|
1585
|
+
try {
|
|
1586
|
+
const receipt = await supervisorJson("/v1/surfaces/" + encodeURIComponent(id) + "/actions", { method: "POST", headers: writeHeaders({ "Content-Type": "application/json" }), body: JSON.stringify({ action }) });
|
|
1587
|
+
const result = receipt.resulting_state || {};
|
|
1588
|
+
supervisorFeedback("Surface " + action + " receipt " + (receipt.operationId || "recorded") + " · " + (result.state || "completed"), result.ok === false);
|
|
1589
|
+
await loadSupervisorCockpit();
|
|
1590
|
+
} catch (err) { supervisorFeedback("Surface " + action + " failed: " + (err.message || err), true); }
|
|
1591
|
+
}
|
|
1379
1592
|
|
|
1380
1593
|
// ── Unlock ──────────────────────────────────
|
|
1381
1594
|
async function unlock() {
|
|
@@ -3710,6 +3923,18 @@ function createServer(initPassword, whitelist, port, tunnelHostnameInit = null,
|
|
|
3710
3923
|
return ok(res, { tunnels: listTunnels() });
|
|
3711
3924
|
}
|
|
3712
3925
|
|
|
3926
|
+
if (method === "GET" && reqPath === "/v1/logs") {
|
|
3927
|
+
const limit = Number(url.searchParams.get("limit") || 100);
|
|
3928
|
+
const boundedLimit = Number.isFinite(limit) ? Math.max(1, Math.min(limit, 500)) : 100;
|
|
3929
|
+
const state = loadSupervisorState();
|
|
3930
|
+
return ok(res, {
|
|
3931
|
+
schema: "clauth.supervisor.logs.v1",
|
|
3932
|
+
log_path: path.join(getSupervisorDir(), "events.jsonl"),
|
|
3933
|
+
events: readSupervisorEvents(boundedLimit).map(supervisorLogDto),
|
|
3934
|
+
operations: (state.operations || []).slice(0, boundedLimit).map(supervisorOperationDto),
|
|
3935
|
+
});
|
|
3936
|
+
}
|
|
3937
|
+
|
|
3713
3938
|
const tunnelRoutesMatch = reqPath.match(/^\/v1\/tunnels\/([^/]+)\/routes$/);
|
|
3714
3939
|
if (method === "POST" && tunnelRoutesMatch) {
|
|
3715
3940
|
if (!hasSupervisorWrite(req)) return rejectSupervisorWrite(res);
|
|
@@ -3731,7 +3956,7 @@ function createServer(initPassword, whitelist, port, tunnelHostnameInit = null,
|
|
|
3731
3956
|
const id = decodeURIComponent(reqPath.split("/").pop());
|
|
3732
3957
|
const operation = (supervisorHealth(), readSupervisorEvents(500)).find((event) => event.operationId === id);
|
|
3733
3958
|
res.writeHead(operation ? 200 : 404, { "Content-Type": "application/json", ...CORS });
|
|
3734
|
-
return res.end(JSON.stringify(operation
|
|
3959
|
+
return res.end(JSON.stringify(operation ? supervisorLogDto(operation) : { error: "operation_not_found" }));
|
|
3735
3960
|
}
|
|
3736
3961
|
|
|
3737
3962
|
if (method === "GET" && reqPath === "/v1/events") {
|
|
@@ -3742,7 +3967,7 @@ function createServer(initPassword, whitelist, port, tunnelHostnameInit = null,
|
|
|
3742
3967
|
...CORS,
|
|
3743
3968
|
});
|
|
3744
3969
|
for (const event of readSupervisorEvents(Number(url.searchParams.get("limit") || 100))) {
|
|
3745
|
-
res.write(`event: supervisor\ndata: ${JSON.stringify(event)}\n\n`);
|
|
3970
|
+
res.write(`event: supervisor\ndata: ${JSON.stringify(supervisorLogDto(event))}\n\n`);
|
|
3746
3971
|
}
|
|
3747
3972
|
res.end();
|
|
3748
3973
|
return;
|
|
@@ -17,7 +17,7 @@ import {
|
|
|
17
17
|
supervisorHealth,
|
|
18
18
|
validatePluginManifest,
|
|
19
19
|
} from "./supervisor-registry.js";
|
|
20
|
-
import { isLoopbackAddress, supervisorRequiresWriteToken } from "./commands/serve.js";
|
|
20
|
+
import { isLoopbackAddress, supervisorLogDto, supervisorRequiresWriteToken } from "./commands/serve.js";
|
|
21
21
|
|
|
22
22
|
function withTempSupervisor(fn) {
|
|
23
23
|
const root = fs.mkdtempSync(path.join(os.tmpdir(), "clauth-supervisor-"));
|
|
@@ -187,6 +187,35 @@ test("supervisor write-token policy is temporarily relaxed only for the localhos
|
|
|
187
187
|
assert.equal(isLoopbackAddress("192.168.1.25"), false);
|
|
188
188
|
});
|
|
189
189
|
|
|
190
|
+
test("supervisor log DTO strips raw operation payloads", () => {
|
|
191
|
+
const dto = supervisorLogDto({
|
|
192
|
+
ts: "2026-07-30T00:00:00.000Z",
|
|
193
|
+
kind: "operation",
|
|
194
|
+
operationId: "op-secret",
|
|
195
|
+
actor: "localhost",
|
|
196
|
+
action: "restart",
|
|
197
|
+
target: { surface_id: "demo", command: ["node", "--token=secret"] },
|
|
198
|
+
prior_state: { start: ["node", "--token=secret"], cwd: "C:/secrets" },
|
|
199
|
+
resulting_state: {
|
|
200
|
+
ok: false,
|
|
201
|
+
state: "operation_failed",
|
|
202
|
+
stderr: "SECRET=abc123",
|
|
203
|
+
evidence: ["CLAUTH_PM2_HOME=C:/safe"],
|
|
204
|
+
},
|
|
205
|
+
stderr: "SECRET=abc123",
|
|
206
|
+
completed_at: "2026-07-30T00:00:01.000Z",
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
const json = JSON.stringify(dto);
|
|
210
|
+
assert.equal(dto.operationId, "op-secret");
|
|
211
|
+
assert.equal(dto.target.surface_id, "demo");
|
|
212
|
+
assert.equal(dto.resulting_state.state, "operation_failed");
|
|
213
|
+
assert.equal(json.includes("prior_state"), false);
|
|
214
|
+
assert.equal(json.includes("stderr"), false);
|
|
215
|
+
assert.equal(json.includes("SECRET"), false);
|
|
216
|
+
assert.equal(json.includes("--token"), false);
|
|
217
|
+
});
|
|
218
|
+
|
|
190
219
|
test("tunnel route add and remove produce reversible operation receipts", () => withTempSupervisor((root) => {
|
|
191
220
|
process.env.CLAUTH_MANAGED_PLUGIN_ROOTS = path.join(root, "managed");
|
|
192
221
|
process.env.CLAUTH_USER_PLUGIN_ROOTS = path.join(root, "user");
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import test from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import fs from 'node:fs';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import { fileURLToPath } from 'node:url';
|
|
6
|
+
|
|
7
|
+
const here = path.dirname(fileURLToPath(import.meta.url));
|
|
8
|
+
const serveSource = fs.readFileSync(path.join(here, 'commands', 'serve.js'), 'utf8');
|
|
9
|
+
|
|
10
|
+
test('clauth supervisor UI exposes every lifecycle action and receipt feedback', () => {
|
|
11
|
+
for (const action of ['start', 'stop', 'restart', 'reconcile', 'test', 'promote', 'rollback']) {
|
|
12
|
+
assert.match(serveSource, new RegExp('"' + action + '"'));
|
|
13
|
+
}
|
|
14
|
+
assert.match(serveSource, /supervisorFeedback\(/);
|
|
15
|
+
assert.match(serveSource, /supervisor-feedback/);
|
|
16
|
+
});
|
package/package.json
CHANGED
|
Binary file
|
|
Binary file
|
|
Binary file
|