Skip to content

feat: harden Ana Telegram memory and actions#383

Open
Th0rgal wants to merge 49 commits intomasterfrom
feat/ana-memory-workflows
Open

feat: harden Ana Telegram memory and actions#383
Th0rgal wants to merge 49 commits intomasterfrom
feat/ana-memory-workflows

Conversation

@Th0rgal
Copy link
Copy Markdown
Owner

@Th0rgal Th0rgal commented Apr 8, 2026

Summary

  • harden Ana's Telegram bridge with webhook dedupe, reply correlation, safer mission/chat routing, and native action logging
  • add structured Telegram memory with user/chat/channel scopes plus a hybrid retrieval layer for better recall
  • expose Telegram memory/action admin surfaces and add a real Telethon smoke script for live DM/group validation

Testing

  • cargo fmt --all
  • bunx tsc --noEmit
  • cargo check --manifest-path /Users/thomas/work/open_agent/Cargo.toml
  • cargo test --manifest-path /Users/thomas/work/open_agent/Cargo.toml hybrid_memory_search_ -- --nocapture
  • cargo test --manifest-path /Users/thomas/work/open_agent/Cargo.toml telegram_bridge_ -- --nocapture
  • cargo test --manifest-path /Users/thomas/work/open_agent/Cargo.toml extract_structured_memory_ -- --nocapture
  • cargo test --manifest-path /Users/thomas/work/open_agent/Cargo.toml structured_memory_context_groups_user_and_chat_scopes -- --nocapture
  • cargo test --manifest-path /Users/thomas/work/open_agent/Cargo.toml internal_telegram_action_token_round_trip -- --nocapture
  • live Telegram validation on dev with @ana_lfgbot: memory recall, native reminder, cross-chat send, and hybrid recall in DM

Note

Medium Risk
Touches authentication identity resolution for single-tenant/disabled auth modes and adds new iOS approval flows, which could affect access partitioning and user interactions if misconfigured. CI/caching and dashboard additions are low risk but may surface latent API/contract mismatches.

Overview
CI is modernized and sped up by bumping GitHub Actions to @v5, splitting Cargo registry vs target caches, caching dashboard Bun/Next.js artifacts, and enforcing cargo --locked (including harness contract scripts).

Telegram admin visibility is expanded in the dashboard with new API types/endpoints for listing recent action executions, scheduled messages, and structured memory plus ranked memory search, all loaded when a bot row is expanded.

iOS dashboard adds FIDO signing approvals via an SSE-driven fido_sign_request handler, a full-screen approve/deny overlay with countdown, and persisted auto-approval rules (optionally requiring Face ID), backed by a new POST /api/fido/respond client call.

Backend auth/provider flows are hardened by allowing an override for the implicit single-tenant user ID via env vars, persisting account_email updates from the frontend, and making Anthropic userinfo fetching more resilient (GET→POST fallback and better logging).

Reviewed by Cursor Bugbot for commit cd36ea2. Bugbot is set up for automated code reviews on this repo. Configure here.

@vercel
Copy link
Copy Markdown

vercel bot commented Apr 8, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
sandboxed-dashboard Ready Ready Preview, Comment Apr 13, 2026 3:30pm
sandboxed-sh Ready Ready Preview, Comment Apr 13, 2026 3:30pm

Request Review

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 4d082603bb

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Telegram Conversation & Workflow System
- Add conversation tracking: persist Telegram conversations and messages
  with direction (inbound/outbound) in SQLite
- Add workflow system: create cross-chat workflow requests that relay
  replies back to the originating chat
- Add `ask-title` command to telegram-action-cli for initiating workflows
- Add six new API endpoints for listing conversations, messages,
  workflows, events, and executing workflow requests
- Wire TELEGRAM_WORKFLOW_URL env var into mission runner

AI Provider OAuth & Account Email
- Extract email from Anthropic token response (account.email_address)
  instead of relying on userinfo endpoint blocked by Cloudflare
- Add browser-side fallback: when server can't fetch email, return
  access token to frontend for client-side userinfo fetch
- Accept account_email in provider update requests
- Add userinfo_access_token field to OAuth callback response

Claude Code Usage & Rotation
- Fix credential scopes: include user:sessions:claude_code so Claude
  Code can use normal Pro/Max allocation (not just extra usage)
- Classify "out of extra usage" / "out of usage" as rate-limit errors
  so automatic account rotation triggers correctly
- Improve fetch_anthropic_account_email logging with Cloudflare
  detection and POST method fallback
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: ccd96cce8b

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 158f1a73f2

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 8850703198

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 4b6ce5ff7d

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

- Fix @username/@target chat resolution passing chat_title as username
  instead of None for initial mapping match (telegram.rs)
- Fix telegram-action CLI helper truncating multi-word text args by
  joining all remaining argv tokens with spaces (mission_runner.rs)
- Fix premature "no matches" message in Telegram memory search UI by
  checking that a search was actually performed before displaying
  empty-results state (page.tsx)

Co-Authored-By: Claude Opus 4.5 <[email protected]>
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 527dc8dc5c

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Keep ana_lfgbot username unchanged per request.

Co-Authored-By: Claude Opus 4.5 <[email protected]>
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 3d37579f9b

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

…are command

LLMs (particularly GLM-5.1 via OpenCode) were calling `telegram-action`
as a bare command instead of using `$TELEGRAM_ACTION_COMMAND`. The script
lived in the workspace directory which wasn't in PATH, causing
"command not found" errors.

Appending (not prepending) the workspace directory to PATH makes
`telegram-action` discoverable while keeping system binaries at higher
priority to avoid shadowing risks. Applied to both Claude Code and
OpenCode backend execution paths.

Co-Authored-By: Claude Opus 4.5 <[email protected]>
- control.rs: Replace hardcoded `true` for telegram_actions_available
  with proper gate checking build_internal_telegram_action_token() and
  localhost_api_base_url_from_env(), matching mission_runner.rs logic

- control.rs: Use chrono_tz::Tz directly in cron evaluation instead of
  snapshotting a FixedOffset, so DST transitions are handled correctly
  by croner's find_next_occurrence

- mission_runner.rs: Make localhost_api_base_url_from_env pub(super) so
  control.rs can access it for the action availability check

Co-Authored-By: Claude Opus 4.5 <[email protected]>
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 466b0218e3

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

…llback

When telegram-action targets a @username that isn't found in
telegram_chat_missions, the resolver now:

1. Searches telegram_conversations for matching titles/usernames
2. For @username targets, checks known group chats and resolves to
   a shared group where the target can be mentioned
3. Falls back to Telegram's getChat API for public groups/channels

Also adds send-chat-id and ask-chat-id CLI commands for direct
numeric chat_id targeting as a fallback.

Co-Authored-By: Claude Opus 4.5 <[email protected]>
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: b41b8054b1

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

When telegram-action targets a @username that gets resolved to a shared
group chat (username-to-group fallback), the message text now starts
with @username so the target bot actually receives it as a mention via
its webhook. Without this, the message was sent to the group but the
target bot had no way to know it was addressed.

Co-Authored-By: Claude Opus 4.5 <[email protected]>
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 82f8b3ceaf

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

The action execution log was storing the original text without the
prepended @mention, making it inconsistent with what was actually
sent to Telegram.

Co-Authored-By: Claude Opus 4.5 <[email protected]>
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 7f459872d5

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

root and others added 3 commits April 11, 2026 16:46
…ce failures

Fix two clippy lints (manual_is_multiple_of, redundant_closure) and
rustfmt formatting across six files. Also log provider account email
persistence errors instead of silently discarding them.

Co-Authored-By: Claude Opus 4.5 <[email protected]>
Three fixes for mission reliability:

1. Plain OpenCode JSON mode now tracks tool depth from tool_use/step_finish
   events, preventing the 30s text-output idle timeout from killing
   processes during active MCP tool execution (web fetch, search).

2. When idle timeout kills an OpenCode process with partial output, append
   a truncation notice so the user knows the response is incomplete.

3. Proactively refresh OAuth credentials before copying to mission
   directory when host credentials are expired/near-expiry. Also extend
   maybe_recover_soft_llm_error to handle AuthError — if the agent
   produced substantive output, downgrade to TurnComplete instead of
   marking the mission as Failed.
…copy

When a mission's credential file exists and hasn't expired locally but the
host has a different (refreshed) token, re-copy the host credentials. This
catches server-side token revocations where the old token's local expiry
hasn't passed yet but the token was already revoked when OAuth refresh
minted a new one.
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: bb93a361c4

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +1637 to +1643
workflow.status = TelegramWorkflowStatus::Completed;
workflow.updated_at = now_string();
workflow.completed_at = Some(workflow.updated_at.clone());
let _ = ctx
.mission_store
.update_telegram_workflow(workflow.clone())
.await;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Keep workflow pending until origin relay finishes

This path marks a workflow as Completed and persists it before relay_workflow_reply_to_origin runs, but the relay itself is asynchronous and can be lost on process crash/restart between these steps. In that failure window the workflow is no longer waiting_external, so the timeout/recovery sweep will never pick it up, and the external reply is effectively dropped instead of being relayed back to the origin mission/chat.

Useful? React with 👍 / 👎.

);
automation_manager.scope = McpScope::Workspace;
automation_manager.default_enabled = false;
automation_manager.default_enabled = true;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Provide API_URL when enabling automation-manager by default

automation-manager is now enabled by default, but its process falls back to http://localhost:3000 when API_URL is missing, while workspace env wiring only sets API_URL when PORT is present. In deployments where the server listens on a non-3000 configured port without exporting PORT, the MCP will boot automatically and send every request to the wrong endpoint, causing tool failures until operators manually override env.

Useful? React with 👍 / 👎.

Th0rgal added 2 commits April 12, 2026 22:08
- CLI helpers: skip rewriting files when content hasn't changed
- PATH safety: use dedicated .sandboxed-sh-bin/ subdirectory instead of
  workspace root to prevent repo files from shadowing system binaries
- React state updater: fix async side-effects and stale closure in
  toggleExpand by reading state before calling setter
- CI cache: add restore-keys fallback for dashboard-bun cache step
- Telegram memory UI: guard "no matches" message behind data-loaded check
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: f5e2df40aa

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +4889 to +4890
tracing::warn!(timezone = %tz, "Unknown timezone, falling back to UTC");
Some(chrono_tz::Tz::UTC)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Reject unknown cron timezones instead of defaulting to UTC

In resolve_tz, an unrecognized timezone is coerced to UTC rather than rejected, so a typo like America/NewYork will silently run on UTC time while the automation still appears valid. That produces incorrect trigger times (often by hours) and is hard to diagnose from the API side; this path should fail closed (or validation should reject invalid timezones on create/update) instead of silently changing semantics.

Useful? React with 👍 / 👎.

Comment on lines +5138 to +5140
"Invalid cron expression, skipping: {}", e
);
false
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Reject malformed cron expressions before persisting automation

When cron parsing fails, the scheduler only logs and returns false, leaving the automation active but permanently non-executing; this turns malformed expressions into silent no-op automations that keep emitting warnings on each scheduler pass. Because creation/update does not validate cron syntax up front, users can save broken schedules that never run instead of receiving a request-time error.

Useful? React with 👍 / 👎.

Th0rgal added 4 commits April 13, 2026 16:16
Add the backend infrastructure for relaying FIDO/SSH signing requests
to the iOS app:

- FidoSigningHub: oneshot-channel map for pending approval requests
- POST /api/fido/request: called by the FIDO agent proxy, blocks until
  iOS responds or 60s timeout
- POST /api/fido/respond: called by iOS app to approve/deny
- AgentEvent::FidoSignRequest: broadcast via SSE to connected clients
…elay

New binary that listens on a Unix socket, speaks the SSH agent protocol,
and intercepts FIDO/SK key sign requests. Non-FIDO operations are
forwarded to the upstream agent. FIDO sign requests are relayed to the
backend for mobile app approval via POST /api/fido/request.
When the FIDO agent proxy socket exists at /run/sandboxed-sh/fido-agent.sock,
bind-mount it into containers and set SSH_AUTH_SOCK so SSH inside containers
automatically uses the signing relay.
- FidoSignRequest model + AutoApprovalRule with expiration/biometric logic
- FidoApprovalState: @observable singleton handling SSE events, rule
  matching, Face ID authentication, and approval/denial API calls
- FidoApprovalOverlay: glass-morphism bottom sheet with countdown timer,
  request details, quick "auto-approve SSH for 5 min" chip, and
  approve/deny buttons
- AutoApprovalRulesView: settings sub-view for managing active rules
- Wired into ControlView SSE event stream and MainTabView overlay
- Settings section for "Require Face ID for all requests" toggle
Copy link
Copy Markdown

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit cd36ea2. Configure here.

"Failed to persist provider account email"
);
}
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Provider account email overwritten for all same-type providers

Medium Severity

When updating a provider's account_email, the code calls update_provider_account with updated.provider_type.id() (e.g., "anthropic"), which is the provider type identifier, not the individual provider instance UUID. This means setting account_email on one Anthropic provider overwrites the stored account email for all Anthropic providers in provider_accounts.json, since update_provider_account keys by the type string. Users with multiple accounts of the same provider type will lose track of which account belongs to which instance.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit cd36ea2. Configure here.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: cd36ea212d

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

);

// Broadcast to all connected SSE clients (iOS app picks it up).
if let Some(control) = state.control.get_any_session_events_tx() {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Route FIDO approval events to the caller's session

post_fido_request emits FidoSignRequest through get_any_session_events_tx(), which selects only one arbitrary control session (values().next()) rather than the authenticated caller’s session. In multi-session/multi-user setups this can send the approval prompt to the wrong client while the real requester blocks until timeout, and it also leaks signing metadata to an unrelated session. The request path should bind to the current authenticated user/session and publish there (or deliberately fan out to all sessions with proper scoping).

Useful? React with 👍 / 👎.

// Simple hash — just use first 16 bytes as a display fingerprint
// (full SHA256 would require a dependency; this is good enough for display)
let mut hex = String::with_capacity(48);
for (i, byte) in blob.iter().take(16).enumerate() {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Compute a real fingerprint for key-based auto-approval

The proxy labels this value as a key fingerprint but actually uses only the first 16 bytes of the SSH key blob, which are largely header/key-type bytes and can collide across different keys. Any auto-approval rule keyed by key_fingerprint can therefore match unrelated keys and approve unintended signatures. Use a full digest (e.g., SHA-256 over the key blob) for stable, unique key identity.

Useful? React with 👍 / 👎.

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.

2 participants