8.9 KiB
PR4 Grounding — recon verified against live code (2026-06-16)
5-reader workflow wf_19393d8b-c2d (~669k tokens). Verdicts: Slice1 minor-drift · Slice2 confirmed · Slice3 confirmed · Slice4 MAJOR-drift · Slice5 minor-drift. Net: the build plan is sound; the recon DOCS had 2 material errors that change Phases C/D execution (not the architecture).
Material drifts (these change HOW a phase is built)
D-A (Slice 4, MAJOR) — Phase D binds to CapabilityRequestCard, NOT the approval gate
Recon doc 04-inline-in-chat.md pointed Phase D at ApprovalGate/InlineApprovalCard (the SSE approval_required singleton). Wrong. The live inline-install card is chat-blocks/CapabilityRequestCard.tsx, parsed from agent TEXT by capability-request-parser.segmentText (marker <!--waggle:capability_request {json}--> or legacy phrasing) and rendered by TextBlock.tsx:25-27. The build plan §2/§5 already names CapabilityRequestCard correctly — so the PLAN is right, only recon-04's framing was misleading. Phase D = (1) widen CapabilityRequest.kind 'skill'|'marketplace' → +'connector'|'mcp' (parser already passes kind through verbatim — parser.ts:22 — no parser change to ACCEPT, only to RENDER/DISPATCH); (2) add connector/mcp dispatch branches in CapabilityRequestCard.handleInstall routing through the shared store; (3) connector approve is FE-direct adapter.connectConnector(id,{token}) — token NEVER transits the boolean approval wire (PendingApproval.resolve(approved:boolean) — index.ts:168). Do NOT build a 3rd ConnectorOfferCard. Marker regex (\{[^}]+\}) forbids nested } — fine, a connector offer is flat {name,source,kind,reason}.
D-B (Slice 5, minor but load-bearing) — searchCapabilities has NO connector/mcp lane
searchCapabilities() only ever EMITS type ∈ {'native','skill','marketplace'} — never 'connector'|'mcp'|'plugin' (truth table below). ALL marketplace results collapse to type:'marketplace' because the searchMarketplace closure (index.ts:646) drops waggle_install_type. Connectors aren't even an input (they live in connectorRegistry, not marketplace.db). So §09's literal "connector + skill + tool" three-up box CANNOT be produced by pickOnePerKind(candidate.type). → PHASE-C DECISION (founder-gate when I reach C): either (a) honest scope — group on what the engine really emits: native-tool + skill + marketplace-pkg, each with real matchReason "why" + installAction (delivers a true three-up box, no connector lane); or (b) add a connector lane — keyword-match connectorRegistry.getDefinitions() in the route and inject connector candidates + carry waggle_install_type through MarketplaceCandidate so mcp/skill packages bucket correctly (scope+). Recommend (a) for v1; flag (b) as the "literal §09" upgrade.
Verified contracts (verbatim — bind to these)
Phase A — shared install store (UNAFFECTED by drifts; fully grounded, build now)
- No FE store exists (grep InstallProvider/useInstallStore/InstallStore → none). Providers pattern = React context (
ServiceProvider/ShellContext/ThemeProvider), no Redux/Zustand.useService()exposes{connecting}; gate authed hydrate on connect-settled (MarketplaceApp.tsx:101). - Value type + normalizers REUSE
apps/web/src/lib/extension-catalog.ts:Extension{id,name,description,type,source,installed,lifecycle:'available'|'installed',installable,kind:'package'|'pack'|'federated',packageId?,scanStatus?,trust?,category?,openIn?}. ID scheme keys the store:pkg:<n>|pack:<slug|id|name>|connector:<id>|agent:<id>|model:<id>|template:<id>|mcp:<id>.installableis hard-coded FALSE today for connector/mcp/pack → Phase B flips connector/mcp to true once the store can install them. - Install verbs (adapter, REUSE): skill
installSkill(id, source:'starter'|'pack'|'marketplace', packageId?)| starterinstallPack(skillId):void| marketplace pkginstallMarketplacePackage(packageId:number):Promise<Response>(RAW — inspect .status/.json; 403 TIER fireswaggle:tier-insufficient, 403{blocked}, 422 fail) | connectorconnectConnector(id,{token?|apiKey?...}):void(UNGATED) | mcpinstallMcp(mcpId,opts?):{installed,server?,status?,requiresApproval?}(PRO). - Hydrate reads:
GET /api/skills{skills:[{name,...}],count} ·GET /api/connectors{connectors:ConnectorDefinition[]} (status derived live: connected/disconnected/expired/error) ·GET /api/mcps{mcps:McpListItem[],total,installed} (per-row installed/status/scope) ·GET /api/marketplace/installed{installations,total} +/searchper-rowinstalledflag (the ONLY queryable installed-set; skills/connectors/mcp derive at read). - Reconcile hazard: install can 403(TIER)/403(SecurityGate)/422 AFTER an optimistic flip → store rolls back + routes to upgrade(
waggle:tier-insufficient)/approval; count bar must never show a gate-rejected item. - Count bar (D1 ratified): FE-derived GLOBAL count (install_audit has NO workspace_id, is append-only + OSS-excluded;
getInstalledCount()is global). Label honestly "installed", not "in this workspace".
Phase B — Variation A grid
ExtensionCardprops{ext, installing?, onInstall?(ext), onUninstall?(ext), onOpenIn?(appId)}; testids rootextension-card, installextension-install-${ext.id}. Today: generic Install/Remove/Open-in, no type-aware verbs, no micro-states. Phase B adds Add/Connect/Enable + idle→in-progress→done from the store.MarketplaceAppexports (named)installRiskFor/buildRemoveRequest/buildInstallRequest+ default. Facets today =['all',...EXTENSION_TYPES]= 7. D2 collapse → 4 (All/Skills/Connectors/MCP). Count today =visible.length→ replace with store count bar.- Tests to rewrite = 10
it()blocks (recon said 11 — DRIFT) inapps/web/src/test/phase4b-marketplace-extend.test.tsx; adapter mocked viavi.hoisted. Pinned testids: extension-facets, federated-note, extension-card, extension-install-pkg:7/:9, approval-modal(-approve); call shapesgetMarketplace({type:'skill',limit:30})/({type:'mcp',limit:30}). - Path correction: ExtensionCard lives at
components/os/apps/extend/ExtensionCard.tsx(nested under apps/), NOTos/extend/.
Phase C — POST /api/marketplace/agent-search (route under packages/server/src/local/routes/, NOT src/routes/)
- Engine
searchCapabilities(input:SearchCapabilitiesInput):AcquisitionProposalexported from@waggle/agent(index.ts:326). Input{need, installedSkills:[{name,content}], starterSkillsDir, nativeToolNames?, marketplaceCandidates?}— engine does NO marketplace IO (candidates passed in). CapabilityCandidate{name,type,availability,description,source,matchScore,matchReason,installAction,trust?}. Emitted-type truth table: native→type:'native',availability:'active',installAction:null; active skill→'skill','active',null; starter→'skill','installable','install_capability'; marketplace→'marketplace','installable','install_capability'.recommendation= SINGLE candidate;candidatescapped at 8;summaryis markdown for the model — return STRUCTURED candidates/recommendation, NOT summary (skill-tools.ts:476 discards it).- Deps to lift (index.ts:620-657):
nativeToolNames= union of mind/system/plan/git/document tool names (closure-local; OMITS search/browser/cli/cron/connector — so web_search can't score even today);getInstalledSkills=server.agentState.skills ?? loadSkills;searchMarketplace=marketplaceDb.search({query,limit:10}).packages → MarketplaceCandidate(drops waggle_install_type + score). Ride-along auditfastify.auditStore?.record({...action:'proposed',initiator:'agent'}).
Phase D — inline (see D-A above for the corrected target)
- Boolean approval channel CONFIRMED:
PendingApproval.resolve:(approved:boolean)(index.ts:168);POST /api/approval/:requestIdbody{approved,always?,reason?,sourceWorkspaceId?}— NO token.adapter.respondApproval(requestId,approved,opts). - Connect route
POST /api/connectors/:id/connectbody{token?|apiKey?,refreshToken?,expiresAt?,scopes?,email?}→ vaultsetConnectorCredential; 404/400/503; authType =registry.get(id)?.authType ?? 'bearer'. Card must branch on authType — token-paste fits bearer/api_key only; OAuth (oauth2) needs redirect → Hub fallback (D3). Google family connector-id ≠ provider key (OAUTH_PROVIDER_FOR_CONNECTOR). - Gate a new agent offer/connect tool: add exact name to
ALWAYS_CONFIRM(confirmation.ts:16) OR name itconnector_<id>_<writeverb>_*(CONNECTOR_WRITE_PATTERNS).
install_audit (do not touch as state)
Append-only INSERT-only, NO workspace_id, action vocab = proposed|approved|installed|rejected|failed|blocked|uninstalled (NO 'synced'/'connected' — adding one is a CHECK migration). OSS-EXCLUDED (interleaved in hive-mind-core schema.ts/db.ts; curated out by hand) → install_audit-only changes have nowhere to land on the mirror. NEVER the source of installed state.