Keeping the demo video fresh#
The demo video is generated by a scripted Playwright runner that drives a real Telegram Web session against a fake ACP agent. Everything — timing, messages, agent responses — is declared in a single JSON file. To update the video you mostly only touch that file.
How it works#
demo_story.json ← the only file you normally edit
│
▼
telegram_web_demo.py ← Playwright driver (opens browser, types, clicks)
│
▼
run_demo_bot.py ← Telegram bot + fake ACP agent wired together
│
▼
fake_acp_agent.py ← replays agent_routes from demo_story.json
telegram_web_demo.py reads the story, opens a persistent Chromium profile
logged into Telegram Web, and executes each user_steps entry in order.
After each step it waits for a wait_for_text pattern to appear before
typing the next message — this keeps the pacing locked to actual bot
responses rather than fixed sleeps.
The fake agent (fake_acp_agent.py) receives the bot’s ACP prompts and
replays whichever agent_routes entry matches the prompt text. It emits
tool events with configurable delays, then sends the final text/images/files.
Recording a new video#
One-time login#
uv run scripts/demo/telegram_web_demo.py --mode login
Scan the QR code that appears in the browser. The session is saved in
.cache/telegram-web-profile/ and reused on every subsequent run.
Record#
cp .env.demo .env # set TELEGRAM_BOT_TOKEN, TELEGRAM_ALLOWED_USER_IDS, etc.
uv run scripts/demo/telegram_web_demo.py
The recording is saved under artifacts/demo-videos/<timestamp>/.
Pass --no-record to run without recording (useful for testing timing).
Changing the script#
Open scripts/demo/demo_story.json. The structure is:
runtime — global typing speed and final pause duration
assets — image/file payloads referenced by agent_routes
user_steps — what the "user" types, in order
agent_routes — what the fake agent replies to each prompt
Editing user messages#
Each entry in user_steps has:
field |
meaning |
|---|---|
|
the message that gets typed into Telegram |
|
regex that must appear on screen before typing |
|
how long to wait before giving up and continuing |
|
extra pause after the pattern appears |
|
list of UI actions after sending: |
Editing agent responses#
Each entry in agent_routes has:
field |
meaning |
|---|---|
|
list of substrings; the first route where any substring matches the prompt wins |
|
tool events to emit (kind, title, text, delay_ms) |
|
plain-text message sent after all events |
|
list of asset IDs to send as photos |
|
list of asset IDs to send as file attachments |
|
reply sent if the agent is interrupted mid-route |
Timing knobs#
delay_mson each event — pause before that tool event is emitted.wait_for_text.after_ms— pause on the user side after the pattern appears.runtime.typing_delay_ms— per-character typing speed.runtime.final_pause_seconds— how long the browser stays open after the last step (gives time for the final messages to settle before closing).
Increase wait_for_text.timeout_seconds if a step keeps timing out in CI or
on slow machines; increase after_ms if the next message arrives before the
previous reply has finished rendering.
Adding a new demo scenario#
Add a new
agent_routesentry with a uniqueidandmatch_anykeywords.Add a
user_stepsentry whosetextcontains one of those keywords.Add a
wait_for_textso the next step waits for the agent’s reply.If the route sends an image or file, add the asset to the
assetsmap and put the asset file inscripts/demo/.Run with
--no-recordfirst to verify timing, then record.
File reference#
file |
purpose |
|---|---|
|
declarative script — edit this |
|
Playwright driver |
|
scripted ACP agent |
|
typed dataclass parser for the JSON |
|
wires the bot + fake agent together |
|
webcam photo asset |
|
PDF attachment asset |
|
persistent Chromium login session (gitignored) |
|
recorded |