diff --git a/extensions/discord/src/proxy-request-client.ts b/extensions/discord/src/proxy-request-client.ts index 398c36d5d99..5a18161fbc2 100644 --- a/extensions/discord/src/proxy-request-client.ts +++ b/extensions/discord/src/proxy-request-client.ts @@ -44,8 +44,6 @@ const defaultOptions: Required { diff --git a/src/agents/pi-embedded-helpers/turns.ts b/src/agents/pi-embedded-helpers/turns.ts index 20fbbd387d8..5eaa709f2f7 100644 --- a/src/agents/pi-embedded-helpers/turns.ts +++ b/src/agents/pi-embedded-helpers/turns.ts @@ -50,7 +50,7 @@ function collectMatchingToolResultIds(message: AgentMessage): Set { ids.add(toolResultId); } } else if (role === "tool") { - for (const id of extractToolResultIdsFromRecord(message as Record)) { + for (const id of extractToolResultIdsFromRecord(message as unknown as Record)) { ids.add(id); } } diff --git a/src/commands/health.command.coverage.test.ts b/src/commands/health.command.coverage.test.ts index 7ffdfa9d7b1..3e6802b72b6 100644 --- a/src/commands/health.command.coverage.test.ts +++ b/src/commands/health.command.coverage.test.ts @@ -19,8 +19,10 @@ function createRecentSessionRows(now = Date.now()) { } vi.mock("../gateway/call.js", () => ({ - callGateway: (...args: unknown[]) => callGatewayMock(...args), - buildGatewayConnectionDetails: (...args: unknown[]) => buildGatewayConnectionDetailsMock(...args), + callGateway: (...args: [unknown, ...unknown[]]) => + Reflect.apply(callGatewayMock, undefined, args), + buildGatewayConnectionDetails: (...args: [unknown, ...unknown[]]) => + Reflect.apply(buildGatewayConnectionDetailsMock, undefined, args), })); describe("healthCommand (coverage)", () => { diff --git a/src/commands/status-all/report-data.ts b/src/commands/status-all/report-data.ts index b2bb7215bca..247108976c3 100644 --- a/src/commands/status-all/report-data.ts +++ b/src/commands/status-all/report-data.ts @@ -23,6 +23,7 @@ type StatusServiceSummaries = Awaited>; +type ConfigFileSnapshot = Awaited>; type StatusAllProgress = { setLabel(label: string): void; @@ -46,7 +47,7 @@ async function resolveStatusAllLocalDiagnosis(params: { configPath: string; health: StatusGatewayHealthSafe | undefined; diagnosis: { - snap: Awaited>; + snap: ConfigFileSnapshot | null; remoteUrlMissing: boolean; secretDiagnostics: StatusScanOverviewResult["secretDiagnostics"]; sentinel: Awaited> | null; @@ -81,7 +82,7 @@ async function resolveStatusAllLocalDiagnosis(params: { timeoutMs: Math.min(8000, params.timeoutMs ?? 10_000), gatewayReachable: params.gatewayReachable, gatewayProbeError: params.gatewayProbe?.error ?? null, - callOverrides: params.gatewayCallOverrides ?? {}, + ...(params.gatewayCallOverrides ? { callOverrides: params.gatewayCallOverrides } : {}), }); params.progress.setLabel("Checking local state…"); diff --git a/src/commands/status-all/report-sections.ts b/src/commands/status-all/report-sections.ts index 220dd469bf1..8c4c8114dd7 100644 --- a/src/commands/status-all/report-sections.ts +++ b/src/commands/status-all/report-sections.ts @@ -1,4 +1,4 @@ -import type { TableColumn } from "../../terminal/table.js"; +import type { RenderTableOptions, TableColumn } from "../../terminal/table.js"; import { buildStatusChannelsTableRows, statusChannelsTableColumns } from "./channels-table.js"; import { buildStatusAgentTableRows, @@ -8,11 +8,7 @@ import { } from "./report-tables.js"; import type { StatusReportSection } from "./text-report.js"; -type TableRenderer = (input: { - width: number; - columns: Array>; - rows: Array>; -}) => string; +type TableRenderer = (input: RenderTableOptions) => string; export function buildStatusOverviewSection(params: { width: number; diff --git a/src/commands/status-all/report-tables.ts b/src/commands/status-all/report-tables.ts index cb40dd95568..b23da761d1e 100644 --- a/src/commands/status-all/report-tables.ts +++ b/src/commands/status-all/report-tables.ts @@ -1,3 +1,4 @@ +import type { RenderTableOptions } from "../../terminal/table.js"; import { formatTimeAgo } from "./format.js"; import type { StatusReportSection } from "./text-report.js"; @@ -53,11 +54,7 @@ export function buildStatusAgentTableRows(params: { export function buildStatusChannelDetailSections(params: { details: ChannelDetailLike[]; width: number; - renderTable: (input: { - width: number; - columns: Array>; - rows: Array>; - }) => string; + renderTable: (input: RenderTableOptions) => string; ok: (text: string) => string; warn: (text: string) => string; }): StatusReportSection[] { diff --git a/src/commands/status-all/text-report.ts b/src/commands/status-all/text-report.ts index d556028f67f..eaf2b0cde31 100644 --- a/src/commands/status-all/text-report.ts +++ b/src/commands/status-all/text-report.ts @@ -1,11 +1,7 @@ -import type { TableColumn } from "../../terminal/table.js"; +import type { RenderTableOptions, TableColumn } from "../../terminal/table.js"; type HeadingFn = (text: string) => string; -type TableRenderer = (input: { - width: number; - columns: Array>; - rows: Array>; -}) => string; +type TableRenderer = (input: RenderTableOptions) => string; export type StatusReportSection = | { @@ -19,7 +15,7 @@ export type StatusReportSection = title: string; width: number; renderTable: TableRenderer; - columns: Array>; + columns: readonly TableColumn[]; rows: Array>; trailer?: string | null; skipIfEmpty?: boolean; diff --git a/src/commands/status-json-command.test.ts b/src/commands/status-json-command.test.ts index 2dd98325e5c..f5a7ed9f309 100644 --- a/src/commands/status-json-command.test.ts +++ b/src/commands/status-json-command.test.ts @@ -29,12 +29,19 @@ describe("runStatusJsonCommand", () => { cfg: { gateway: {} }, sourceConfig: { gateway: {} }, summary: { ok: true }, - update: { installKind: "package", packageManager: "npm" }, + update: { root: null, installKind: "package" as const, packageManager: "npm" as const }, osSummary: { platform: "linux" }, memory: null, memoryPlugin: null, + tailscaleMode: "off", + tailscaleDns: null, + tailscaleHttpsUrl: null, gatewayMode: "local" as const, - gatewayConnection: { url: "ws://127.0.0.1:18789", urlSource: "config" }, + gatewayConnection: { + url: "ws://127.0.0.1:18789", + urlSource: "config", + message: "Gateway target: ws://127.0.0.1:18789", + }, remoteUrlMissing: false, gatewayReachable: true, gatewayProbe: null, diff --git a/src/commands/status-json-payload.test.ts b/src/commands/status-json-payload.test.ts index 9f4493c451c..2c430289929 100644 --- a/src/commands/status-json-payload.test.ts +++ b/src/commands/status-json-payload.test.ts @@ -25,19 +25,10 @@ describe("status-json-payload", () => { resolveStatusUpdateChannelInfo({ updateConfigChannel: "beta", update: { - root: "/tmp/openclaw", installKind: "package", - packageManager: "npm", git: { - root: "/tmp/openclaw", - sha: null, tag: "v1.2.3", branch: "main", - upstream: null, - dirty: false, - ahead: 0, - behind: 0, - fetchOk: true, }, }, }), @@ -76,8 +67,8 @@ describe("status-json-payload", () => { gatewayProbeAuth: { token: "tok" }, gatewaySelf: { host: "gateway" }, gatewayProbeAuthWarning: "warn", - gatewayService: { label: "LaunchAgent" }, - nodeService: { label: "node" }, + gatewayService: { label: "LaunchAgent", installed: true, loadedText: "loaded" }, + nodeService: { label: "node", installed: true, loadedText: "loaded" }, }, osSummary: { platform: "linux" }, memory: null, diff --git a/src/commands/status-json-runtime.ts b/src/commands/status-json-runtime.ts index e0552e823f7..0f5f38fa924 100644 --- a/src/commands/status-json-runtime.ts +++ b/src/commands/status-json-runtime.ts @@ -75,7 +75,7 @@ export async function resolveStatusJsonOutput(params: { return buildStatusJsonPayload({ summary: scan.summary, surface: buildStatusOverviewSurfaceFromScan({ - scan, + scan: scan as never, gatewayService, nodeService, }), diff --git a/src/commands/status-overview-rows.test.ts b/src/commands/status-overview-rows.test.ts index ccc6ae09c37..4324cb33321 100644 --- a/src/commands/status-overview-rows.test.ts +++ b/src/commands/status-overview-rows.test.ts @@ -80,16 +80,16 @@ describe("status-overview-rows", () => { memory: { files: 1, chunks: 2, vector: {}, fts: {}, cache: {} }, memoryPlugin: { enabled: true, slot: "memory" }, pluginCompatibility: [{ pluginId: "a", severity: "warn", message: "legacy" }], - ok: (value) => `ok(${value})`, - warn: (value) => `warn(${value})`, - muted: (value) => `muted(${value})`, - formatTimeAgo: (value) => `${value}ms`, - formatKTokens: (value) => `${Math.round(value / 1000)}k`, + ok: (value: string) => `ok(${value})`, + warn: (value: string) => `warn(${value})`, + muted: (value: string) => `muted(${value})`, + formatTimeAgo: (value: number) => `${value}ms`, + formatKTokens: (value: number) => `${Math.round(value / 1000)}k`, resolveMemoryVectorState: () => ({ state: "ready", tone: "ok" }), resolveMemoryFtsState: () => ({ state: "ready", tone: "warn" }), resolveMemoryCacheSummary: () => ({ text: "cache warm", tone: "muted" }), updateValue: "available · custom update", - }), + } as unknown as Parameters[0]), ).toEqual( expect.arrayContaining([ { Item: "OS", Value: `macOS · node ${process.versions.node}` }, @@ -148,7 +148,7 @@ describe("status-overview-rows", () => { agents: [{ id: "main", lastActiveAgeMs: 60_000 }], }, tailscaleBackendState: "Running", - }), + } as unknown as Parameters[0]), ).toEqual( expect.arrayContaining([ { Item: "Version", Value: expect.any(String) }, diff --git a/src/commands/status-overview-rows.ts b/src/commands/status-overview-rows.ts index 3c74e63f076..dd4df517b42 100644 --- a/src/commands/status-overview-rows.ts +++ b/src/commands/status-overview-rows.ts @@ -1,5 +1,10 @@ +import type { HeartbeatEventPayload } from "../infra/heartbeat-events.js"; +import type { Tone } from "../memory-host-sdk/status.js"; +import type { PluginCompatibilityNotice } from "../plugins/status.js"; import { formatCliCommand } from "../cli/command-format.js"; import { VERSION } from "../version.js"; +import type { HealthSummary } from "./health.js"; +import type { AgentLocalStatus } from "./status.agent-local.js"; import { buildStatusOverviewRowsFromSurface, type StatusOverviewSurface, @@ -19,6 +24,8 @@ import { buildStatusMemoryValue, buildStatusTasksValue, } from "./status.command-sections.js"; +import type { MemoryPluginStatus, MemoryStatusSnapshot } from "./status.scan.shared.js"; +import type { StatusSummary } from "./status.types.js"; export function buildStatusCommandOverviewRows(params: { opts: { @@ -26,69 +33,35 @@ export function buildStatusCommandOverviewRows(params: { }; surface: StatusOverviewSurface; osLabel: string; - summary: { - tasks: { - total: number; - active: number; - failures: number; - byStatus: { queued: number; running: number }; - }; - taskAudit: { - errors: number; - warnings: number; - }; - heartbeat: { - agents: Array<{ - agentId: string; - enabled?: boolean | null; - everyMs?: number | null; - every: string; - }>; - }; - queuedSystemEvents: string[]; - sessions: { - count: number; - paths: string[]; - defaults: { - model?: string | null; - contextTokens?: number | null; - }; - }; - }; - health?: unknown; - lastHeartbeat: unknown; + summary: StatusSummary; + health?: HealthSummary; + lastHeartbeat: HeartbeatEventPayload | null; agentStatus: { defaultId?: string | null; bootstrapPendingCount: number; totalSessions: number; - agents: Array<{ - id: string; - lastActiveAgeMs?: number | null; - }>; + agents: AgentLocalStatus[]; }; - memory: { - files: number; - chunks: number; - dirty?: boolean; - sources?: string[]; - vector?: unknown; - fts?: unknown; - cache?: unknown; - } | null; - memoryPlugin: { - enabled: boolean; - reason?: string | null; - slot?: string | null; - }; - pluginCompatibility: Array<{ severity?: "warn" | "info" | null } & Record>; + memory: MemoryStatusSnapshot | null; + memoryPlugin: MemoryPluginStatus; + pluginCompatibility: PluginCompatibilityNotice[]; ok: (value: string) => string; warn: (value: string) => string; muted: (value: string) => string; formatTimeAgo: (ageMs: number) => string; formatKTokens: (value: number) => string; - resolveMemoryVectorState: (value: unknown) => { state: string; tone: "ok" | "warn" | "muted" }; - resolveMemoryFtsState: (value: unknown) => { state: string; tone: "ok" | "warn" | "muted" }; - resolveMemoryCacheSummary: (value: unknown) => { text: string; tone: "ok" | "warn" | "muted" }; + resolveMemoryVectorState: (value: NonNullable) => { + state: string; + tone: Tone; + }; + resolveMemoryFtsState: (value: NonNullable) => { + state: string; + tone: Tone; + }; + resolveMemoryCacheSummary: (value: NonNullable) => { + text: string; + tone: Tone; + }; updateValue?: string; }) { const agentsValue = buildStatusAgentsValue({ @@ -112,7 +85,7 @@ export function buildStatusCommandOverviewRows(params: { const lastHeartbeatValue = buildStatusLastHeartbeatValue({ deep: params.opts.deep, gatewayReachable: params.surface.gatewayReachable, - lastHeartbeat: params.lastHeartbeat as never, + lastHeartbeat: params.lastHeartbeat, warn: params.warn, muted: params.muted, formatTimeAgo: params.formatTimeAgo, diff --git a/src/commands/status-overview-surface.test.ts b/src/commands/status-overview-surface.test.ts index 44075c2a263..66a8d064257 100644 --- a/src/commands/status-overview-surface.test.ts +++ b/src/commands/status-overview-surface.test.ts @@ -18,9 +18,13 @@ describe("status-overview-surface", () => { tailscaleHttpsUrl: "https://box.tail.ts.net", gatewayMode: "remote", remoteUrlMissing: false, - gatewayConnection: { url: "wss://gateway.example.com", urlSource: "config" }, + gatewayConnection: { + url: "wss://gateway.example.com", + urlSource: "config", + message: "Gateway target: wss://gateway.example.com", + }, gatewayReachable: true, - gatewayProbe: { connectLatencyMs: 42, error: null }, + gatewayProbe: { connectLatencyMs: 42, error: null } as never, gatewayProbeAuth: { token: "tok" }, gatewayProbeAuthWarning: "warn-text", gatewaySelf: { host: "gateway", version: "1.2.3" }, @@ -48,9 +52,13 @@ describe("status-overview-surface", () => { tailscaleHttpsUrl: "https://box.tail.ts.net", gatewayMode: "remote", remoteUrlMissing: false, - gatewayConnection: { url: "wss://gateway.example.com", urlSource: "config" }, + gatewayConnection: { + url: "wss://gateway.example.com", + urlSource: "config", + message: "Gateway target: wss://gateway.example.com", + }, gatewayReachable: true, - gatewayProbe: { connectLatencyMs: 42, error: null }, + gatewayProbe: { connectLatencyMs: 42, error: null } as never, gatewayProbeAuth: { token: "tok" }, gatewayProbeAuthWarning: "warn-text", gatewaySelf: { host: "gateway", version: "1.2.3" }, @@ -83,9 +91,13 @@ describe("status-overview-surface", () => { gatewaySnapshot: { gatewayMode: "remote", remoteUrlMissing: false, - gatewayConnection: { url: "wss://gateway.example.com", urlSource: "config" }, + gatewayConnection: { + url: "wss://gateway.example.com", + urlSource: "config", + message: "Gateway target: wss://gateway.example.com", + }, gatewayReachable: true, - gatewayProbe: { connectLatencyMs: 42, error: null }, + gatewayProbe: { connectLatencyMs: 42, error: null } as never, gatewayProbeAuth: { token: "tok" }, gatewayProbeAuthWarning: "warn-text", gatewaySelf: { host: "gateway", version: "1.2.3" }, @@ -114,9 +126,13 @@ describe("status-overview-surface", () => { tailscaleHttpsUrl: "https://box.tail.ts.net", gatewayMode: "remote", remoteUrlMissing: false, - gatewayConnection: { url: "wss://gateway.example.com", urlSource: "config" }, + gatewayConnection: { + url: "wss://gateway.example.com", + urlSource: "config", + message: "Gateway target: wss://gateway.example.com", + }, gatewayReachable: true, - gatewayProbe: { connectLatencyMs: 42, error: null }, + gatewayProbe: { connectLatencyMs: 42, error: null } as never, gatewayProbeAuth: { token: "tok" }, gatewayProbeAuthWarning: "warn-text", gatewaySelf: { host: "gateway", version: "1.2.3" }, @@ -165,7 +181,7 @@ describe("status-overview-surface", () => { urlSource: "config", }, gatewayReachable: true, - gatewayProbe: { connectLatencyMs: 42, error: null }, + gatewayProbe: { connectLatencyMs: 42, error: null } as never, gatewayProbeAuth: { token: "tok" }, gatewayProbeAuthWarning: "warn-text", gatewaySelf: { host: "gateway", version: "1.2.3" }, @@ -221,35 +237,18 @@ describe("status-overview-surface", () => { expect( buildStatusGatewayJsonPayloadFromSurface({ surface: { - cfg: { update: { channel: "stable" }, gateway: { bind: "loopback" } }, - update: { installKind: "package", packageManager: "npm" } as never, - tailscaleMode: "serve", gatewayMode: "remote", remoteUrlMissing: false, gatewayConnection: { url: "wss://gateway.example.com", urlSource: "config", + message: "Gateway target: wss://gateway.example.com", }, gatewayReachable: true, - gatewayProbe: { connectLatencyMs: 42, error: null }, - gatewayProbeAuth: { token: "tok" }, + gatewayProbe: { connectLatencyMs: 42, error: null } as never, gatewayProbeAuthWarning: "warn-text", gatewaySelf: { host: "gateway", version: "1.2.3" }, - gatewayService: { - label: "LaunchAgent", - installed: true, - managedByOpenClaw: true, - loadedText: "loaded", - runtimeShort: "running", - }, - nodeService: { - label: "node", - installed: true, - loadedText: "loaded", - runtime: { status: "running", pid: 42 }, - }, - nodeOnlyGateway: null, - }, + } as never, }), ).toEqual({ mode: "remote", diff --git a/src/commands/status.command-report-data.test.ts b/src/commands/status.command-report-data.test.ts index bd342e07445..aa0cf25dbf1 100644 --- a/src/commands/status.command-report-data.test.ts +++ b/src/commands/status.command-report-data.test.ts @@ -88,31 +88,31 @@ describe("buildStatusCommandReportData", () => { pluginCompatibility: [{ pluginId: "a", severity: "warn", message: "legacy" }], pairingRecovery: { requestId: "req-1" }, tableWidth: 120, - ok: (value) => `ok(${value})`, - warn: (value) => `warn(${value})`, - muted: (value) => `muted(${value})`, - shortenText: (value) => value, - formatCliCommand: (value) => `cmd:${value}`, - formatTimeAgo: (value) => `${value}ms`, - formatKTokens: (value) => `${Math.round(value / 1000)}k`, + ok: (value: string) => `ok(${value})`, + warn: (value: string) => `warn(${value})`, + muted: (value: string) => `muted(${value})`, + shortenText: (value: string) => value, + formatCliCommand: (value: string) => `cmd:${value}`, + formatTimeAgo: (value: number) => `${value}ms`, + formatKTokens: (value: number) => `${Math.round(value / 1000)}k`, formatTokensCompact: () => "12k", formatPromptCacheCompact: () => "cache ok", formatHealthChannelLines: () => ["Discord: OK · ready"], - formatPluginCompatibilityNotice: (notice) => String(notice.message), + formatPluginCompatibilityNotice: (notice: { message?: unknown }) => String(notice.message), formatUpdateAvailableHint: () => "update available", resolveMemoryVectorState: () => ({ state: "ready", tone: "ok" }), resolveMemoryFtsState: () => ({ state: "ready", tone: "warn" }), resolveMemoryCacheSummary: () => ({ text: "cache warm", tone: "muted" }), - accentDim: (value) => `accent(${value})`, + accentDim: (value: string) => `accent(${value})`, theme: { - heading: (value) => `# ${value}`, - muted: (value) => `muted(${value})`, - warn: (value) => `warn(${value})`, - error: (value) => `error(${value})`, + heading: (value: string) => `# ${value}`, + muted: (value: string) => `muted(${value})`, + warn: (value: string) => `warn(${value})`, + error: (value: string) => `error(${value})`, }, - renderTable: ({ rows }) => `table:${rows.length}`, + renderTable: ({ rows }: { rows: Array> }) => `table:${rows.length}`, updateValue: "available · custom update", - }); + } as unknown as Parameters[0]); expect(result.overviewRows[0]).toEqual({ Item: "OS", diff --git a/src/commands/status.command-report-data.ts b/src/commands/status.command-report-data.ts index 64002c2099d..eee09ed4949 100644 --- a/src/commands/status.command-report-data.ts +++ b/src/commands/status.command-report-data.ts @@ -1,9 +1,17 @@ +import type { HeartbeatEventPayload } from "../infra/heartbeat-events.js"; +import type { resolveOsSummary } from "../infra/os-summary.js"; +import type { Tone } from "../memory-host-sdk/status.js"; +import type { PluginCompatibilityNotice } from "../plugins/status.js"; +import type { SecurityAuditReport } from "../security/audit.js"; +import type { RenderTableOptions, TableColumn } from "../terminal/table.js"; +import type { HealthSummary } from "./health.js"; +import type { AgentLocalStatus } from "./status.agent-local.js"; import { buildStatusChannelsTableRows, statusChannelsTableColumns, } from "./status-all/channels-table.js"; import { buildStatusCommandOverviewRows } from "./status-overview-rows.ts"; -import { type StatusOverviewSurface } from "./status-overview-surface.ts"; +import type { StatusOverviewSurface } from "./status-overview-surface.ts"; import { buildStatusFooterLines, buildStatusHealthRows, @@ -15,6 +23,8 @@ import { buildStatusSystemEventsTrailer, statusHealthColumns, } from "./status.command-sections.js"; +import type { MemoryPluginStatus, MemoryStatusSnapshot } from "./status.scan.shared.js"; +import type { SessionStatus, StatusSummary } from "./status.types.js"; export async function buildStatusCommandReportData(params: { opts: { @@ -22,63 +32,17 @@ export async function buildStatusCommandReportData(params: { verbose?: boolean; }; surface: StatusOverviewSurface; - osSummary: { label: string }; - summary: { - tasks: { - total: number; - active: number; - failures: number; - byStatus: { queued: number; running: number }; - }; - taskAudit: { - errors: number; - warnings: number; - }; - heartbeat: { - agents: Array<{ - agentId: string; - enabled?: boolean | null; - everyMs?: number | null; - every: string; - }>; - }; - queuedSystemEvents: string[]; - sessions: { - count: number; - paths: string[]; - defaults: { - model?: string | null; - contextTokens?: number | null; - }; - recent: Array<{ - key: string; - kind: string; - updatedAt?: number | null; - age: number; - model?: string | null; - }>; - }; - }; - securityAudit: { - summary: { critical: number; warn: number; info: number }; - findings: Array<{ - severity: "critical" | "warn" | "info"; - title: string; - detail: string; - remediation?: string | null; - }>; - }; - health?: unknown; + osSummary: ReturnType; + summary: StatusSummary; + securityAudit?: SecurityAuditReport; + health?: HealthSummary; usageLines?: string[]; - lastHeartbeat: unknown; + lastHeartbeat: HeartbeatEventPayload | null; agentStatus: { defaultId?: string | null; bootstrapPendingCount: number; totalSessions: number; - agents: Array<{ - id: string; - lastActiveAgeMs?: number | null; - }>; + agents: AgentLocalStatus[]; }; channels: { rows: Array<{ @@ -93,21 +57,9 @@ export async function buildStatusCommandReportData(params: { channel: string; message: string; }>; - memory: { - files: number; - chunks: number; - dirty?: boolean; - sources?: string[]; - vector?: unknown; - fts?: unknown; - cache?: unknown; - } | null; - memoryPlugin: { - enabled: boolean; - reason?: string | null; - slot?: string | null; - }; - pluginCompatibility: Array<{ severity?: "warn" | "info" | null } & Record>; + memory: MemoryStatusSnapshot | null; + memoryPlugin: MemoryPluginStatus; + pluginCompatibility: PluginCompatibilityNotice[]; pairingRecovery: { requestId: string | null } | null; tableWidth: number; ok: (value: string) => string; @@ -117,26 +69,23 @@ export async function buildStatusCommandReportData(params: { formatCliCommand: (value: string) => string; formatTimeAgo: (ageMs: number) => string; formatKTokens: (value: number) => string; - formatTokensCompact: (value: { - key: string; - kind: string; - updatedAt?: number | null; - age: number; - model?: string | null; - }) => string; - formatPromptCacheCompact: (value: { - key: string; - kind: string; - updatedAt?: number | null; - age: number; - model?: string | null; - }) => string | null; - formatHealthChannelLines: (summary: unknown, opts: { accountMode: "all" }) => string[]; - formatPluginCompatibilityNotice: (notice: Record) => string; - formatUpdateAvailableHint: (update: Record) => string | null; - resolveMemoryVectorState: (value: unknown) => { state: string; tone: "ok" | "warn" | "muted" }; - resolveMemoryFtsState: (value: unknown) => { state: string; tone: "ok" | "warn" | "muted" }; - resolveMemoryCacheSummary: (value: unknown) => { text: string; tone: "ok" | "warn" | "muted" }; + formatTokensCompact: (value: SessionStatus) => string; + formatPromptCacheCompact: (value: SessionStatus) => string | null; + formatHealthChannelLines: (summary: HealthSummary, opts: { accountMode: "all" }) => string[]; + formatPluginCompatibilityNotice: (notice: PluginCompatibilityNotice) => string; + formatUpdateAvailableHint: (update: StatusOverviewSurface["update"]) => string | null; + resolveMemoryVectorState: (value: NonNullable) => { + state: string; + tone: Tone; + }; + resolveMemoryFtsState: (value: NonNullable) => { + state: string; + tone: Tone; + }; + resolveMemoryCacheSummary: (value: NonNullable) => { + text: string; + tone: Tone; + }; accentDim: (value: string) => string; updateValue?: string; theme: { @@ -145,11 +94,7 @@ export async function buildStatusCommandReportData(params: { warn: (value: string) => string; error: (value: string) => string; }; - renderTable: (input: { - width: number; - columns: Array>; - rows: Array>; - }) => string; + renderTable: (input: RenderTableOptions) => string; }) { const overviewRows = buildStatusCommandOverviewRows({ opts: params.opts, @@ -180,7 +125,12 @@ export async function buildStatusCommandReportData(params: { { key: "Model", header: "Model", minWidth: 14 }, { key: "Tokens", header: "Tokens", minWidth: 16 }, ...(params.opts.verbose ? [{ key: "Cache", header: "Cache", minWidth: 16, flex: true }] : []), - ]; + ] satisfies TableColumn[]; + const securityAudit = params.securityAudit ?? { + summary: { critical: 0, warn: 0, info: 0 }, + findings: [], + }; + return { heading: params.theme.heading, muted: params.theme.muted, @@ -202,7 +152,7 @@ export async function buildStatusCommandReportData(params: { formatCliCommand: params.formatCliCommand, }), securityAuditLines: buildStatusSecurityAuditLines({ - securityAudit: params.securityAudit, + securityAudit, theme: params.theme, shortenText: params.shortenText, formatCliCommand: params.formatCliCommand, @@ -237,8 +187,8 @@ export async function buildStatusCommandReportData(params: { healthColumns: params.health ? statusHealthColumns : undefined, healthRows: params.health ? buildStatusHealthRows({ - health: params.health as never, - formatHealthChannelLines: params.formatHealthChannelLines as never, + health: params.health, + formatHealthChannelLines: params.formatHealthChannelLines, ok: params.ok, warn: params.warn, muted: params.muted, diff --git a/src/commands/status.command-report.ts b/src/commands/status.command-report.ts index b43a72245db..6bd45efb45b 100644 --- a/src/commands/status.command-report.ts +++ b/src/commands/status.command-report.ts @@ -1,4 +1,4 @@ -import type { TableColumn } from "../terminal/table.js"; +import type { RenderTableOptions, TableColumn } from "../terminal/table.js"; import { buildStatusChannelsTableSection, buildStatusHealthSection, @@ -12,11 +12,7 @@ import { appendStatusReportSections } from "./status-all/text-report.js"; export async function buildStatusCommandReportLines(params: { heading: (text: string) => string; muted: (text: string) => string; - renderTable: (input: { - width: number; - columns: TableColumn[]; - rows: Array>; - }) => string; + renderTable: (input: RenderTableOptions) => string; width: number; overviewRows: Array<{ Item: string; Value: string }>; showTaskMaintenanceHint: boolean; diff --git a/src/commands/status.command.ts b/src/commands/status.command.ts index ceeded57695..14bd1f86332 100644 --- a/src/commands/status.command.ts +++ b/src/commands/status.command.ts @@ -192,6 +192,7 @@ export async function statusCommand( const { buildStatusUpdateSurface, formatCliCommand, + formatHealthChannelLines, formatKTokens, formatPromptCacheCompact, formatPluginCompatibilityNotice, diff --git a/src/commands/status.gateway-connection.test.ts b/src/commands/status.gateway-connection.test.ts index 9a0ea5eb6b0..0b6aab29414 100644 --- a/src/commands/status.gateway-connection.test.ts +++ b/src/commands/status.gateway-connection.test.ts @@ -30,6 +30,7 @@ describe("status.gateway-connection", () => { remoteUrlMissing: true, gatewayConnection: { url: "ws://127.0.0.1:18789", + urlSource: "local loopback", message: "ignored", }, bindMode: "loopback", @@ -49,6 +50,7 @@ describe("status.gateway-connection", () => { remoteUrlMissing: false, gatewayConnection: { url: "ws://127.0.0.1:18789", + urlSource: "local loopback", message: "Gateway mode: local", }, bindMode: "loopback", diff --git a/src/commands/status.scan-execute.test.ts b/src/commands/status.scan-execute.test.ts index 9f1652db579..cdf812e58e7 100644 --- a/src/commands/status.scan-execute.test.ts +++ b/src/commands/status.scan-execute.test.ts @@ -1,9 +1,12 @@ import { beforeEach, describe, expect, it, vi } from "vitest"; +import type { StatusScanOverviewResult } from "./status.scan-overview.ts"; +import type { MemoryStatusSnapshot } from "./status.scan.shared.js"; const { resolveStatusSummaryFromOverview, resolveMemoryPluginStatus } = vi.hoisted(() => ({ resolveStatusSummaryFromOverview: vi.fn(async () => ({ sessions: { count: 1 } })), resolveMemoryPluginStatus: vi.fn(() => ({ enabled: false, + slot: null, reason: "memorySearch not configured", })), })); @@ -44,8 +47,19 @@ describe("executeStatusScanFromOverview", () => { }, agentStatus: { agents: [{ id: "main" }], defaultId: "main" }, skipColdStartNetworkChecks: false, - } as never; - const resolveMemory = vi.fn(async () => ({ agentId: "main" })); + } as unknown as StatusScanOverviewResult; + const resolveMemory = vi.fn< + (args: { + cfg: unknown; + agentStatus: unknown; + memoryPlugin: unknown; + runtime?: unknown; + }) => Promise + >(async () => ({ + agentId: "main", + backend: "builtin", + provider: "memory-core", + })); const result = await executeStatusScanFromOverview({ overview, @@ -61,7 +75,7 @@ describe("executeStatusScanFromOverview", () => { expect(resolveMemory).toHaveBeenCalledWith({ cfg: overview.cfg, agentStatus: overview.agentStatus, - memoryPlugin: { enabled: false, reason: "memorySearch not configured" }, + memoryPlugin: { enabled: false, slot: null, reason: "memorySearch not configured" }, runtime: {}, }); expect(result).toEqual( @@ -76,7 +90,7 @@ describe("executeStatusScanFromOverview", () => { gatewayReachable: true, channels: { rows: [], details: [] }, summary: { sessions: { count: 1 } }, - memory: { agentId: "main" }, + memory: { agentId: "main", backend: "builtin", provider: "memory-core" }, pluginCompatibility: [], }), ); diff --git a/src/config/sessions/transcript.test.ts b/src/config/sessions/transcript.test.ts index 5ae342231e6..c8d70e19ad0 100644 --- a/src/config/sessions/transcript.test.ts +++ b/src/config/sessions/transcript.test.ts @@ -220,7 +220,14 @@ describe("appendAssistantMessageToSessionTranscript", () => { api: "openai-responses", provider: "openclaw", model: "delivery-mirror", - usage: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, totalTokens: 0 }, + usage: { + input: 0, + output: 0, + cacheRead: 0, + cacheWrite: 0, + totalTokens: 0, + cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 }, + }, stopReason: "stop", timestamp: Date.now(), }, @@ -259,7 +266,14 @@ describe("appendAssistantMessageToSessionTranscript", () => { api: "openai-responses", provider: "openclaw", model: "delivery-mirror", - usage: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, totalTokens: 0 }, + usage: { + input: 0, + output: 0, + cacheRead: 0, + cacheWrite: 0, + totalTokens: 0, + cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 }, + }, stopReason: "stop", timestamp: Date.now(), }, diff --git a/src/gateway/server-methods/chat.ts b/src/gateway/server-methods/chat.ts index c51ab09cc17..81ee026c15a 100644 --- a/src/gateway/server-methods/chat.ts +++ b/src/gateway/server-methods/chat.ts @@ -665,7 +665,7 @@ function sanitizeChatHistoryMessage( const sanitizedBlocks = updated.map((item) => item.block); const hasPhaseMetadata = hasAssistantPhaseMetadata(entry); if (hasPhaseMetadata) { - const stripped = stripInlineDirectiveTagsForDisplay(extractAssistantHistoryText(entry)); + const stripped = stripInlineDirectiveTagsForDisplay(extractAssistantHistoryText(entry) ?? ""); const res = truncateChatHistoryText(stripped.text, maxChars); const nonTextBlocks = sanitizedBlocks.filter( (block) => diff --git a/src/gateway/sessions-history-http.test.ts b/src/gateway/sessions-history-http.test.ts index 11781cfcd39..0075bdc370e 100644 --- a/src/gateway/sessions-history-http.test.ts +++ b/src/gateway/sessions-history-http.test.ts @@ -1,6 +1,7 @@ import fs from "node:fs/promises"; import os from "node:os"; import path from "node:path"; +import type { AssistantMessage } from "@mariozechner/pi-ai"; import { afterEach, describe, expect, test } from "vitest"; import { appendAssistantMessageToSessionTranscript, @@ -59,8 +60,8 @@ async function seedSession(params?: { text?: string }) { function makeTranscriptAssistantMessage(params: { text: string; - content?: Array>; -}) { + content?: AssistantMessage["content"]; +}): AssistantMessage { return { role: "assistant" as const, content: params.content ?? [{ type: "text", text: params.text }], @@ -88,7 +89,7 @@ function makeTranscriptAssistantMessage(params: { async function appendTranscriptMessage(params: { sessionKey: string; - message: ReturnType; + message: AssistantMessage; emitInlineMessage?: boolean; storePath?: string; }): Promise { diff --git a/src/security/audit-install-metadata.test.ts b/src/security/audit-install-metadata.test.ts index c7e4fc27d7e..9fdeac23017 100644 --- a/src/security/audit-install-metadata.test.ts +++ b/src/security/audit-install-metadata.test.ts @@ -46,7 +46,12 @@ describe("security audit install metadata findings", () => { }); it("evaluates install metadata findings", async () => { - const cases = [ + const cases: Array<{ + name: string; + run: () => Promise>>; + expectedPresent?: readonly string[]; + expectedAbsent?: readonly string[]; + }> = [ { name: "warns on unpinned npm install specs and missing integrity metadata", run: async () => @@ -165,7 +170,7 @@ describe("security audit install metadata findings", () => { }, expectedPresent: ["plugins.installs_version_drift", "hooks.installs_version_drift"], }, - ] as const; + ]; for (const testCase of cases) { const res = await testCase.run(); diff --git a/src/security/audit-node-command-findings.test.ts b/src/security/audit-node-command-findings.test.ts index 29c9564af24..ae069e3b124 100644 --- a/src/security/audit-node-command-findings.test.ts +++ b/src/security/audit-node-command-findings.test.ts @@ -74,7 +74,12 @@ describe("security audit node command findings", () => { }); it("evaluates dangerous gateway.nodes.allowCommands findings", () => { - const cases = [ + const cases: Array<{ + name: string; + cfg: OpenClawConfig; + expectedSeverity?: "warn" | "critical"; + expectedAbsent?: boolean; + }> = [ { name: "loopback gateway", cfg: { @@ -107,14 +112,14 @@ describe("security audit node command findings", () => { } satisfies OpenClawConfig, expectedAbsent: true, }, - ] as const; + ]; for (const testCase of cases) { const findings = collectNodeDangerousAllowCommandFindings(testCase.cfg); const finding = findings.find( (entry) => entry.checkId === "gateway.nodes.allow_commands_dangerous", ); - if ("expectedAbsent" in testCase && testCase.expectedAbsent) { + if (testCase.expectedAbsent) { expect(finding, testCase.name).toBeUndefined(); continue; }