@lifeaitools/clauth 1.30.11 → 1.30.13
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
|
@@ -989,6 +989,8 @@ function dashboardHtml(port, whitelist, isStaged = false, initWriteToken = null)
|
|
|
989
989
|
.supervisor-name{font-size:.82rem;color:#e2e8f0;font-weight:600;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
|
990
990
|
.supervisor-row-actions{display:flex;gap:5px;flex-wrap:wrap;margin-top:7px}
|
|
991
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)}
|
|
992
994
|
</style>
|
|
993
995
|
</head>
|
|
994
996
|
<body>
|
|
@@ -1216,6 +1218,7 @@ function dashboardHtml(port, whitelist, isStaged = false, initWriteToken = null)
|
|
|
1216
1218
|
</div>
|
|
1217
1219
|
</div>
|
|
1218
1220
|
<div id="supervisor-status" class="supervisor-sub">Loading supervisor state…</div>
|
|
1221
|
+
<div id="supervisor-feedback" class="supervisor-feedback" role="status"></div>
|
|
1219
1222
|
<div class="supervisor-grid" style="margin-top:10px">
|
|
1220
1223
|
<div class="supervisor-card">
|
|
1221
1224
|
<h4>Plugins</h4>
|
|
@@ -1506,6 +1509,14 @@ async function supervisorJson(path, options) {
|
|
|
1506
1509
|
return data;
|
|
1507
1510
|
}
|
|
1508
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
|
+
|
|
1509
1520
|
async function loadSupervisorCockpit() {
|
|
1510
1521
|
const status = document.getElementById("supervisor-status");
|
|
1511
1522
|
if (status) status.textContent = "Loading supervisor state…";
|
|
@@ -1530,9 +1541,10 @@ async function loadSupervisorCockpit() {
|
|
|
1530
1541
|
const label = e.kind === "operation" ? (e.action + " " + JSON.stringify(e.target || {})) : (e.kind + " " + (e.plugin_id || ""));
|
|
1531
1542
|
return (e.ts || e.created_at || "") + " " + label;
|
|
1532
1543
|
}).join("\\n") || "No events yet.";
|
|
1533
|
-
if (status) status.innerHTML = supervisorBadge("supervisor " + (health.status || "unknown"), "ok") + supervisorBadge("vault " + (health.vault_locked ? "locked" : "unlocked"), health.vault_locked ? "warn" : "ok") + supervisorBadge("v" + (health.clauth_version || "${VERSION}"), "");
|
|
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}"), "");
|
|
1534
1545
|
} catch (err) {
|
|
1535
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);
|
|
1536
1548
|
}
|
|
1537
1549
|
}
|
|
1538
1550
|
|
|
@@ -1546,31 +1558,36 @@ function renderSupervisorPlugin(plugin) {
|
|
|
1546
1558
|
|
|
1547
1559
|
function renderSupervisorSurface(surface) {
|
|
1548
1560
|
const ownerKind = surface.lifecycle_owner === "clauth" ? "ok" : (surface.lifecycle_owner === "plugin" ? "warn" : "");
|
|
1549
|
-
|
|
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>' +
|
|
1550
1563
|
'<div class="supervisor-meta">' + htmlEscape(surface.destination || "—") + ' · port ' + htmlEscape(surface.port || "—") + '</div>' +
|
|
1551
1564
|
'<div class="supervisor-meta">' + htmlEscape(surface.health || surface.health_url || "no health") + '</div>' +
|
|
1552
|
-
'<div class="supervisor-row-actions">' + ["
|
|
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>';
|
|
1553
1566
|
}
|
|
1554
1567
|
|
|
1555
1568
|
async function rescanSupervisorPlugins() {
|
|
1556
1569
|
try {
|
|
1557
1570
|
await supervisorJson("/v1/plugins/rescan", { method: "POST", headers: writeHeaders() });
|
|
1571
|
+
supervisorFeedback("Plugin rescan completed.", false);
|
|
1558
1572
|
await loadSupervisorCockpit();
|
|
1559
|
-
} catch (err) {
|
|
1573
|
+
} catch (err) { supervisorFeedback("Plugin rescan failed: " + (err.message || err), true); }
|
|
1560
1574
|
}
|
|
1561
1575
|
|
|
1562
1576
|
async function runSupervisorPlugin(id, action) {
|
|
1563
1577
|
try {
|
|
1564
|
-
await supervisorJson("/v1/plugins/" + encodeURIComponent(id) + "/" + encodeURIComponent(action), { method: "POST", headers: writeHeaders() });
|
|
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);
|
|
1565
1580
|
await loadSupervisorCockpit();
|
|
1566
|
-
} catch (err) {
|
|
1581
|
+
} catch (err) { supervisorFeedback("Plugin " + action + " failed: " + (err.message || err), true); }
|
|
1567
1582
|
}
|
|
1568
1583
|
|
|
1569
1584
|
async function runSupervisorSurface(id, action) {
|
|
1570
1585
|
try {
|
|
1571
|
-
await supervisorJson("/v1/surfaces/" + encodeURIComponent(id) + "/actions", { method: "POST", headers: writeHeaders({ "Content-Type": "application/json" }), body: JSON.stringify({ action }) });
|
|
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);
|
|
1572
1589
|
await loadSupervisorCockpit();
|
|
1573
|
-
} catch (err) {
|
|
1590
|
+
} catch (err) { supervisorFeedback("Surface " + action + " failed: " + (err.message || err), true); }
|
|
1574
1591
|
}
|
|
1575
1592
|
|
|
1576
1593
|
// ── Unlock ──────────────────────────────────
|
|
@@ -140,6 +140,10 @@ export function validatePluginManifest(manifest, sourcePath = "") {
|
|
|
140
140
|
id,
|
|
141
141
|
version,
|
|
142
142
|
publisher: String(manifest.publisher || "unknown"),
|
|
143
|
+
// Only trusted managed manifests may opt into automatic startup. User
|
|
144
|
+
// plugins remain awaiting_enable until an operator explicitly enables them.
|
|
145
|
+
core: manifest.core === true,
|
|
146
|
+
enable_default: manifest.enable_default === true,
|
|
143
147
|
sourcePath,
|
|
144
148
|
destination: normalizeDestination(manifest.destination),
|
|
145
149
|
lifecycle_owner: normalizeLifecycleOwner(manifest.lifecycle_owner),
|
|
@@ -226,15 +230,17 @@ export function discoverPlugins() {
|
|
|
226
230
|
throw new Error("user plugin cannot silently shadow a managed plugin id");
|
|
227
231
|
}
|
|
228
232
|
const prior = byId.get(manifest.id);
|
|
229
|
-
const
|
|
230
|
-
const
|
|
233
|
+
const autoEnabled = source === "managed" && manifest.core === true && manifest.enable_default === true;
|
|
234
|
+
const enabled = Boolean(prior?.enabled || autoEnabled);
|
|
235
|
+
const state = enabled ? (prior?.manifest_hash === hash || autoEnabled ? "current" : "version_drift") : "awaiting_enable";
|
|
236
|
+
const plugin = { ...manifest, source, discovery_root: root, manifest_hash: hash, state, enabled, discovered_at: now() };
|
|
231
237
|
plugins.push(plugin);
|
|
232
238
|
for (const surface of plugin.surfaces) surfaces.push({ ...surface, enabled: plugin.enabled, state });
|
|
233
239
|
for (const credential of plugin.credentials) {
|
|
234
240
|
events.push({ kind: "credential_required", plugin_id: plugin.id, credential, value_set: false });
|
|
235
241
|
}
|
|
236
242
|
seen.add(plugin.id);
|
|
237
|
-
events.push({ kind: "plugin_discovered", plugin_id: plugin.id, state, source, manifest_hash: hash });
|
|
243
|
+
events.push({ kind: autoEnabled && !prior?.enabled ? "core_plugin_auto_enabled" : "plugin_discovered", plugin_id: plugin.id, state, source, manifest_hash: hash });
|
|
238
244
|
} catch (error) {
|
|
239
245
|
const id = path.basename(path.dirname(manifestPath));
|
|
240
246
|
plugins.push({
|
|
@@ -109,6 +109,24 @@ test("discovery merges managed and user roots without silent managed-id shadowin
|
|
|
109
109
|
assert.match(invalid.error, /shadow/);
|
|
110
110
|
}));
|
|
111
111
|
|
|
112
|
+
test("trusted managed core plugins auto-enable while user plugins remain opt-in", () => withTempSupervisor((root) => {
|
|
113
|
+
const managed = path.join(root, "managed");
|
|
114
|
+
const user = path.join(root, "user");
|
|
115
|
+
process.env.CLAUTH_MANAGED_PLUGIN_ROOTS = managed;
|
|
116
|
+
process.env.CLAUTH_USER_PLUGIN_ROOTS = user;
|
|
117
|
+
writePlugin(root, "managed", "core-mcp", baseManifest("core-mcp", { core: true, enable_default: true }));
|
|
118
|
+
writePlugin(root, "user", "user-mcp", baseManifest("user-mcp", { core: true, enable_default: true }));
|
|
119
|
+
|
|
120
|
+
const result = discoverPlugins();
|
|
121
|
+
const core = result.plugins.find((plugin) => plugin.id === "core-mcp");
|
|
122
|
+
const userPlugin = result.plugins.find((plugin) => plugin.id === "user-mcp");
|
|
123
|
+
assert.equal(core.enabled, true);
|
|
124
|
+
assert.equal(core.state, "current");
|
|
125
|
+
assert.equal(result.events.some((event) => event.kind === "core_plugin_auto_enabled" && event.plugin_id === "core-mcp"), true);
|
|
126
|
+
assert.equal(userPlugin.enabled, false);
|
|
127
|
+
assert.equal(userPlugin.state, "awaiting_enable");
|
|
128
|
+
}));
|
|
129
|
+
|
|
112
130
|
test("plugin test marks a private candidate and never creates a public route", () => withTempSupervisor((root) => {
|
|
113
131
|
const managed = path.join(root, "managed");
|
|
114
132
|
process.env.CLAUTH_MANAGED_PLUGIN_ROOTS = managed;
|
|
@@ -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
|
+
});
|