In-App Extensions

An in-app extension is a Store package that extends an APP instead of being an app of its own — the way an in-app purchase extends a phone app. A person never meets it as a tile on their home: they meet it inside the app it belongs to, get it (or buy it) there, and from then on its settings are part of that app.

Host app Where Its extensions
Threads AI/AiThreads the coding-assistant harnesses (Claude Code, Copilot, Codex, …) and the model providers (Anthropic, OpenAI, Azure AI Foundry, …)
Person app /{user}/Settings (Settings by Owner) the signing authority — Electronic Signature, with Swisscom AIS, Skribble and DeepSign

Why not apps of their own: a harness means nothing outside a thread, and a signature provider means nothing outside the person who signs. As separate apps they were tiles on every home that each opened a page asking you to go somewhere else first.

The three parts

1. The extension declares its host — hostedIn + extensionSlot

On the package root (PluginContent, owned by the Store):

"hostedIn": "{user}/Settings",
"extensionSlot": "signingAuthority"

Nothing else changes about the package: it keeps its entitlement, its gating, its install plan, its cover at its own URL, and its own NodeTypes.

2. The host offers it — the Store's Extensions shelf

The host embeds ONE shared control, served by the Store:

Controls.LayoutArea(new Address("Store"), new LayoutAreaReference("Extensions") { Id = "AI/AiThreads#modelProvider" })

The shelf lists every package whose hostedIn is the host (and, after #, whose extensionSlot is the slot). Each entry is the package's card plus the package's own CoverCta — Get, Buy (a Stripe order), Update or Uninstall, decided on the package's hub from the viewer's entitlement and install state. That button is the in-app purchase: the host never learns what a purchase is, and the Store's one acquisition chain (entitle → stamp → install) runs unchanged.

The person app's shelf is itself a person-app tab — Extensions — contributed by the Store as data (below), so a mesh without the Store simply has no Extensions tab.

3. The acquired extension surfaces in the host

For the person app — compiled in core, so a node-native package cannot register a tab on it — the surface is a UiContribution with Context: PersonApp:

{
  "$type": "UiContribution",
  "context": "PersonApp",
  "address": "Signature/Workspace",
  "area": "SigningAuthority",
  "label": "Signing authority",
  "labelKey": "personApp.signingAuthority",
  "order": 80,
  "requiredPermission": "Update",
  "gates": { "requireAddressAccess": true }
}

For a compiled host (Threads, in MeshWeaver.AI) the host renders its extensions' settings itself, as it always could — the harness option links to {user}/Harness/{id}; a model provider opens its own entry point.

The rules

Where it is implemented

Part Code
PersonApp context, RequireAddressAccess gate UiContribution / UiContributionGates (MeshWeaver.Graph), UiContributionProjection.ProjectPersonAppTabs, SettingsMenuItemsExtensions.ContributedPersonAppTabs / ApplyAddressAccess; tests PersonAppContributionTest
hostedIn, extensionSlot, the Extensions shelf, the person app's Extensions tab Store package (Store/Core/Source/PluginContent.cs, Store/Catalog/Source/ExtensionShelf.cs, Store/PersonAppTabs/Extensions) — MeshWeaver.Plugins
Threads app sections AiThreadsApplication (MeshWeaver.Plugins src/MeshWeaver.AI)
Signing authority Signature package (Signature/PersonAppTabs/SigningAuthority, SigningAuthority area of Signature/Workspace) — MeshWeaver.Plugins