Media, pairing và approval trên Telegram
Tổng quan
Phase 18 cho gia đình một bot chỉ có text trong các chat riêng. Giai đoạn này đưa kênh Telegram của hango lại gần những gì goclaw và OpenClaw cung cấp. Thành viên gia đình gửi ảnh, tài liệu và voice note. Agent gửi file trở lại. Người mới liên kết tài khoản Telegram bằng một mã pairing thay vì sửa config rồi restart. Một tool call cần approval (Phase 15) hiện lên thành các nút ngay trong chat.
Bài học là một kênh dồn phần lớn công việc ra các rìa. Media biến thành các block đầu vào cho model và file trong filesystem. Pairing biến một danh tính không đáng tin từ nền tảng thành một người dùng hango. Các nút cần callback data không thể bị giả mạo thành approval của người khác. Lõi harness chỉ đổi ở một chỗ: mô hình message của provider có thêm một image block.
Giai đoạn có hai điểm dừng review. 20a là media: ảnh, tài liệu, album, voice note, file gửi ra và inbound debounce. 20b là truy cập và điều khiển: mã pairing, nút approval và status reaction. File giai đoạn liệt kê bước và tiêu chí nào thuộc điểm dừng nào.
Group chat được hoãn. Hiện tại mỗi người nói chuyện với bot trong một chat riêng, và tin nhắn group vẫn bị bỏ qua như ở Phase 18. Thiết kế group theo kiểu goclaw được giữ ở cuối file này để một giai đoạn sau có thể nhặt lại.
Giai đoạn chạy sau Phase 16, nên các bảng của nó được tạo với row-level security ngay từ đầu, và chạy trước Phase 17, nên eval có thể gồm một tác vụ về ảnh.
Yêu cầu
Chức năng
20a: media
- Bot phục vụ một tenant,
HANGO_TELEGRAM_TENANT_ID. Mọi lookup theo Telegram id chạy trong scope của tenant đó, nên bot không bao giờ cần system pool của Phase 16, vốn vẫn chỉ có năm nơi gọi. Các ánh xạ tĩnh của Phase 18 phải thuộc tenant này, được kiểm tra lúc khởi động. - Ảnh: kích thước lớn nhất mà vẫn dưới
HANGO_TELEGRAM_MEDIA_MAX_MB(mặc định 20) được tải bằnggetFilevà ghi vào virtual filesystem tại/uploads/{session_id}/{telegram file unique id}.{ext}. Input của run có một image block tham chiếu đường dẫn đó. Một middlewareWrapModelCallnạp các ảnh được tham chiếu và nhúng chúng dưới dạng base64 cho lời gọi provider, nên các message được lưu bền chứa đường dẫn, không chứa byte. - Mô hình provider:
provider.Blockcó thêmImage *ImageBlock(MediaType,Path, vàDatachỉ được điền cho lời gọi provider). Adapter Anthropic ánh xạ nó thành một image content block, và adapter tương thích OpenAI ánh xạ nó thành một data URIimage_url. Một model mà mục registry không hỗ trợ vision thì nhận một placeholder text ("[image: /uploads/…]") thay vào đó, và câu trả lời nói rằng agent không thấy được ảnh. - Tài liệu: được lưu vào
/uploads/{session_id}/theo cùng cách. Input của run nêu tên file, kích thước và đường dẫn, để agent đọc các file text bằngread_file. PDF và các file nhị phân khác được lưu và nêu tên nhưng chưa được phân tích trong giai đoạn này. - Album: các update chung
media_group_idđược gom trong 500 ms và trở thành một run với mọi ảnh cùng caption của album. - Inbound debounce: các tin nhắn text từ một chat đến cách nhau dưới 300 ms được nối thành một run, như OpenClaw làm, để một tin nhắn dài mà Telegram tách làm hai trở thành một câu hỏi.
- Voice note và audio: được tải xuống, rồi phiên âm qua một endpoint
/audio/transcriptionstương thích OpenAI (HANGO_STT_BASE_URL,HANGO_STT_MODEL, thông tin xác thực từ mục registry của Phase 12 mà nó nêu tên). Bản phiên âm trở thành input của run, có tiền tố “(voice)”, và được gửi lại thành một dòng trích dẫn ngắn để người gửi thấy hango đã nghe gì. Voice note của Telegram là OGG/Opus, endpoint chấp nhận nguyên dạng, nên không cần bước chuyển đổi. - Phiên âm tiếng Việt:
HANGO_STT_LANGUAGE(ISO 639-1, để trống nghĩa là tự nhận diện) được gửi làm form fieldlanguage. Deploy của gia đình đặtvi, vì gợi ý ngôn ngữ giúp các clip tiếng Việt ngắn không bị nhận nhầm sang ngôn ngữ khác. Họ Whisper hỗ trợ tiếng Việt: OpenAIwhisper-1,gpt-4o-transcribevàgpt-4o-mini-transcribe, cùng Groqwhisper-large-v3vàwhisper-large-v3-turbo, nên dùng provider nào cũng được. Bản phiên âm được chuẩn hóa về Unicode NFC trước khi dùng, để dấu thanh so sánh và hiển thị giống nhau ở mọi nơi (dòng trích dẫn lặp lại và message được lưu). Việc phiên âm ghi một rowusage_recordsvới purposetranscription, tính giá theo giây từ bảng giá của Phase 13. - File gửi ra: một tool dựng sẵn
attach_file(path)đánh dấu một file trong virtual filesystem là tệp đính kèm của run hiện tại và phát một run eventattachment. Luồng Telegram gửi ảnh bằngsendPhotovà mọi thứ khác bằngsendDocumentsau câu trả lời text. Web chat sau này đọc cùng event đó. Tệp đính kèm quá giới hạn 50 MB của Telegram bị từ chối bằng một lỗi tool mà model có thể giải thích. - Sticker, video note, vị trí và poll vẫn nhận “I can’t read that yet.” và không tạo run.
20b: truy cập và điều khiển
- Mã pairing, như goclaw và OpenClaw làm: người gửi lạ nhận một mã 8 ký tự (hợp lệ 1 giờ, dùng một lần) thay cho câu trả lời cố định của Phase 18. Một owner hoặc admin duyệt nó bằng
POST /v1/telegram/pairings/{code}(body:user_id,agent) hoặc bằng cách gửi/pair <code> <user email> <agent slug>từ tài khoản Telegram đã liên kết của chính họ. Việc duyệt ghi một rowtelegram_links. Các liên kết có hiệu lực mà không cần restart.DELETE /v1/telegram/links/{telegram_user_id}và/unlink <telegram id>gỡ một liên kết. Các ánh xạ tĩnh của Phase 18 vẫn hoạt động và thắng các liên kết, để chủ dự án không thể bị khóa ngoài bởi một liên kết xấu. - Audit: hai action mới,
telegram.linkvàtelegram.unlink, được thêm vào tập action đóng từ Phase 2. Các yêu cầu pairing từ người gửi lạ được đếm và log, không được audit, vì chúng không phải hành động của tenant. - Nút approval: khi một run tạo từ Telegram chuyển sang
awaiting_approval, bot gửi tool call đang chờ (tên tool và đối số, cắt còn 1000 ký tự) tới chat riêng của người tạo run kèm các nút inline Approve và Reject. Callback data làap:<approval id>:<decision>:<hmac>, trong đó HMAC (SHA-256 cắt ngắn trên approval id, decision và Telegram user id, khóa bằngHANGO_TELEGRAM_CALLBACK_KEY) ngăn một nút bị giả mạo hoặc bị chuyển tiếp quyết định approval của người khác. Callback đi qua đường quyết định của Phase 15 với scope của người bấm, ghi cùng audit rowapproval.decidevớidetail.channel = "telegram", trả lời callback query, và sửa tin nhắn để hiện quyết định cùng người đã quyết định. Lần bấm thứ hai nhận “Already decided.” Các quyết định edit và respond vẫn ở trên HTTP API. - Status reaction: tin nhắn của người dùng nhận một reaction khi run bắt đầu (👀), thành công (👍) hoặc thất bại (😢), như
StatusReactionControllercủa goclaw. Một lần reaction thất bại được log và bỏ qua. - Các câu trả lời lỗi theo một quy tắc: một dòng ngắn cho mỗi run thất bại kèm run id, không bao giờ kèm nội dung lỗi, vì nó có thể chứa đầu ra của tool.
Phi chức năng
- Không có package mới. Công việc nằm trong
internal/telegram, với image block tronginternal/providervà các adapter của nó, client phiên âm tronginternal/provider/openai, vàattach_filetronginternal/tool/builtin. - Migration
00012_telegram.sqltạotelegram_links(tenant_id,telegram_user_id,user_id,agent_id,created_by,created_at) vàtelegram_pairing_codes(tenant_id,code_hash,telegram_user_id,expires_at,used_at), cả hai vớiFORCE ROW LEVEL SECURITYvà policy tenant của Phase 16 trong cùng migration. Mã pairing được lưu dạng băm. - Việc tải xuống dùng URL file của Bot API, vốn chứa token, nên áp dụng cùng cách cắt lỗi như Phase 18. Byte của media không bao giờ bị log hay đặt vào span attribute.
- File upload được tính vào giới hạn filesystem của Phase 8, và một upload vượt giới hạn bị từ chối bằng một câu trả lời.
Kiến trúc
// internal/provider/types.go (added)
type ImageBlock struct {
MediaType string `json:"media_type"` // image/jpeg, image/png, image/webp, image/gif
Path string `json:"path"` // virtual-filesystem path, persisted
Data []byte `json:"data,omitempty"` // filled per call by the inline-images middleware, never persisted
}
// internal/telegram/links.go
type LinkStore interface {
Resolve(ctx context.Context, telegramUserID int64) (Link, error) // runs in HANGO_TELEGRAM_TENANT_ID scope
Approve(ctx context.Context, code string, userID, agentID uuid.UUID) (Link, error)
Delete(ctx context.Context, telegramUserID int64) error
}update ──▶ debounce / album window ──▶ resolve sender (static map, then telegram_links)
unknown private sender ──▶ pairing code reply
group or channel ──▶ ignored, as in phase 18
media ──▶ getFile ──▶ /uploads/{session}/… ──▶ image block or file note or transcript
run.Create (sender scope, session) ──▶ stream (phase 18) ──▶ attachments ──▶ sendPhoto/sendDocument
awaiting_approval ──▶ inline buttons ──▶ callback_query ──▶ HMAC check ──▶ phase-15 decisionInstrumentation
telegram.updatecó thêmhango.telegram.media_kind(photo|album|document|voice|none). Tập outcome được mở rộng thêmpairing_codevàcallback.- Span:
telegram.download(kích thước file, loại media),stt.transcribe(model, số giây audio, dưới dạng một client span kiểu GenAI để Langfuse hiển thị nó như một generation),telegram.send_attachment. - Metric: counter
hango.telegram.media(attributekind), histogramhango.telegram.download_bytes, counterhango.telegram.pairings(attributeoutcome=issued|approved|expired|invalid), counterhango.telegram.callbacks(attributeoutcome=decided|duplicate|forged|expired), histogramhango.stt.duration. - Dashboard
hango-channelscủa Phase 14 có thêm các panel cho những thứ này, và một alert:hango.telegram.callbacks{outcome="forged"}khác không.
Các file liên quan
Tạo mới
migrations/00012_telegram.sql.internal/telegram/media.go,media_test.go— tải xuống, trần kích thước, đặt tên đường dẫn, cửa sổ album, debounce.internal/telegram/links.go,links_test.go, vàinternal/store/pg/telegram_links.go— link store, mã pairing, kiểm tra scope.internal/telegram/approvals.go,approvals_test.go— nút, HMAC của callback, quyết định qua Phase 15.internal/telegram/reactions.go.internal/agent/inline_images.go,inline_images_test.go— middlewareWrapModelCallđiềnImageBlock.Data.internal/provider/openai/transcribe.go,transcribe_test.go.internal/tool/builtin/attach_file.go,attach_file_test.go.docs/learning/20-telegram-media-pairing-approvals.md.
Sửa đổi
internal/provider/types.go,internal/provider/anthropic/convert.go,internal/provider/openai/convert.go— image block và ánh xạ của nó, cộng một cờ vision trên các mục registry.internal/server/handlers_telegram.go(route mới trong package hiện có) — duyệt pairing và xóa liên kết, chỉ owner hoặc admin, với các audit action mới.internal/store/audit.go—telegram.linkvàtelegram.unlinktrong tập đóng.internal/run/events.go— loại eventattachment.internal/telegram/bot.go,internal/telegram/stream.go— định tuyến cho media, callback và tệp đính kèm.internal/usage/pricing.go— giá phiên âm theo giây.deploy/grafana/dashboards/hango-channels.json,deploy/grafana/provisioning/alerting.yaml— các panel mới và alert callback giả mạo.internal/config/config.go,internal/app/app.go,internal/telemetry/metrics_names.go,.env.example,README.md,docs/learning/README.md,docs/system-architecture.md.
Các bước triển khai
Điểm dừng review 20a gồm các bước 1 đến 6, và điểm dừng review 20b gồm các bước 7 đến 11.
Thêm
ImageBlockvào mô hình provider và cả hai adapter, với test chuyển đổi hai chiều và placeholder cho model không có vision.Viết middleware
inline_images: nạp từng đường dẫn được tham chiếu qua backend của Phase 8 trong scope của run, điềnData, và để các message đã lưu nguyên vẹn (test rằng message đã lưu không chứa byte).Viết
media.go: tảigetFilevới trần kích thước, đặt tên file dưới/uploads/{session_id}/, cửa sổ album 500 ms và debounce text 300 ms, được test với Bot API giả và đồng hồ giả.Viết
transcribe.govới một serverhttptest, spanstt.transcribevà usage row; nối voice note vào đó.Viết
attach_file, run eventattachment, vàsendPhoto/sendDocumenttrong stream.Gửi một ảnh, một album, một file text và một voice note từ một chiếc điện thoại thật và ghi các trace vào learning doc. Dừng để review 20a.
Viết
00012_telegram.sqlcùng các policy RLS của nó, và link store, với một test isolation (một liên kết ở tenant A vô hình với tenant B) và một test visibility trong cùng tenant.Viết pairing: phát mã, lưu dạng băm, hết hạn, dùng một lần, route duyệt qua HTTP và
/pair, với các audit action mới. Test việc hết hạn, dùng lại, và một member cố duyệt (403).Viết nút approval: tin nhắn khi
awaiting_approval, callback data có HMAC, quyết định qua Phase 15, sửa tin nhắn, bấm lặp. Test một callback với HMAC sai, một callback do Telegram user khác bấm, và một callback cho approval đã hết hạn.Thêm status reaction, metric, các panel dashboard và alert.
Pair tài khoản của một thành viên gia đình bằng một mã, duyệt một tool call từ điện thoại, và ghi lại trong learning doc. Dừng để review 20b.
Cập nhật docs và hoàn tất learning doc.
Dàn ý tài liệu học
docs/learning/20-telegram-media-pairing-approvals.md phải có:
- Khái niệm: công việc của một kênh nằm ở các rìa. Media trở thành đầu vào model và file, danh tính trở thành một người dùng hango có scope, và một lần bấm nút trở thành một quyết định được audit.
- Các lựa chọn thiết kế: ảnh được lưu bền dưới dạng đường dẫn filesystem và được nhúng cho từng lời gọi; một tenant cho mỗi bot nên không lookup nào cần system pool; mã pairing do một owner duyệt; chỉ chat riêng, group chat được hoãn; callback data ký bằng HMAC.
- Đánh đổi: nhúng byte ảnh so với đường dẫn (đơn giản so với giữ bảng messages nhỏ và lịch sử có thể phát lại); phiên âm trong hango so với gửi audio cho model (model nào cũng dùng được so với một lời gọi).
- So sánh với goclaw:
internal/channels/telegram(media.gocùng STT manager của nó,album_aggregator.go,commands_pairing.go,reactions.go,send_media_group.go), các channel instance trong database. Code group chat của goclaw được so sánh trong thiết kế bị hoãn ở cuối file giai đoạn. - So sánh với OpenClaw: pairing theo
dmPolicy, các cửa sổ inbound debounce, và các nút approval gốc (approval-native.ts,approval-callback-data.ts). - Cách debug tầng này:
telegram.updatetheomedia_kindvà outcome, các spantelegram.downloadvàstt.transcribekhi câu trả lời chậm,hango.telegram.callbackstheo outcome khi một nút “không làm gì”, và các audit rowtelegram.linkkhi ai đó nói bot đã quên họ. - Voice note tiếng Việt: vì sao gợi ý
languagequan trọng với các clip ngắn, model nào hỗ trợ tiếng Việt, và vì sao bản phiên âm được chuẩn hóa NFC. - Việc tiếp theo: group chat (thiết kế được giữ trong file giai đoạn), chế độ webhook, phân tích PDF, web chat, multi-bot.
- Bài tập: chuyển tiếp nút approval của người khác vào chat của bạn và bấm thử; gửi một ảnh 30 MB; gửi ba tin nhắn trong vòng 200 ms; gửi một voice note tiếng Việt với
HANGO_STT_LANGUAGEđể trống rồi đặt thànhvi. - Ngân sách thời gian: 23h triển khai + 3h viết learning doc.
Tiêu chí thành công
Điểm dừng review 20a:
-
go build ./... && go vet ./... && go test ./...đều pass, và danh sách package không đổi so với Phase 19. - Một ảnh trở thành một image block mà message đã lưu chứa một đường dẫn và không chứa byte, và cả hai adapter đều gửi được nó (test).
- Một album trở thành một run với mọi ảnh, và ba tin nhắn text trong vòng 300 ms trở thành một run (test với đồng hồ giả).
- Một voice note trở thành một run từ bản phiên âm với một usage row
transcriptionvà một spanstt.transcribe, vàHANGO_STT_LANGUAGEđược gửi làm fieldlanguage(test). - Một fixture voice note tiếng Việt đã ghi (
internal/provider/openai/testdata/voice-vi.ogg, một câu ngắn do chủ dự án ghi) được phiên âm qua endpoint thật với dấu thanh được giữ nguyên, so với các từ mong đợi sau khi chuẩn hóa NFC (integration test, chặn bởiHANGO_STT_IT=1). -
attach_filechuyển một file từ virtual filesystem tới chat sau câu trả lời text (test). - Media vượt trần kích thước bị từ chối bằng một câu trả lời, và không byte media hay token nào xuất hiện trong log hoặc span (test).
Điểm dừng review 20b:
-
telegram_linksvàtelegram_pairing_codescó policy RLS, và các test isolation và visibility đều pass. - Một mã pairing dùng được một lần, hết hạn sau một giờ, và chỉ owner hoặc admin duyệt được; việc duyệt và unlink ghi các audit row
telegram.linkvàtelegram.unlink(test). - Một nút approval quyết định approval qua đường của Phase 15 với một audit row
approval.decide; một lần bấm giả mạo, của người ngoài hoặc lặp lại không quyết định gì (test). - System pool của Phase 16 vẫn có đúng năm nơi gọi (test hiện có pass).
- Một lần pairing thật, một voice note tiếng Việt và một approval từ điện thoại được ghi trong learning doc.
Đánh giá rủi ro
| Rủi ro | Khả năng x Tác động | Dấu hiệu | Cách xử lý đã quyết định trước |
|---|---|---|---|
| Một nút bị chuyển tiếp hoặc giả mạo quyết định approval của người khác | Low x High | hango.telegram.callbacks{outcome="forged"} lớn hơn không | HMAC ràng buộc approval, decision và Telegram user; quyết định cũng chạy với scope của người bấm, nên các quy tắc visibility áp dụng |
| Một mã pairing bị lộ liên kết một người lạ | Low x High | Một audit row telegram.link mà không ai nhớ | Mã dùng một lần, hết hạn sau một giờ và cần owner hoặc admin duyệt; /unlink gỡ liên kết ngay |
| Ảnh lớn làm phình chi phí token | Medium x Medium | Chi phí mỗi run tăng trong hango-llm | Trần kích thước, chỉ lấy kích thước lớn nhất dưới trần, và quota của Phase 13 |
| Clip tiếng Việt bị phiên âm thành ngôn ngữ khác | Medium x Low | Bản phiên âm sai ngôn ngữ trong dòng trích dẫn lặp lại | Đặt HANGO_STT_LANGUAGE=vi; đổi HANGO_STT_MODEL sang gpt-4o-transcribe nếu chất lượng kém |
| Endpoint STT ngừng hoạt động | Medium x Low | Lỗi ở stt.transcribe | Trả lời “Couldn’t hear that, please type it.”; không tạo run |
Rollback
Mỗi tính năng tắt trừ khi được cấu hình: không có HANGO_STT_BASE_URL thì voice note nhận câu trả lời “can’t read that”, không có HANGO_TELEGRAM_CALLBACK_KEY thì approval vẫn ở trên HTTP API. Rollback migration xóa các bảng link, và các ánh xạ tĩnh của Phase 18 vẫn hoạt động. Image block là phần cộng thêm, nên revert nó chỉ gỡ đầu vào ảnh.
Hoãn lại: group chat
Chủ dự án đã hoãn group chat vào 2026-10-14: hiện tại mỗi người nói chuyện với bot trong một chat riêng. Khi group quay lại, chúng theo thiết kế của goclaw bên dưới, và câu hỏi còn mở là run trong group thuộc về một group principal duy nhất hay thuộc về từng người gửi. Mục này không nằm trong effort hay tiêu chí thành công của Phase 20.
Thiết kế theo goclaw (internal/channels/telegram/handlers.go, topic_config.go, wake_words.go, commands_writers.go, internal/channels/history.go), được điều chỉnh cho tenancy của hango:
- Group pairing: lần đầu bot bị mention trong một group lạ, nó trả lời bằng một mã group pairing 8 ký tự, như
group_policy = "pairing"mặc định của goclaw. Một owner hoặc admin duyệt bằng cùng routePOST /v1/telegram/pairings/{code}(body:agent) hoặc/pair <code> <agent slug>. Việc duyệt ghi một rowtelegram_groupsvà một audit rowtelegram.linkvớidetail.kind = "group". Tin nhắn trong một group chưa được duyệt bị bỏ mà không ghi lại, như goclaw làm./unlinkgửi trong group bởi một owner hoặc admin sẽ gỡ nó. - Danh tính group: việc duyệt tạo một group principal, một row
usersvớikind = 'telegram_group', rolemember, tiêu đề chat làm tên hiển thị và một email dưới domain dành riênggroups.hango.invalid. Mọi run của group chạy dưới principal này, khớp với shared scopegroup:telegram:<chat id>của goclaw. Vì vậy usage, session và audit row của các run trong group thuộc về group, không thuộc về ai đã nói. Admin API từ chối cấp API key cho một group principal, nên không ai đăng nhập được bằng nó. Telegram user id và tên của người gửi đi vào metadata input của run (telegram_sender_id,telegram_sender_name) và vào span của run, nên một trace vẫn cho thấy ai đã hỏi. - Session: một session dùng chung cho mỗi group, và một cho mỗi forum topic, với key
telegram:group:<chat id>:<unix seconds>vàtelegram:group:<chat id>:topic:<topic id>:<unix seconds>, nhưBuildSessionKeyvàBuildGroupTopicSessionKeycủa goclaw./newtrong một group bắt đầu một session mới và chỉ được phép với các writer của group (bên dưới). - Group policy, được kiểm tra trước cổng mention:
open(mọi thành viên của một group đã duyệt đều đánh thức được agent),allowlist(chỉ các Telegram id trongallow_from), hoặcdisabled. Mặc định làopen, như goclaw. Có mặt trong một group đã duyệt là đủ để nói chuyện với agent ở đó, vì chủ dự án đã chọn duyệt group đó. - Cổng mention:
require_mention(mặc định true) vàmention_mode. Ở chế độstrict, agent chỉ trả lời một mention@bottường minh, một reply tới một tin nhắn của nó, một lệnh, hoặc một wake word. Ở chế độyield, nó trả lời mọi tin nhắn trừ khi một bot hoặc người dùng khác được mention tường minh, và nó không bao giờ trả lời tin nhắn của một bot khác mà không mention nó, điều này ngăn hai bot trả lời nhau mãi mãi. Mention của chính bot bị cắt khỏi input. - Wake word: config của một agent có thể liệt kê
telegram.wake_words(ví dụ tên của nó, “Bơ”). Một wake word trong text hoặc caption được tính là một mention. Việc khớp không phân biệt hoa thường trên text đã chuẩn hóa NFC tại ranh giới từ, nên các từ tiếng Việt có dấu khớp đúng như được gõ. - Lịch sử group: các tin nhắn không đánh thức agent được ghi vào
telegram_group_pending(tên người gửi, text hoặc caption, một ghi chú “[photo]” hoặc “[file: name]” cho media, Telegram message id), khóa theo chat và topic và bị chặn ởhistory_limit. Giới hạn này cấu hình được:HANGO_TELEGRAM_GROUP_HISTORY_LIMITđặt mặc định (50 khi chưa đặt, goclaw dùng 200, và 0 tắt lịch sử), và mỗi group hoặc topic có thể ghi đè qua config của nó. Khi agent được đánh thức, các mục đang chờ được đặt trước input dưới dạng một block “Messages since your last reply”, rồi bị xóa. Media trong lịch sử chỉ được ghi chú, không tải xuống, khác với việc tải lazy của goclaw và giữ các ảnh không nhắm tới agent ra khỏi model. - Nhãn người gửi: mỗi input trong một group bắt đầu bằng
[From: <first name> (@username)], như goclaw chú thích, để agent biết ai đang nói trong một session dùng chung. - Writer: mỗi group có một danh sách writer gồm các Telegram id, những người được chạy
/newtrong group. Owner và admin quản lý nó bằng/addwritervà/removewritergửi dưới dạng reply tin nhắn của người đó, và/writersliệt kê danh sách. Người duyệt là một writer ngay từ đầu. - Config theo group và theo topic: row của group chứa
policy,require_mention,mention_mode,allow_from,history_limit,enabled,system_promptvàtools, và một JSON maptopicsvới cùng các field cho từng forum topic. Config được resolve theo thứ tự của goclaw: mặc định từ môi trường (HANGO_TELEGRAM_GROUP_*, tương đương group"*"của goclaw), rồi group, rồi topic.system_promptcủa một topic được nối vào cuối của group, vàtoolschỉ có thể thu hẹp danh sách tool của agent, không bao giờ mở rộng.PATCH /v1/telegram/groups/{chat id}(owner hoặc admin) thay đổi nó và ghi một audit rowtelegram.group_update. - Một tenant cho mỗi bot vẫn đúng: mọi lookup group chạy trong scope
HANGO_TELEGRAM_TENANT_ID, nên system pool vẫn có năm nơi gọi.
Effort ước tính khi được nhặt lại: khoảng 9h, cộng các phần thêm vào migration (telegram_groups, telegram_group_pending, users.kind, một cột kind trên mã pairing) và một audit action telegram.group_update.