diff --git a/CHANGELOG.md b/CHANGELOG.md index 80757c8fe71..a3ba0a7711c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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. diff --git a/docs/cli/status.md b/docs/cli/status.md index 4b40f4ea86c..93782f27e16 100644 --- a/docs/cli/status.md +++ b/docs/cli/status.md @@ -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. diff --git a/extensions/telegram/src/bot-message.test.ts b/extensions/telegram/src/bot-message.test.ts index 0ada6fa833f..56bf99da122 100644 --- a/extensions/telegram/src/bot-message.test.ts +++ b/extensions/telegram/src/bot-message.test.ts @@ -76,7 +76,10 @@ describe("telegram bot message processor", () => { sendMessage: ReturnType, ) { 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( diff --git a/extensions/telegram/src/bot-message.ts b/extensions/telegram/src/bot-message.ts index 332fa8a91f8..7c6a1b4376e 100644 --- a/extensions/telegram/src/bot-message.ts +++ b/extensions/telegram/src/bot-message.ts @@ -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, diff --git a/src/commands/status-all/channels.ts b/src/commands/status-all/channels.ts index 9e0f9a363d3..c1101ae837d 100644 --- a/src/commands/status-all/channels.ts +++ b/src/commands/status-all/channels.ts @@ -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({ diff --git a/src/commands/status.command-report-data.test.ts b/src/commands/status.command-report-data.test.ts index 53234de9f59..f1ed11d5113 100644 --- a/src/commands/status.command-report-data.test.ts +++ b/src/commands/status.command-report-data.test.ts @@ -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)", + ]); + }); }); diff --git a/src/commands/status.command-report-data.ts b/src/commands/status.command-report-data.ts index d5d8c72fb54..d24e9f77eea 100644 --- a/src/commands/status.command-report-data.ts +++ b/src/commands/status.command-report-data.ts @@ -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, diff --git a/src/commands/status.command.ts b/src/commands/status.command.ts index b01ba162905..8a057cc6b88 100644 --- a/src/commands/status.command.ts +++ b/src/commands/status.command.ts @@ -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( { diff --git a/src/commands/status.scan.test.ts b/src/commands/status.scan.test.ts index 2bddd125580..1f7a92aa966 100644 --- a/src/commands/status.scan.test.ts +++ b/src/commands/status.scan.test.ts @@ -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 }); diff --git a/src/commands/status.scan.ts b/src/commands/status.scan.ts index 3d6e9812f71..0257d148673 100644 --- a/src/commands/status.scan.ts +++ b/src/commands/status.scan.ts @@ -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, diff --git a/src/commands/status.test.ts b/src/commands/status.test.ts index f4cb8e0c055..5d5780c363c 100644 --- a/src/commands/status.test.ts +++ b/src/commands/status.test.ts @@ -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",