14 KiB
Gap Card — S07 · Connector Hub
Execution model: in-place incremental refactor of
apps/web+ targeted backend extensions. PRD acceptance criteria win over the (directional) mockup. Every claim below is grounded in real files. PRD =docs/Waggle_OS_UX_Refactor_Master_Handoff_Package/Waggle_OS_UX_Refactor_PRD.md.
1. Screen & purpose
Connector Hub (PRD §12.7, blueprint Screen 7, dock app id connectors). Purpose: "Connect
external tools and data." It is the Extend layer surface for service connectors (distinct from
S08 MCP Hub — PRD §12.8 — which the current ConnectorsApp folds in as a second "MCP Servers" tab
via McpCatalog; the refactor should keep the catalog but the two are separate PRD screens).
PRD §12.7 acceptance criterion (the bar this card must hit):
"Users see exactly what tools are connected and whether data is flowing."
The "whether data is flowing" half is the load-bearing gap — today there is no sync state, no last-sync timestamp, and no recent-activity surface anywhere in the stack.
2. Required states (PRD/Blueprint)
PRD §12.7 functional requirements + blueprint Screen-7 state list (_blueprint_extracted.txt:326-331,
state list :467):
- Connected connectors with status and last sync.
- Available connectors grouped by category.
- Health surface: health, recent sync activity, token expiry, and errors.
- Actions: connect, sync now, manage, revoke, reconnect.
- Recommended connectors based on onboarding + workspace needs.
- State machine per blueprint:
connected · disconnected · expired token · syncing · failed · recommended(plus list-levelloading / empty / populated / error). - RBAC/trust (PRD §17.3, §18.1): "Connectors require consent and revocation path" + "Connector/MCP install audit" (append-only trust trail).
Mockup (screen_07_connector_hub.png, directional) adds visual chrome the PRD does not mandate but
implies: top tabs (Connected / Available / Recommended / Built by Waggle), a Connector Summary donut
(connected/syncing/error/disconnected counts), a System Health tile (API/sync/error-rate gauges), a
Recent Activity feed, and a "Need Help?" card. Treat these as direction, not a contract.
3. Current state in repo (disposition: rework)
Frontend — apps/web/src/components/os/apps/ConnectorsApp.tsx (356 LOC):
- Two-tab sidebar: Services (native connectors) + MCP Servers (
McpCatalog). Filter rail =all / connected / available. - Renders connectors from
adapter.getConnectors()grouped by a hardcodedCATEGORIESmap (:41-50)- hardcoded
SETUP_HINTS(:52-60). Per-row expand reveals capability chips, setup steps, and a token/API-key input.
- hardcoded
- Connect flow (
handleConnect,:113-135): writes credential viaadapter.addVaultSecret('connector:<id>')then callsadapter.connectConnector(id)(note: the adapter'sconnectConnectorPOSTs no body —adapter.ts:1161; the credential is pre-seeded in vault). Disconnect =adapter.disconnectConnector(id). shouldResetCredentialInputs(:69-71) is an exported, regression-tested pure guard (phase5b-connectors.test) — keep, it prevents credential cross-submission.- Subcomponents (
apps/web/src/components/os/apps/connectors/):BrandTile.tsx,brand-identity.ts(logo/colour identity — keep, reuse),McpCatalog.tsx+McpServerCard.tsx+mcp-registry.ts(the MCP tab — belongs to S08, leave for that card). - What's MISSING in the UI vs PRD: no last-sync, no health probe call, no "sync now", no "revoke"
(only disconnect), no recommended tab, no recent-activity feed, no summary/health tiles, no token-expiry
surfacing.
adapter.getConnectorHealthexists (adapter.ts:1156) but is never called anywhere (grep-confirmed) — the UI shows only the coarse liststatus.
Backend — packages/server/src/local/routes/connectors.ts (121 LOC): 4 routes —
GET /api/connectors, GET /api/connectors/:id/health, POST /api/connectors/:id/connect,
POST /api/connectors/:id/disconnect. Backed by fastify.connectorRegistry (ConnectorRegistry in
packages/agent/src/connectors/connector-registry.ts; 30 connectors registered via
setup-connectors.ts; per-connector healthCheck() impls exist in every
packages/agent/src/connectors/*-connector.ts).
Disposition: rework — the shell, brand tiles, category grouping, and connect/disconnect flow are solid and reusable, but the screen is missing the entire "is data flowing?" dimension (sync/health/ activity/recommended/audit). This is additive rework on a good base, not a rebuild.
4. Frontend work
Rework ConnectorsApp.tsx into the PRD Connector Hub (keep MCP tab in place for S08):
- Add tabs matching PRD:
Connected · Available · Recommended(mockup also shows "Built by Waggle" — optional). The existingall/connected/availablefilter rail collapses into these tabs. - Per-row health + last sync: lazy-call
adapter.getConnectorHealth(id)for connected rows (already in adapter, currently dead) to renderstatus(connected/expired/error),tokenExpiresAt(token-expiry badge),lastChecked, and (new)lastSyncAt. Extract a newConnectorCard.tsxcomponent (blueprint namesConnectorCard,:488) from the inline row JSX (:262-340) — status pill, last-sync, health dot, actions menu (Connect / Sync now / Manage / Revoke / Reconnect). - Recommended tab: reuse the existing
recommendConnectors(personaId)from@waggle/shared(connector-recommendations.ts:146) — today wired only into the MCP tab (McpCatalog.tsx:69), not service connectors. ForwardpersonaId(already a prop,:28) to a recommended-connectors section. - Summary + health + activity tiles (mockup, directional): a small
ConnectorSummarydonut from client-side status counts (no new endpoint), a System Health tile from the newGET /api/connectors/healthaggregate (§5), and a Recent Activity feed from the newGET /api/connectors/activity(sync/connect/revoke events). The blueprint's reusableTimelineandEvidencePanel(:488) can host the activity list. - Actions wiring: add
syncConnector(id)→POST /api/connectors/:id/sync; addrevokeConnector(id)→POST /api/connectors/:id/revoke(PRD verb; aliases existing disconnect); "Reconnect" reuses the existing connect flow. Surface asyncingrow state + toasts onfailed. - State: extend the local
Connectorinterface (:31-38) with optionallastSyncAt,tokenExpiresAt,health,category,recommended. Move category off the hardcoded map onto theConnectorDefinition.categoryfield that already exists in@waggle/shared(types.ts:299) but is not yet emitted by the row data — reconcile so the UI stops carrying its ownCATEGORIES.
Adapter (apps/web/src/lib/adapter.ts) — add 2 methods, reuse 1:
syncConnector(id): POST /api/connectors/:id/sync(NEW).revokeConnector(id): POST /api/connectors/:id/revoke(NEW; or alias to existingdisconnectConnector).getConnectorHealth(id)already exists (:1156) — start calling it; optionally addgetConnectorsHealth(): GET /api/connectors/healthaggregate +getConnectorActivity().
5. Backend work (PRD §16.9 connectors subset)
§16.9 spans Connectors + MCPs + Marketplace. This card scopes the Connectors rows only; MCP rows (
/api/mcps*) and/api/marketplace*belong to S08 / S13.
| PRD §16.9 endpoint | Status | Action |
|---|---|---|
GET /api/connectors |
EXISTS | connectors.ts:6 → registry.getDefinitions(). EXTEND the definition payload to carry category (already on the type, types.ts:299) + lastSyncAt so the UI drops its hardcoded CATEGORIES/sync-shim. |
POST /api/connectors/:id/connect |
EXISTS | connectors.ts:55. EXTEND: write an install-audit entry (see audit gap below) on success. |
POST /api/connectors/:id/sync |
MISSING | NET-NEW route in connectors.ts. No sync substrate exists — WaggleConnector (connector-sdk.ts) has connect/healthCheck/execute but no sync(). Minimum viable: probe healthCheck() + record a lastSyncAt timestamp (persist in vault sub-key connector:<id>:lastSync or a small store) + emit an activity event. Full sync (re-pull data) is a larger connector-SDK addition — flag as phased. |
POST /api/connectors/:id/revoke |
PARTIAL | Closest is POST /api/connectors/:id/disconnect (connectors.ts:107, removes connector:<id> + sub-keys). EXTEND: add a /revoke alias (or rename) that additionally writes an install-audit action:'rejected'/revoke entry. Same intent, PRD verb. |
Additional backend work the PRD requires but §16.9 does not enumerate (implied by §18.1 / §12.7):
- Install/consent audit on connect & revoke (MISSING write path).
connectors.tsdoes NOT callfastify.auditStore.record(...)(grep-confirmed — onlymarketplace.ts:224-319writes audit today). PRD §18.1 mandates "Connector/MCP install audit". The substrate exists:InstallAuditStore(packages/core/src/install-audit.ts) withAuditCapabilityTypeincluding'connector'(:22), actionsinstalled/rejected(:15). EXTEND connect/revoke to record entries. ⚠ Latent enum-drift bug to avoid: TSAuditRiskLevelallows'critical'(install-audit.ts:16) but both DDL CHECKs only allowlow/medium/high(install-audit.ts:65,schema.ts:130) — pass'high', never'critical'. - Audit read route (MISSING). No HTTP surface lists the audit trail (
getRecent/getByCapabilityhave no route). The Connector Hub's "Recent Activity" + the Extend trust-trail view need a NEW read, e.g.GET /api/connectors/activity(or a sharedGET /api/extend/auditfiltered totype:'connector'). Net-new but reads existinginstall_auditrows — no migration. - System-health aggregate (optional, mockup). A
GET /api/connectors/healththat fansregistry.healthCheck()over connected connectors for the System-Health tile. Net-new thin aggregator; no new substrate.
.mind migration: None required. install_audit already exists in schema.ts:119; connector
credentials live in the vault, not .mind. lastSyncAt can live as a vault sub-key or a tiny store —
no schema change. (The audit table's pre-existing risk_level CHECK drift is a code-discipline note, not
a migration.)
6. Shared types needed (PRD §15 vs lib/types.ts)
ConnectorDefinition/ConnectorHealth/ConnectorStatusalready exist inpackages/shared/src/types.ts:266-312and are the canonical contract — the FE should import these instead of the ad-hoc localConnectorinterface inConnectorsApp.tsx:31-38(a lossy duplicate).lib/types.tshas a thinConnector(frontend mirror, §(d) substrate inventory) — reconcile it to the sharedConnectorDefinitionshape (addcategory,tokenExpiresAt,lastSyncAt, optionalhealth).- New optional fields:
ConnectorDefinition.lastSyncAt?: string(and surfacecategorywhich is already declared but not always populated). PRD §15.2 lists anExtensionTypeunion — connectors are one member; not blocking for S07 but should be defined when S13 (Marketplace) lands. ConnectorRecommendationalready exists (connector-recommendations.ts:38) — reuse as-is for the Recommended tab.
7. Dependencies (screens/phases first)
- Onboarding (S12, PRD §12.12 Tool Discovery / §16.1) feeds the Recommended tab's "based on
onboarding" signal.
recommendConnectors(personaId)works standalone today, so S07 is not blocked — but the richer "based on workspace needs" recommendation improves once onboarding tool-discovery answers are persisted. - S08 MCP Hub shares this app shell (the MCP tab). Coordinate the tab split so reworking S07 doesn't regress the MCP catalog; ideally S07 and S08 land in the same phase or S07 first with the MCP tab untouched.
- Extend trust-trail / Marketplace (S13) shares the install-audit read route — build the audit write + read once, consume in both. Sequence the audit endpoint before/with S07's Recent Activity.
- No hard dependency on Home/Workspace screens.
8. Effort: M
Frontend rework on a solid existing shell (new tabs, ConnectorCard extraction, wire two dead/easy adapter
paths) is modest. The backend lift is small-but-real: /sync and /revoke routes + audit write on
connect/revoke + an audit read route + a health aggregate — all over existing substrate (registry, vault,
install_audit), zero migration. The one true unknown (real data-pull sync in the connector SDK) is
phaseable to a health-probe-only v1, which keeps this M rather than L.
9. Open questions
- Sync semantics. Does "sync now" mean (a) re-probe health + stamp
lastSyncAt(v1, cheap), or (b) re-pull/refresh cached connector data (needs a newsync()onWaggleConnector+ a cache)? PRD §12.7 says "show recent sync activity" but connectors are currently runtime tools (execute(action)), not background data-syncers. Recommend v1 = health-probe + timestamp. lastSyncAtstorage. Vault sub-key (connector:<id>:lastSync) vs a small dedicated store vs aninstall_auditderived value? Vault sub-key is lowest-risk and matches the existingconnector:<id>:emailpattern.- revoke vs disconnect. Add
/revokeas a true alias ofdisconnect, or haverevokeadditionally purge OAuth tokens (<provider>_oauth_token,oauth.ts) + write a stronger audit entry? PRD §17.3 ("revocation path") implies the latter for OAuth connectors. - Audit scope. Connector-specific
GET /api/connectors/activityvs a sharedGET /api/extend/audit?type=connector? The latter serves S07 + S08 + S13 with one route (preferred). - Audit
.mindlocation.install_auditis per-.mind(per-workspace). Connectors are global (vault-scoped, not workspace-scoped). Confirm the audit-store decorator binds topersonal.mindso the global Connector Hub trail is consistent (substrate inventory §(d) flags this exact ambiguity). - Token-expiry source.
ConnectorHealth.tokenExpiresAtis populated only on the fallback path (connectors.ts:49); confirm each connector'shealthCheck()surfaces real expiry, else the expired-token state will never show for SDK-backed connectors.