fix: speed up Telegram status diagnostics

This commit is contained in:
Peter Steinberger
2026-04-28 00:27:59 +01:00
parent 3ae796b649
commit fc055e2393
11 changed files with 90 additions and 22 deletions

View File

@@ -40,6 +40,8 @@ Docs: https://docs.openclaw.ai
- Gateway/supervisor: exit cleanly when a supervised restart finds an existing healthy gateway and bound retries when the existing gateway stays unhealthy, so stale lock contention cannot loop indefinitely. Refs #72846. Thanks @azgardtek.
- Gateway/startup: scope primary-model provider discovery during channel prewarm to the configured provider owner and add split startup trace timings, so boot avoids staging unrelated bundled provider dependencies while setup discovery remains broad. Fixes #73002. Thanks @Schnup03.
- Plugins/runtime deps: declare retained staged bundled plugin dependencies in the npm staging manifest while installing only newly missing packages, so Gateway restarts avoid reinstalling the full retained dependency set when one runtime dependency is absent. Fixes #73055. Thanks @GCorp2026.
- CLI/status: keep default `openclaw status` off the heavyweight security audit, plugin compatibility, and memory-vector probes while still showing configured Telegram channels through setup metadata, so routine health checks stay fast and no longer render an empty Channels table. Fixes #72993. Thanks @comick1.
- Channels/Telegram: send a best-effort native typing cue immediately after an inbound message is accepted, so slow pre-dispatch turns show Telegram liveness before queueing, compaction, model, or tool work starts. Fixes #63759. Thanks @alessandropcostabr.
- Channels/Microsoft Teams: unwrap staged CommonJS JWT runtime dependencies before Bot Connector token validation so inbound Teams messages no longer 401 after the bundled runtime-deps move. Fixes #73026. Thanks @kbrown10000.
- Gateway/auth: allow local direct callers in trusted-proxy mode to use the configured gateway password as an internal fallback while keeping token fallback rejected. Fixes #17761. Thanks @dashed, @vincentkoc, and @jetd1.
- Channels/sessions: prevent guarded inbound session recording from creating route-only phantom sessions while still allowing last-route updates for sessions that already exist. Carries forward #73009. Thanks @jzakirov.

View File

@@ -20,6 +20,7 @@ openclaw status --usage
Notes:
- `--deep` runs live probes (WhatsApp Web + Telegram + Discord + Slack + Signal).
- Plain `openclaw status` stays on the fast read-only path. Heavy security audit, plugin compatibility, and memory-vector probes are left to `openclaw status --all`, `openclaw status --deep`, `openclaw security audit`, and `openclaw memory status --deep`.
- `--usage` prints normalized provider usage windows as `X% left`.
- Session status output separates `Execution:` from `Runtime:`. `Execution` is the sandbox path (`direct`, `docker/*`), while `Runtime` tells you whether the session is using `OpenClaw Pi Default`, `OpenAI Codex`, a CLI backend, or an ACP backend such as `codex (acp/acpx)`. See [Agent runtimes](/concepts/agent-runtimes) for the provider/model/runtime distinction.
- MiniMax's raw `usage_percent` / `usagePercent` fields are remaining quota, so OpenClaw inverts them before display; count-based fields win when present. `model_remains` responses prefer the chat-model entry, derive the window label from timestamps when needed, and include the model name in the plan label.

View File

@@ -76,7 +76,10 @@ describe("telegram bot message processor", () => {
sendMessage: ReturnType<typeof vi.fn>,
) {
const runtimeError = vi.fn();
buildTelegramMessageContext.mockResolvedValue(context);
buildTelegramMessageContext.mockResolvedValue({
sendTyping: vi.fn().mockResolvedValue(undefined),
...context,
});
dispatchTelegramMessage.mockRejectedValue(new Error("dispatch exploded"));
const processMessage = createTelegramMessageProcessor({
...baseDeps,
@@ -87,12 +90,21 @@ describe("telegram bot message processor", () => {
}
it("dispatches when context is available", async () => {
buildTelegramMessageContext.mockResolvedValue({ route: { sessionKey: "agent:main:main" } });
const sendTyping = vi.fn().mockResolvedValue(undefined);
buildTelegramMessageContext.mockResolvedValue({
chatId: 123,
route: { sessionKey: "agent:main:main" },
sendTyping,
});
const processMessage = createTelegramMessageProcessor(baseDeps);
await processSampleMessage(processMessage);
expect(sendTyping).toHaveBeenCalledTimes(1);
expect(dispatchTelegramMessage).toHaveBeenCalledTimes(1);
expect(sendTyping.mock.invocationCallOrder[0]).toBeLessThan(
dispatchTelegramMessage.mock.invocationCallOrder[0],
);
});
it("skips dispatch when no context is produced", async () => {
@@ -102,6 +114,21 @@ describe("telegram bot message processor", () => {
expect(dispatchTelegramMessage).not.toHaveBeenCalled();
});
it("keeps dispatch running when the early typing cue fails", async () => {
const sendTyping = vi.fn().mockRejectedValue(new Error("typing failed"));
buildTelegramMessageContext.mockResolvedValue({
chatId: 123,
route: { sessionKey: "agent:main:main" },
sendTyping,
});
const processMessage = createTelegramMessageProcessor(baseDeps);
await processSampleMessage(processMessage);
expect(sendTyping).toHaveBeenCalledTimes(1);
expect(dispatchTelegramMessage).toHaveBeenCalledTimes(1);
});
it("sends user-visible fallback when dispatch throws", async () => {
const sendMessage = vi.fn().mockResolvedValue(undefined);
const { processMessage, runtimeError } = createDispatchFailureHarness(

View File

@@ -107,6 +107,9 @@ export const createTelegramMessageProcessor = (deps: TelegramMessageProcessorDep
(options?.ingressBuffer ? ` buffer=${options.ingressBuffer}` : ""),
);
}
void context.sendTyping().catch((err) => {
logVerbose(`telegram early typing cue failed for chat ${context.chatId}: ${String(err)}`);
});
try {
await dispatchTelegramMessage({
context,

View File

@@ -208,7 +208,7 @@ export async function buildChannelsTable(
const sourceConfig = opts?.sourceConfig ?? cfg;
for (const plugin of listReadOnlyChannelPluginsForConfig(cfg, {
activationSourceConfig: sourceConfig,
includeSetupRuntimeFallback: false,
includeSetupRuntimeFallback: true,
})) {
const accountIds = plugin.config.listAccountIds(cfg);
const defaultAccountId = resolveChannelDefaultAccountId({

View File

@@ -48,4 +48,17 @@ describe("buildStatusCommandReportData", () => {
});
expect(result.footerLines.at(-1)).toBe(" Need to test channels? cmd:openclaw status --deep");
});
it("shows skipped audit text when fast status omits the security audit", async () => {
const result = await buildStatusCommandReportData(
createStatusCommandReportDataParams({
securityAudit: undefined,
}),
);
expect(result.securityAuditLines).toEqual([
"muted(Skipped in fast status. Full report: cmd:openclaw security audit)",
"muted(Deep probe: cmd:openclaw status --deep)",
]);
});
});

View File

@@ -121,10 +121,19 @@ export async function buildStatusCommandReportData(
{ 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: [],
};
const securityAuditLines = params.securityAudit
? buildStatusSecurityAuditLines({
securityAudit: params.securityAudit,
theme: params.theme,
shortenText: params.shortenText,
formatCliCommand: params.formatCliCommand,
})
: [
params.theme.muted(
`Skipped in fast status. Full report: ${params.formatCliCommand("openclaw security audit")}`,
),
params.theme.muted(`Deep probe: ${params.formatCliCommand("openclaw status --deep")}`),
];
return {
heading: params.theme.heading,
@@ -146,12 +155,7 @@ export async function buildStatusCommandReportData(
muted: params.theme.muted,
formatCliCommand: params.formatCliCommand,
}),
securityAuditLines: buildStatusSecurityAuditLines({
securityAudit,
theme: params.theme,
shortenText: params.shortenText,
formatCliCommand: params.formatCliCommand,
}),
securityAuditLines,
channelsColumns: statusChannelsTableColumns,
channelsRows: buildStatusChannelsTableRows({
rows: params.channels.rows,

View File

@@ -172,7 +172,7 @@ export async function statusCommand(
usage: opts.usage,
deep: opts.deep,
gatewayReachable,
includeSecurityAudit: true,
includeSecurityAudit: opts.all === true || opts.deep === true,
resolveSecurityAudit: async (input) =>
await withProgress(
{

View File

@@ -168,6 +168,15 @@ describe("scanStatus", () => {
expect(mocks.getMemorySearchManager).not.toHaveBeenCalled();
});
it("keeps default text status off plugin compatibility and memory scans", async () => {
configureScanStatus({ memoryConfigured: true });
await scanStatus({ json: false }, {} as never);
expect(mocks.buildPluginCompatibilityNotices).not.toHaveBeenCalled();
expect(mocks.getMemorySearchManager).not.toHaveBeenCalled();
});
it("inspects memory backend when memory search is explicitly configured", async () => {
configureScanStatus({ memoryConfigured: true });

View File

@@ -63,18 +63,22 @@ export async function scanStatus(
});
progress.setLabel("Checking plugins…");
const pluginCompatibility = buildPluginCompatibilitySnapshotNotices({ config: overview.cfg });
const pluginCompatibility = opts.all
? buildPluginCompatibilitySnapshotNotices({ config: overview.cfg })
: [];
progress.tick();
progress.setLabel("Checking memory and sessions…");
const result = await executeStatusScanFromOverview({
overview,
resolveMemory: async ({ cfg, agentStatus, memoryPlugin }) =>
await resolveStatusMemoryStatusSnapshot({
cfg,
agentStatus,
memoryPlugin,
}),
opts.all
? await resolveStatusMemoryStatusSnapshot({
cfg,
agentStatus,
memoryPlugin,
})
: null,
channelIssues: overview.channelIssues,
channels: overview.channels,
pluginCompatibility,

View File

@@ -1012,6 +1012,12 @@ describe("statusCommand", () => {
);
});
it("keeps default text status off the security audit path", async () => {
await statusCommand({}, runtime as never);
expect(mocks.runSecurityAudit).not.toHaveBeenCalled();
});
it("surfaces unknown usage when totalTokens is missing", async () => {
await withUnknownUsageStore(async () => {
runtimeLogMock.mockClear();
@@ -1052,8 +1058,7 @@ describe("statusCommand", () => {
"OpenClaw status",
"Overview",
"Security audit",
"Summary:",
"CRITICAL",
"Skipped in fast status",
"Dashboard",
"macos 14.0 (arm64)",
"Memory",