mirror of
https://github.com/openclaw/openclaw.git
synced 2026-07-09 15:59:30 +00:00
test: fold channel message flows into qa e2e (#93174)
* test: fold channel flows into qa e2e * test: keep channel flow skill pointed at qa * test: move channel flow proof under telegram
This commit is contained in:
parent
37eea55afa
commit
4ca0e52d0e
5 changed files with 110 additions and 225 deletions
|
|
@ -1,44 +1,34 @@
|
|||
---
|
||||
name: channel-message-flows
|
||||
description: "Use when previewing local channel message flow fixtures."
|
||||
description: "Use when running QA Lab channel message flow evidence."
|
||||
---
|
||||
|
||||
# Channel Message Flows
|
||||
|
||||
Use this from the OpenClaw repo root to send canned channel preview flows while iterating on message UX. These are real sends/edits/deletes against the configured channel target.
|
||||
Use this from the OpenClaw repo root to run the QA Lab evidence for Telegram
|
||||
draft/final delivery sequencing. This skill no longer launches a standalone
|
||||
script; the behavior is owned by the QA scenario and its Vitest-backed e2e test.
|
||||
|
||||
## Telegram
|
||||
## QA Scenario
|
||||
|
||||
Native Telegram `sendMessageDraft` tool progress, then a final answer:
|
||||
Run the scenario through QA Lab:
|
||||
|
||||
```bash
|
||||
node --import tsx scripts/dev/channel-message-flows.ts \
|
||||
--channel telegram \
|
||||
--target <telegram-chat-id> \
|
||||
--flow working-final \
|
||||
--duration-ms 20000
|
||||
pnpm openclaw qa suite --scenario channel-message-flows
|
||||
```
|
||||
|
||||
Thinking preview, then a final answer:
|
||||
Run the focused e2e test directly in a Codex worktree:
|
||||
|
||||
```bash
|
||||
node --import tsx scripts/dev/channel-message-flows.ts \
|
||||
--channel telegram \
|
||||
--target <telegram-chat-id> \
|
||||
--flow thinking-final
|
||||
node scripts/run-vitest.mjs extensions/telegram/src/channel-message-flows.qa.e2e.test.ts
|
||||
```
|
||||
|
||||
## Options
|
||||
## References
|
||||
|
||||
- `--account <accountId>`: Telegram account id when not using the default.
|
||||
- `--thread-id <id>`: Telegram forum topic/message thread id.
|
||||
- `--delay-ms <ms>`: Override preview update cadence.
|
||||
- `--duration-ms <ms>`: Simulated working duration for `working-final`.
|
||||
- `--final-text <text>`: Override the durable final message.
|
||||
- `qa/scenarios/channels/channel-message-flows.yaml`
|
||||
- `extensions/telegram/src/channel-message-flows.qa.e2e.test.ts`
|
||||
- `extensions/telegram/src/test-support/channel-message-flows.ts`
|
||||
|
||||
## Notes
|
||||
|
||||
- `--target` is the numeric Telegram chat id.
|
||||
- `working-final` exercises native Telegram `sendMessageDraft` with static `Working` status and sample tool progress.
|
||||
- `thinking-final` exercises formatted `Thinking` reasoning preview clearing before the final answer.
|
||||
- Only `--channel telegram` is implemented for now.
|
||||
The scenario covers `channels.streaming` as primary evidence and records
|
||||
secondary coverage for thread preservation, delivery ordering, and reasoning
|
||||
preview visibility.
|
||||
|
|
|
|||
|
|
@ -55,6 +55,7 @@ describe("qa scenario catalog", () => {
|
|||
.toSorted(),
|
||||
).toStrictEqual(
|
||||
[
|
||||
"channel-message-flows",
|
||||
"control-ui-chat-flow-playwright",
|
||||
"gateway-smoke",
|
||||
"package-openclaw-for-docker",
|
||||
|
|
|
|||
|
|
@ -1,14 +1,13 @@
|
|||
// Channel Message Flows tests cover channel message flows script behavior.
|
||||
// Channel Message Flows tests cover QA Lab channel delivery evidence.
|
||||
import type { OpenClawConfig } from "openclaw/plugin-sdk/config-contracts";
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import {
|
||||
parseChannelMessageFlowArgs,
|
||||
resolveTelegramFlowThreadSpec,
|
||||
runTelegramThinkingFinalFlow,
|
||||
runTelegramWorkingFinalFlow,
|
||||
} from "../../scripts/dev/channel-message-flows.ts";
|
||||
import type { OpenClawConfig } from "../../src/config/types.openclaw.js";
|
||||
} from "./test-support/channel-message-flows.js";
|
||||
|
||||
describe("channel message flows dev runner", () => {
|
||||
describe("channel message flows QA e2e", () => {
|
||||
function createTestDraftStream(params?: {
|
||||
update?: (text: string) => void;
|
||||
flush?: () => Promise<void>;
|
||||
|
|
@ -16,6 +15,7 @@ describe("channel message flows dev runner", () => {
|
|||
}) {
|
||||
return {
|
||||
update: vi.fn(params?.update ?? (() => {})),
|
||||
updatePreview: vi.fn(),
|
||||
flush: vi.fn(params?.flush ?? (async () => {})),
|
||||
clear: vi.fn(params?.clear ?? (async () => {})),
|
||||
stop: vi.fn(async () => {}),
|
||||
|
|
@ -24,55 +24,6 @@ describe("channel message flows dev runner", () => {
|
|||
};
|
||||
}
|
||||
|
||||
it("parses the Telegram thinking-final flow from channel/target flags", () => {
|
||||
const parsed = parseChannelMessageFlowArgs([
|
||||
"--channel",
|
||||
"telegram",
|
||||
"--target",
|
||||
"123",
|
||||
"--flow",
|
||||
"thinking-final",
|
||||
"--account",
|
||||
"sut",
|
||||
"--thread-id",
|
||||
"42",
|
||||
"--delay-ms",
|
||||
"0",
|
||||
]);
|
||||
|
||||
expect(parsed).toEqual({
|
||||
accountId: "sut",
|
||||
channel: "telegram",
|
||||
delayMs: 0,
|
||||
flow: "thinking-final",
|
||||
target: "123",
|
||||
threadId: 42,
|
||||
});
|
||||
});
|
||||
|
||||
it("parses the Telegram working-final flow from channel/chat flags", () => {
|
||||
const parsed = parseChannelMessageFlowArgs([
|
||||
"--channel",
|
||||
"telegram",
|
||||
"--chat",
|
||||
"123",
|
||||
"--flow",
|
||||
"working-final",
|
||||
"--duration-ms",
|
||||
"12000",
|
||||
"--delay-ms",
|
||||
"0",
|
||||
]);
|
||||
|
||||
expect(parsed).toEqual({
|
||||
channel: "telegram",
|
||||
delayMs: 0,
|
||||
durationMs: 12000,
|
||||
flow: "working-final",
|
||||
target: "123",
|
||||
});
|
||||
});
|
||||
|
||||
it("streams thinking updates, clears the preview, then sends the final answer", async () => {
|
||||
const events: string[] = [];
|
||||
const stream = {
|
||||
|
|
@ -85,6 +36,7 @@ describe("channel message flows dev runner", () => {
|
|||
clear: vi.fn(async () => {
|
||||
events.push("clear");
|
||||
}),
|
||||
updatePreview: vi.fn(),
|
||||
stop: vi.fn(async () => {}),
|
||||
messageId: vi.fn(() => 17),
|
||||
forceNewMessage: vi.fn(),
|
||||
|
|
@ -96,9 +48,11 @@ describe("channel message flows dev runner", () => {
|
|||
|
||||
const result = await runTelegramThinkingFinalFlow(
|
||||
{
|
||||
accountId: "sut",
|
||||
cfg: {} as OpenClawConfig,
|
||||
delayMs: 0,
|
||||
target: "123",
|
||||
threadId: 42,
|
||||
thinkingUpdates: ["Checking the request.", "Reading the Telegram code.", "Ready."],
|
||||
},
|
||||
{
|
||||
|
|
@ -114,11 +68,11 @@ describe("channel message flows dev runner", () => {
|
|||
expect(events.at(-2)).toBe("clear");
|
||||
expect(events.at(-1)).toBe("final");
|
||||
expect(sendFinal).toHaveBeenCalledWith({
|
||||
accountId: undefined,
|
||||
accountId: "sut",
|
||||
cfg: {},
|
||||
target: "123",
|
||||
text: "Final answer: the Telegram thinking preview cleared and this durable reply landed.",
|
||||
threadId: undefined,
|
||||
threadId: 42,
|
||||
});
|
||||
expect(result).toEqual({ finalMessageId: "99", previewUpdates: 3 });
|
||||
});
|
||||
|
|
@ -130,6 +84,7 @@ describe("channel message flows dev runner", () => {
|
|||
throw new Error("flush failed");
|
||||
}),
|
||||
clear: vi.fn(async () => {}),
|
||||
updatePreview: vi.fn(),
|
||||
stop: vi.fn(async () => {}),
|
||||
messageId: vi.fn(() => 17),
|
||||
forceNewMessage: vi.fn(),
|
||||
|
|
@ -161,6 +116,7 @@ describe("channel message flows dev runner", () => {
|
|||
update: vi.fn(() => {}),
|
||||
flush: vi.fn(async () => {}),
|
||||
clear: vi.fn(async () => {}),
|
||||
updatePreview: vi.fn(),
|
||||
stop: vi.fn(async () => {}),
|
||||
messageId: vi.fn(() => 17),
|
||||
forceNewMessage: vi.fn(),
|
||||
|
|
@ -1,42 +1,27 @@
|
|||
#!/usr/bin/env -S node --import tsx
|
||||
// Channel Message Flows script supports OpenClaw repository automation.
|
||||
// Channel Message Flows runtime supports QA Lab channel delivery evidence.
|
||||
import { setTimeout as sleep } from "node:timers/promises";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import type { Bot } from "grammy";
|
||||
import type { Message } from "grammy/types";
|
||||
import {
|
||||
deleteMessageTelegram,
|
||||
editMessageTelegram,
|
||||
sendMessageTelegram,
|
||||
} from "../../extensions/telegram/runtime-api.js";
|
||||
import type { TelegramThreadSpec } from "../../extensions/telegram/src/bot/helpers.js";
|
||||
import {
|
||||
createTelegramDraftStream,
|
||||
type TelegramDraftStream,
|
||||
} from "../../extensions/telegram/src/draft-stream.js";
|
||||
import { formatReasoningMessage } from "openclaw/plugin-sdk/agent-runtime";
|
||||
import { formatChannelProgressDraftText } from "openclaw/plugin-sdk/channel-outbound";
|
||||
import type { OpenClawConfig } from "openclaw/plugin-sdk/config-contracts";
|
||||
import type { TelegramThreadSpec } from "../bot/helpers.js";
|
||||
import { createTelegramDraftStream, type TelegramDraftStream } from "../draft-stream.js";
|
||||
import {
|
||||
buildTelegramRichMarkdown,
|
||||
type TelegramEditRichMessageTextParams,
|
||||
type TelegramInputRichMessage,
|
||||
} from "../../extensions/telegram/src/rich-message.js";
|
||||
import { formatReasoningMessage } from "../../src/agents/embedded-agent-utils.js";
|
||||
import { getRuntimeConfig } from "../../src/config/config.js";
|
||||
import type { OpenClawConfig } from "../../src/config/types.openclaw.js";
|
||||
import { formatChannelProgressDraftText } from "../../src/plugin-sdk/channel-outbound.js";
|
||||
type TelegramSendRichMessageParams,
|
||||
} from "../rich-message.js";
|
||||
import { deleteMessageTelegram, editMessageTelegram, sendMessageTelegram } from "../send.js";
|
||||
|
||||
type TelegramApi = Bot["api"];
|
||||
type TelegramSendMessageParams = Parameters<TelegramApi["sendMessage"]>;
|
||||
type TelegramEditMessageTextParams = Parameters<TelegramApi["editMessageText"]>;
|
||||
type TelegramDeleteMessageParams = Parameters<TelegramApi["deleteMessage"]>;
|
||||
|
||||
type SupportedChannel = "telegram";
|
||||
type SupportedFlow = "thinking-final" | "working-final";
|
||||
|
||||
export type ChannelMessageFlowArgs = {
|
||||
accountId?: string;
|
||||
channel: SupportedChannel;
|
||||
delayMs?: number;
|
||||
durationMs?: number;
|
||||
finalText?: string;
|
||||
flow: SupportedFlow;
|
||||
target: string;
|
||||
threadId?: number;
|
||||
};
|
||||
|
||||
type TelegramSendFinalParams = {
|
||||
accountId?: string;
|
||||
cfg: OpenClawConfig;
|
||||
|
|
@ -50,19 +35,7 @@ type TelegramFlowResult = {
|
|||
previewUpdates: number;
|
||||
};
|
||||
|
||||
function toError(value: unknown): Error {
|
||||
return value instanceof Error ? value : new Error(String(value));
|
||||
}
|
||||
|
||||
function requireFinalMessageId(final: { messageId?: string }, flow: SupportedFlow): string {
|
||||
const messageId = final.messageId?.trim();
|
||||
if (!messageId) {
|
||||
throw new Error(`${flow} final send did not return a durable Telegram message id`);
|
||||
}
|
||||
return messageId;
|
||||
}
|
||||
|
||||
type TelegramThinkingFinalDeps = {
|
||||
type TelegramFlowDeps = {
|
||||
createDraftStream?: (params: {
|
||||
accountId?: string;
|
||||
cfg: OpenClawConfig;
|
||||
|
|
@ -73,13 +46,18 @@ type TelegramThinkingFinalDeps = {
|
|||
sleep?: (ms: number) => Promise<void>;
|
||||
};
|
||||
|
||||
export type TelegramThinkingFinalFlowOptions = ChannelMessageFlowArgs & {
|
||||
export type TelegramThinkingFinalFlowOptions = {
|
||||
accountId?: string;
|
||||
cfg: OpenClawConfig;
|
||||
delayMs?: number;
|
||||
finalText?: string;
|
||||
target: string;
|
||||
threadId?: number;
|
||||
thinkingUpdates?: readonly string[];
|
||||
};
|
||||
|
||||
export type TelegramWorkingFinalFlowOptions = ChannelMessageFlowArgs & {
|
||||
cfg: OpenClawConfig;
|
||||
export type TelegramWorkingFinalFlowOptions = TelegramThinkingFinalFlowOptions & {
|
||||
durationMs?: number;
|
||||
};
|
||||
|
||||
const DEFAULT_THINKING_FINAL_UPDATES = [
|
||||
|
|
@ -123,71 +101,16 @@ const DEFAULT_WORKING_PROGRESS_TIMELINE = [
|
|||
},
|
||||
] as const;
|
||||
|
||||
function usage(): string {
|
||||
return [
|
||||
"Usage:",
|
||||
" node --import tsx scripts/dev/channel-message-flows.ts --channel telegram --target <chat-id> --flow <flow> [options]",
|
||||
"",
|
||||
"Flows:",
|
||||
" thinking-final Reasoning/Thinking preview, then a final answer",
|
||||
" working-final Editable tool-progress preview, then a final answer",
|
||||
"",
|
||||
"Options:",
|
||||
" --account <accountId> Telegram account id to use",
|
||||
" --thread-id <id> Telegram forum topic/message thread id",
|
||||
" --delay-ms <ms> Delay between preview updates (default: flow-specific)",
|
||||
" --duration-ms <ms> Simulated working duration for working-final (default: 12000)",
|
||||
" --final-text <text> Override the final durable message",
|
||||
].join("\n");
|
||||
function toError(value: unknown): Error {
|
||||
return value instanceof Error ? value : new Error(String(value));
|
||||
}
|
||||
|
||||
function readFlagValue(args: readonly string[], flag: string): string | undefined {
|
||||
const index = args.indexOf(flag);
|
||||
if (index === -1) {
|
||||
return undefined;
|
||||
function requireFinalMessageId(final: { messageId?: string }, flow: SupportedFlow): string {
|
||||
const messageId = final.messageId?.trim();
|
||||
if (!messageId) {
|
||||
throw new Error(`${flow} final send did not return a durable Telegram message id`);
|
||||
}
|
||||
return args[index + 1];
|
||||
}
|
||||
|
||||
function parseIntegerFlag(raw: string | undefined, label: string): number | undefined {
|
||||
if (raw == null) {
|
||||
return undefined;
|
||||
}
|
||||
if (!/^\d+$/u.test(raw)) {
|
||||
throw new Error(`${label} must be a non-negative integer.\n\n${usage()}`);
|
||||
}
|
||||
return Number(raw);
|
||||
}
|
||||
|
||||
export function parseChannelMessageFlowArgs(args: readonly string[]): ChannelMessageFlowArgs {
|
||||
if (args.includes("--help") || args.includes("-h")) {
|
||||
throw new Error(usage());
|
||||
}
|
||||
|
||||
const channel = readFlagValue(args, "--channel");
|
||||
const flow = readFlagValue(args, "--flow");
|
||||
const target = readFlagValue(args, "--target") ?? readFlagValue(args, "--chat");
|
||||
|
||||
if (channel !== "telegram") {
|
||||
throw new Error(`Only --channel telegram is supported for now.\n\n${usage()}`);
|
||||
}
|
||||
if (flow !== "thinking-final" && flow !== "working-final") {
|
||||
throw new Error(`Unsupported --flow ${flow ?? "<missing>"}.\n\n${usage()}`);
|
||||
}
|
||||
if (!target) {
|
||||
throw new Error(`Missing --target <chat-id>.\n\n${usage()}`);
|
||||
}
|
||||
|
||||
return {
|
||||
accountId: readFlagValue(args, "--account") ?? readFlagValue(args, "--account-id"),
|
||||
channel,
|
||||
delayMs: parseIntegerFlag(readFlagValue(args, "--delay-ms"), "--delay-ms"),
|
||||
durationMs: parseIntegerFlag(readFlagValue(args, "--duration-ms"), "--duration-ms"),
|
||||
finalText: readFlagValue(args, "--final-text"),
|
||||
flow,
|
||||
target,
|
||||
threadId: parseIntegerFlag(readFlagValue(args, "--thread-id"), "--thread-id"),
|
||||
};
|
||||
return messageId;
|
||||
}
|
||||
|
||||
function resolveWorkingProgressLines(elapsedMs: number): string[] {
|
||||
|
|
@ -207,15 +130,15 @@ function richMessageText(richMessage: TelegramInputRichMessage): {
|
|||
text: string;
|
||||
textMode: "markdown" | "html";
|
||||
} {
|
||||
return "html" in richMessage
|
||||
return richMessage.html !== undefined
|
||||
? { text: richMessage.html, textMode: "html" }
|
||||
: { text: richMessage.markdown, textMode: "markdown" };
|
||||
}
|
||||
|
||||
function createTelegramFlowApi(params: { accountId?: string; cfg: OpenClawConfig }): Bot["api"] {
|
||||
return {
|
||||
const api = {
|
||||
raw: {
|
||||
sendRichMessage: async (sendParams) => {
|
||||
sendRichMessage: async (sendParams: TelegramSendRichMessageParams) => {
|
||||
const richText = richMessageText(sendParams.rich_message);
|
||||
const result = await sendMessageTelegram(String(sendParams.chat_id), richText.text, {
|
||||
accountId: params.accountId,
|
||||
|
|
@ -225,7 +148,7 @@ function createTelegramFlowApi(params: { accountId?: string; cfg: OpenClawConfig
|
|||
});
|
||||
return { message_id: Number(result.messageId) } as Message;
|
||||
},
|
||||
editMessageText: async (editParams) => {
|
||||
editMessageText: async (editParams: TelegramEditRichMessageTextParams) => {
|
||||
if (typeof editParams.message_id !== "number") {
|
||||
throw new Error("Telegram flow rich edit requires message_id.");
|
||||
}
|
||||
|
|
@ -243,7 +166,11 @@ function createTelegramFlowApi(params: { accountId?: string; cfg: OpenClawConfig
|
|||
return true;
|
||||
},
|
||||
},
|
||||
sendMessage: async (chatId, text, sendParams) => {
|
||||
sendMessage: async (
|
||||
chatId: TelegramSendMessageParams[0],
|
||||
text: TelegramSendMessageParams[1],
|
||||
sendParams: TelegramSendMessageParams[2],
|
||||
) => {
|
||||
const result = await sendMessageTelegram(String(chatId), text, {
|
||||
accountId: params.accountId,
|
||||
cfg: params.cfg,
|
||||
|
|
@ -252,7 +179,12 @@ function createTelegramFlowApi(params: { accountId?: string; cfg: OpenClawConfig
|
|||
});
|
||||
return { message_id: Number(result.messageId) };
|
||||
},
|
||||
editMessageText: async (chatId, messageId, text, editParams) => {
|
||||
editMessageText: async (
|
||||
chatId: TelegramEditMessageTextParams[0],
|
||||
messageId: TelegramEditMessageTextParams[1],
|
||||
text: TelegramEditMessageTextParams[2],
|
||||
editParams: TelegramEditMessageTextParams[3],
|
||||
) => {
|
||||
await editMessageTelegram(String(chatId), messageId, text, {
|
||||
accountId: params.accountId,
|
||||
cfg: params.cfg,
|
||||
|
|
@ -260,14 +192,18 @@ function createTelegramFlowApi(params: { accountId?: string; cfg: OpenClawConfig
|
|||
});
|
||||
return true;
|
||||
},
|
||||
deleteMessage: async (chatId, messageId) => {
|
||||
deleteMessage: async (
|
||||
chatId: TelegramDeleteMessageParams[0],
|
||||
messageId: TelegramDeleteMessageParams[1],
|
||||
) => {
|
||||
await deleteMessageTelegram(String(chatId), messageId, {
|
||||
accountId: params.accountId,
|
||||
cfg: params.cfg,
|
||||
});
|
||||
return true;
|
||||
},
|
||||
} as Bot["api"];
|
||||
};
|
||||
return api as unknown as Bot["api"];
|
||||
}
|
||||
|
||||
export function resolveTelegramFlowThreadSpec(threadId?: number): TelegramThreadSpec | undefined {
|
||||
|
|
@ -300,7 +236,7 @@ async function sendTelegramFinal(params: TelegramSendFinalParams): Promise<{ mes
|
|||
|
||||
export async function runTelegramThinkingFinalFlow(
|
||||
options: TelegramThinkingFinalFlowOptions,
|
||||
deps: TelegramThinkingFinalDeps = {},
|
||||
deps: TelegramFlowDeps = {},
|
||||
): Promise<TelegramFlowResult> {
|
||||
const delayMs = options.delayMs ?? 900;
|
||||
const thinkingUpdates = options.thinkingUpdates ?? DEFAULT_THINKING_FINAL_UPDATES;
|
||||
|
|
@ -358,7 +294,7 @@ export async function runTelegramThinkingFinalFlow(
|
|||
|
||||
export async function runTelegramWorkingFinalFlow(
|
||||
options: TelegramWorkingFinalFlowOptions,
|
||||
deps: TelegramThinkingFinalDeps = {},
|
||||
deps: TelegramFlowDeps = {},
|
||||
): Promise<TelegramFlowResult> {
|
||||
const delayMs = options.delayMs ?? 2_000;
|
||||
const durationMs = options.durationMs ?? 12_000;
|
||||
|
|
@ -421,28 +357,3 @@ export async function runTelegramWorkingFinalFlow(
|
|||
previewUpdates,
|
||||
};
|
||||
}
|
||||
|
||||
export async function main(args = process.argv.slice(2)): Promise<void> {
|
||||
if (args.includes("--help") || args.includes("-h")) {
|
||||
process.stdout.write(`${usage()}\n`);
|
||||
return;
|
||||
}
|
||||
|
||||
const parsed = parseChannelMessageFlowArgs(args);
|
||||
const cfg = getRuntimeConfig();
|
||||
const result =
|
||||
parsed.flow === "working-final"
|
||||
? await runTelegramWorkingFinalFlow({ ...parsed, cfg })
|
||||
: await runTelegramThinkingFinalFlow({ ...parsed, cfg });
|
||||
|
||||
process.stdout.write(
|
||||
`Sent ${parsed.channel}/${parsed.flow} to ${parsed.target} (${result.previewUpdates} preview updates, final message ${result.finalMessageId ?? "unknown"}).\n`,
|
||||
);
|
||||
}
|
||||
|
||||
if (process.argv[1] && fileURLToPath(import.meta.url) === process.argv[1]) {
|
||||
main().catch((err: unknown) => {
|
||||
process.stderr.write(`${err instanceof Error ? err.message : String(err)}\n`);
|
||||
process.exitCode = 1;
|
||||
});
|
||||
}
|
||||
27
qa/scenarios/channels/channel-message-flows.yaml
Normal file
27
qa/scenarios/channels/channel-message-flows.yaml
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
title: Channel message flow evidence
|
||||
|
||||
scenario:
|
||||
id: channel-message-flows
|
||||
surface: channels
|
||||
coverage:
|
||||
primary:
|
||||
- channels.streaming
|
||||
secondary:
|
||||
- channels.threads
|
||||
- runtime.delivery
|
||||
- runtime.reasoning-visibility
|
||||
objective: Exercise Telegram draft/final delivery sequencing through QA Lab evidence.
|
||||
successCriteria:
|
||||
- Thinking updates flush, clear, and send the final answer in order.
|
||||
- Working previews are cleared before final delivery.
|
||||
- Thread IDs are preserved in Telegram flow thread specs.
|
||||
docsRefs:
|
||||
- docs/channels/telegram.md
|
||||
- docs/concepts/qa-e2e-automation.md
|
||||
codeRefs:
|
||||
- extensions/telegram/src/channel-message-flows.qa.e2e.test.ts
|
||||
- extensions/telegram/src/test-support/channel-message-flows.ts
|
||||
execution:
|
||||
kind: vitest
|
||||
path: extensions/telegram/src/channel-message-flows.qa.e2e.test.ts
|
||||
summary: Vitest coverage for channel message flow sequencing.
|
||||
Loading…
Add table
Add a link
Reference in a new issue