feat: add exec approvals tooling and service status
This commit is contained in:
@@ -2,7 +2,9 @@ import { buildWorkspaceSkillStatus } from "../agents/skills-status.js";
|
||||
import { withProgress } from "../cli/progress.js";
|
||||
import { loadConfig, readConfigFileSnapshot, resolveGatewayPort } from "../config/config.js";
|
||||
import { readLastGatewayErrorLine } from "../daemon/diagnostics.js";
|
||||
import type { GatewayService } from "../daemon/service.js";
|
||||
import { resolveGatewayService } from "../daemon/service.js";
|
||||
import { resolveNodeService } from "../daemon/node-service.js";
|
||||
import { buildGatewayConnectionDetails, callGateway } from "../gateway/call.js";
|
||||
import { normalizeControlUiBasePath } from "../gateway/control-ui.js";
|
||||
import { probeGateway } from "../gateway/probe.js";
|
||||
@@ -130,10 +132,9 @@ export async function statusAllCommand(
|
||||
const gatewaySelf = pickGatewaySelfPresence(gatewayProbe?.presence ?? null);
|
||||
progress.tick();
|
||||
|
||||
progress.setLabel("Checking daemon…");
|
||||
const daemon = await (async () => {
|
||||
progress.setLabel("Checking services…");
|
||||
const readServiceSummary = async (service: GatewayService) => {
|
||||
try {
|
||||
const service = resolveGatewayService();
|
||||
const [loaded, runtimeInfo, command] = await Promise.all([
|
||||
service.isLoaded({ env: process.env }).catch(() => false),
|
||||
service.readRuntime(process.env).catch(() => undefined),
|
||||
@@ -150,7 +151,9 @@ export async function statusAllCommand(
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
})();
|
||||
};
|
||||
const daemon = await readServiceSummary(resolveGatewayService());
|
||||
const nodeService = await readServiceSummary(resolveNodeService());
|
||||
progress.tick();
|
||||
|
||||
progress.setLabel("Scanning agents…");
|
||||
@@ -340,13 +343,22 @@ export async function statusAllCommand(
|
||||
: { Item: "Gateway self", Value: "unknown" },
|
||||
daemon
|
||||
? {
|
||||
Item: "Daemon",
|
||||
Item: "Gateway service",
|
||||
Value:
|
||||
daemon.installed === false
|
||||
? `${daemon.label} not installed`
|
||||
: `${daemon.label} ${daemon.installed ? "installed · " : ""}${daemon.loadedText}${daemon.runtime?.status ? ` · ${daemon.runtime.status}` : ""}${daemon.runtime?.pid ? ` (pid ${daemon.runtime.pid})` : ""}`,
|
||||
}
|
||||
: { Item: "Daemon", Value: "unknown" },
|
||||
: { Item: "Gateway service", Value: "unknown" },
|
||||
nodeService
|
||||
? {
|
||||
Item: "Node service",
|
||||
Value:
|
||||
nodeService.installed === false
|
||||
? `${nodeService.label} not installed`
|
||||
: `${nodeService.label} ${nodeService.installed ? "installed · " : ""}${nodeService.loadedText}${nodeService.runtime?.status ? ` · ${nodeService.runtime.status}` : ""}${nodeService.runtime?.pid ? ` (pid ${nodeService.runtime.pid})` : ""}`,
|
||||
}
|
||||
: { Item: "Node service", Value: "unknown" },
|
||||
{
|
||||
Item: "Agents",
|
||||
Value: `${agentStatus.agents.length} total · ${agentStatus.bootstrapPendingCount} bootstrapping · ${aliveAgents} active · ${agentStatus.totalSessions} sessions`,
|
||||
|
||||
@@ -15,7 +15,7 @@ import {
|
||||
} from "../memory/status-format.js";
|
||||
import { formatHealthChannelLines, type HealthSummary } from "./health.js";
|
||||
import { resolveControlUiLinks } from "./onboard-helpers.js";
|
||||
import { getDaemonStatusSummary } from "./status.daemon.js";
|
||||
import { getDaemonStatusSummary, getNodeDaemonStatusSummary } from "./status.daemon.js";
|
||||
import {
|
||||
formatAge,
|
||||
formatDuration,
|
||||
@@ -116,6 +116,10 @@ export async function statusCommand(
|
||||
: undefined;
|
||||
|
||||
if (opts.json) {
|
||||
const [daemon, nodeDaemon] = await Promise.all([
|
||||
getDaemonStatusSummary(),
|
||||
getNodeDaemonStatusSummary(),
|
||||
]);
|
||||
runtime.log(
|
||||
JSON.stringify(
|
||||
{
|
||||
@@ -134,6 +138,8 @@ export async function statusCommand(
|
||||
self: gatewaySelf,
|
||||
error: gatewayProbe?.error ?? null,
|
||||
},
|
||||
gatewayService: daemon,
|
||||
nodeService: nodeDaemon,
|
||||
agents: agentStatus,
|
||||
securityAudit,
|
||||
...(health || usage ? { health, usage } : {}),
|
||||
@@ -210,12 +216,20 @@ export async function statusCommand(
|
||||
return `${agentStatus.agents.length} · ${pending} · sessions ${agentStatus.totalSessions}${defSuffix}`;
|
||||
})();
|
||||
|
||||
const daemon = await getDaemonStatusSummary();
|
||||
const [daemon, nodeDaemon] = await Promise.all([
|
||||
getDaemonStatusSummary(),
|
||||
getNodeDaemonStatusSummary(),
|
||||
]);
|
||||
const daemonValue = (() => {
|
||||
if (daemon.installed === false) return `${daemon.label} not installed`;
|
||||
const installedPrefix = daemon.installed === true ? "installed · " : "";
|
||||
return `${daemon.label} ${installedPrefix}${daemon.loadedText}${daemon.runtimeShort ? ` · ${daemon.runtimeShort}` : ""}`;
|
||||
})();
|
||||
const nodeDaemonValue = (() => {
|
||||
if (nodeDaemon.installed === false) return `${nodeDaemon.label} not installed`;
|
||||
const installedPrefix = nodeDaemon.installed === true ? "installed · " : "";
|
||||
return `${nodeDaemon.label} ${installedPrefix}${nodeDaemon.loadedText}${nodeDaemon.runtimeShort ? ` · ${nodeDaemon.runtimeShort}` : ""}`;
|
||||
})();
|
||||
|
||||
const defaults = summary.sessions.defaults;
|
||||
const defaultCtx = defaults.contextTokens
|
||||
@@ -298,7 +312,8 @@ export async function statusCommand(
|
||||
Value: updateAvailability.available ? warn(`available · ${updateLine}`) : updateLine,
|
||||
},
|
||||
{ Item: "Gateway", Value: gatewayValue },
|
||||
{ Item: "Daemon", Value: daemonValue },
|
||||
{ Item: "Gateway service", Value: daemonValue },
|
||||
{ Item: "Node service", Value: nodeDaemonValue },
|
||||
{ Item: "Agents", Value: agentsValue },
|
||||
{ Item: "Memory", Value: memoryValue },
|
||||
{ Item: "Probes", Value: probesValue },
|
||||
|
||||
@@ -1,14 +1,20 @@
|
||||
import type { GatewayService } from "../daemon/service.js";
|
||||
import { resolveGatewayService } from "../daemon/service.js";
|
||||
import { resolveNodeService } from "../daemon/node-service.js";
|
||||
import { formatDaemonRuntimeShort } from "./status.format.js";
|
||||
|
||||
export async function getDaemonStatusSummary(): Promise<{
|
||||
type DaemonStatusSummary = {
|
||||
label: string;
|
||||
installed: boolean | null;
|
||||
loadedText: string;
|
||||
runtimeShort: string | null;
|
||||
}> {
|
||||
};
|
||||
|
||||
async function buildDaemonStatusSummary(
|
||||
service: GatewayService,
|
||||
fallbackLabel: string,
|
||||
): Promise<DaemonStatusSummary> {
|
||||
try {
|
||||
const service = resolveGatewayService();
|
||||
const [loaded, runtime, command] = await Promise.all([
|
||||
service.isLoaded({ env: process.env }).catch(() => false),
|
||||
service.readRuntime(process.env).catch(() => undefined),
|
||||
@@ -20,10 +26,18 @@ export async function getDaemonStatusSummary(): Promise<{
|
||||
return { label: service.label, installed, loadedText, runtimeShort };
|
||||
} catch {
|
||||
return {
|
||||
label: "Daemon",
|
||||
label: fallbackLabel,
|
||||
installed: null,
|
||||
loadedText: "unknown",
|
||||
runtimeShort: null,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export async function getDaemonStatusSummary(): Promise<DaemonStatusSummary> {
|
||||
return await buildDaemonStatusSummary(resolveGatewayService(), "Daemon");
|
||||
}
|
||||
|
||||
export async function getNodeDaemonStatusSummary(): Promise<DaemonStatusSummary> {
|
||||
return await buildDaemonStatusSummary(resolveNodeService(), "Node");
|
||||
}
|
||||
|
||||
@@ -243,6 +243,19 @@ vi.mock("../daemon/service.js", () => ({
|
||||
}),
|
||||
}),
|
||||
}));
|
||||
vi.mock("../daemon/node-service.js", () => ({
|
||||
resolveNodeService: () => ({
|
||||
label: "LaunchAgent",
|
||||
loadedText: "loaded",
|
||||
notLoadedText: "not loaded",
|
||||
isLoaded: async () => true,
|
||||
readRuntime: async () => ({ status: "running", pid: 4321 }),
|
||||
readCommand: async () => ({
|
||||
programArguments: ["node", "dist/entry.js", "node-host"],
|
||||
sourcePath: "/tmp/Library/LaunchAgents/com.clawdbot.node.plist",
|
||||
}),
|
||||
}),
|
||||
}));
|
||||
vi.mock("../security/audit.js", () => ({
|
||||
runSecurityAudit: mocks.runSecurityAudit,
|
||||
}));
|
||||
@@ -273,6 +286,8 @@ describe("statusCommand", () => {
|
||||
expect(payload.sessions.recent[0].flags).toContain("verbose:on");
|
||||
expect(payload.securityAudit.summary.critical).toBe(1);
|
||||
expect(payload.securityAudit.summary.warn).toBe(1);
|
||||
expect(payload.gatewayService.label).toBe("LaunchAgent");
|
||||
expect(payload.nodeService.label).toBe("LaunchAgent");
|
||||
});
|
||||
|
||||
it("prints formatted lines otherwise", async () => {
|
||||
|
||||
Reference in New Issue
Block a user