Skip to content

feat(operator_inbox): message triage + draft + follow-up engine#4147

Closed
Sathvik-1007 wants to merge 2 commits into
tinyhumansai:mainfrom
Sathvik-1007:feat/vc-operator-inbox
Closed

feat(operator_inbox): message triage + draft + follow-up engine#4147
Sathvik-1007 wants to merge 2 commits into
tinyhumansai:mainfrom
Sathvik-1007:feat/vc-operator-inbox

Conversation

@Sathvik-1007

@Sathvik-1007 Sathvik-1007 commented Jun 26, 2026

Copy link
Copy Markdown
Contributor

Self-contained domain module — split from #2261 (PR 6/7). Depends on #4142.

9 files: types, engine, connection, imap_client, parser, poller, rpc, schemas, mod.
E2E: triage_message → generate_draft.

Summary by CodeRabbit

  • New Features

    • Added an operator inbox workflow for email triage, draft replies, follow-up scheduling, inbox polling, and reply sending.
    • Expanded app capabilities to include voice, captions, actions, inbox, chat-with-data, and guided recommendations support.
    • Added utility improvements for generating UUIDs and timestamps.
  • Documentation

    • Added capability and implementation planning docs covering the new AI-assisted workflow set.
  • Bug Fixes

    • Updated email transport support to work with an additional secure connection mode.

- Cargo.toml: sqlparser, whatlang, nnnoiseless, strsim, lettre async
- util.rs: uuid_v4(), now_epoch() shared helpers
- about_app: UserContent privacy variant, 6 capability catalog entries
- docs: capability plan + full API reference
9-file domain: types, engine (priority scoring), connection (TLS IMAP/SMTP),
imap_client (async fetch UNSEEN), parser (body extraction), poller (background),
rpc handlers, schemas.

E2E: triage_message -> generate_draft.
@Sathvik-1007 Sathvik-1007 requested a review from a team June 26, 2026 05:56
@coderabbitai

coderabbitai Bot commented Jun 26, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

This PR adds the operator_inbox domain, new email parsing and IMAP/SMTP helpers, an in-memory triage engine, poller and RPC/schema wiring, capability catalog updates, dependency changes, and two new documentation pages describing the capability set and rollout plan.

Changes

Operator Inbox Rollout

Layer / File(s) Summary
Capability metadata and docs
src/openhuman/about_app/types.rs, src/openhuman/about_app/catalog_data.rs, docs/boost-vc-capability-plan.md, docs/VC_CAPABILITIES.md
PrivacyDataKind::UserContent is added, the capability catalog removes three older entries and adds six new capability foundations, and the new docs describe the capability set and rollout plan.
Module tree and types
Cargo.toml, src/openhuman/mod.rs, src/openhuman/operator_inbox/mod.rs, src/openhuman/operator_inbox/types.rs, src/openhuman/util.rs
New dependencies are added, openhuman exposes operator_inbox, shared UUID/time helpers are added, and the operator inbox domain types and re-exports are defined.
RFC 5322 parsing and urgency scoring
src/openhuman/operator_inbox/parser.rs
Raw email parsing extracts headers, bodies, attachments, reply state, and urgency signals, and bounds the computed priority score.
Threading and IMAP helpers
src/openhuman/operator_inbox/imap_client.rs
Email threading, prompt builders, priority parsing, config validation, and follow-up deadline extraction are added with tests.
Inbox runtime
src/openhuman/operator_inbox/connection.rs, src/openhuman/operator_inbox/engine.rs, src/openhuman/operator_inbox/poller.rs
IMAP fetch and SMTP reply functions are added, triage records are stored in memory with follow-up and archive updates, and the background poller fetches and triages new mail.
RPC and registration
src/openhuman/operator_inbox/rpc.rs, src/openhuman/operator_inbox/schemas.rs, src/core/all.rs, tests/json_rpc_e2e.rs
RPC handlers expose triage, draft, follow-up, inbox fetch, reply send, and poller controls; the operator_inbox schemas and core registry include the new namespace, and JSON-RPC e2e coverage exercises the flow.

Sequence Diagram(s)

sequenceDiagram
  participant Client as "JSON-RPC client"
  participant RPC as "operator_inbox::rpc"
  participant Engine as "operator_inbox::engine"
  participant Connection as "operator_inbox::connection"
  participant SMTP as "SMTP server"

  Client->>RPC: openhuman.operator_inbox_triage_message
  RPC->>Engine: triage_message(...)
  Engine-->>RPC: TriageRecord
  RPC-->>Client: ok + triage_id

  Client->>RPC: openhuman.operator_inbox_generate_draft
  RPC->>Engine: generate_draft(...)
  Engine-->>RPC: DraftReply
  RPC-->>Client: ok + draft

  Client->>RPC: openhuman.operator_inbox_send_reply
  RPC->>Engine: get_triage(...)
  RPC->>Connection: send_reply(...)
  Connection->>SMTP: deliver mail
  SMTP-->>Connection: accepted
  Connection-->>RPC: message sent
  RPC-->>Client: ok + message_id
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~90+ minutes

Suggested labels

feature, rust-core, agent

Suggested reviewers

  • sanil-23

Poem

(_/)
( •_•) The inbox hops in moonlit light,
/ >🥕 With triage, drafts, and mail in flight.
The poller drums, the schemas chime,
And rabbit paws keep perfect time.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 72.57% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the main operator_inbox change: triage, draft generation, and follow-up handling.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot added agent Built-in agents, prompts, orchestration, and agent runtime in src/openhuman/agent/. feature Net-new user-facing capability or product behavior. rust-core Core Rust runtime in src/: CLI, core_server, shared infrastructure. labels Jun 26, 2026

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

Copy link
Copy Markdown

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: 6634a088ad

ℹ️ 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 thread tests/json_rpc_e2e.rs
Comment on lines +13391 to +13395
&rpc_base,
501,
"openhuman.voice_actions_list_mappings",
json!({}),
)

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 Remove the orphaned RPC call fragment

After the previous test closes, this new fragment starts with arguments to a missing post_json_rpc( call and is outside any function, so tests/json_rpc_e2e.rs no longer parses when the Rust test target is built. Restore the enclosing let list = post_json_rpc( block/test or remove the orphaned lines.

Useful? React with 👍 / 👎.

/// Non-sensitive metadata (capability ids, feature flags, settings shape).
Metadata,
/// User-generated content (audio, text, queries) sent to cloud LLM for inference.
UserContent,

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 Handle the new privacy kind in the UI

PrivacyDataKind::UserContent serializes as user_content, but the existing frontend contract/rendering only recognizes raw | derived | credentials | diagnostics | metadata (app/src/utils/tauriCommands/aboutApp.ts and PrivacyPanel.tsx). Any new capability using this value will show an undefined badge/label in Settings > Privacy; update the client type, label, badge mapping, and i18n keys together with the enum.

Useful? React with 👍 / 👎.

if RUNNING.swap(true, Ordering::SeqCst) {
return false;
}
let interval = Duration::from_secs(interval_secs.unwrap_or(DEFAULT_POLL_INTERVAL_SECS));

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 zero poll intervals

If openhuman.operator_inbox_start_poller is called with interval_secs: 0, this builds a zero-duration interval and the loop immediately repeats after every fetch/error, which can hammer the IMAP server and spin CPU until stopped. Reject zero or treat it as the default before spawning the loop.

Useful? React with 👍 / 👎.

Comment on lines +26 to +29
if !RUNNING.load(Ordering::SeqCst) {
info!("{LOG_PREFIX} stopped");
break;
}

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 Prevent old pollers from surviving restarts

Stopping only flips the shared RUNNING flag, but if start_poller is called again before the old task wakes from sleep(interval), the old loop sees RUNNING=true here and continues alongside the new task with its previous config. That restart path duplicates IMAP fetches/triage; keep a task handle/generation token or await/abort the old task before allowing a new start.

Useful? React with 👍 / 👎.

Comment on lines +35 to +37
info!(
"{LOG_PREFIX} connecting to {}:{} user={}",
config.host, config.port, config.username

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 Redact the IMAP username in logs

This logs config.username on every IMAP fetch; for common providers that is the user's full email address, and AGENTS.md explicitly says not to log full PII. Redact to a domain/hash or omit it so support logs do not capture mailbox identities.

Useful? React with 👍 / 👎.

Comment on lines +66 to +68
let mut session = client
.login(&config.username, &config.password)
.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.

P2 Badge Implement or reject OAuth-only IMAP auth

validate_imap_config allows passwordless configs when oauth2_token is set, but this path still authenticates with LOGIN using config.password, so Gmail/Outlook OAuth-only configs that pass validation fail with an empty password. Either implement XOAUTH2 here or reject OAuth-only configs until it is supported.

Useful? React with 👍 / 👎.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Note

Due to the large number of review comments, Critical severity comments were prioritized as inline comments.

🟠 Major comments (22)
src/openhuman/about_app/types.rs-159-160 (1)

159-160: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Backfill existing cloud-bound chat/voice capabilities to UserContent.

Adding this variant without migrating the existing message-sending entries leaves the privacy catalog internally inconsistent: conversation.send_text, conversation.send_voice, and voice.ptt in src/openhuman/about_app/catalog_data.rs still describe the same cloud-bound text/audio flow as PrivacyDataKind::Derived. The catalog API will now report identical data movement two different ways.

Based on the catalog entries already present in src/openhuman/about_app/catalog_data.rs, the same user text/audio flow is still classified as Derived there.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/openhuman/about_app/types.rs` around lines 159 - 160, The new
PrivacyDataKind::UserContent variant in about_app/types.rs needs to be applied
to the existing cloud-bound chat and voice catalog entries so the privacy model
stays consistent. Update the matching entries in catalog_data.rs for
conversation.send_text, conversation.send_voice, and voice.ptt to use
UserContent instead of Derived, keeping the same user text/audio flow
classification across the catalog API and the enum definition.
docs/VC_CAPABILITIES.md-3-18 (1)

3-18: 📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Reframe this as roadmap material, not shipped capability documentation.

The intro says all six modules are production-grade and already implemented, but the supplied PR scope only substantiates operator_inbox plus capability foundations/docs for the others. Publishing this as current-state documentation will advertise RPC methods, limits, and security guarantees for modules that are not in this stack.

As per coding guidelines, features should be surfaced after they are proved in Rust and over RPC; the supplied PR context only shows that proof for operator_inbox.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/VC_CAPABILITIES.md` around lines 3 - 18, Reframe the document in terms
of planned roadmap rather than shipped capability status: the current intro and
module table in VC_CAPABILITIES.md present all six modules as production-grade
and implemented, but the PR scope only supports operator_inbox plus foundation
docs for the rest. Update the wording in the intro and module descriptions so
only operator_inbox is described as current capability, while voice_assistant,
live_captions, voice_actions, chat_with_data, and guided_flows are clearly
marked as planned or forthcoming. Keep the existing structure, but avoid
claiming RPC exposure, safety guarantees, or Rust-core implementation for
modules not evidenced by the current stack.

Source: Coding guidelines

docs/boost-vc-capability-plan.md-58-65 (1)

58-65: 📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

The operator inbox non-goal is already false for this PR.

This section says operator inbox is "schema-only, no transport", but the supplied PR scope includes IMAP fetching, SMTP reply sending, connection handling, polling, and the triage_message -> generate_draft flow. Leaving this here will mislead anyone using the plan to understand what actually shipped.

Based on the PR objectives and review-stack context, operator_inbox in this stack is not transport-free or schema-only.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/boost-vc-capability-plan.md` around lines 58 - 65, Update the non-goals
list in the capability plan to match the shipped operator inbox behavior. The
current “No actual email/Slack integration” entry is no longer accurate given
the implemented operator inbox flow; revise or remove it so it does not claim
the inbox is schema-only or transport-free. Keep the wording aligned with the
actual operator inbox modules and flow, especially the triage_message to
generate_draft path, IMAP fetching, SMTP reply sending, connection handling, and
polling.
src/openhuman/about_app/catalog_data.rs-1452-1538 (1)

1452-1538: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Don’t publish the unimplemented VC foundations as live Beta capabilities yet.

src/openhuman/about_app/catalog.rs serves this array directly to clients, so these entries are the live capability contract. In the supplied PR scope, only operator_inbox is actually wired through runtime/RPC; the other five are still described as foundations/planned work. Shipping them here as Beta will make the catalog advertise capabilities and RPC surfaces that do not exist yet.

As per coding guidelines, src/openhuman/about_app/** should follow "Specify → prove in Rust → prove over RPC → surface in UI → test", and src/openhuman/about_app/catalog.rs exposes this data directly.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/openhuman/about_app/catalog_data.rs` around lines 1452 - 1538, The new VC
foundation entries are being exposed through the live capability catalog as if
they are shipped Beta features. Update the `Capability` entries in
`catalog_data.rs` so only actually wired capabilities stay surfaced as live Beta
items, and move the unimplemented foundations out of the public contract or mark
them as non-live/planned instead of `CapabilityStatus::Beta`. Use the existing
`Capability` records and `CapabilityStatus` values to keep
`operator_inbox.triage` published while preventing the other five `id` entries
from advertising unavailable RPC surfaces.

Source: Coding guidelines

src/openhuman/operator_inbox/mod.rs-12-13 (1)

12-13: 📐 Maintainability & Code Quality | 🟠 Major | 🏗️ Heavy lift

Route schema handlers through ops.rs, not a separate rpc.rs.

This new domain is bypassing the repo’s controller-migration boundary: the paired handlers in src/openhuman/operator_inbox/schemas.rs:334-336 dispatch into super::rpc::... instead of an ops.rs business-logic layer. That makes operator_inbox non-canonical from the start and pushes controller flow away from the required schemas.rs -> ops.rs shape.

As per coding guidelines, src/openhuman/*/{mod,schemas}.rs requires “schemas with handlers in schemas.rs delegating to ops.rs,” and src/openhuman/*/ defines the canonical shape with ops.rs as the business-logic module.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/openhuman/operator_inbox/mod.rs` around lines 12 - 13, The operator_inbox
module is routing schema handlers through a separate rpc layer instead of the
canonical ops business-logic layer. Update the module layout and handler wiring
so the relevant functions in schemas.rs delegate to ops.rs rather than
super::rpc, and ensure the module exports/include structure in mod.rs reflects
ops.rs as the handler target instead of rpc.rs.

Source: Coding guidelines

src/openhuman/util.rs-663-668 (1)

663-668: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Don’t turn clock failures into a real-looking timestamp.

now_epoch() feeds TriageRecord.created_at, and src/openhuman/operator_inbox/engine.rs:27-66 later uses that field for archived-record eviction. Falling back to 0 silently makes fresh records look oldest, so a bad system clock corrupts ordering/retention instead of surfacing the failure.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/openhuman/util.rs` around lines 663 - 668, The now_epoch() helper is
swallowing SystemTime clock errors by falling back to 0, which can make fresh
records appear oldest and break retention/ordering. Update now_epoch() in
util.rs to surface the time error instead of returning a real-looking timestamp,
and adjust callers such as TriageRecord.created_at and the archived-record
eviction path in operator_inbox::engine to handle the failure explicitly.
src/openhuman/util.rs-652-669 (1)

652-669: 📐 Maintainability & Code Quality | 🟠 Major | 🏗️ Heavy lift

Move new shared helpers out of util.rs.

This file is already at least 669 lines long, so adding more shared APIs here keeps it above the repo’s module-size cap and further entrenches the catch-all utility layer.

As per coding guidelines, src/**/*.rs: “Rust modules must be ≤ ~500 lines in size”.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/openhuman/util.rs` around lines 652 - 669, The new shared helpers in
util.rs should be moved out of the oversized catch-all module to keep it within
the module-size cap. Relocate uuid_v4 and now_epoch into a smaller, more focused
shared module used by the domain modules, and update any call sites to reference
the new location instead of openhuman::util so util.rs stops accumulating
generic APIs.

Source: Coding guidelines

src/openhuman/operator_inbox/schemas.rs-316-345 (1)

316-345: 📐 Maintainability & Code Quality | 🟠 Major | 🏗️ Heavy lift

Delegate schema handlers to the ops layer.

The schema wrappers currently route through rpc.rs; the repo migration rule expects schema handlers to delegate to ops.rs, keeping controller wiring separate from RPC/business logic.

As per coding guidelines, “define schemas with handlers in schemas.rs delegating to ops.rs.”

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/openhuman/operator_inbox/schemas.rs` around lines 316 - 345, The schema
handler wrappers in h_triage, h_draft, h_followup, h_get, h_list, h_archive,
h_fetch_inbox, h_send_reply, h_start_poller, and h_stop_poller still delegate to
super::rpc, but they should route through the ops layer instead. Update each
controller future to call the corresponding super::ops function so schemas.rs
stays as wiring only and business logic remains in ops.rs.

Source: Coding guidelines

src/openhuman/operator_inbox/schemas.rs-365-369 (1)

365-369: 🔒 Security & Privacy | 🟠 Major | 🏗️ Heavy lift

Don’t expose raw mailbox passwords as controller inputs.

These schemas put IMAP/SMTP passwords directly on the JSON-RPC/tool surface. Prefer a credential/secret reference and resolve it server-side.

Also applies to: 442-446, 508-512

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/openhuman/operator_inbox/schemas.rs` around lines 365 - 369, The inbox
schemas currently expose raw IMAP/SMTP passwords as required string inputs on
the controller/tool surface. Update the relevant schema builders in
operator_inbox/schemas.rs, including the FieldSchema entries for password in the
IMAP and SMTP-related schema definitions, to accept a credential or secret
reference instead of a plaintext password, and resolve the secret server-side
inside the corresponding request handling logic rather than in the JSON-RPC
surface.
src/openhuman/operator_inbox/rpc.rs-106-107 (1)

106-107: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Return ok:false instead of propagating lookup errors.

These ? paths bypass the handler’s JSON response shape, unlike the surrounding handlers that return {ok:false,error}.

Also applies to: 256-259

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/openhuman/operator_inbox/rpc.rs` around lines 106 - 107, The triage
lookup path in the handler currently propagates errors with `?`, which breaks
the JSON `{ok:false,error}` response shape used elsewhere. Update the handler
around `engine::get_triage(id)` so lookup failures are caught and converted into
the existing error response instead of bubbling up, and apply the same treatment
to the related `try_llm_draft` flow mentioned in the comment. Keep the fix
localized to the handler logic so the response format stays consistent with the
surrounding RPC methods.
src/openhuman/operator_inbox/rpc.rs-60-63 (1)

60-63: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Add timeouts around LLM calls.

These awaits sit on the RPC path; if the provider stalls, triage/draft requests can hang instead of falling back or returning ok:false.

Also applies to: 149-152

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/openhuman/operator_inbox/rpc.rs` around lines 60 - 63, The RPC path
currently awaits LLM provider calls directly, so stalled requests can hang
triage/draft handling instead of failing fast. Wrap the async calls in the
relevant RPC handlers in rpc.rs (including the chat_with_system usage and the
other call at the referenced later location) with a timeout boundary, and on
timeout return the existing fallback/ok:false path rather than blocking the
request indefinitely. Use the surrounding handler functions and provider call
sites to apply the timeout consistently.
src/openhuman/operator_inbox/parser.rs-95-100 (1)

95-100: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Don’t log sender addresses or subjects.

from and subject can contain PII/user content; keep this debug log to non-sensitive derived metadata.

Proposed fix
     debug!(
-        from = %from,
-        subject = %subject,
+        has_from = !from.is_empty(),
+        has_subject = !subject.is_empty(),
         attachments = attachments.len(),
         is_reply = is_reply,
         "[operator-inbox-parser] email parsed"
     );
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/openhuman/operator_inbox/parser.rs` around lines 95 - 100, The debug log
in parser.rs’s email parsing path is exposing PII by recording the raw sender
address and subject. Update the debug! call in the operator-inbox parser to
remove from and subject entirely, and keep only non-sensitive derived metadata
such as attachments.len() and is_reply so the log still identifies parse
behavior without user content.
src/openhuman/operator_inbox/imap_client.rs-17-41 (1)

17-41: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Redact credentials from Debug.

Both configs derive Debug while containing plaintext passwords/tokens, so any {:?} log or error context can leak mailbox credentials.

Proposed fix
-#[derive(Debug, Clone, Serialize, Deserialize)]
+#[derive(Clone, Serialize, Deserialize)]
 pub struct ImapConfig {
@@
 }

-#[derive(Debug, Clone, Serialize, Deserialize)]
+#[derive(Clone, Serialize, Deserialize)]
 pub struct SmtpConfig {
@@
 }

Add custom Debug impls that redact password and only expose whether oauth2_token is present.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/openhuman/operator_inbox/imap_client.rs` around lines 17 - 41,
`ImapConfig` and `SmtpConfig` currently derive `Debug`, which can leak plaintext
`password` and `oauth2_token` values in logs or error contexts. Remove the
auto-derived `Debug` on these structs and add custom `Debug` implementations
that redact sensitive fields, such as hiding `password` entirely and only
indicating whether `oauth2_token` is present. Keep the existing `Serialize` and
`Deserialize` derives unchanged.
src/openhuman/operator_inbox/rpc.rs-203-203 (1)

203-203: 🎯 Functional Correctness | 🟠 Major

Validate ports before converting to u16

The as u16 cast on lines 203 and 246 silently wraps values exceeding 65535 (e.g., 65536 becomes 0), allowing invalid RPC parameters to connect to unintended ports.

Code context
// Line 203
let port = p.get("port").and_then(|v| v.as_u64()).unwrap_or(993) as u16;
// Line 246
let smtp_port = p.get("smtp_port").and_then(|v| v.as_u64()).unwrap_or(587) as u16;

Replace the cast with explicit range validation returning an error for out-of-range values:

let port_val = p.get("port").and_then(|v| v.as_u64()).unwrap_or(993);
if port_val > 65535 {
    return Err("port must be <= 65535".to_string());
}
let port = port_val as u16;

Apply the same fix to smtp_port.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/openhuman/operator_inbox/rpc.rs` at line 203, Validation is missing
before narrowing RPC port values to u16 in the operator inbox request handling,
which can silently wrap invalid values. Update the port parsing in the relevant
request handlers that read port and smtp_port from p to first keep them as u64,
check they are within 0..=65535, and return an error when out of range; only
then convert to u16. Use the existing parsing sites around the port and
smtp_port extraction logic to apply the same fix consistently.
src/openhuman/operator_inbox/schemas.rs-97-114 (1)

97-114: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Align triage required fields with the handler.

The schema requires sender, subject, and body, but handle_triage_message accepts any one of them. This makes generated clients reject requests the server supports.

Proposed fix
             FieldSchema {
                 name: "sender",
                 ty: TypeSchema::String,
-                comment: "Sender.",
-                required: true,
+                comment: "Sender. At least one of sender, subject, or body is required.",
+                required: false,
             },
@@
             FieldSchema {
                 name: "subject",
                 ty: TypeSchema::String,
-                comment: "Subject.",
-                required: true,
+                comment: "Subject. At least one of sender, subject, or body is required.",
+                required: false,
             },
@@
             FieldSchema {
                 name: "body",
                 ty: TypeSchema::String,
-                comment: "Body.",
-                required: true,
+                comment: "Body. At least one of sender, subject, or body is required.",
+                required: false,
             },
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/openhuman/operator_inbox/schemas.rs` around lines 97 - 114, The triage
schema is stricter than the handler: `handle_triage_message` accepts any one of
`sender`, `subject`, or `body`, but the `FieldSchema` entries currently mark all
three as required. Update the schema definition in `operator_inbox::schemas` so
the generated contract matches `handle_triage_message` by making these fields
optional/allowing any single field to be present, while keeping the
`FieldSchema` entries for `sender`, `subject`, and `body` aligned with the
handler’s validation logic.
src/openhuman/operator_inbox/poller.rs-18-23 (1)

18-23: 🚀 Performance & Scalability | 🟠 Major | ⚡ Quick win

Reject interval_secs = 0.

Duration::from_secs(0) turns this into a busy loop that hammers IMAP and logs as fast as Tokio can reschedule it. Clamp to a sane minimum or return an error/false for zero.

Also applies to: 45-45

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/openhuman/operator_inbox/poller.rs` around lines 18 - 23, Reject
zero-valued polling intervals in start_polling by handling interval_secs before
constructing the Duration; a value of 0 currently creates a busy loop. Update
the start_polling path to either return false/error when interval_secs is
Some(0) or clamp it to a safe minimum, and keep the behavior consistent in the
polling loop that uses the interval derived from DEFAULT_POLL_INTERVAL_SECS.
src/openhuman/operator_inbox/engine.rs-15-24 (1)

15-24: 🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

Email threading metadata is lost during triage.

fetch_new_emails already extracts message_id, in_reply_to, and references, but this engine only accepts/stores sender/subject/body. By the time send_reply runs, the original threading headers are gone, so SMTP replies cannot set In-Reply-To/References and will go out as new conversations instead of true replies.

Also applies to: 27-48

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/openhuman/operator_inbox/engine.rs` around lines 15 - 24, The triage flow
drops email threading metadata because triage_message and
triage_message_with_priority only carry sender, subject, and body, so send_reply
cannot preserve the original conversation headers. Update the operator_inbox
engine to thread through message_id, in_reply_to, and references from
fetch_new_emails into the TriageRecord and the
triage_message/triage_message_with_priority path, then have send_reply use those
stored values when building the SMTP reply headers.
src/openhuman/operator_inbox/connection.rs-113-156 (1)

113-156: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Malformed emails are silently dropped.

If msg.body() is missing or MessageParser::parse returns None, the loop just skips that message and still returns Ok. The caller then sees fewer inbox items with no indication that parsing failed, which makes message loss very hard to detect or recover from.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/openhuman/operator_inbox/connection.rs` around lines 113 - 156, The inbox
fetch loop is silently dropping malformed messages when `msg.body()` is missing
or `mail_parser::MessageParser::parse` returns `None`, so `fetch` can return
`Ok` with missing emails and no signal of failure. Update the logic in the
stream-processing loop in `connection.rs` to detect these cases and surface an
error instead of skipping them, using the existing `LOG_PREFIX`, `msg_result`,
and `FetchedEmail` flow so the caller can tell which message failed to parse.
src/openhuman/operator_inbox/engine.rs-50-62 (1)

50-62: 🩺 Stability & Availability | 🟠 Major | 🏗️ Heavy lift

MAX_RECORDS is not actually enforced.

When the map is full and there are no archived entries, this branch removes nothing and still inserts the new record. Under the poller path that means the in-memory store can grow past the advertised cap indefinitely.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/openhuman/operator_inbox/engine.rs` around lines 50 - 62, The eviction
logic in operator_inbox::engine does not enforce MAX_RECORDS when there are no
archived entries to remove, so the store can still grow past the cap. Update the
insertion path around the archive eviction block so that insert only proceeds
when capacity is available, or evict a record unconditionally when store.len()
>= MAX_RECORDS; use the existing store, MAX_RECORDS, and TriageStatus::Archived
logic in this branch to ensure the limit is always respected.
src/openhuman/operator_inbox/connection.rs-31-166 (1)

31-166: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

All remote mail operations are unbounded.

TCP connect, TLS handshake, IMAP login/select/search/fetch, and SMTP send all await without a timeout. A slow or half-open server can therefore pin the RPC call or the background poller forever. These external calls need tokio::time::timeout(...) around them and timeout-specific errors back to callers.

Also applies to: 169-221

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/openhuman/operator_inbox/connection.rs` around lines 31 - 166, The remote
mail operations in fetch_new_emails are unbounded, so a slow or half-open server
can hang the RPC or poller indefinitely. Wrap each external await in
tokio::time::timeout, including TcpStream::connect, the rustls
TlsConnector::connect handshake, async_imap::Client::login, session.select,
session.uid_search, and session.uid_fetch, and return timeout-specific errors
from fetch_new_emails. Apply the same timeout handling to the SMTP send path
mentioned in the review, using the existing LOG_PREFIX and the relevant
connection/session methods to locate the changes.
src/openhuman/operator_inbox/poller.rs-15-24 (1)

15-24: 🩺 Stability & Availability | 🟠 Major | 🏗️ Heavy lift

stop_polling does not retire the old task before a restart.

The running flag is shared across all poller generations. If a caller does stop → start while the first task is sleeping or mid-fetch, the old task can observe RUNNING = true again and continue alongside the new one, duplicating IMAP fetches and triage inserts. This needs a per-run cancellation token / JoinHandle, not just a global boolean.

Also applies to: 25-48, 51-54

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/openhuman/operator_inbox/poller.rs` around lines 15 - 24,
`start_polling`/`stop_polling` currently rely on the shared `RUNNING` atomic
only, so an old `tokio::spawn` can survive a stop→start cycle and run alongside
the new poller. Update the poller lifecycle to use a per-run cancellation
mechanism and retain the spawned task handle inside
`start_polling`/`stop_polling`, so stopping first signals the active task to
exit and awaits or aborts its `JoinHandle` before allowing a restart. Keep the
fix anchored around `RUNNING`, `start_polling`, and `stop_polling` so only one
IMAP polling loop can exist at a time.
src/openhuman/operator_inbox/connection.rs-35-38 (1)

35-38: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Don't log the full mailbox username.

This writes the account identifier to info logs on every IMAP fetch. Redact it or log only host/mailbox metadata so inbox credentials are not exposed through routine logs.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/openhuman/operator_inbox/connection.rs` around lines 35 - 38, The
connection logging in the IMAP setup is exposing the full mailbox username in
routine info logs. Update the logging in the connection flow around the existing
info! call in operator_inbox::connection so it redacts the username or omits it
entirely, while still keeping non-sensitive context like host and port. Use the
existing LOG_PREFIX and the connection setup path to locate the log statement,
and ensure any replacement metadata does not reveal account identifiers.
🟡 Minor comments (3)
src/openhuman/operator_inbox/rpc.rs-310-319 (1)

310-319: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Avoid live LLM dependency in this unit test.

handle_triage_message tries the configured chat provider first, so this test can become network/config-dependent and flaky when a provider is available.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/openhuman/operator_inbox/rpc.rs` around lines 310 - 319, The triage_rpc
test is still exercising the live chat provider path through
handle_triage_message, which makes it flaky and environment-dependent. Update
the test to avoid any real LLM/network dependency by mocking or stubbing the
chat provider used by handle_triage_message, or by injecting a deterministic
test provider before calling it, so the assertion only validates the triage
logic and not external configuration.
src/openhuman/operator_inbox/imap_client.rs-142-142 (1)

142-142: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Count recipients as thread participants too.

participant_count currently only includes senders, so one inbound email to the operator reports one participant instead of sender + recipient(s).

Proposed fix
             let email = &emails[idx];
             participants.insert(email.from.clone());
+            for recipient in &email.to {
+                participants.insert(recipient.clone());
+            }
             messages.push(ThreadMessage {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/openhuman/operator_inbox/imap_client.rs` at line 142, participant_count
is only tracking senders via the IMAP parsing path, so recipients are missing
from the thread participant set. Update the participant collection in the inbox
parsing logic around imap_client and the email/thread aggregation so each
message adds both the sender (email.from) and all recipient addresses to the
same participants set. Make the change in the code that builds the thread
metadata/participant list so participant_count reflects sender plus
recipient(s), not just the inbound sender.
src/openhuman/operator_inbox/parser.rs-92-93 (1)

92-93: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Make reply-prefix detection case-insensitive.

Lowercase re: subjects without In-Reply-To are currently treated as new messages, which can skew reply/thread scoring.

Proposed fix
-    let is_reply =
-        in_reply_to.is_some() || subject.starts_with("Re:") || subject.starts_with("RE:");
+    let is_reply = in_reply_to.is_some()
+        || subject.trim_start().to_ascii_lowercase().starts_with("re:");
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/openhuman/operator_inbox/parser.rs` around lines 92 - 93, The reply
detection in parser.rs is still case-sensitive because it only checks subject
prefixes in a fixed form. Update the is_reply logic in the parser function to
treat the reply prefix case-insensitively so subjects like lowercase re: are
classified as replies even without In-Reply-To, while preserving the existing
in_reply_to check.
🧹 Nitpick comments (5)
docs/boost-vc-capability-plan.md (1)

69-77: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Align this architecture section with the repo’s canonical module layout.

This still documents engine.rs, rpc.rs, and about_app/catalog.rs, but the repo guidance for src/openhuman/*/ is export-only mod.rs, business logic in ops.rs, and controller wiring in schemas.rs (with types.rs/store.rs/tools.rs/bus.rs alongside it). As written, this page points follow-up work at the wrong files.

As per coding guidelines, src/openhuman/*/ uses the canonical mod.rs/types.rs/store.rs/ops.rs/schemas.rs/tools.rs/bus.rs shape, and src/openhuman/*/mod.rs must stay export-focused.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/boost-vc-capability-plan.md` around lines 69 - 77, The architecture
section is pointing contributors to the wrong module layout for
src/openhuman/*/. Update the capability-plan wording to match the repo’s
canonical shape: export-focused mod.rs plus types.rs, store.rs, ops.rs,
schemas.rs, tools.rs, and bus.rs, with business logic in ops.rs and controller
wiring in schemas.rs. Also remove references that imply engine.rs, rpc.rs, or
about_app/catalog.rs are the expected files, and align the guidance with the
existing module symbols so follow-up work lands in the correct places.

Source: Coding guidelines

src/openhuman/operator_inbox/imap_client.rs (2)

173-177: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Use debug-level logging here.

The repo guidance asks Rust domain logging to use debug/trace; this operational counter should not be info by default.

Proposed fix
-    info!(
+    debug!(
         thread_count = threads.len(),
         email_count = emails.len(),
         "[operator-inbox-imap] threads built"
     );

As per coding guidelines, “Add debug logging … using log/tracing at debug/trace level in Rust.”

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/openhuman/operator_inbox/imap_client.rs` around lines 173 - 177, The
logging in the thread-building step is too high-visibility for an operational
counter. Update the log call in the IMAP thread construction path (the block
emitting "[operator-inbox-imap] threads built") from info to debug, keeping the
existing thread_count and email_count fields so it remains available for
troubleshooting without defaulting to info-level output.

Source: Coding guidelines


301-537: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Move the tests out to keep the module under the size limit.

This module is 538 lines; moving the #[cfg(test)] block into imap_client_tests.rs brings the production module back under the repo’s ~500-line limit.

As per coding guidelines, “Rust modules must be ≤ ~500 lines in size.”

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/openhuman/operator_inbox/imap_client.rs` around lines 301 - 537, The
#[cfg(test)] block in imap_client.rs is pushing the module over the size limit.
Move the entire tests module, including helpers like sample_emails and the unit
tests for build_threads, validate_imap_config, validate_smtp_config,
build_priority_prompt, build_draft_prompt, parse_priority_response, and
extract_followup_deadline, into a separate imap_client_tests.rs file. Keep the
production code in imap_client.rs and wire the new test module in so the
existing test coverage remains unchanged.

Source: Coding guidelines

src/openhuman/operator_inbox/schemas.rs (1)

538-559: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Move schema tests or factor helpers to get under the module limit.

schemas.rs is 560 lines; moving tests to schemas_tests.rs and/or extracting repetitive field builders would satisfy the repo size guideline.

As per coding guidelines, “Rust modules must be ≤ ~500 lines in size.”

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/openhuman/operator_inbox/schemas.rs` around lines 538 - 559, The
schemas.rs module exceeds the repo size guideline, and the test-only block is
contributing to the length. Move the #[cfg(test)] tests from the tests module
into a separate schemas_tests.rs module, or factor repeated schema-building
helpers out of all_controller_schemas/schemas-related code to reduce duplication
and bring the main module under the limit. Keep the behavior of handlers_match,
namespace, and unknown unchanged while relocating them so the core schemas
module stays below the line cap.

Source: Coding guidelines

src/openhuman/operator_inbox/engine.rs (1)

1-8: 📐 Maintainability & Code Quality | 🔵 Trivial | 🏗️ Heavy lift

Move this domain back to the canonical openhuman/* layout.

This PR introduces engine.rs/connection.rs/poller.rs under operator_inbox, but the repo convention for new src/openhuman/*/ domains is the mod.rs + types.rs + store.rs + ops.rs + schemas.rs + tools.rs + bus.rs shape. Landing the inbox domain off that structure will make later controller wiring and maintenance inconsistent with the rest of the tree.

As per coding guidelines, src/openhuman/*/ should use the canonical mod.rs / types.rs / store.rs / ops.rs / schemas.rs / tools.rs / bus.rs layout.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/openhuman/operator_inbox/engine.rs` around lines 1 - 8, The
operator_inbox domain is using non-canonical entry files, so move it back to the
standard openhuman layout by replacing the standalone engine.rs-style entrypoint
with a mod.rs-based module structure and splitting domain logic into the
expected types.rs, store.rs, ops.rs, schemas.rs, tools.rs, and bus.rs modules.
Update the operator_inbox module wiring so the engine-related symbols (like the
triage/draft engine imports and now_epoch usage) are exposed through the
canonical module tree instead of a custom file layout.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Major comments:
In `@docs/boost-vc-capability-plan.md`:
- Around line 58-65: Update the non-goals list in the capability plan to match
the shipped operator inbox behavior. The current “No actual email/Slack
integration” entry is no longer accurate given the implemented operator inbox
flow; revise or remove it so it does not claim the inbox is schema-only or
transport-free. Keep the wording aligned with the actual operator inbox modules
and flow, especially the triage_message to generate_draft path, IMAP fetching,
SMTP reply sending, connection handling, and polling.

In `@docs/VC_CAPABILITIES.md`:
- Around line 3-18: Reframe the document in terms of planned roadmap rather than
shipped capability status: the current intro and module table in
VC_CAPABILITIES.md present all six modules as production-grade and implemented,
but the PR scope only supports operator_inbox plus foundation docs for the rest.
Update the wording in the intro and module descriptions so only operator_inbox
is described as current capability, while voice_assistant, live_captions,
voice_actions, chat_with_data, and guided_flows are clearly marked as planned or
forthcoming. Keep the existing structure, but avoid claiming RPC exposure,
safety guarantees, or Rust-core implementation for modules not evidenced by the
current stack.

In `@src/openhuman/about_app/catalog_data.rs`:
- Around line 1452-1538: The new VC foundation entries are being exposed through
the live capability catalog as if they are shipped Beta features. Update the
`Capability` entries in `catalog_data.rs` so only actually wired capabilities
stay surfaced as live Beta items, and move the unimplemented foundations out of
the public contract or mark them as non-live/planned instead of
`CapabilityStatus::Beta`. Use the existing `Capability` records and
`CapabilityStatus` values to keep `operator_inbox.triage` published while
preventing the other five `id` entries from advertising unavailable RPC
surfaces.

In `@src/openhuman/about_app/types.rs`:
- Around line 159-160: The new PrivacyDataKind::UserContent variant in
about_app/types.rs needs to be applied to the existing cloud-bound chat and
voice catalog entries so the privacy model stays consistent. Update the matching
entries in catalog_data.rs for conversation.send_text, conversation.send_voice,
and voice.ptt to use UserContent instead of Derived, keeping the same user
text/audio flow classification across the catalog API and the enum definition.

In `@src/openhuman/operator_inbox/connection.rs`:
- Around line 113-156: The inbox fetch loop is silently dropping malformed
messages when `msg.body()` is missing or `mail_parser::MessageParser::parse`
returns `None`, so `fetch` can return `Ok` with missing emails and no signal of
failure. Update the logic in the stream-processing loop in `connection.rs` to
detect these cases and surface an error instead of skipping them, using the
existing `LOG_PREFIX`, `msg_result`, and `FetchedEmail` flow so the caller can
tell which message failed to parse.
- Around line 31-166: The remote mail operations in fetch_new_emails are
unbounded, so a slow or half-open server can hang the RPC or poller
indefinitely. Wrap each external await in tokio::time::timeout, including
TcpStream::connect, the rustls TlsConnector::connect handshake,
async_imap::Client::login, session.select, session.uid_search, and
session.uid_fetch, and return timeout-specific errors from fetch_new_emails.
Apply the same timeout handling to the SMTP send path mentioned in the review,
using the existing LOG_PREFIX and the relevant connection/session methods to
locate the changes.
- Around line 35-38: The connection logging in the IMAP setup is exposing the
full mailbox username in routine info logs. Update the logging in the connection
flow around the existing info! call in operator_inbox::connection so it redacts
the username or omits it entirely, while still keeping non-sensitive context
like host and port. Use the existing LOG_PREFIX and the connection setup path to
locate the log statement, and ensure any replacement metadata does not reveal
account identifiers.

In `@src/openhuman/operator_inbox/engine.rs`:
- Around line 15-24: The triage flow drops email threading metadata because
triage_message and triage_message_with_priority only carry sender, subject, and
body, so send_reply cannot preserve the original conversation headers. Update
the operator_inbox engine to thread through message_id, in_reply_to, and
references from fetch_new_emails into the TriageRecord and the
triage_message/triage_message_with_priority path, then have send_reply use those
stored values when building the SMTP reply headers.
- Around line 50-62: The eviction logic in operator_inbox::engine does not
enforce MAX_RECORDS when there are no archived entries to remove, so the store
can still grow past the cap. Update the insertion path around the archive
eviction block so that insert only proceeds when capacity is available, or evict
a record unconditionally when store.len() >= MAX_RECORDS; use the existing
store, MAX_RECORDS, and TriageStatus::Archived logic in this branch to ensure
the limit is always respected.

In `@src/openhuman/operator_inbox/imap_client.rs`:
- Around line 17-41: `ImapConfig` and `SmtpConfig` currently derive `Debug`,
which can leak plaintext `password` and `oauth2_token` values in logs or error
contexts. Remove the auto-derived `Debug` on these structs and add custom
`Debug` implementations that redact sensitive fields, such as hiding `password`
entirely and only indicating whether `oauth2_token` is present. Keep the
existing `Serialize` and `Deserialize` derives unchanged.

In `@src/openhuman/operator_inbox/mod.rs`:
- Around line 12-13: The operator_inbox module is routing schema handlers
through a separate rpc layer instead of the canonical ops business-logic layer.
Update the module layout and handler wiring so the relevant functions in
schemas.rs delegate to ops.rs rather than super::rpc, and ensure the module
exports/include structure in mod.rs reflects ops.rs as the handler target
instead of rpc.rs.

In `@src/openhuman/operator_inbox/parser.rs`:
- Around line 95-100: The debug log in parser.rs’s email parsing path is
exposing PII by recording the raw sender address and subject. Update the debug!
call in the operator-inbox parser to remove from and subject entirely, and keep
only non-sensitive derived metadata such as attachments.len() and is_reply so
the log still identifies parse behavior without user content.

In `@src/openhuman/operator_inbox/poller.rs`:
- Around line 18-23: Reject zero-valued polling intervals in start_polling by
handling interval_secs before constructing the Duration; a value of 0 currently
creates a busy loop. Update the start_polling path to either return false/error
when interval_secs is Some(0) or clamp it to a safe minimum, and keep the
behavior consistent in the polling loop that uses the interval derived from
DEFAULT_POLL_INTERVAL_SECS.
- Around line 15-24: `start_polling`/`stop_polling` currently rely on the shared
`RUNNING` atomic only, so an old `tokio::spawn` can survive a stop→start cycle
and run alongside the new poller. Update the poller lifecycle to use a per-run
cancellation mechanism and retain the spawned task handle inside
`start_polling`/`stop_polling`, so stopping first signals the active task to
exit and awaits or aborts its `JoinHandle` before allowing a restart. Keep the
fix anchored around `RUNNING`, `start_polling`, and `stop_polling` so only one
IMAP polling loop can exist at a time.

In `@src/openhuman/operator_inbox/rpc.rs`:
- Around line 106-107: The triage lookup path in the handler currently
propagates errors with `?`, which breaks the JSON `{ok:false,error}` response
shape used elsewhere. Update the handler around `engine::get_triage(id)` so
lookup failures are caught and converted into the existing error response
instead of bubbling up, and apply the same treatment to the related
`try_llm_draft` flow mentioned in the comment. Keep the fix localized to the
handler logic so the response format stays consistent with the surrounding RPC
methods.
- Around line 60-63: The RPC path currently awaits LLM provider calls directly,
so stalled requests can hang triage/draft handling instead of failing fast. Wrap
the async calls in the relevant RPC handlers in rpc.rs (including the
chat_with_system usage and the other call at the referenced later location) with
a timeout boundary, and on timeout return the existing fallback/ok:false path
rather than blocking the request indefinitely. Use the surrounding handler
functions and provider call sites to apply the timeout consistently.
- Line 203: Validation is missing before narrowing RPC port values to u16 in the
operator inbox request handling, which can silently wrap invalid values. Update
the port parsing in the relevant request handlers that read port and smtp_port
from p to first keep them as u64, check they are within 0..=65535, and return an
error when out of range; only then convert to u16. Use the existing parsing
sites around the port and smtp_port extraction logic to apply the same fix
consistently.

In `@src/openhuman/operator_inbox/schemas.rs`:
- Around line 316-345: The schema handler wrappers in h_triage, h_draft,
h_followup, h_get, h_list, h_archive, h_fetch_inbox, h_send_reply,
h_start_poller, and h_stop_poller still delegate to super::rpc, but they should
route through the ops layer instead. Update each controller future to call the
corresponding super::ops function so schemas.rs stays as wiring only and
business logic remains in ops.rs.
- Around line 365-369: The inbox schemas currently expose raw IMAP/SMTP
passwords as required string inputs on the controller/tool surface. Update the
relevant schema builders in operator_inbox/schemas.rs, including the FieldSchema
entries for password in the IMAP and SMTP-related schema definitions, to accept
a credential or secret reference instead of a plaintext password, and resolve
the secret server-side inside the corresponding request handling logic rather
than in the JSON-RPC surface.
- Around line 97-114: The triage schema is stricter than the handler:
`handle_triage_message` accepts any one of `sender`, `subject`, or `body`, but
the `FieldSchema` entries currently mark all three as required. Update the
schema definition in `operator_inbox::schemas` so the generated contract matches
`handle_triage_message` by making these fields optional/allowing any single
field to be present, while keeping the `FieldSchema` entries for `sender`,
`subject`, and `body` aligned with the handler’s validation logic.

In `@src/openhuman/util.rs`:
- Around line 663-668: The now_epoch() helper is swallowing SystemTime clock
errors by falling back to 0, which can make fresh records appear oldest and
break retention/ordering. Update now_epoch() in util.rs to surface the time
error instead of returning a real-looking timestamp, and adjust callers such as
TriageRecord.created_at and the archived-record eviction path in
operator_inbox::engine to handle the failure explicitly.
- Around line 652-669: The new shared helpers in util.rs should be moved out of
the oversized catch-all module to keep it within the module-size cap. Relocate
uuid_v4 and now_epoch into a smaller, more focused shared module used by the
domain modules, and update any call sites to reference the new location instead
of openhuman::util so util.rs stops accumulating generic APIs.

---

Minor comments:
In `@src/openhuman/operator_inbox/imap_client.rs`:
- Line 142: participant_count is only tracking senders via the IMAP parsing
path, so recipients are missing from the thread participant set. Update the
participant collection in the inbox parsing logic around imap_client and the
email/thread aggregation so each message adds both the sender (email.from) and
all recipient addresses to the same participants set. Make the change in the
code that builds the thread metadata/participant list so participant_count
reflects sender plus recipient(s), not just the inbound sender.

In `@src/openhuman/operator_inbox/parser.rs`:
- Around line 92-93: The reply detection in parser.rs is still case-sensitive
because it only checks subject prefixes in a fixed form. Update the is_reply
logic in the parser function to treat the reply prefix case-insensitively so
subjects like lowercase re: are classified as replies even without In-Reply-To,
while preserving the existing in_reply_to check.

In `@src/openhuman/operator_inbox/rpc.rs`:
- Around line 310-319: The triage_rpc test is still exercising the live chat
provider path through handle_triage_message, which makes it flaky and
environment-dependent. Update the test to avoid any real LLM/network dependency
by mocking or stubbing the chat provider used by handle_triage_message, or by
injecting a deterministic test provider before calling it, so the assertion only
validates the triage logic and not external configuration.

---

Nitpick comments:
In `@docs/boost-vc-capability-plan.md`:
- Around line 69-77: The architecture section is pointing contributors to the
wrong module layout for src/openhuman/*/. Update the capability-plan wording to
match the repo’s canonical shape: export-focused mod.rs plus types.rs, store.rs,
ops.rs, schemas.rs, tools.rs, and bus.rs, with business logic in ops.rs and
controller wiring in schemas.rs. Also remove references that imply engine.rs,
rpc.rs, or about_app/catalog.rs are the expected files, and align the guidance
with the existing module symbols so follow-up work lands in the correct places.

In `@src/openhuman/operator_inbox/engine.rs`:
- Around line 1-8: The operator_inbox domain is using non-canonical entry files,
so move it back to the standard openhuman layout by replacing the standalone
engine.rs-style entrypoint with a mod.rs-based module structure and splitting
domain logic into the expected types.rs, store.rs, ops.rs, schemas.rs, tools.rs,
and bus.rs modules. Update the operator_inbox module wiring so the
engine-related symbols (like the triage/draft engine imports and now_epoch
usage) are exposed through the canonical module tree instead of a custom file
layout.

In `@src/openhuman/operator_inbox/imap_client.rs`:
- Around line 173-177: The logging in the thread-building step is too
high-visibility for an operational counter. Update the log call in the IMAP
thread construction path (the block emitting "[operator-inbox-imap] threads
built") from info to debug, keeping the existing thread_count and email_count
fields so it remains available for troubleshooting without defaulting to
info-level output.
- Around line 301-537: The #[cfg(test)] block in imap_client.rs is pushing the
module over the size limit. Move the entire tests module, including helpers like
sample_emails and the unit tests for build_threads, validate_imap_config,
validate_smtp_config, build_priority_prompt, build_draft_prompt,
parse_priority_response, and extract_followup_deadline, into a separate
imap_client_tests.rs file. Keep the production code in imap_client.rs and wire
the new test module in so the existing test coverage remains unchanged.

In `@src/openhuman/operator_inbox/schemas.rs`:
- Around line 538-559: The schemas.rs module exceeds the repo size guideline,
and the test-only block is contributing to the length. Move the #[cfg(test)]
tests from the tests module into a separate schemas_tests.rs module, or factor
repeated schema-building helpers out of all_controller_schemas/schemas-related
code to reduce duplication and bring the main module under the limit. Keep the
behavior of handlers_match, namespace, and unknown unchanged while relocating
them so the core schemas module stays below the line cap.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 1f5f966a-a943-40c1-9300-7109ac4fac56

📥 Commits

Reviewing files that changed from the base of the PR and between 2652db7 and 6634a08.

📒 Files selected for processing (18)
  • Cargo.toml
  • docs/VC_CAPABILITIES.md
  • docs/boost-vc-capability-plan.md
  • src/core/all.rs
  • src/openhuman/about_app/catalog_data.rs
  • src/openhuman/about_app/types.rs
  • src/openhuman/mod.rs
  • src/openhuman/operator_inbox/connection.rs
  • src/openhuman/operator_inbox/engine.rs
  • src/openhuman/operator_inbox/imap_client.rs
  • src/openhuman/operator_inbox/mod.rs
  • src/openhuman/operator_inbox/parser.rs
  • src/openhuman/operator_inbox/poller.rs
  • src/openhuman/operator_inbox/rpc.rs
  • src/openhuman/operator_inbox/schemas.rs
  • src/openhuman/operator_inbox/types.rs
  • src/openhuman/util.rs
  • tests/json_rpc_e2e.rs

@Sathvik-1007

Copy link
Copy Markdown
Contributor Author

Closing — these need to be restacked properly with CI passing. Will resubmit once #4142 (shared wiring) lands and each domain PR compiles cleanly against it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agent Built-in agents, prompts, orchestration, and agent runtime in src/openhuman/agent/. feature Net-new user-facing capability or product behavior. rust-core Core Rust runtime in src/: CLI, core_server, shared infrastructure.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant