Skip to content

feat(sharing): external client roster on the Sharing tab (trinity-enterprise#20)#1335

Merged
vybe merged 2 commits into
devfrom
feature/20-client-roster
Jul 2, 2026
Merged

feat(sharing): external client roster on the Sharing tab (trinity-enterprise#20)#1335
vybe merged 2 commits into
devfrom
feature/20-client-roster

Conversation

@dolho

@dolho dolho commented Jun 24, 2026

Copy link
Copy Markdown
Contributor

Summary

Surfaces the external client roster on the agent Sharing tab — outside channel users (no Trinity account) who have messaged the agent, aggregated across Telegram + WhatsApp and ordered by most-recent activity. The per-channel activity (verified_email, message_count, last_active) was already collected but never shown.

Read-only surface for the Access/Sharing redesign (epic trinity-enterprise#16). Per-client block / revoke / approve controls build on this next (trinity-enterprise#21).

Changes

  • dblist_clients_for_agent() join methods (binding → chat_links) on Telegram + WhatsApp ops; exposed via the database.py facade
  • serviceservices/client_roster_service.py: cross-channel aggregation, sort by last_active desc (never-active last), per-channel failure degradation
  • apiGET /api/agents/{name}/clients (OwnedAgentByName, read-only, DB-sourced so it renders when the agent container is stopped); ClientRosterEntry model is channel-extensible
  • ui — read-only Client Roster table in SharingPanel.vue
  • teststests/unit/test_client_roster.py (5): join normalization, tenant isolation, sort/nulls-last, channel-failure degradation
  • docs — requirements §44, architecture endpoint + service entries

Scope

Roster v1 = Telegram + WhatsApp (the channels recording the full email/count/last-active triple). Slack (verifications carry email but no activity counters) and VoIP (call logs) are additive follow-ups — the response model already accommodates them without a contract change.

Testing

24 passed — 5 new roster tests + existing Telegram/WhatsApp channel suites, no regressions.

Related to Abilityai/trinity-enterprise#20

🤖 Generated with Claude Code

@dolho
dolho requested a review from vybe June 24, 2026 09:23
@github-actions

Copy link
Copy Markdown

⚠️ Nightly unit-suite check skipped — merge conflict against dev.

Resolve by running git merge dev locally and pushing the result. The next nightly run will re-test once the conflict is gone.

@vybe vybe 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.

Validated via /validate-pr — no schema/security issues, but CONFLICTING with dev after #1317. #1317 restructured SharingPanel.vue (operator management moved to the new Access tab; the panel was reframed around Identity Proof / access policy). This PR was built on the old standalone "Team Sharing Section" layout, so the SharingPanel.vue conflict is a semantic re-integration — the Client Roster section needs to be grafted onto the new structure (the #18/#20 Sharing redesign you scoped), not just line-merged. The backend conflicts (sharing.py imports, architecture.md table row) are trivial.

Please rebase onto current dev.

— posted via /validate-pr

@dolho
dolho force-pushed the feature/20-client-roster branch from 02567d2 to de41ccd Compare June 25, 2026 14:28
@dolho

dolho commented Jun 25, 2026

Copy link
Copy Markdown
Contributor Author

Rebased onto dev + review

Fix (rebase): resolved a semantic conflict, not just mechanical. PR was branched before #17 (the Access-tab split), so its base still carried the Team Sharing allow-list section in SharingPanel that #17 moved to AccessPanel. Resolution:

  • SharingPanel.vue: dropped the re-inherited Team Sharing section (its shareWithUser/shareEmail handlers no longer exist on dev — keeping it would've been broken), kept the new Client Roster section.
  • routers/sharing.py: unioned imports + kept both /access (Client/Viewer User Role (AUTH-002) #17 operators) and /clients (Audit Trail System (SEC-001) #20 roster) endpoints.
  • architecture.md: kept both endpoint rows.

Verified: SharingPanel.vue compiles (vue/compiler-sfc), routers/sharing.py compiles, test_client_roster.py 5/5 pass. Now mergeable.

Review: backend is clean and additive — client_roster_service aggregates Telegram + WhatsApp channel users, /clients is owner-only (OwnedAgentByName) and DB-sourced (renders when the agent is stopped). ✅

⚠️ Coordination flag (important)

This adds the Client Roster to the current (pre-reframe) SharingPanel layout. My open PRs #1347 (#18 reframe) and #1348 (#19 channel dialogs) rewrite SharingPanel.vue substantially. When those merge, this roster section will re-conflict and need re-placing into the reframed tab.

Recommended sequencing: merge #1347#1348 → then rebase #1335, slotting the Client Roster into the reframed Sharing tab (it's epic #20, designed to sit alongside the reframe). The backend half is independent and can land anytime.

@dolho

dolho commented Jun 26, 2026

Copy link
Copy Markdown
Contributor Author

Rebased onto dev — Client Roster grafted into the reframed Sharing tab

Resolved the semantic SharingPanel.vue conflict from #1317/#18/#19 (the reframe: "Share this agent" → access policy → Channels disclosures → Distribution). The roster was built on the old standalone layout; re-integrated as follows:

  • Removed the obsolete pieces the old branch carried (the inline roster between the Slack/Telegram disclosures, the re-inherited standalone SlackChannelPanel block) so dev's ChannelDisclosure channel list stays intact (Slack · Telegram · WhatsApp · Voice).
  • Added the Client Roster as its own section placed after Channels, before Distribution — it reads naturally as "who's actually reaching this agent" right under the channel config. Same read-only table (Client · Channel · Verified email · Messages · Last active), empty-state preserved.
  • Script: kept dev's accessMode computed and the roster state (clients/loadClients/formatLastActive); loadClients() stays wired into the agentName watch alongside loadPolicy/loadAccessRequests.

Backend is unchanged and additive — GET /api/agents/{name}/clients (OwnedAgentByName, DB-sourced) auto-merged clean.

Verification

  • vue/compiler-sfc parse + compileScriptSFC compiles clean, no conflict markers.
  • test_client_roster.py5 passed.

Now mergeable.

@dolho
dolho force-pushed the feature/20-client-roster branch from f2a5731 to 7244bb3 Compare June 29, 2026 14:21
…erprise#20)

Surface external channel users (no Trinity account) who have messaged an agent,
aggregated across Telegram + WhatsApp into one read-only roster on the Sharing
tab. Activity (verified_email, message_count, last_active) was already recorded
per channel link but never shown.

- db: list_clients_for_agent() join methods (binding -> chat_links) on the
  Telegram + WhatsApp ops, exposed via the database facade
- service: client_roster_service aggregates + sorts by last_active desc
  (never-active last), degrades per-channel on read failure
- api: GET /api/agents/{name}/clients (OwnedAgentByName, read-only, DB-sourced
  so it renders when the agent container is stopped); ClientRosterEntry model
  is channel-extensible (Slack/VoIP additive)
- ui: read-only Client Roster section in SharingPanel.vue
- tests: join normalization, tenant isolation, sort/nulls-last, channel-failure
  degradation (5 new, all green)
- docs: requirements section 44, architecture endpoint + service entries

Roster v1 = Telegram + WhatsApp (channels recording the full
email/count/last-active triple). Per-client block/revoke/approve controls are
the follow-up (trinity-enterprise#21).

Related to Abilityai/trinity-enterprise#20

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@dolho
dolho force-pushed the feature/20-client-roster branch from 7244bb3 to ff90b92 Compare June 30, 2026 10:10
@dolho
dolho requested a review from vybe June 30, 2026 11:03
Resolve the requirements.md conflict from the #1406/#1412 monolith→area-file
split: relocate the "External Client Roster (#20)" requirement (§46) from the
retired docs/memory/requirements.md monolith into docs/memory/requirements/
public-access.md, which owns the Telegram/WhatsApp channel integrations the
roster aggregates. All code (database.py, models.py, SharingPanel.vue,
architecture.md) auto-merged clean.

@vybe vybe 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.

Prior CHANGES_REQUESTED is resolved (the semantic SharingPanel.vue re-integration after the Access-tab reframe was addressed across dolho's rebases). Re-validated: three-layer separation clean, ClientRosterEntry in models.py, tenant-isolation test present, no schema/migration/security/packaging gaps. Rebased onto today's dev — resolved the #1412 requirements-split conflict by relocating the roster requirement into requirements/public-access.md; all 4 required checks green. Approving.

@vybe
vybe merged commit d4741d2 into dev Jul 2, 2026
21 checks passed
vybe pushed a commit that referenced this pull request Jul 2, 2026
/#1335/#1406 conflicts

Resolutions:
- requirements.md: take dev's index (#1406 split); relocate the #1205 section
  into requirements/public-access.md as Section 47 (46 was taken by #1335's
  roster requirement)
- Alembic: renumber 0011_agent_ownership_public_channel_prompt -> 0012,
  re-parent on 0011_agent_ownership_tts_voice (#1397 landed first; whichever
  lands second renumbers)
- db/migrations.py: keep both MIGRATIONS entries (tts_voice + public_channel_prompt)
- routers/sharing.py + SharingPanel.vue + architecture.md: keep both #1335
  roster and #1205 public-prompt surfaces (independent features)
- .claude + src/backend/enterprise submodules: reset to dev's pointers (the
  branch pinned unrelated/stale commits)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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