Contrat addon — référence formelle
Ce document décrit l’interface formelle qu’un addon doit respecter pour s’intégrer dans arka-deck. Il complète le tutoriel écrire un addon avec les types exacts et les garanties contractuelles.
Source de vérité pour les types : core/ports/outbound/, core/domain/events/arka-event.ts.
manifest.json
Section intitulée « manifest.json »Chaque addon déclare un manifest.json à sa racine.
{ "name": "mon-addon", "version": "1.0.0", "kind": "capability", "description": "Description courte de l'addon.", "status": "active", "workers": ["nom-worker-1"], "dependencies": [], "entrypoint": "src/index.ts"}| Champ | Type | Valeurs | Obligatoire |
|---|---|---|---|
name | string | kebab-case | oui |
version | string | semver | oui |
kind | string | "capability" / "orchestration" / "feature" / "convention" | oui |
description | string | max ~200 chars | oui |
status | string | "active" / "draft" | oui |
workers | string[] | noms des workers rattachés | oui (vide si aucun) |
dependencies | string[] | noms d’autres addons requis | oui (vide si aucun) |
entrypoint | string | chemin relatif depuis la racine de l’addon | oui pour capability / orchestration |
label | string | label affichable | non |
runtime | string | "cli" / "web" / "shared" | non |
tier | string | "free" / "standalone" / "cortex" / "squad-business" | non |
exposes | object | métadonnées déclaratives | non |
Le schéma exécutable est composition/addons/manifest-schema.ts.
Les valeurs kind du manifest JSON sont alignées avec la catégorie runtime AddonManifest.category du registre TypeScript. Les providers LLM ne sont pas un kind addon : ils utilisent le schéma séparé ProviderManifestSchema.
Chargement first-party
Section intitulée « Chargement first-party »Au démarrage, la composition charge les manifests first-party via composition/addons/loader.ts et vérifie aussi les références addon ↔ worker. Les warnings sont journalisés, mais le chargement déclaratif ne remplace pas le câblage explicite des runtimes dans la composition root.
Limite publique actuelle
Section intitulée « Limite publique actuelle »Le registre d’addons est une brique interne et first-party. Il valide les manifests et fournit un contrat stable, mais arka-deck ne charge pas encore un marketplace tiers arbitraire en production.
Conséquence pour la documentation publique : parler de “contrat addon” ou “addons first-party extensibles”, pas d’un marketplace public déjà actif. Les ajouts runtime restent câblés explicitement dans la composition root.
Le registre accepte deux formes distinctes :
Addon: le registre appelleregister(deps)et construit le runtime.RegisteredRuntimeAddon: la composition root a déjà construit le runtime ; le registre le stocke tel quel et ne rappelle pasregister(deps).
Cette distinction évite de présenter les addons déjà câblés (cortex-actions, gouvernance-lite, memory-local) comme s’ils étaient construits par le registre.
Fonction register
Section intitulée « Fonction register »Le point d’entrée d’un addon est une fonction register<Name>Addon(deps) exportée depuis src/index.ts.
export interface MonAddonDeps { readonly clock: Clock; readonly filesystem: Filesystem; readonly eventBus: EventBus; readonly idGenerator?: IdGenerator; readonly resolveSession: (sessionId: string) => Promise<SessionContext | null>; readonly cortexBaseUrl?: string; // si l'addon fait des appels HTTP Cortex}
export interface MonAddonRuntime { readonly forMonAddon: ForMonAddon; readonly beforeTurnAugmenter: BeforeTurnAugmenter; readonly unsubscribe: () => void;}
export function registerMonAddon(deps: MonAddonDeps): MonAddonRuntime { // 1. Instancier les adapters // 2. Construire les use-cases // 3. S'abonner au bus // 4. Construire l'augmenter (si applicable) return { forMonAddon, beforeTurnAugmenter, unsubscribe };}Règle : unsubscribe() doit toujours être présent et idempotent. Il est appelé au shutdown.
Dépendances disponibles
Section intitulée « Dépendances disponibles »La composition (composition/core-container.ts) fournit ces dépendances à chaque addon :
| Dep | Type | Source | Usage |
|---|---|---|---|
clock | Clock | core/ports/outbound/clock.ts | Timestamp actuel (clock.now()) |
filesystem | Filesystem | core/ports/outbound/filesystem.ts | Lecture/écriture fichiers (allowlist) |
eventBus | EventBus | core/ports/outbound/event-bus.ts | S’abonner et publier des events |
idGenerator | IdGenerator | core/ports/outbound/id-generator.ts | Générer des IDs stables |
resolveSession | (sessionId) => Promise<Context|null> | Composition | Résoudre projectPath depuis sessionId |
cortexBaseUrl | string? | Env ARKA_DECK_CORTEX_URL | Override URL Cortex pour tests |
EventBus
Section intitulée « EventBus »export type Unsubscribe = () => void;
export interface EventBus { publish(event: ArkaEvent): Promise<void>; publishAsync(event: ArkaEvent): void; subscribe<T extends ArkaEventType>(type: T, handler: EventHandler<T>): Unsubscribe;}Garanties :
- Une erreur dans un handler n’impacte pas les autres (isolation via
allSettled) - L’ordre des handlers n’est pas garanti
unsubscribeest idempotent- Le bus n’est pas persistant — les events produits avant l’abonnement ne sont pas rejouables
Pour le guide détaillé, voir ../architecture/event-bus.
Catalogue des events (ArkaEvent)
Section intitulée « Catalogue des events (ArkaEvent) »Source de vérité : core/domain/events/arka-event.ts.
Workspace
Section intitulée « Workspace »| Type | Champs clés |
|---|---|
workspace.created | workspaceId, name, createdAt |
workspace.updated | workspaceId, name, description |
workspace.deleted | workspaceId |
| Type | Champs clés |
|---|---|
project.created | projectId, projectPath, workspaceId, name |
project.updated | projectId, name, workspaceId |
project.forgotten | projectId |
project.purged | projectId, projectPath, deletedPaths |
project.last-used | projectId, lastUsedAt |
project.detected-by-cwd | projectId, projectPath, cwd |
Provider instances
Section intitulée « Provider instances »| Type | Champs clés |
|---|---|
provider.instance.created | instanceId, manifestId, modelId, isDefault |
provider.instance.updated | instanceId, changes |
provider.instance.deleted | instanceId |
provider.instance.tested | instanceId, kind, ok, latencyMs, error? |
Catalogue / Installs
Section intitulée « Catalogue / Installs »| Type | Champs clés |
|---|---|
catalogue.refreshed | source, entriesCount |
agent.installed | projectPath, sourceId, version, localPath |
agent.removed | projectPath, sourceId |
agent.updated | projectPath, sourceId, fromVersion, toVersion |
agent.relinked | projectPath, sourceId |
agent.divergence-detected | projectPath, transitions, items |
Chat — session lifecycle
Section intitulée « Chat — session lifecycle »| Type | Champs clés |
|---|---|
chat.session.started | sessionId, projectId, projectPath?, agentId?, runtimeId, modelId, createdAt |
chat.session.resumed | sessionId, runtimeSessionId? |
chat.session.ended | sessionId, projectPath?, transcript, reason, endedAt |
chat.session.superseded | sessionId, supersededBySessionId, projectId, agentId, archivedAt |
chat.session.renamed | sessionId, name |
chat.session.deleted | sessionId |
chat.session.preparing.started | sessionId, projectId, agentId?, startedAt |
chat.session.preparing.ended | sessionId, runtimeSessionId?, durationMs, usage?, endedAt |
chat.session.preparing.failed | sessionId, error, durationMs, failedAt |
Chat — turn lifecycle
Section intitulée « Chat — turn lifecycle »| Type | Champs clés |
|---|---|
chat.turn.started | sessionId, userText, attachmentsCount, startedAt |
chat.turn.ended | sessionId, success: true, durationMs, usage? |
chat.turn.aborted | sessionId, reason, error? |
Chat — blocs
Section intitulée « Chat — blocs »| Type | Champs clés |
|---|---|
chat.block.started | sessionId, blockId, kind, tool? |
chat.block.ended | sessionId, blockId, kind, content? |
Chat — tools
Section intitulée « Chat — tools »| Type | Champs clés |
|---|---|
chat.tool.called | sessionId, toolUseId, tool, input |
chat.tool.result | sessionId, toolUseId, status |
chat.tool.approval-requested | sessionId, requestId, tool, input |
chat.tool.approval-decided | sessionId, requestId, decision, message? |
Chat — compaction
Section intitulée « Chat — compaction »| Type | Champs clés |
|---|---|
chat.compact.pre | sessionId, projectPath?, transcript |
chat.compact.post | sessionId |
Chat — slash commands
Section intitulée « Chat — slash commands »| Type | Champs clés |
|---|---|
chat.slash-command.detected | sessionId, commands |
chat.memorize | sessionId, projectPath?, text |
chat.recap-project | sessionId, projectPath |
chat.memorize-learning | sessionId, projectPath?, text |
Chat — attachments et runtime
Section intitulée « Chat — attachments et runtime »| Type | Champs clés |
|---|---|
chat.attachment.added | sessionId, kind, mediaType, sizeBytes |
chat.runtime.session-id-captured | sessionId, runtimeSessionId |
chat.runtime.failure | sessionId, projectPath?, error, transcript |
| Type | Champs clés |
|---|---|
memory.entry.captured | projectPath, entryId, kind, health, trigger |
memory.recall-pack.composed | projectPath, tokens, entriesCount |
memory.learning.proposed | projectPath, learningId |
memory.learning.validated | projectPath, learningId |
memory.degraded | projectPath, reason |
Settings, System, Addons, Squad
Section intitulée « Settings, System, Addons, Squad »| Type | Champs clés |
|---|---|
settings.preferences.changed | key, value |
settings.launch-options.changed | projectId, key, value |
system.boot.completed | version, addons |
system.online-state.changed | online |
system.shutdown.requested | reason |
addon.registered | addonId, kind, version |
worker.registered | workerId, addonParent, runtime |
squad.mission.event-emitted | missionId, projectPath, sequence, persistedAt, event |
orchestration.run.event-emitted | runId, projectPath, sequence, persistedAt, event |
BeforeTurnAugmenter
Section intitulée « BeforeTurnAugmenter »Port core/ports/outbound/before-turn-augmenter.ts. Implémentez ce port si votre addon doit injecter du contexte dans un tour de chat.
export interface BeforeTurnAugmenter { augmentPrompt(input: BeforeTurnAugmentInput): Promise<string | null>;}
export interface BeforeTurnAugmentInput { readonly sessionId: string; readonly turnKind?: 'silent_prepare' | 'visible_user' | 'resume'; readonly visibleTurnIndex?: number;}Comportement attendu :
- Retourner
nullsi pas d’augmentation pour ce tour - Si l’addon a un état (par exemple un artefact sélectionné), le consommer dans
augmentPrompt— le deuxième appel retourneranull - Ne pas lancer d’exception — retourner
nullen cas d’erreur non fatale
Conventions de stockage
Section intitulée « Conventions de stockage »Les addons écrivent dans .arka-deck/addons/<addon-name>/ à l’intérieur du dossier projet.
<project-root>/└── .arka-deck/ └── addons/ └── mon-addon/ ├── options.json └── logs/Règle : n’écrivez jamais en dehors de .arka-deck/addons/<votre-addon>/. Le port Filesystem applique une allowlist — les écritures hors périmètre sont rejetées.
Pour les données globales (non liées à un projet) : ~/.arka-deck/addons/<addon-name>/.
Types importables depuis core
Section intitulée « Types importables depuis core »import type { ArkaEvent, ArkaEventType } from '../../core/domain/events/arka-event.js';import type { EventBus } from '../../core/ports/outbound/event-bus.js';import type { Filesystem } from '../../core/ports/outbound/filesystem.js';import type { Clock } from '../../core/ports/outbound/clock.js';import type { IdGenerator } from '../../core/ports/outbound/id-generator.js';import type { BeforeTurnAugmenter } from '../../core/ports/outbound/before-turn-augmenter.js';Chemin relatif : depuis addons/<name>/src/, core/ est à ../../../core/.
Template de fiche publique
Section intitulée « Template de fiche publique »Pour rédiger une fiche d’addon destinée à un usage marketing/site public, utiliser le gabarit bilingue docs/addons/_TEMPLATE.md (sections : pitch court, hero, what it does, mechanics/flow, compatibility, stay tuned).
Ce gabarit est distinct des fiches dev (qui restent dans docs/dev/addons-firstparty/).