From 110aa9baf40bd046c3a755a54ad2e7717f3f2cc2 Mon Sep 17 00:00:00 2001
From: Talos Maintainers <82345214+morningstarnasser@users.noreply.github.com>
Date: Tue, 10 Nov 2026 22:08:35 +0100
Subject: [PATCH] feat(telegram): clarify live activity phases and tool
outcomes
---
CHANGELOG.md | 8 +++
CLAUDE.md | 4 +-
README.md | 10 ++-
site/console.html | 4 +-
site/docs/index.html | 2 +-
site/index.html | 2 +-
site/llms.txt | 2 +-
site/redteam/index.html | 2 +-
site/registry/index.html | 2 +-
site/setup/index.html | 2 +-
site/status.json | 2 +-
site/vergleich/index.html | 2 +-
talos/schema.py | 3 +
talos/telegram.py | 39 ++++++++++--
tests/test_telegram_live_tracking.py | 95 ++++++++++++++++++++++++++++
15 files changed, 161 insertions(+), 18 deletions(-)
create mode 100644 tests/test_telegram_live_tracking.py
diff --git a/CHANGELOG.md b/CHANGELOG.md
index a737be3..4c6c0bc 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -9,6 +9,14 @@ remain historical snapshots. Beta does not mean error-free operation.
## [Unreleased]
+### Changed
+
+- Make expressive Telegram activity show the current phase, code-styled known tool
+ identifiers, completed/failed counts and an explicit long-wait hint. The header
+ indicator uses the existing heartbeat and can be disabled with
+ `TALOS_STATUS_ANIMATION=0`. Approval waits stop cosmetic edits; fatal errors freeze
+ the existing card as Stopped. Reply receipts and pending worker semantics remain.
+
## [0.20.0-rc.3] — 2026-11-05
### Added
diff --git a/CLAUDE.md b/CLAUDE.md
index e49d1ac..ca0095d 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -14,7 +14,7 @@ preferences live in `USER.md`. All three are operator-owned prompt state and rel
|---|---|
| Gate path | `policy.py`, **996 lines** — has to stay readable in one sitting |
| Tools | **37**, every one gated |
-| Suites | **3492** collected tests · **277** adversarial · 44 end-to-end |
+| Suites | **3499** collected tests · **277** adversarial · 44 end-to-end |
| Home |
-
-
+
@@ -330,6 +330,12 @@ compact labelled rows. Approval prompts keep the exact command visible.
`/stop` interrupts the run and `/log` opens its receipts. A finished conversation
does not imply that a delegated background job has finished.
+The next release adds phase-specific headings, code-styled tool identifiers and
+separate completed/failed counts to the expressive card. A quiet activity indicator
+uses the existing five-second heartbeat; `TALOS_STATUS_ANIMATION=0` disables it.
+Approval waits stay still, and a long wait is labelled without inventing progress
+or an ETA. Tool arguments and full commands remain confined to approval prompts.
+
Operator-defined OpenAI-compatible endpoints can be listed in the JSON file named by
`TALOS_CUSTOM_PROVIDERS` (default: `data/custom-providers.json`). Each entry needs
`name`, `base_url` and `models`; optional `env_key` names that endpoint's own key.
diff --git a/site/console.html b/site/console.html
index 0bdd866..efd5372 100644
--- a/site/console.html
+++ b/site/console.html
@@ -378,7 +378,7 @@
That one decision shapes the rest of this manual. It is why the command list is short, and why a few obvious conveniences are missing on purpose.
None of this asks for belief. The installer runs the unit and adversarial suites in front of you; the real-model cases are a separate opt-in E2E run.
Release-candidate source and signed release. Counts describe the source test suite, not speed or real-world success rates; signed release contents follow their tag.
examples/mcp-servers.json.
{m[1]}" if m[1] in TOOL_LABELS else m[0], line)
# Quiet metadata under a clear heading; every dynamic value stays escaped.
lines[index] = f"{line}" if index == 1 else line
return "\n".join(lines)
diff --git a/tests/test_telegram_live_tracking.py b/tests/test_telegram_live_tracking.py
new file mode 100644
index 0000000..1dc6755
--- /dev/null
+++ b/tests/test_telegram_live_tracking.py
@@ -0,0 +1,95 @@
+"""Telegram activity communicates measured states without exposing tool arguments."""
+from talos.agent_loop import AgentProgress, ProgressStage
+from test_telegram_progress_updates import setup_activity
+from test_telegram_ux import _tool, _result
+
+
+def test_live_phase_indicator_changes_only_the_header_and_uses_existing_tick():
+ client, activity = setup_activity()
+ activity.progress(_tool('run_shell', summary='shell — pytest'))
+ initial = client.sent[0][1]
+ assert 'Running .' in initial
+ assert 'run_shell' in initial and 'Running pytest' in initial
+ client.now[0] = 5
+ activity.tick()
+ assert 'Running ..' in client.edited[-1][2]
+ assert len(client.sent) == 1
+ activity.progress(_result('run_shell', 'done'))
+ client.now[0] = 10
+ activity.progress(AgentProgress(ProgressStage.THINKING, step=2))
+ assert 'Thinking ...' in client.edited[-1][2]
+ assert '1 completed · 0 failed or refused' in client.edited[-1][2]
+ activity.succeed()
+
+
+def test_reduced_motion_disables_the_indicator_but_keeps_measured_time(monkeypatch):
+ monkeypatch.setenv('TALOS_STATUS_ANIMATION', '0')
+ client, activity = setup_activity()
+ activity.progress(_tool('read_file', summary='read — notes.md'))
+ before = client.sent[0][1].splitlines()[0]
+ client.now[0] = 15
+ activity.tick()
+ assert client.edited[-1][2].splitlines()[0] == before
+ assert '15s' in client.edited[-1][2]
+ activity.succeed()
+
+
+def test_approval_parks_live_edits_and_resumes_on_a_real_result():
+ client, activity = setup_activity()
+ activity.progress(_tool('write_file', summary='write — notes.md'))
+ activity.progress(_result('write_file', 'needs_human'))
+ before = len(client.edited), len(client.sent)
+ client.now[0] = 90
+ activity.tick()
+ assert (len(client.edited), len(client.sent)) == before
+ assert 'Needs your approval' in client.edited[-1][2]
+ activity.progress(_result('write_file', 'done'))
+ assert '1 completed' in client.edited[-1][2]
+ activity.succeed()
+
+
+def test_waiting_hint_is_based_on_events_and_clears_when_work_returns():
+ client, activity = setup_activity()
+ activity.progress(_tool('run_shell', summary='shell — pytest'))
+ client.now[0] = 30
+ activity.tick()
+ assert 'Waiting for the next result' in client.edited[-1][2]
+ assert '0 completed' in client.edited[-1][2]
+ activity.progress(_result('run_shell', 'done'))
+ assert 'Waiting for the next result' not in client.edited[-1][2]
+ assert '1 completed' in client.edited[-1][2]
+ activity.succeed()
+
+
+def test_code_labels_never_reveal_commands_and_dynamic_html_is_escaped():
+ client, activity = setup_activity(name='Example')
+ activity.progress(_tool('run_shell', summary='curl --password PRIVATE_CANARY'))
+ activity.progress(_tool('read_file', summary='read — '))
+ rendered = client.edited[-1][2]
+ assert 'PRIVATE_CANARY' not in rendered and 'curl' not in rendered
+ assert '