Spracherkennung für: .ts vermutete Sprache: Unknown {[0] [0] [0]} [Methode: Schwerpunktbildung, einfache Gewichte, sechs Dimensionen]
import type { Mock } from "vitest";
import { afterAll, afterEach, beforeAll, describe, expect, it, vi } from "vitest";
import type { PluginCompatibilityNotice } from "../plugins/status.js";
import { createCompatibilityNotice } from "../plugins/status.test-helpers.js";
import { captureEnv } from "../test-utils/env.js";
let envSnapshot: ReturnType<typeof captureEnv>;
beforeAll(() => {
envSnapshot = captureEnv(["OPENCLAW_PROFILE"]);
process.env.OPENCLAW_PROFILE = "isolated";
});
afterAll(() => {
envSnapshot.restore();
});
function createDefaultSessionStoreEntry() {
return {
updatedAt: Date.now() - 60_000,
verboseLevel: "on",
thinkingLevel: "low",
inputTokens: 2_000,
outputTokens: 3_000,
cacheRead: 2_000,
cacheWrite: 1_000,
totalTokens: 5_000,
totalTokensFresh: true as boolean,
contextTokens: 10_000,
model: "pi:opus",
sessionId: "abc123",
systemSent: true,
};
}
function createUnknownUsageSessionStore() {
return {
"+1000": {
updatedAt: Date.now() - 60_000,
inputTokens: 2_000,
outputTokens: 3_000,
contextTokens: 10_000,
model: "pi:opus",
},
};
}
function createChannelIssueCollector(channel: string) {
return (accounts: Array<Record<string, unknown>>) =>
accounts
.filter((account) => typeof account.lastError === "string" && account.lastError)
.map((account) => ({
channel,
accountId: typeof account.accountId === "string" ? account.accountId : "default",
message: `Channel error: ${String(account.lastError)}`,
}));
}
function createErrorChannelPlugin(params: { id: string; label: string; docsPath: string }) {
return {
id: params.id,
meta: {
id: params.id,
label: params.label,
selectionLabel: params.label,
docsPath: params.docsPath,
blurb: "mock",
},
config: {
listAccountIds: () => ["default"],
resolveAccount: () => ({}),
},
status: {
collectStatusIssues: createChannelIssueCollector(params.id),
},
};
}
async function withUnknownUsageStore(run: () => Promise<void>) {
mocks.loadSessionStore.mockReturnValue(createUnknownUsageSessionStore());
await run();
}
function getRuntimeLogs() {
return runtimeLogMock.mock.calls.map((call: unknown[]) => String(call[0]));
}
function getJoinedRuntimeLogs() {
return getRuntimeLogs().join("\n");
}
async function runStatusAndGetLogs(args: Parameters<typeof statusCommand>[0] = {}) {
runtimeLogMock.mockClear();
await statusCommand(args, runtime as never);
return getRuntimeLogs();
}
async function runStatusAndGetJoinedLogs(args: Parameters<typeof statusCommand>[0] = {}) {
await runStatusAndGetLogs(args);
return getJoinedRuntimeLogs();
}
type ProbeGatewayResult = {
ok: boolean;
url: string;
connectLatencyMs: number | null;
error: string | null;
connectErrorDetails?: unknown;
close: { code: number; reason: string } | null;
health: unknown;
status: unknown;
presence: unknown;
configSnapshot: unknown;
};
function mockProbeGatewayResult(overrides: Partial<ProbeGatewayResult>) {
mocks.probeGateway.mockReset();
mocks.probeGateway.mockResolvedValue({
...createDefaultProbeGatewayResult(),
...overrides,
});
}
function createDefaultProbeGatewayResult(): ProbeGatewayResult {
return {
ok: false,
url: "ws://127.0.0.1:18789",
connectLatencyMs: null,
error: "timeout",
close: null,
health: null,
status: null,
presence: null,
configSnapshot: null,
};
}
function createDefaultSecurityAuditResult() {
return {
ts: 0,
summary: { critical: 1, warn: 1, info: 2 },
findings: [
{
checkId: "test.critical",
severity: "critical",
title: "Test critical finding",
detail: "Something is very wrong\nbut on two lines",
remediation: "Do the thing",
},
{
checkId: "test.warn",
severity: "warn",
title: "Test warning finding",
detail: "Something is maybe wrong",
},
{
checkId: "test.info",
severity: "info",
title: "Test info finding",
detail: "FYI only",
},
{
checkId: "test.info2",
severity: "info",
title: "Another info finding",
detail: "More FYI",
},
],
};
}
async function createStatusServiceSummary(
service: ReturnType<(typeof mocks)["resolveGatewayService"]>,
) {
const [loaded, runtime, command] = await Promise.all([
service.isLoaded(),
service.readRuntime(),
service.readCommand(),
]);
return {
label: service.label,
installed: Boolean(command) || runtime?.status === "running",
loaded,
managedByOpenClaw: Boolean(command),
externallyManaged: !command && runtime?.status === "running",
loadedText: service.loadedText,
runtime,
runtimeShort: runtime?.pid ? `pid ${runtime.pid}` : null,
};
}
function createSessionStatusRows() {
const agents = (mocks.listGatewayAgentsBasic().agents ?? [
{ id: "main", name: "Main" },
]) as Array<{
id: string;
}>;
const byAgent = agents.map((agent: { id: string }) => {
const path = mocks.resolveStorePath("sessions", { agentId: agent.id });
const store = mocks.loadSessionStore(path) as Record<
string,
ReturnType<typeof createDefaultSessionStoreEntry>
>;
const recent = Object.entries(store).map(([key, entry]) => {
const contextTokens = typeof entry.contextTokens === "number" ? entry.contextTokens : null;
const total = typeof entry.totalTokens === "number" ? entry.totalTokens : null;
return {
agentId: agent.id,
key,
kind: key.startsWith("+") ? ("direct" as const) : ("unknown" as const),
sessionId: entry.sessionId,
updatedAt: entry.updatedAt ?? null,
age: typeof entry.updatedAt === "number" ? Math.max(0, Date.now() - entry.updatedAt) : null,
thinkingLevel: entry.thinkingLevel,
verboseLevel: entry.verboseLevel,
inputTokens: entry.inputTokens,
outputTokens: entry.outputTokens,
totalTokens: total,
totalTokensFresh: typeof entry.totalTokens === "number" ? entry.totalTokensFresh : false,
cacheRead: entry.cacheRead,
cacheWrite: entry.cacheWrite,
remainingTokens:
total !== null && contextTokens !== null ? Math.max(0, contextTokens - total) : null,
percentUsed:
total !== null && contextTokens ? Math.round((total / contextTokens) * 100) : null,
model: typeof entry.model === "string" ? entry.model : null,
contextTokens,
flags: [
...(entry.verboseLevel ? [`verbose:${entry.verboseLevel}`] : []),
...(entry.thinkingLevel ? [`think:${entry.thinkingLevel}`] : []),
],
};
});
return { agentId: agent.id, path, count: recent.length, recent };
});
const recent = byAgent.flatMap((entry) => entry.recent);
return {
paths: byAgent.map((entry) => entry.path),
count: recent.length,
defaults: {
model: recent[0]?.model ?? "pi:opus",
contextTokens: recent[0]?.contextTokens ?? 10_000,
},
recent,
byAgent,
};
}
async function createMockStatusScanResult(params: { includePluginCompatibility?: boolean } = {}) {
const cfg = mocks.loadConfig();
const gatewayProbe = await mocks.probeGateway();
const gatewayReachable = gatewayProbe.ok === true;
const gatewayAuthWarning =
cfg.gateway?.auth?.token && typeof cfg.gateway.auth.token === "object"
? "gateway.auth.token unavailable"
: undefined;
const agentStatus = {
...mocks.listGatewayAgentsBasic(),
bootstrapPendingCount: 0,
totalSessions: 1,
agents: mocks
.listGatewayAgentsBasic()
.agents.map((agent: { id: string; name?: string }) =>
Object.assign({}, agent, { bootstrapPending: false, activeSessions: 1 }),
),
};
const sessions = createSessionStatusRows();
const channelIssues = gatewayReachable
? [
{
channel: "signal",
accountId: "default",
message: "gateway: signal-cli unreachable",
},
{
channel: "imessage",
accountId: "default",
message: "gateway: imessage permission denied",
},
]
: [
{
channel: "signal",
accountId: "default",
message: "Channel error: signal-cli unreachable",
},
{
channel: "imessage",
accountId: "default",
message: "Channel error: imessage permission denied",
},
];
const pluginCompatibility =
params.includePluginCompatibility === false ? [] : mocks.buildPluginCompatibilityNotices();
return {
cfg,
sourceConfig: cfg,
secretDiagnostics: gatewayAuthWarning ? ["gateway.auth.token unavailable"] : [],
osSummary: {
platform: "darwin",
arch: "arm64",
release: "23.0.0",
label: "macos 14.0 (arm64)",
},
tailscaleMode: "off",
tailscaleDns: null,
tailscaleHttpsUrl: null,
update: {
root: "/tmp/openclaw",
installKind: "git",
packageManager: "pnpm",
git: {
root: "/tmp/openclaw",
branch: "main",
upstream: "origin/main",
dirty: false,
ahead: 0,
behind: 0,
fetchOk: true,
},
deps: {
manager: "pnpm",
status: "ok",
lockfilePath: "/tmp/openclaw/pnpm-lock.yaml",
markerPath: "/tmp/openclaw/node_modules/.modules.yaml",
},
registry: { latestVersion: "0.0.0" },
},
gatewayConnection: { url: "ws://127.0.0.1:18789" },
remoteUrlMissing: false,
gatewayMode: "local" as const,
gatewayProbeAuth: process.env.OPENCLAW_GATEWAY_TOKEN
? { token: process.env.OPENCLAW_GATEWAY_TOKEN }
: {},
gatewayProbeAuthWarning: gatewayAuthWarning,
gatewayProbe,
gatewayReachable,
gatewaySelf: gatewayProbe.presence ? { host: "gateway", ip: "127.0.0.1" } : null,
channelIssues,
agentStatus,
channels: {
rows: [
{ id: "whatsapp", label: "WhatsApp", enabled: true, state: "ok", detail: "linked" },
{ id: "signal", label: "Signal", enabled: true, state: "warn", detail: "gateway warning" },
{
id: "imessage",
label: "iMessage",
enabled: true,
state: "warn",
detail: "gateway warning",
},
],
details: [],
},
summary: {
runtimeVersion: null,
heartbeat: { defaultAgentId: "main", agents: [] },
channelSummary: [],
queuedSystemEvents: [],
tasks: mocks.getInspectableTaskRegistrySummary(),
taskAudit: mocks.getInspectableTaskAuditSummary(),
sessions,
},
memory: null,
memoryPlugin: { enabled: true, slot: "memory-core" },
pluginCompatibility,
};
}
async function withEnvVar<T>(key: string, value: string, run: () => Promise<T>): Promise<T> {
const prevValue = process.env[key];
process.env[key] = value;
try {
return await run();
} finally {
if (prevValue === undefined) {
delete process.env[key];
} else {
process.env[key] = prevValue;
}
}
}
const mocks = vi.hoisted(() => ({
hasPotentialConfiguredChannels: vi.fn(() => true),
loadConfig: vi.fn().mockReturnValue({ session: {} }),
loadSessionStore: vi.fn().mockReturnValue({
"+1000": createDefaultSessionStoreEntry(),
}),
resolveMainSessionKey: vi.fn().mockReturnValue("agent:main:main"),
resolveStorePath: vi.fn().mockReturnValue("/tmp/sessions.json"),
loadNodeHostConfig: vi.fn().mockResolvedValue(null),
webAuthExists: vi.fn().mockResolvedValue(true),
getWebAuthAgeMs: vi.fn().mockReturnValue(5000),
readWebSelfId: vi.fn().mockReturnValue({ e164: "+1999" }),
logWebSelfId: vi.fn(),
probeGateway: vi.fn().mockResolvedValue({
...createDefaultProbeGatewayResult(),
}),
callGateway: vi.fn().mockResolvedValue({}),
listGatewayAgentsBasic: vi.fn().mockReturnValue({
defaultId: "main",
mainKey: "agent:main:main",
scope: "per-sender",
agents: [{ id: "main", name: "Main" }],
}),
runSecurityAudit: vi.fn().mockResolvedValue(createDefaultSecurityAuditResult()),
buildPluginCompatibilityNotices: vi.fn((): PluginCompatibilityNotice[] => []),
getInspectableTaskRegistrySummary: vi.fn().mockReturnValue({
total: 0,
active: 0,
terminal: 0,
failures: 0,
byStatus: {
queued: 0,
running: 0,
succeeded: 0,
failed: 0,
timed_out: 0,
cancelled: 0,
lost: 0,
},
byRuntime: {
subagent: 0,
acp: 0,
cli: 0,
cron: 0,
},
}),
getInspectableTaskAuditSummary: vi.fn().mockReturnValue({
total: 0,
warnings: 0,
errors: 0,
byCode: {
stale_queued: 0,
stale_running: 0,
lost: 0,
delivery_failed: 0,
missing_cleanup: 0,
inconsistent_timestamps: 0,
},
}),
resolveGatewayService: vi.fn().mockReturnValue({
label: "LaunchAgent",
loadedText: "loaded",
notLoadedText: "not loaded",
stage: async () => {},
install: async () => {},
uninstall: async () => {},
stop: async () => {},
restart: async () => ({ outcome: "completed" as const }),
isLoaded: async () => true,
readRuntime: async () => ({ status: "running", pid: 1234 }),
readCommand: async () => ({
programArguments: ["node", "dist/entry.js", "gateway"],
sourcePath: "/tmp/Library/LaunchAgents/ai.openclaw.gateway.plist",
}),
}),
resolveNodeService: vi.fn().mockReturnValue({
label: "LaunchAgent",
loadedText: "loaded",
notLoadedText: "not loaded",
stage: async () => {},
install: async () => {},
uninstall: async () => {},
stop: async () => {},
restart: async () => ({ outcome: "completed" as const }),
isLoaded: async () => true,
readRuntime: async () => ({ status: "running", pid: 4321 }),
readCommand: async () => ({
programArguments: ["node", "dist/entry.js", "node-host"],
sourcePath: "/tmp/Library/LaunchAgents/ai.openclaw.node.plist",
}),
}),
}));
vi.mock("../channels/config-presence.js", () => ({
hasPotentialConfiguredChannels: mocks.hasPotentialConfiguredChannels,
hasMeaningfulChannelConfig: (entry: unknown) =>
Boolean(
entry && typeof entry === "object" && Object.keys(entry as Record<string, unknown>).length,
),
listPotentialConfiguredChannelIds: (cfg: { channels?: Record<string, unknown> }) =>
Object.keys(cfg.channels ?? {}).filter((key) => key !== "defaults" && key !== "modelByChannel"),
listPotentialConfiguredChannelPresenceSignals: (cfg: { channels?: Record<string, unknown> }) =>
Object.keys(cfg.channels ?? {})
.filter((key) => key !== "defaults" && key !== "modelByChannel")
.map((channelId) => ({ channelId, source: "config" })),
}));
vi.mock("../plugins/memory-runtime.js", () => ({
getActiveMemorySearchManager: vi.fn(async ({ agentId }: { agentId: string }) => ({
manager: {
probeVectorAvailability: vi.fn(async () => true),
status: () => ({
files: 2,
chunks: 3,
dirty: false,
workspaceDir: "/tmp/openclaw",
dbPath: "/tmp/memory.sqlite",
provider: "openai",
model: "text-embedding-3-small",
requestedProvider: "openai",
sources: ["memory"],
sourceCounts: [{ source: "memory", files: 2, chunks: 3 }],
cache: { enabled: true, entries: 10, maxEntries: 500 },
fts: { enabled: true, available: true },
vector: {
enabled: true,
available: true,
extensionPath: "/opt/vec0.dylib",
dims: 1024,
},
}),
close: vi.fn(async () => {}),
__agentId: agentId,
},
})),
}));
vi.mock("../config/sessions/main-session.js", () => ({
resolveMainSessionKey: mocks.resolveMainSessionKey,
}));
vi.mock("../config/sessions/paths.js", () => ({
resolveStorePath: mocks.resolveStorePath,
}));
vi.mock("../config/sessions/store-read.js", () => ({
readSessionStoreReadOnly: mocks.loadSessionStore,
}));
vi.mock("../config/sessions/types.js", () => ({
resolveSessionTotalTokens: vi.fn((entry?: { totalTokens?: number }) =>
typeof entry?.totalTokens === "number" ? entry.totalTokens : undefined,
),
resolveFreshSessionTotalTokens: vi.fn(
(entry?: { totalTokens?: number; totalTokensFresh?: boolean }) =>
typeof entry?.totalTokens === "number" && entry?.totalTokensFresh !== false
? entry.totalTokens
: undefined,
),
}));
vi.mock("../channels/plugins/index.js", () => ({
listChannelPlugins: () => {
const plugins = [
{
id: "whatsapp",
meta: {
id: "whatsapp",
label: "WhatsApp",
selectionLabel: "WhatsApp",
docsPath: "/platforms/whatsapp",
blurb: "mock",
},
config: {
hasPersistentAuth: () => true,
listAccountIds: () => ["default"],
resolveAccount: () => ({}),
},
status: {
buildChannelSummary: async () => ({ linked: true, authAgeMs: 5000 }),
},
},
{
...createErrorChannelPlugin({
id: "signal",
label: "Signal",
docsPath: "/platforms/signal",
}),
},
{
...createErrorChannelPlugin({
id: "imessage",
label: "iMessage",
docsPath: "/platforms/mac",
}),
},
] as const;
return plugins as unknown;
},
getChannelPlugin: (channelId: string) =>
[
{
id: "whatsapp",
meta: {
id: "whatsapp",
label: "WhatsApp",
selectionLabel: "WhatsApp",
docsPath: "/platforms/whatsapp",
blurb: "mock",
},
config: {
hasPersistentAuth: () => true,
listAccountIds: () => ["default"],
resolveAccount: () => ({}),
},
status: {
buildChannelSummary: async () => ({ linked: true, authAgeMs: 5000 }),
},
},
{
...createErrorChannelPlugin({
id: "signal",
label: "Signal",
docsPath: "/platforms/signal",
}),
},
{
...createErrorChannelPlugin({
id: "imessage",
label: "iMessage",
docsPath: "/platforms/mac",
}),
},
].find((plugin) => plugin.id === channelId) as unknown,
}));
vi.mock("../plugins/runtime/runtime-web-channel-plugin.js", () => ({
webAuthExists: mocks.webAuthExists,
getWebAuthAgeMs: mocks.getWebAuthAgeMs,
readWebSelfId: mocks.readWebSelfId,
logWebSelfId: mocks.logWebSelfId,
}));
vi.mock("../gateway/probe.js", () => ({
probeGateway: mocks.probeGateway,
}));
vi.mock("../gateway/call.js", () => ({
callGateway: mocks.callGateway,
buildGatewayConnectionDetails: vi.fn(() => ({
message: "Gateway mode: local\nGateway target: ws://127.0.0.1:18789",
})),
resolveGatewayCredentialsWithSecretInputs: vi.fn(
async (params: {
config?: {
gateway?: {
auth?: {
token?: unknown;
};
};
};
}) => {
const token = params.config?.gateway?.auth?.token;
if (token && typeof token === "object" && "source" in token) {
throw Object.assign(new Error("gateway.auth.token unavailable"), {
name: "GatewaySecretRefUnavailableError",
path: "gateway.auth.token",
});
}
const envToken = process.env.OPENCLAW_GATEWAY_TOKEN?.trim();
return envToken ? { token: envToken } : {};
},
),
}));
vi.mock("../gateway/agent-list.js", () => ({
listGatewayAgentsBasic: mocks.listGatewayAgentsBasic,
}));
vi.mock("../infra/openclaw-root.js", () => ({
resolveOpenClawPackageRoot: vi.fn().mockResolvedValue("/tmp/openclaw"),
resolveOpenClawPackageRootSync: vi.fn(() => "/tmp/openclaw"),
}));
vi.mock("../infra/os-summary.js", () => ({
resolveOsSummary: () => ({
platform: "darwin",
arch: "arm64",
release: "23.0.0",
label: "macos 14.0 (arm64)",
}),
}));
vi.mock("../infra/update-check.js", () => ({
checkUpdateStatus: vi.fn().mockResolvedValue({
root: "/tmp/openclaw",
installKind: "git",
packageManager: "pnpm",
git: {
root: "/tmp/openclaw",
branch: "main",
upstream: "origin/main",
dirty: false,
ahead: 0,
behind: 0,
fetchOk: true,
},
deps: {
manager: "pnpm",
status: "ok",
lockfilePath: "/tmp/openclaw/pnpm-lock.yaml",
markerPath: "/tmp/openclaw/node_modules/.modules.yaml",
},
registry: { latestVersion: "0.0.0" },
}),
formatGitInstallLabel: vi.fn(() => "main · @ deadbeef"),
compareSemverStrings: vi.fn(() => 0),
}));
vi.mock("../config/config.js", () => ({
loadConfig: mocks.loadConfig,
readBestEffortConfig: vi.fn(async () => mocks.loadConfig()),
resolveGatewayPort: vi.fn(() => 18789),
}));
vi.mock("../daemon/service.js", () => ({
resolveGatewayService: mocks.resolveGatewayService,
}));
vi.mock("../daemon/node-service.js", () => ({
resolveNodeService: mocks.resolveNodeService,
}));
vi.mock("../node-host/config.js", () => ({
loadNodeHostConfig: mocks.loadNodeHostConfig,
}));
vi.mock("../tasks/task-registry.maintenance.js", () => ({
getInspectableTaskRegistrySummary: mocks.getInspectableTaskRegistrySummary,
getInspectableTaskAuditSummary: mocks.getInspectableTaskAuditSummary,
}));
vi.mock("../security/audit.js", () => ({
runSecurityAudit: mocks.runSecurityAudit,
}));
vi.mock("../plugins/status.js", () => ({
buildPluginCompatibilityNotices: mocks.buildPluginCompatibilityNotices,
summarizePluginCompatibility: (warnings: PluginCompatibilityNotice[]) => ({
noticeCount: warnings.length,
pluginCount: new Set(warnings.map((warning) => warning.pluginId)).size,
}),
formatPluginCompatibilityNotice: (notice: PluginCompatibilityNotice) =>
`${notice.pluginId} ${notice.message}`,
}));
vi.mock("./status.scan.fast-json.js", () => ({
scanStatusJsonFast: vi.fn(async () =>
createMockStatusScanResult({ includePluginCompatibility: false }),
),
}));
vi.mock("./status.scan.js", () => ({
scanStatus: vi.fn(async () => createMockStatusScanResult()),
}));
vi.mock("./status-runtime-shared.ts", () => ({
loadStatusProviderUsageModule: vi.fn(async () => ({
formatUsageReportLines: vi.fn(() => []),
})),
resolveStatusGatewayHealth: vi.fn(async () => ({})),
resolveStatusSecurityAudit: vi.fn(async (input: unknown) =>
mocks.runSecurityAudit({
...(typeof input === "object" && input ? input : {}),
deep: false,
includeFilesystem: true,
includeChannelSecurity: true,
}),
),
resolveStatusUsageSummary: vi.fn(async () => undefined),
resolveStatusRuntimeSnapshot: vi.fn(
async (params: {
includeSecurityAudit?: boolean;
resolveSecurityAudit?: (input: unknown) => Promise<unknown>;
config: unknown;
sourceConfig: unknown;
}) => {
const securityAudit = params.includeSecurityAudit
? await (
params.resolveSecurityAudit ??
(async (input) =>
await mocks.runSecurityAudit({
...(typeof input === "object" && input ? input : {}),
deep: false,
includeFilesystem: true,
includeChannelSecurity: true,
}))
)({
config: params.config,
sourceConfig: params.sourceConfig,
})
: undefined;
return {
securityAudit,
usage: undefined,
health: undefined,
lastHeartbeat: null,
gatewayService: await createStatusServiceSummary(mocks.resolveGatewayService()),
nodeService: await createStatusServiceSummary(mocks.resolveNodeService()),
};
},
),
}));
import { resolvePairingRecoveryContext, statusCommand } from "./status.command.js";
const runtime = {
log: vi.fn(),
error: vi.fn(),
exit: vi.fn(),
};
const runtimeLogMock = runtime.log as Mock<(...args: unknown[]) => void>;
vi.mock("../channels/chat-meta.js", () => {
const mockChatChannels = [
"telegram",
"whatsapp",
"discord",
"irc",
"googlechat",
"slack",
"signal",
"imessage",
"line",
] as const;
const entries = mockChatChannels.map((id) => ({
id,
label: id,
selectionLabel: id,
docsPath: `/channels/${id}`,
blurb: "mock",
}));
const byId = Object.fromEntries(entries.map((entry) => [entry.id, entry]));
return {
CHAT_CHANNEL_ALIASES: {},
listChatChannels: () => entries,
listChatChannelAliases: () => [],
getChatChannelMeta: (id: (typeof mockChatChannels)[number]) => byId[id],
normalizeChatChannelId: (raw?: string | null) => {
const value = raw?.trim().toLowerCase();
return mockChatChannels.includes(value as (typeof mockChatChannels)[number])
? (value as (typeof mockChatChannels)[number])
: null;
},
};
});
vi.mock("./status.daemon.js", () => ({
getDaemonStatusSummary: vi.fn(async () => {
const service = mocks.resolveGatewayService();
const loaded = await service.isLoaded();
const runtime = await service.readRuntime();
const command = await service.readCommand();
return {
label: service.label,
installed: Boolean(command) || runtime?.status === "running",
loaded,
managedByOpenClaw: Boolean(command),
externallyManaged: !command && runtime?.status === "running",
loadedText: loaded ? service.loadedText : service.notLoadedText,
runtimeShort: runtime?.pid ? `pid ${runtime.pid}` : null,
};
}),
getNodeDaemonStatusSummary: vi.fn(async () => {
const service = mocks.resolveNodeService();
const loaded = await service.isLoaded();
const runtime = await service.readRuntime();
const command = await service.readCommand();
return {
label: service.label,
installed: Boolean(command) || runtime?.status === "running",
loaded,
managedByOpenClaw: Boolean(command),
externallyManaged: !command && runtime?.status === "running",
loadedText: loaded ? service.loadedText : service.notLoadedText,
runtimeShort: runtime?.pid ? `pid ${runtime.pid}` : null,
};
}),
}));
describe("statusCommand", () => {
afterEach(() => {
mocks.hasPotentialConfiguredChannels.mockReset();
mocks.hasPotentialConfiguredChannels.mockReturnValue(true);
mocks.loadConfig.mockReset();
mocks.loadConfig.mockReturnValue({ session: {} });
mocks.loadSessionStore.mockReset();
mocks.loadSessionStore.mockReturnValue({
"+1000": createDefaultSessionStoreEntry(),
});
mocks.resolveMainSessionKey.mockReset();
mocks.resolveMainSessionKey.mockReturnValue("agent:main:main");
mocks.resolveStorePath.mockReset();
mocks.resolveStorePath.mockReturnValue("/tmp/sessions.json");
mocks.loadNodeHostConfig.mockReset();
mocks.loadNodeHostConfig.mockResolvedValue(null);
mocks.probeGateway.mockReset();
mocks.probeGateway.mockResolvedValue(createDefaultProbeGatewayResult());
mocks.callGateway.mockReset();
mocks.callGateway.mockResolvedValue({});
mocks.listGatewayAgentsBasic.mockReset();
mocks.listGatewayAgentsBasic.mockReturnValue({
defaultId: "main",
mainKey: "agent:main:main",
scope: "per-sender",
agents: [{ id: "main", name: "Main" }],
});
mocks.buildPluginCompatibilityNotices.mockReset();
mocks.buildPluginCompatibilityNotices.mockReturnValue([]);
mocks.getInspectableTaskRegistrySummary.mockReset();
mocks.getInspectableTaskRegistrySummary.mockReturnValue({
total: 0,
active: 0,
terminal: 0,
failures: 0,
byStatus: {
queued: 0,
running: 0,
succeeded: 0,
failed: 0,
timed_out: 0,
cancelled: 0,
lost: 0,
},
byRuntime: {
subagent: 0,
acp: 0,
cli: 0,
cron: 0,
},
});
mocks.getInspectableTaskAuditSummary.mockReset();
mocks.getInspectableTaskAuditSummary.mockReturnValue({
total: 0,
warnings: 0,
errors: 0,
byCode: {
stale_queued: 0,
stale_running: 0,
lost: 0,
delivery_failed: 0,
missing_cleanup: 0,
inconsistent_timestamps: 0,
},
});
mocks.runSecurityAudit.mockReset();
mocks.runSecurityAudit.mockResolvedValue(createDefaultSecurityAuditResult());
mocks.resolveGatewayService.mockReset();
mocks.resolveGatewayService.mockReturnValue({
label: "LaunchAgent",
loadedText: "loaded",
notLoadedText: "not loaded",
stage: async () => {},
install: async () => {},
uninstall: async () => {},
stop: async () => {},
restart: async () => ({ outcome: "completed" as const }),
isLoaded: async () => true,
readRuntime: async () => ({ status: "running", pid: 1234 }),
readCommand: async () => ({
programArguments: ["node", "dist/entry.js", "gateway"],
sourcePath: "/tmp/Library/LaunchAgents/ai.openclaw.gateway.plist",
}),
});
mocks.resolveNodeService.mockReset();
mocks.resolveNodeService.mockReturnValue({
label: "LaunchAgent",
loadedText: "loaded",
notLoadedText: "not loaded",
stage: async () => {},
install: async () => {},
uninstall: async () => {},
stop: async () => {},
restart: async () => ({ outcome: "completed" as const }),
isLoaded: async () => true,
readRuntime: async () => ({ status: "running", pid: 4321 }),
readCommand: async () => ({
programArguments: ["node", "dist/entry.js", "node-host"],
sourcePath: "/tmp/Library/LaunchAgents/ai.openclaw.node.plist",
}),
});
runtimeLogMock.mockClear();
(runtime.error as Mock<(...args: unknown[]) => void>).mockClear();
});
it("prints JSON and includes security audit only when all is requested", async () => {
mocks.hasPotentialConfiguredChannels.mockReturnValue(false);
mocks.buildPluginCompatibilityNotices.mockReturnValue([
createCompatibilityNotice({ pluginId: "legacy-plugin", code: "legacy-before-agent-start" }),
]);
await statusCommand({ json: true }, runtime as never);
const payload = JSON.parse(String(runtimeLogMock.mock.calls[0]?.[0]));
expect(payload.linkChannel).toBeUndefined();
expect(payload.memory).toBeNull();
expect(payload.memoryPlugin.enabled).toBe(true);
expect(payload.memoryPlugin.slot).toBe("memory-core");
expect(payload.sessions.count).toBe(1);
expect(payload.sessions.paths).toContain("/tmp/sessions.json");
expect(payload.sessions.defaults.model).toBeTruthy();
expect(payload.sessions.defaults.contextTokens).toBeGreaterThan(0);
expect(payload.sessions.recent[0].percentUsed).toBe(50);
expect(payload.sessions.recent[0].cacheRead).toBe(2_000);
expect(payload.sessions.recent[0].cacheWrite).toBe(1_000);
expect(payload.sessions.recent[0].totalTokensFresh).toBe(true);
expect(payload.sessions.recent[0].remainingTokens).toBe(5000);
expect(payload.sessions.recent[0].flags).toContain("verbose:on");
expect(payload.securityAudit).toBeUndefined();
expect(payload.gatewayService.label).toBe("LaunchAgent");
expect(payload.nodeService.label).toBe("LaunchAgent");
expect(payload.pluginCompatibility).toEqual({
count: 0,
warnings: [],
});
expect(payload.tasks).toEqual(
expect.objectContaining({
total: 0,
active: 0,
byStatus: expect.objectContaining({ queued: 0, running: 0 }),
}),
);
expect(mocks.runSecurityAudit).not.toHaveBeenCalled();
runtimeLogMock.mockClear();
await statusCommand({ json: true, all: true }, runtime as never);
const allPayload = JSON.parse(String(runtimeLogMock.mock.calls[0]?.[0]));
expect(allPayload.securityAudit.summary.critical).toBe(1);
expect(allPayload.securityAudit.summary.warn).toBe(1);
expect(mocks.runSecurityAudit).toHaveBeenCalledWith(
expect.objectContaining({
includeFilesystem: true,
includeChannelSecurity: true,
}),
);
});
it("surfaces unknown usage when totalTokens is missing", async () => {
await withUnknownUsageStore(async () => {
runtimeLogMock.mockClear();
await statusCommand({ json: true }, runtime as never);
const payload = JSON.parse(String(runtimeLogMock.mock.calls.at(-1)?.[0]));
expect(payload.sessions.recent[0].totalTokens).toBeNull();
expect(payload.sessions.recent[0].totalTokensFresh).toBe(false);
expect(payload.sessions.recent[0].percentUsed).toBeNull();
expect(payload.sessions.recent[0].remainingTokens).toBeNull();
});
});
it("surfaces stale usage when totalTokens is preserved but not fresh", async () => {
mocks.loadSessionStore.mockReturnValue({
"+1000": {
updatedAt: Date.now() - 60_000,
totalTokens: 5_000,
totalTokensFresh: false,
contextTokens: 10_000,
model: "pi:opus",
},
});
runtimeLogMock.mockClear();
await statusCommand({ json: true }, runtime as never);
const payload = JSON.parse(String(runtimeLogMock.mock.calls.at(-1)?.[0]));
expect(payload.sessions.recent[0].totalTokens).toBe(5000);
expect(payload.sessions.recent[0].totalTokensFresh).toBe(false);
expect(payload.sessions.recent[0].percentUsed).toBe(50);
expect(payload.sessions.recent[0].remainingTokens).toBe(5000);
});
it("prints formatted lines with verbose cache details", async () => {
mocks.buildPluginCompatibilityNotices.mockReturnValue([
createCompatibilityNotice({ pluginId: "legacy-plugin", code: "legacy-before-agent-start" }),
]);
const logs = await runStatusAndGetLogs({ verbose: true });
for (const token of [
"OpenClaw status",
"Overview",
"Security audit",
"Summary:",
"CRITICAL",
"Dashboard",
"macos 14.0 (arm64)",
"Memory",
"Plugin compatibility",
"Channels",
"WhatsApp",
"bootstrap files",
"Tasks",
"Sessions",
"+1000",
"50%",
"40% cached",
"LaunchAgent",
"FAQ:",
"Troubleshooting:",
"Next steps:",
]) {
expect(logs.some((line) => line.includes(token))).toBe(true);
}
expect(
logs.some((line) => line.includes("legacy-plugin still uses legacy before_agent_start")),
).toBe(true);
expect(
logs.some(
(line) =>
line.includes("openclaw status --all") ||
line.includes("openclaw --profile isolated status --all"),
),
).toBe(true);
expect(logs.some((line) => line.includes("Cache"))).toBe(true);
expect(logs.some((line) => line.includes("40% hit"))).toBe(true);
expect(logs.some((line) => line.includes("read 2.0k"))).toBe(true);
});
it("shows a maintenance hint when task audit errors are present", async () => {
mocks.getInspectableTaskRegistrySummary.mockReturnValue({
total: 1,
active: 1,
terminal: 0,
failures: 1,
byStatus: {
queued: 0,
running: 1,
succeeded: 0,
failed: 0,
timed_out: 0,
cancelled: 0,
lost: 0,
},
byRuntime: {
subagent: 0,
acp: 1,
cli: 0,
cron: 0,
},
});
mocks.getInspectableTaskAuditSummary.mockReturnValue({
total: 1,
warnings: 0,
errors: 1,
byCode: {
stale_queued: 0,
stale_running: 1,
lost: 0,
delivery_failed: 0,
missing_cleanup: 0,
inconsistent_timestamps: 0,
},
});
const joined = await runStatusAndGetJoinedLogs();
expect(joined).toContain("tasks maintenance --apply");
});
it("uses prompt-side denominator for cached percentages", async () => {
mocks.loadSessionStore.mockReturnValue({
"+1000": {
...createDefaultSessionStoreEntry(),
inputTokens: undefined,
cacheRead: 1_200,
cacheWrite: 0,
totalTokens: 1_000,
},
});
const logs = await runStatusAndGetLogs();
expect(logs.some((line) => line.includes("100% cached"))).toBe(true);
expect(logs.some((line) => line.includes("120% cached"))).toBe(false);
mocks.loadSessionStore.mockReturnValue({
"+1000": {
...createDefaultSessionStoreEntry(),
inputTokens: 500,
cacheRead: 2_000,
cacheWrite: 500,
totalTokens: 5_000,
},
});
const promptSideLogs = await runStatusAndGetLogs();
expect(promptSideLogs.some((line) => line.includes("67% cached"))).toBe(true);
expect(promptSideLogs.some((line) => line.includes("40% cached"))).toBe(false);
});
it("shows node-only gateway info when no local gateway service is installed", async () => {
mocks.resolveGatewayService.mockReturnValueOnce({
label: "LaunchAgent",
loadedText: "loaded",
notLoadedText: "not loaded",
stage: async () => {},
install: async () => {},
uninstall: async () => {},
stop: async () => {},
restart: async () => ({ outcome: "completed" as const }),
isLoaded: async () => false,
readRuntime: async () => undefined,
readCommand: async () => null,
});
mocks.loadNodeHostConfig.mockResolvedValueOnce({
version: 1,
nodeId: "node-1",
gateway: { host: "gateway.example.com", port: 19000 },
});
const joined = await runStatusAndGetJoinedLogs();
expect(joined).toContain("node → gateway.example.com:19000 · no local gateway");
expect(joined).not.toContain("Gateway: local · ws://127.0.0.1:18789");
expect(joined).toContain("openclaw --profile isolated node status");
expect(joined).not.toContain("Fix reachability first");
});
it("shows gateway auth when reachable", async () => {
mocks.loadConfig.mockReturnValue({
session: {},
channels: { whatsapp: { allowFrom: ["*"] } },
});
await withEnvVar("OPENCLAW_GATEWAY_TOKEN", "abcd1234", async () => {
mockProbeGatewayResult({
ok: true,
connectLatencyMs: 123,
error: null,
health: {},
status: {},
presence: [],
});
const logs = await runStatusAndGetLogs();
expect(logs.some((l: string) => l.includes("auth token"))).toBe(true);
});
});
it("warns instead of crashing when gateway auth SecretRef is unresolved for probe auth", async () => {
mocks.loadConfig.mockReturnValue({
session: {},
channels: { whatsapp: { allowFrom: ["*"] } },
gateway: {
auth: {
mode: "token",
token: { source: "env", provider: "default", id: "MISSING_GATEWAY_TOKEN" },
},
},
secrets: {
providers: {
default: { source: "env" },
},
},
});
await statusCommand({ json: true }, runtime as never);
const payload = JSON.parse(String(runtimeLogMock.mock.calls.at(-1)?.[0]));
expect(payload.gateway.error ?? payload.gateway.authWarning ?? null).not.toBeNull();
if (Array.isArray(payload.secretDiagnostics) && payload.secretDiagnostics.length > 0) {
expect(
payload.secretDiagnostics.some((entry: string) => entry.includes("gateway.auth.token")),
).toBe(true);
}
expect(runtime.error).not.toHaveBeenCalled();
});
it("surfaces channel runtime errors from the gateway", async () => {
mocks.loadConfig.mockReturnValue({
session: {},
channels: { whatsapp: { allowFrom: ["*"] } },
});
mockProbeGatewayResult({
ok: true,
connectLatencyMs: 10,
error: null,
health: {},
status: {},
presence: [],
});
mocks.callGateway.mockResolvedValueOnce({
channelAccounts: {
signal: [
{
accountId: "default",
enabled: true,
configured: true,
running: false,
lastError: "signal-cli unreachable",
},
],
imessage: [
{
accountId: "default",
enabled: true,
configured: true,
running: false,
lastError: "imessage permission denied",
},
],
},
});
const joined = await runStatusAndGetJoinedLogs();
expect(joined).toMatch(/Signal/i);
expect(joined).toMatch(/iMessage/i);
expect(joined).toMatch(/gateway:/i);
expect(joined).toMatch(/WARN/);
});
it("prints safe gateway pairing recovery guidance", async () => {
expect(
resolvePairingRecoveryContext({
error: "scope upgrade pending approval (requestId: req-123)",
closeReason: "pairing required",
}),
).toEqual({ requestId: "req-123", reason: "scope-upgrade", remediationHint: null });
expect(
resolvePairingRecoveryContext({
error: "connect failed: pairing required",
closeReason: "connect failed",
}),
).toEqual({ requestId: null, reason: "not-paired", remediationHint: null });
expect(
resolvePairingRecoveryContext({
error: "connect failed: pairing required (requestId: req-123;rm -rf /)",
closeReason: "pairing required (requestId: req-123;rm -rf /)",
}),
).toEqual({ requestId: null, reason: "not-paired", remediationHint: null });
expect(
resolvePairingRecoveryContext({
error: "connect failed: pairing required",
closeReason: "pairing required (requestId: req-close-456)",
}),
).toEqual({ requestId: "req-close-456", reason: "not-paired", remediationHint: null });
expect(
resolvePairingRecoveryContext({
details: {
code: "PAIRING_REQUIRED",
reason: "scope-upgrade",
requestId: "req-structured-789",
remediationHint: "Review the requested scopes, then approve the pending upgrade.",
},
}),
).toEqual({
requestId: "req-structured-789",
reason: "scope-upgrade",
remediationHint: "Review the requested scopes, then approve the pending upgrade.",
});
expect(
resolvePairingRecoveryContext({
details: {
code: "PAIRING_REQUIRED",
reason: "scope-upgrade",
requestId: "req-structured-789;rm -rf /",
remediationHint: "\u001b[31mReview\nfirst\u001b[0m",
},
}),
).toEqual({
requestId: null,
reason: "scope-upgrade",
remediationHint: "Review\\nfirst",
});
mocks.loadConfig.mockReturnValue({
session: {},
channels: { whatsapp: { allowFrom: ["*"] } },
});
mockProbeGatewayResult({
error: "scope upgrade pending approval (requestId: req-123)",
connectErrorDetails: {
code: "PAIRING_REQUIRED",
reason: "scope-upgrade",
requestId: "req-123",
remediationHint: "Review the requested scopes, then approve the pending upgrade.",
},
close: {
code: 1008,
reason: "pairing required",
},
});
const joined = await runStatusAndGetJoinedLogs();
expect(joined).toContain("Gateway scope upgrade approval required.");
expect(joined).toContain("more scopes than currently approved");
expect(joined).toContain("devices approve req-123");
expect(joined).toContain("devices approve --latest");
expect(joined).toContain("devices list");
});
it("includes sessions across agents in JSON output", async () => {
const originalAgents = mocks.listGatewayAgentsBasic.getMockImplementation();
const originalResolveStorePath = mocks.resolveStorePath.getMockImplementation();
const originalLoadSessionStore = mocks.loadSessionStore.getMockImplementation();
mocks.listGatewayAgentsBasic.mockReturnValue({
defaultId: "main",
mainKey: "agent:main:main",
scope: "per-sender",
agents: [
{ id: "main", name: "Main" },
{ id: "ops", name: "Ops" },
],
});
mocks.resolveStorePath.mockImplementation((_store, opts) =>
opts?.agentId === "ops" ? "/tmp/ops.json" : "/tmp/main.json",
);
mocks.loadSessionStore.mockImplementation((storePath) => {
if (storePath === "/tmp/ops.json") {
return {
"agent:ops:main": {
updatedAt: Date.now() - 120_000,
inputTokens: 1_000,
outputTokens: 1_000,
totalTokens: 2_000,
contextTokens: 10_000,
model: "pi:opus",
},
};
}
return {
"+1000": createDefaultSessionStoreEntry(),
};
});
await statusCommand({ json: true }, runtime as never);
const payload = JSON.parse(String(runtimeLogMock.mock.calls.at(-1)?.[0]));
expect(payload.sessions.count).toBe(2);
expect(payload.sessions.paths.length).toBe(2);
expect(
payload.sessions.recent.some((sess: { key?: string }) => sess.key === "agent:ops:main"),
).toBe(true);
if (originalAgents) {
mocks.listGatewayAgentsBasic.mockImplementation(originalAgents);
}
if (originalResolveStorePath) {
mocks.resolveStorePath.mockImplementation(originalResolveStorePath);
}
if (originalLoadSessionStore) {
mocks.loadSessionStore.mockImplementation(originalLoadSessionStore);
}
});
});
¤ Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.0.33Bemerkung:
(Wie Sie bei der Firma Beratungs- und Dienstleistungen beauftragen können 2026-04-27)
¤
*Bot Zugriff
|
|