refactor(status): own summary preparation

This commit is contained in:
Vincent Koc
2026-07-29 17:44:44 +08:00
parent 6c6816952a
commit fa16b0fcf0
32 changed files with 49 additions and 49 deletions

View File

@@ -122,7 +122,7 @@ export const migratedSessionAccessorFiles = new Set([
"src/commands/sessions-tail.ts",
"src/commands/sessions.ts",
"src/commands/status.agent-local.ts",
"src/commands/status.summary.ts",
"src/status/summary.ts",
"src/commands/tasks.ts",
"src/config/sessions/combined-store-gateway.ts",
"src/config/sessions/delivery-info.ts",

View File

@@ -1,10 +1,10 @@
// Resolves a channel plugin's default account with strict or read-only diagnostics.
import { resolveChannelDefaultAccountId } from "../channels/plugins/helpers.js";
import type { ChannelPlugin } from "../channels/plugins/types.plugin.js";
import { inspectReadOnlyChannelAccount } from "../channels/read-only-account-inspect.js";
import type { OpenClawConfig } from "../config/types.openclaw.js";
import { formatErrorMessage } from "../infra/errors.js";
import { isRecord } from "../utils.js";
// Resolves a channel plugin's default account with strict or read-only diagnostics.
import { resolveChannelDefaultAccountId } from "./plugins/helpers.js";
import type { ChannelPlugin } from "./plugins/types.plugin.js";
import { inspectReadOnlyChannelAccount } from "./read-only-account-inspect.js";
type ChannelDefaultAccountContext = {
accountIds: string[];

View File

@@ -1,8 +1,8 @@
// Channel account context tests cover default account resolution and read-only account inspection fallback.
import { describe, expect, it, vi } from "vitest";
import { resolveDefaultChannelAccountContext } from "../channels/account-context.js";
import type { ChannelPlugin } from "../channels/plugins/types.public.js";
import type { OpenClawConfig } from "../config/config.js";
import { resolveDefaultChannelAccountContext } from "./channel-account-context.js";
vi.mock("../channels/read-only-account-inspect.js", () => ({
inspectReadOnlyChannelAccount: vi.fn(async () => null),

View File

@@ -17,6 +17,7 @@ import { collectChannelStatusIssues } from "../infra/channels-status-issues.js";
import { formatErrorMessage } from "../infra/errors.js";
import type { RuntimeEnv } from "../runtime.js";
import { redactSecretDegradationReason } from "../secrets/runtime-degraded-state.js";
import type { StatusSummary } from "../status/types.js";
import { VERSION } from "../version.js";
import {
GATEWAY_HEALTH_CREDENTIALS_REQUIRED_MESSAGE,
@@ -24,7 +25,6 @@ import {
gatewayProbeResultSawGateway,
} from "./gateway-health-auth-diagnostic.js";
import { formatGatewayClosedDiagnostic, formatHealthCheckFailure } from "./health-format.js";
import type { StatusSummary } from "./status.types.js";
type GatewayMemoryProbe = {
checked: boolean;

View File

@@ -1,6 +1,7 @@
/** Security warnings for gateway exposure, exec policy drift, channel DMs, and plaintext secrets. */
import { normalizeOptionalString } from "@openclaw/normalization-core/string-coerce";
import { note } from "../../packages/terminal-core/src/note.js";
import { resolveDefaultChannelAccountContext } from "../channels/account-context.js";
import { resolveDmAllowAuditState } from "../channels/message-access/dm-allow-state.js";
import { listReadOnlyChannelPluginsForConfig } from "../channels/plugins/read-only.js";
import type { ChannelId } from "../channels/plugins/types.public.js";
@@ -23,7 +24,6 @@ import { isLikelySensitiveModelProviderHeaderName } from "../secrets/model-provi
import { hasConfiguredPlaintextSecretValue } from "../secrets/secret-value.js";
import { discoverConfigSecretTargets } from "../secrets/target-registry.js";
import { collectExecFilesystemPolicyDriftHits } from "../security/exec-filesystem-policy.js";
import { resolveDefaultChannelAccountContext } from "./channel-account-context.js";
function collectImplicitHeartbeatDirectPolicyWarnings(cfg: OpenClawConfig): string[] {
const warnings: string[] = [];

View File

@@ -5,8 +5,8 @@ import { note } from "../../packages/terminal-core/src/note.js";
import { formatCliCommand } from "../cli/command-format.js";
import type { OpenClawConfig } from "../config/types.openclaw.js";
import type { HealthFinding } from "../flows/health-checks.js";
import type { StatusSummary } from "../status/types.js";
import { sleep } from "../utils/sleep.js";
import type { StatusSummary } from "./status.types.js";
type LocalTuiProcess = {
pid: number;

View File

@@ -13,7 +13,7 @@ import {
* Catalog #19 — `kind` misclassified as `"direct"` for ACP spawn-child sessions.
*
* Bug summary: `classifySessionKey` (defined twice — `src/commands/sessions.ts:136-152`
* and `src/commands/status.summary.runtime.ts:129-145`) classifies a session
* and `src/status/summary.runtime.ts:129-145`) classifies a session
* based ONLY on the key shape (`:group:` / `:channel:` substrings) plus
* `entry.chatType`. It ignores `entry.spawnedBy` and `entry.deliveryContext`,
* so ACP spawn-child sessions (e.g., `agent:copilot:acp:<uuid>` with
@@ -32,7 +32,7 @@ import {
* NOTE ON DUPLICATION: the same logic lives in two places —
* - `src/commands/sessions.ts:136-152` (called by `sessionsCommand`,
* the path under test here)
* - `src/commands/status.summary.runtime.ts:129-145`
* - `src/status/summary.runtime.ts:129-145`
* The eventual fix MUST update both, or extract a shared helper.
*
* NOTE ON SURFACE: `classifySessionKey` is private to each file (not exported),
@@ -177,7 +177,7 @@ describe("sessionsCommand kind classification (catalog #19)", () => {
`ACP spawn-child session ${ACP_SPAWN_CHILD_KEY} should classify as "spawn-child" ` +
`(or whichever non-direct label the fix author chooses). Got "${row?.kind}". ` +
`Fix locations: src/commands/sessions.ts:136-152 AND ` +
`src/commands/status.summary.runtime.ts:129-145 (the same logic is duplicated; ` +
`src/status/summary.runtime.ts:129-145 (the same logic is duplicated; ` +
`extract to a shared helper or update both).`,
).toBe("spawn-child");
});

View File

@@ -4,6 +4,7 @@
import { formatCliCommand } from "../cli/command-format.js";
import type { HeartbeatEventPayload } from "../infra/heartbeat-events.js";
import type { PluginCompatibilityNotice } from "../plugins/status.js";
import type { StatusSummary } from "../status/types.js";
import { VERSION } from "../version.js";
import type { HealthSummary } from "./health.js";
import {
@@ -28,7 +29,6 @@ import {
type StatusMemoryStateResolvers,
} from "./status.command-sections.js";
import type { MemoryPluginStatus, MemoryStatusSnapshot } from "./status.scan.shared.js";
import type { StatusSummary } from "./status.types.js";
/** Builds the default `openclaw status` overview rows from scan, health, memory, and session inputs. */
export function buildStatusCommandOverviewRows(

View File

@@ -8,6 +8,7 @@ import type { HeartbeatEventPayload } from "../infra/heartbeat-events.js";
import type { resolveOsSummary } from "../infra/os-summary.js";
import type { PluginCompatibilityNotice } from "../plugins/status.js";
import type { SecurityAuditReport } from "../security/audit.js";
import type { SessionStatus, StatusSummary } from "../status/types.js";
import type { HealthSummary } from "./health.js";
import {
buildStatusChannelsTableRows,
@@ -30,7 +31,6 @@ import {
type StatusMemoryStateResolvers,
} from "./status.command-sections.js";
import type { MemoryPluginStatus, MemoryStatusSnapshot } from "./status.scan.shared.js";
import type { SessionStatus, StatusSummary } from "./status.types.js";
/** Builds all table rows, section lines, and footer data needed by the status report renderer. */
export async function buildStatusCommandReportData(

View File

@@ -11,10 +11,10 @@ import type { TableColumn } from "../../packages/terminal-core/src/table.js";
import { areRuntimeModelRefsEquivalent } from "../agents/model-runtime-aliases.js";
import type { HeartbeatEventPayload } from "../infra/heartbeat-events.js";
import type { Tone } from "../memory-host-sdk/status.js";
import type { SessionStatus, StatusSummary } from "../status/types.js";
import type { HealthSummary } from "./health.js";
import type { AgentLocalStatus } from "./status.agent-local.js";
import type { MemoryStatusSnapshot, MemoryPluginStatus } from "./status.scan.shared.js";
import type { SessionStatus, StatusSummary } from "./status.types.js";
type AgentStatusLike = {
defaultId?: string | null;

View File

@@ -5,8 +5,8 @@ import { normalizeLowercaseStringOrEmpty } from "@openclaw/normalization-core/st
import { getSystemdCgroupHygieneSummary } from "../daemon/service-runtime.js";
import { formatDurationPrecise } from "../infra/format-time/format-duration.ts";
import { formatRuntimeStatusWithDetails } from "../infra/runtime-status.ts";
import type { SessionStatus } from "../status/types.js";
import { formatTokenCount } from "../utils/token-format.js";
import type { SessionStatus } from "./status.types.js";
export { shortenText } from "./text-format.js";
export const formatKTokens = formatTokenCount;

View File

@@ -12,7 +12,7 @@ vi.mock("../channels/read-only-account-inspect.js", () => ({
inspectReadOnlyChannelAccount: () => undefined,
}));
import { resolveLinkChannelContext } from "./status.link-channel.js";
import { resolveLinkChannelContext } from "../status/link-channel.js";
describe("resolveLinkChannelContext", () => {
it("returns linked context from read-only inspected account state", async () => {

View File

@@ -29,7 +29,7 @@ const statusScanRuntimeModuleLoader = createLazyImportLoader(
() => import("./status.scan.runtime.js"),
);
const gatewayCallModuleLoader = createLazyImportLoader(() => import("../gateway/call.js"));
const statusSummaryModuleLoader = createLazyImportLoader(() => import("./status.summary.js"));
const statusSummaryModuleLoader = createLazyImportLoader(() => import("../status/summary.js"));
const channelPluginIdsModuleLoader = createLazyImportLoader(
() => import("../plugins/channel-plugin-ids.js"),
);

View File

@@ -6,6 +6,7 @@ import type { collectChannelStatusIssues as collectChannelStatusIssuesFn } from
import { resolveOsSummary } from "../infra/os-summary.js";
import type { UpdateCheckResult } from "../infra/update-check.js";
import type { PluginCompatibilityNotice } from "../plugins/status.js";
import type { getStatusSummary as getStatusSummaryFn } from "../status/summary.js";
import type { pickGatewaySelfPresence } from "./gateway-presence.js";
import type { buildChannelsTable as buildChannelsTableFn } from "./status-all/channels.js";
import type { getAgentLocalStatuses as getAgentLocalStatusesFn } from "./status.agent-local.js";
@@ -14,7 +15,6 @@ import type {
MemoryPluginStatus,
MemoryStatusSnapshot,
} from "./status.scan.shared.js";
import type { getStatusSummary as getStatusSummaryFn } from "./status.summary.js";
export type StatusScanResult = {
cfg: OpenClawConfig;

View File

@@ -259,7 +259,7 @@ export async function loadStatusScanModuleForTest(
}));
vi.doMock("./status.update.js", () => createStatusUpdateModuleMock(mocks));
vi.doMock("./status.agent-local.js", () => createStatusAgentLocalModuleMock(mocks));
vi.doMock("./status.summary.js", () => createStatusSummaryModuleMock(mocks));
vi.doMock("../status/summary.js", () => createStatusSummaryModuleMock(mocks));
vi.doMock("../infra/os-summary.js", () => createStatusOsSummaryModuleMock());
vi.doMock("./status.scan.deps.runtime.js", () => createStatusScanDepsRuntimeModuleMock(mocks));
vi.doMock("../gateway/call.js", () => createStatusGatewayCallModuleMock(mocks));

View File

@@ -1,7 +1,7 @@
// Status summary redaction tests cover sensitive field removal from summarized runtime state.
import { describe, expect, it } from "vitest";
import { redactSensitiveStatusSummary } from "./status.summary.js";
import type { SessionStatus, StatusSummary } from "./status.types.js";
import { redactSensitiveStatusSummary } from "../status/summary.js";
import type { SessionStatus, StatusSummary } from "../status/types.js";
function createRecentSessionRow(): SessionStatus {
return {

View File

@@ -19,7 +19,7 @@ describe("statusSummaryRuntime configured model normalization", () => {
});
it("skips manifest and plugin model normalization for configured model refs", async () => {
const { statusSummaryRuntime } = await import("./status.summary.runtime.js");
const { statusSummaryRuntime } = await import("../status/summary.runtime.js");
expect(
statusSummaryRuntime.resolveConfiguredStatusModelRef({
@@ -63,7 +63,7 @@ describe("statusSummaryRuntime configured model normalization", () => {
});
it("skips manifest and plugin model normalization for providerless persisted session models", async () => {
const { statusSummaryRuntime } = await import("./status.summary.runtime.js");
const { statusSummaryRuntime } = await import("../status/summary.runtime.js");
const cfg = {
agents: {
defaults: {

View File

@@ -2,7 +2,7 @@
import { describe, expect, it } from "vitest";
import { ANTHROPIC_CONTEXT_1M_TOKENS } from "../agents/context-resolution.js";
import { migratePersistedImplicitMainRoster } from "../config/legacy.roster.js";
import { statusSummaryRuntime } from "./status.summary.runtime.js";
import { statusSummaryRuntime } from "../status/summary.runtime.js";
function resolveSessionRuntimeLabel(
params: Parameters<typeof statusSummaryRuntime.resolveSessionRuntimeLabel>[0],

View File

@@ -70,7 +70,7 @@ vi.mock("../plugins/channel-plugin-ids.js", () => ({
hasConfiguredChannelsForReadOnlyScope: statusSummaryMocks.hasConfiguredChannelsForReadOnlyScope,
}));
vi.mock("./status.summary.runtime.js", () => ({
vi.mock("../status/summary.runtime.js", () => ({
statusSummaryRuntime: {
classifySessionKey: vi.fn(() => "direct"),
resolveConfiguredStatusModelRef: vi.fn(() => ({
@@ -185,16 +185,16 @@ vi.mock("../version.js", async () => {
};
});
vi.mock("./status.link-channel.js", () => ({
vi.mock("../status/link-channel.js", () => ({
resolveLinkChannelContext: vi.fn(async () => undefined),
}));
const { buildChannelSummary } = await import("../infra/channel-summary.js");
const { resolveStorePath } = await import("../config/sessions/paths.js");
const { listGatewayAgentsBasic } = await import("../gateway/agent-list.js");
const { resolveLinkChannelContext } = await import("./status.link-channel.js");
let getStatusSummary: typeof import("./status.summary.js").getStatusSummary;
let statusSummaryRuntime: typeof import("./status.summary.runtime.js").statusSummaryRuntime;
const { resolveLinkChannelContext } = await import("../status/link-channel.js");
let getStatusSummary: typeof import("../status/summary.js").getStatusSummary;
let statusSummaryRuntime: typeof import("../status/summary.runtime.js").statusSummaryRuntime;
function toSessionEntrySummaries(store: Record<string, Record<string, unknown>>) {
return Object.entries(store).map(([sessionKey, entry]) => ({ sessionKey, entry }));
@@ -202,8 +202,8 @@ function toSessionEntrySummaries(store: Record<string, Record<string, unknown>>)
describe("getStatusSummary", () => {
beforeAll(async () => {
({ getStatusSummary } = await import("./status.summary.js"));
({ statusSummaryRuntime } = await import("./status.summary.runtime.js"));
({ getStatusSummary } = await import("../status/summary.js"));
({ statusSummaryRuntime } = await import("../status/summary.runtime.js"));
});
beforeEach(() => {

View File

@@ -3,13 +3,13 @@ import type { HeartbeatEventPayload } from "../infra/heartbeat-events.js";
import { isBetaTag } from "../infra/update-channels.js";
import type { Tone } from "../memory-host-sdk/status.js";
import type { PluginCompatibilityNotice } from "../plugins/status.js";
import type { StatusSummary } from "../status/types.js";
import { VERSION } from "../version.js";
import type { buildStatusCommandOverviewRows } from "./status-overview-rows.ts";
import type { StatusOverviewSurface } from "./status-overview-surface.ts";
import type { AgentLocalStatus } from "./status.agent-local.js";
import type { buildStatusCommandReportData } from "./status.command-report-data.ts";
import type { MemoryPluginStatus, MemoryStatusSnapshot } from "./status.scan.shared.js";
import type { StatusSummary } from "./status.types.js";
type StatusCommandOverviewRowsParams = Parameters<typeof buildStatusCommandOverviewRows>[0];
type StatusCommandReportDataParams = Parameters<typeof buildStatusCommandReportData>[0];

View File

@@ -2,4 +2,4 @@
// Exposes the command and summary builder without importing implementation details.
export { statusCommand } from "./status.command.js";
export { getStatusSummary } from "./status.summary.js";
export { getStatusSummary } from "../status/summary.js";

View File

@@ -40,7 +40,7 @@ export type DoctorHealthFlowContext = {
healthOk?: boolean;
gatewayHealthAuthenticated?: boolean;
gatewayHealthSkipped?: boolean;
gatewayStatus?: import("../commands/status.types.js").StatusSummary;
gatewayStatus?: import("../status/types.js").StatusSummary;
gatewayMemoryProbe?: Awaited<ReturnType<typeof probeGatewayMemoryStatus>>;
postInstallDoctorResult?: UpdatePostInstallDoctorResult;
};

View File

@@ -314,7 +314,7 @@ export function resolveFinalDoctorHealthContributions(params: {
async detect(ctx) {
const { collectWhatsappResponsivenessHealthFindings } =
await import("../commands/doctor-whatsapp-responsiveness.js");
let status: import("../commands/status.types.js").StatusSummary | undefined;
let status: import("../status/types.js").StatusSummary | undefined;
if (
!(
(await hasActiveGatewayExecCredential({ cfg: ctx.cfg })) &&
@@ -322,7 +322,7 @@ export function resolveFinalDoctorHealthContributions(params: {
)
) {
const { callGateway } = await import("../gateway/call.js");
status = await callGateway<import("../commands/status.types.js").StatusSummary>({
status = await callGateway<import("../status/types.js").StatusSummary>({
method: "status",
params: { includeChannelSummary: false },
timeoutMs: 3000,

View File

@@ -4,8 +4,8 @@ import { ErrorCodes, errorShape } from "../../../packages/gateway-protocol/src/i
import type { ChannelAccountSnapshot } from "../../channels/plugins/types.public.js";
import { buildDeliveryQueueHealthSummary } from "../../commands/health.js";
import type { ChannelHealthSummary, HealthSummary } from "../../commands/health.types.js";
import { getStatusSummary } from "../../commands/status.js";
import { listContextEngineQuarantines } from "../../context-engine/registry.js";
import { getStatusSummary } from "../../status/summary.js";
import type { GatewayHotReloadStatus } from "../config-reload-status.types.js";
import type { ChannelRuntimeSnapshot } from "../server-channel-runtime.types.js";
import { HEALTH_REFRESH_INTERVAL_MS } from "../server-constants.js";

View File

@@ -54,7 +54,7 @@ function waitForFast<T>(
const AGENT_RUN_CACHE_ENTRY_LIMIT = 5_000;
vi.mock("../../commands/status.js", () => ({
vi.mock("../../status/summary.js", () => ({
getStatusSummary: vi.fn().mockResolvedValue({ ok: true }),
}));
@@ -4733,11 +4733,11 @@ describe("exec approval handlers", () => {
});
describe("gateway healthHandlers.status scope handling", () => {
let statusModule: typeof import("../../commands/status.js");
let statusModule: typeof import("../../status/summary.js");
let healthHandlers: typeof import("./health.js").healthHandlers;
beforeAll(async () => {
statusModule = await import("../../commands/status.js");
statusModule = await import("../../status/summary.js");
({ healthHandlers } = await import("./health.js"));
});

View File

@@ -244,7 +244,7 @@ vi.mock("/src/agents/embedded-agent-runner/runs.js", async () => {
vi.mock("../commands/health.js", () => ({
getHealthSnapshot: vi.fn().mockResolvedValue({ ok: true, stub: true }),
}));
vi.mock("../commands/status.js", () => ({
vi.mock("../status/summary.js", () => ({
getStatusSummary: vi.fn().mockResolvedValue({ ok: true }),
}));
vi.mock("../commands/agent.js", () => ({

View File

@@ -1,11 +1,11 @@
// Resolves the first channel that can report linked/unlinked auth state for status summaries.
// Channel-specific linking logic stays inside plugin status hooks.
import { resolveDefaultChannelAccountContext } from "../channels/account-context.js";
import { listReadOnlyChannelPluginsForConfig } from "../channels/plugins/read-only.js";
import type { ChannelPlugin } from "../channels/plugins/types.plugin.js";
import type { ChannelAccountSnapshot } from "../channels/plugins/types.public.js";
import type { OpenClawConfig } from "../config/types.openclaw.js";
import { resolveDefaultChannelAccountContext } from "./channel-account-context.js";
type LinkChannelContext = {
linked: boolean;

View File

@@ -19,7 +19,7 @@ import type { SessionEntry } from "../config/sessions/types.js";
import type { OpenClawConfig } from "../config/types.js";
import { resolveStoredSessionKeyForAgentStore } from "../gateway/session-store-key.js";
import { classifySessionKind } from "../sessions/classify-session-kind.js";
import { resolveAgentRuntimeLabel } from "../status/agent-runtime-label.js";
import { resolveAgentRuntimeLabel } from "./agent-runtime-label.js";
function resolveStatusModelRefFromRaw(params: {
cfg: OpenClawConfig;

View File

@@ -32,7 +32,7 @@ import {
summarizeRetainedLostTaskAuditFindings,
} from "../tasks/task-registry.audit.js";
import { resolveRuntimeServiceVersion } from "../version.js";
import type { HeartbeatStatus, SessionStatus, StatusSummary } from "./status.types.js";
import type { HeartbeatStatus, SessionStatus, StatusSummary } from "./types.js";
const RECENT_SESSION_LIMIT = 10;
@@ -42,7 +42,7 @@ const channelSummaryModuleLoader = createLazyImportLoader(
const channelPluginIdsModuleLoader = createLazyImportLoader(
() => import("../plugins/channel-plugin-ids.js"),
);
const linkChannelModuleLoader = createLazyImportLoader(() => import("./status.link-channel.js"));
const linkChannelModuleLoader = createLazyImportLoader(() => import("./link-channel.js"));
const taskRegistryMaintenanceModuleLoader = createLazyImportLoader(
() => import("../tasks/task-registry.maintenance.js"),
);
@@ -70,7 +70,7 @@ function loadLinkChannelModule() {
}
const loadStatusSummaryRuntimeModule = createLazyRuntimeSurface(
() => import("./status.summary.runtime.js"),
() => import("./summary.runtime.js"),
({ statusSummaryRuntime }) => statusSummaryRuntime,
);

View File

@@ -80,7 +80,7 @@ describe("session accessor boundary guard", () => {
"src/commands/sessions-tail.ts",
"src/commands/sessions.ts",
"src/commands/status.agent-local.ts",
"src/commands/status.summary.ts",
"src/status/summary.ts",
"src/commands/tasks.ts",
"src/config/sessions/combined-store-gateway.ts",
"src/config/sessions/delivery-info.ts",

View File

@@ -298,7 +298,7 @@ function buildCoreDistEntries(): Record<string, string> {
"media-understanding/apply.runtime": "src/media-understanding/apply.runtime.ts",
"commands/doctor/shared/plugin-registry-migration":
"src/commands/doctor/shared/plugin-registry-migration.ts",
"commands/status.summary.runtime": "src/commands/status.summary.runtime.ts",
"commands/status.summary.runtime": "src/status/summary.runtime.ts",
"infra/boundary-file-read": "src/infra/boundary-file-read.ts",
"plugins/provider-discovery.runtime": "src/plugins/provider-discovery.runtime.ts",
"plugins/provider-runtime.runtime": "src/plugins/provider-runtime.runtime.ts",