diff --git a/services/hermes/chat-router.yaml b/services/hermes/chat-router.yaml
index fa9a4c347..09a5fc2d6 100644
--- a/services/hermes/chat-router.yaml
+++ b/services/hermes/chat-router.yaml
@@ -20,7 +20,7 @@ spec:
app: hermes-chat-router
annotations:
ai.bstein.dev/role: privacy-preserving-chat-tenant-router
- ai.bstein.dev/config-rev: "20260812-session-sidebar"
+ ai.bstein.dev/config-rev: "20260813-telegram-setup"
vault.hashicorp.com/agent-inject: "true"
vault.hashicorp.com/agent-pre-populate-only: "true"
vault.hashicorp.com/agent-init-first: "true"
@@ -62,7 +62,7 @@ spec:
values: [rpi5]
containers:
- name: router
- image: registry.bstein.dev/bstein/hermes-chat-router@sha256:4e318a35353772cf16e39b2038209abd8b774cd065f39d7a99b6dcc6e28e2474
+ image: registry.bstein.dev/bstein/hermes-chat-router@sha256:5e8fbc8f9f57e33c6fe1828a60f6e4f8f49cc0b9410a3d54abcb4219717fb168
imagePullPolicy: IfNotPresent
ports:
- {name: http, containerPort: 8080, protocol: TCP}
diff --git a/services/hermes/router/main_test.go b/services/hermes/router/main_test.go
index 0cf9e16d0..7fb08ed5b 100644
--- a/services/hermes/router/main_test.go
+++ b/services/hermes/router/main_test.go
@@ -134,11 +134,37 @@ func TestRouterProxiesWebUIAndAddsTelegramShortcut(t *testing.T) {
if !strings.Contains(asset, "hermes-files-sidebar") || !strings.Contains(asset, "hermes-telegram-sidebar") {
t.Fatal("Files and Telegram were not integrated into the existing sidebar")
}
+ if !strings.Contains(asset, "document.querySelector('.rail')") || !strings.Contains(asset, "data-tooltip', 'Telegram") {
+ t.Fatal("Telegram was not integrated as a native WebUI navigation action")
+ }
+ if strings.Contains(asset, "if (!files) return") {
+ t.Fatal("Telegram navigation still depends on the removed legacy Files link")
+ }
if strings.Contains(asset, "position:fixed") || strings.Contains(asset, "hermes-chat-tools") {
t.Fatal("legacy floating chat controls remain in the mobile bridge")
}
}
+func TestTelegramPageExplainsOneTimeOperatorSetup(t *testing.T) {
+ router, err := newTenantRouter(filepath.Join(t.TempDir(), "state.json"), 1, func(slot int) string { return "" })
+ if err != nil {
+ t.Fatal(err)
+ }
+ request := httptest.NewRequest(http.MethodGet, "/telegram", nil)
+ request.Header.Set("X-Forwarded-User", "subject")
+ response := httptest.NewRecorder()
+ router.ServeHTTP(response, request)
+ if response.Code != http.StatusOK {
+ t.Fatalf("got status %d", response.Code)
+ }
+ body := response.Body.String()
+ for _, expected := range []string{"https://t.me/BotFather", "kv/atlas/hermes/chat-telegram", "bot_token", "relay_key"} {
+ if !strings.Contains(body, expected) {
+ t.Fatalf("Telegram operator setup omitted %q", expected)
+ }
+ }
+}
+
func TestRouterRedirectsNativeLoginToSafeChatDestination(t *testing.T) {
router, err := newTenantRouter(filepath.Join(t.TempDir(), "state.json"), 1, func(slot int) string { return "" })
if err != nil {
diff --git a/services/hermes/router/web.go b/services/hermes/router/web.go
index 7c0334cfa..e00e4d9de 100644
--- a/services/hermes/router/web.go
+++ b/services/hermes/router/web.go
@@ -17,7 +17,7 @@ const telegramPage = `
Hermes on Telegram
-
+
@@ -30,9 +30,19 @@ const telegramPage = `
Unlink Telegram
+
+ Activate the shared bot (operator, once)
+
+ Open @BotFather and send /newbot.
+ Choose the bot's display name and a username ending in bot, then copy the token BotFather returns.
+ Store the token in Vault at kv/atlas/hermes/chat-telegram under the bot_token key. Preserve the existing relay_key.
+
+ vault kv patch -mount=kv atlas/hermes/chat-telegram bot_token='<BOTFATHER_TOKEN>'
+ Do not paste the token into Hermes or send it in a message. After it is saved, ask the Hermes operator to apply the tracked router rollout, then return here to link your account.
+
Codes expire after 10 minutes. Only direct messages are accepted; group messages are ignored.
-
+