Skip to content

fix: extract text from TG Rich Message for bot-to-bot communication#92

Open
changhansung wants to merge 332 commits into
suzuke:mainfrom
songsid:fix/tg-rich-message-receive
Open

fix: extract text from TG Rich Message for bot-to-bot communication#92
changhansung wants to merge 332 commits into
suzuke:mainfrom
songsid:fix/tg-rich-message-receive

Conversation

@changhansung

Copy link
Copy Markdown
Contributor
  • Add extractRichText() helper that recursively walks RichBlock/RichText nodes
  • When msg.text is empty but msg.rich_message exists, extract plain text from blocks
  • Remove TG-DEBUG console.log statements
  • Enables bot-to-bot @mention to work when sender uses sendRichMessage

- daemon.ts: emit message_queued (when pasteQueueDepth > 0) and
  message_delivered (after deliverMessage) with chatId + messageId
- instance-lifecycle.ts: listen for both events, call ctx.reactMessageStatus
- fleet-manager.ts: reactMessageStatus reacts on the user's message,
  gated to Discord adapter only
- /chat respond now returns the bot reply message ID
- Pass channelId as chatId + reply messageId to daemon meta
- Optimize react() to try chatId as channelId first (fast path)
- Fixes: ⏳/✅ reactions not showing because messageId was empty
…sy TUI

When multiple messages are queued and the TUI is busy outputting, the
first send-keys Enter after paste-buffer can be swallowed. Add a retry
Enter after 1s delay. All supported CLIs (kiro-cli, claude-code,
gemini-cli, codex, opencode) ignore Enter on empty input, making the
retry safe.
…allow

Root cause: with 10s/30s timeout, waitForIdle expires while the agent is
still outputting. The forced paste succeeds but Enter is swallowed by the
busy TUI. With 120s, paste only happens when the CLI has been silent for
2s (truly idle), so Enter is reliably accepted.

The retry Enter in pasteText remains as a safety net for the rare case
where the 120s timeout expires.
Part 1: restart_instance MCP tool
- outbound-schemas.ts: RestartInstanceArgs (name only)
- outbound-handlers.ts: restartInstance handler calling ctx.restartSingleInstance
- OutboundContext: add restartSingleInstance method
- mcp-tools.ts: register restart_instance tool
- daemon.ts: add to CROSS_INSTANCE_TOOLS set

Part 2: lightweight waitForIdle
- deliverMessage: lightweight 30s, normal 120s (was 120s for both)
After /start registers a new classic channel, setOpenChannels on the
adapter wasn't updated. Messages from non-primary guilds to the new
channel were silently dropped because the channel wasn't in openChannels.
This also affected attachment URL storage — images couldn't be downloaded.
1. handleClassicStop: add reregisterClassicChannels() after unregister
   to remove stopped channel from adapter openChannels.

2. reregisterClassicChannels: always call setOpenChannels even when
   channels list is empty, so the last /stop properly clears all
   stale entries from the adapter.
No admin required. Reads context from statusline.json first, falls
back to tmux capture-pane with kiro-cli regex parser.
Shows context percentage, backend, and instance name.
OpenCode shows 'Permission required' when accessing external directories,
followed by a 'confirm' prompt. Add getRuntimeDialogs to auto-press Enter
on both layers.
Runtime dialogs (Permission required, confirm) need fast response.
30s meant each prompt waited up to 30s before auto-dismiss.
5s keeps CPU impact minimal (single capture-pane per cycle).
Select 'Allow always' instead of 'Allow once' so the same directory
won't prompt again. Added Right/Left to sendSpecialKey and dialog
handler SPECIAL_KEYS set.
Kiro CLI 2.0 TUI mode uses ~1.1GB per instance. Classic mode uses
~300MB. Add --classic flag to kiro-cli chat command.
1. classic-channel-manager.ts:
   - ClassicChannel.collab field (persisted to classicBot.yaml)
   - toggleCollab(channelId): toggle + save, returns new state
   - isCollab(channelId): check collab status

2. discord-adapter.ts:
   - Register /collab slash command
   - Only block own messages (not all bots) — let fleet-manager decide
   - Pass isBotMessage flag in message emit
   - Emit bot user ID with 'started' event

3. channel/types.ts:
   - InboundMessage.isBotMessage?: boolean

4. fleet-manager.ts:
   - /collab handler: admin-only toggle, responds with new state
   - handleInboundMessage: bot messages only pass through to collab channels
   - handleClassicChannelMessage collab mode:
     - Log all messages (including bots) to chat-logs
     - Trigger on exact @mention (<@BOT_USER_ID>) only
     - @everyone/@here/@ROLE don't trigger (not <@id> format)
     - Strip @mention, forward clean text to agent
   - Store botUserId from adapter 'started' event
Part 1: pre_task_command
- types.ts: InstanceConfig.pre_task_command?: string
- classic-channel-manager.ts: ClassicChannel.preTaskCommand, load/save,
  getPreTaskCommand(channelId)
- daemon.ts: in pasteLock, deliverMessage(pre_task_command) before the
  actual message (waits for idle between both)
- fleet-manager.ts: startClassicInstance passes preTaskCommand from
  classicChannels config into InstanceConfig

Part 2: General steering
- GENERAL_INSTRUCTIONS: add 'Instance Configuration Tips' section with
  reviewer pre_task_command, collab mode, and cost_guard suggestions
Fleet start syncs src/general-knowledge/*.md to General instance's
.kiro/steering/ directory. Knowledge files are version-controlled —
updating AgEnD automatically gives General new skills.

Includes initial skills.md: tmux health check, reviewer session
management, fork instance (session cloning).
Previously, any instructions change (including knowledge file sync)
forced skipResume=true, losing conversation context on every restart.

Now: resume is always preserved. When instructions change, a [system]
notice is injected before the next user message telling the agent to
re-read its steering files.

Also: syncGeneralKnowledge only writes files when content actually
changed, preventing unnecessary instructions hash diffs.
Telegram ClassicBot allows /start in private chats and regular groups
(not the primary forum group) to spawn lightweight agent instances.

Changes:
- telegram.ts: allow non-primary-group messages through access control
  for classic mode routing (fleet-manager handles its own ACL)
- classic-channel-manager.ts: add allowed_groups/allowed_users to
  ClassicBotYaml interface + isGroupAllowed/isUserAllowed methods
- fleet-manager.ts: add Telegram classic mode routing in
  handleInboundMessage — /start, /stop, /chat commands + private chat
  auto-forward (no /chat prefix needed)
- topic-commands.ts: register /start, /stop, /chat as default-scope
  bot commands via setMyCommands API

Coexists with topic mode on the same bot token:
- Primary forum group (group_id) → topic mode (unchanged)
- Other groups/private chats → classic mode
Telegram classic mode messages (threadId=null, chatId != primaryGroupId)
were blocked by fleet-level access control before reaching the classic
mode routing block. Now these messages bypass fleet ACL and rely on
classicBot.yaml's own isGroupAllowed/isUserAllowed checks.
Core multi-adapter architecture enabling simultaneous Telegram + Discord:

types.ts:
- ChannelConfig: add optional 'id' field (adapter identifier)
- FleetConfig: add 'channels?: ChannelConfig[]' (backward compat with 'channel')

config.ts:
- loadFleetConfig: normalize 'channel' singular → 'channels' array
- Auto-assign id = type when not specified

fleet-manager.ts:
- Add adapters Map<string, ChannelAdapter> for multi-adapter lookup
- Add instanceAdapterBinding Map to track which adapter each instance uses
- Add getAdapterForInstance() / bindInstanceAdapter() helpers
- Split startSharedAdapter into loop + startSingleAdapter + startAdditionalAdapter
- Primary adapter (first channel) stays as this.adapter for backward compat
- Additional adapters get full event wiring (message, callback_query, slash_command)
- handleOutboundFromInstance: route via getAdapterForInstance
- handleToolStatusFromInstance: route via getAdapterForInstance
- Bind instances to adapters on inbound message (topic, classic, general)
- stopAll: stop all adapters

Backward compatible: single 'channel' config works exactly as before.
Multi-channel: add 'channels' array to run both platforms simultaneously.
changhansung and others added 30 commits June 16, 2026 15:12
feat: quickstart supports adding platform while fleet is running
…assign

fix: quickstart systemd restart + assignTopicIds channel_id platform detection
…ates

When adding a second platform, existing general without channel_id was
not recognized as belonging to any adapter. Auto-create spawned duplicates.

Fix: 3-phase approach:
1. Match unbound generals by topic_id (TG general has topic_id=1, DC has general_channel_id)
2. Adopt remaining unbound generals first-come
3. Only create new instances if no unbound generals left

This correctly handles: existing TG general + add DC → general gets channel_id=telegram,
new general-discord created for DC.
fix: adopt existing general when adding second platform
Without it, General has no valid topic_id and routing breaks.
Loop until user provides a value with explanation why it's needed.
fix: Discord general_channel_id required in quickstart
On machines without D-Bus user session (root, SSH without linger,
containers), systemctl --user fails. Now falls back to fleet.pid
kill like agend fleet stop does.
fix: agend stop fallback to PID kill when systemd unavailable
…lable

Same D-Bus issue as agend stop. When startService() fails or no service
is installed, spawns 'agend fleet start' in background instead of
giving up.
fix: agend start fallback to direct fleet start when systemd unavailable
docs: complete configuration and commands reference
fix: add admin check to /restart + correct docs permissions
fix: add version to /sysinfo + improve kiro-cli context parser
fix: /status adds tmux capture fallback for context percentage
fix: remove /chat from TG ClassicBot menu
fix: TG ClassicBot chat-log + error notification delivery
Reviewer feedback: rawText starting with / would trigger /chat forwarding.
Guard: any /command without @mention is logged as empty (won't forward).
fix: guard against /command forwarding without @mention
1. Log bot outbound replies to classic instance chat-log
2. TG classic: allow bot messages through if they @mention our bot
   (previously all bot messages dropped due to !threadId early return)

Both are multi-adapter refactor regressions (ecb3169e714649).
fix: TG ClassicBot bot reply logging + bot-to-bot @mention
…pter

1. TG adapter was never setting isBotMessage (from.is_bot) — now included
2. Debug logs at adapter access control + fleet-manager entry point
3. This is likely the root cause: without isBotMessage, the bot-to-bot
   fix in beta.17 never triggered
debug: TG bot message trace + fix missing isBotMessage
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant