Platform plugins
Put specialist systems such as HR, warehouse, compliance, email, and cameras on the app desktop as full web modules.
Source docs/en/site/sdk-platform-plugin.md
Directory: sdk/platform-plugin Who this is for: app-style platform plugins (apps-desktop iframe) — register platform_plugins[], launch_token, knowledge sync, in-plugin assistant, plugin_invoke.
Code packages: go/ (Go), js/ (TypeScript), contracts/, templates/starter/.
Conversational capabilities (call APIs from Messages, do not register platform_plugins) live in
agent-capability. They are not part of this directory’s registration flow.
Related:
- Product specification §4.2.4 (platform plugin module)
- Workspace capability packs (work-assistant internet,
http_request) - Skill composition spec (how conversation calls business APIs)
- host-embed/Embed contract (in-plugin embed assistant)
- How agents use knowledge documents (how knowledge enters conversation)
- Sibling SDKs: appsdk · host-embed · agent-capability · this directory
1. Which to pick (vs agent-capability)
Only apps-desktop iframe plugins use platform_plugins[] + this directory’s code packages. Do not register “face recognition, drawing, call a business API” as apps-desktop plugins by default — those belong in agent-capability.
| | Agent capabilities (agent-capability) | Plugin apps (this directory) | |--|-----------------------------------|------------------------| | User scene | Talk to a work assistant in Messages | Open full web on the apps desktop | | Register platform_plugins | No | Yes | | Docs | agent-capability | This page + sdk/README.md |
1.2 App-style platform plugin (example: org chart)
- Register
platform_plugins[]inmindlink.json(entry_base_url,upstream_url, and similar). - The user opens it from the apps desktop; Cadau issues a
launch_token; the iframe loads your web. - Optional: sync knowledge documents for workspace agents to retrieve; embed an assistant on the plugin page (embed widget).
Examples: plugins/hr/ (official HR + knowledge sync + plugin_invoke); plugins/email/ (email, IMAP/SMTP); plugins/camera/ (cameras, face-recognition attendance, security, recording playback). User notes: help/product-features/platform-hr.md, platform-email.md, platform-camera.md. When a plugin is open, the action assistant on the Cadau shell uses the workspace default app assistant (work assistant); other product modules still use the help assistant.
1.3 UI plus plugin capability from Messages
You can combine these (current implementation). Registering one platform_plugins row does not automatically give you all of them:
| Capability | Path |
|---|---|
| UI and human operations | App-style platform plugin (this SDK) |
| Agents understand plugin background | Plugin knowledge sync → workspace knowledge plugin-{module_id}/ (§7) |
| Step-by-step plugin-domain work from Messages | Plugin repo ships a skill pack → install into the workspace skill center (§7.5); agents in the workspace can recall it by default |
| Agent calls an HTTP API outside the plugin from Messages | agent-capability (skills + internet capability pack) |
Synced plugin knowledge is injected by conversation retrieval. It does not automatically register plugin REST as agent tools, and it does not automatically turn the skills/ directory into skill-center entries — install per §7.5. To “run a face-recognition API” from Messages, you still need a skill + http_request (or MCP later).
1.4 Decision tree
Need a full web UI on the apps desktop?
├─ No → agent-capability: capability service + skills + internet capability pack
└─ Yes → app-style platform plugin: platform_plugins + sdk/platform-plugin/go*
├─ Also need background notes in conversation? → knowledge/ sync (§7)
├─ Also need a procedural how-to? → skills/ shipped → install into skill center (§7.5)
└─ Also need to call an external HTTP API from Messages? → add skills + internet capability pack
2. Who does what (app-style platform plugins)
| Who | Does |
|---|---|
| Cadau | Sign-in, workspace, apps-desktop iframe shell, launch_token, session check, workspace knowledge retrieval, embed assistant script |
| Plugin | Own UI, business APIs, database; Markdown source of truth lives in the plugin repo; sync to Cadau as needed |
| SDK | sdk/README.md — Go/TS packages, API contract, starter skeleton |
Cadau does not provide a full business SDK (for example org-chart CRUD). The plugin builds its own business APIs, keys data by workspace_id, and has no separate sign-in.
An open fill dialog must let the app assistant recognize it and fill it back (§6.2). That is a platform capability, not special to one app.
3. Connection flow
sequenceDiagram participant U as User participant ML as Cadau participant P as Platform plugin U->>ML: Open plugin from apps desktop ML->>ML: POST launch → launch_token ML->>P: iframe entry_url?launch_token=… P->>ML: GET /platform-plugins/session P->>P: Optional SyncKnowledgeFromDir P->>ML: POST /platform-plugins/embed-token P->>ML: Load /embed/mindlink-widget.min.js U->>ML: Talk to the assistant in the plugin (including synced knowledge)
- Register
platform_plugins[]inmindlink.json(module_id,entry_base_url,upstream_url, and similar). - The plugin entry reads
launch_tokenand callsGET /api/v1/platform-plugins/sessionto verify identity. - The plugin syncs
knowledge/*.mdto Cadau (see §5). - If there is a fill dialog: report
plugin_ui_changedand implementfill_open_form(see §6.1). - Optional: issue an embed-token and embed a Cadau assistant on the plugin page.
4. SDK packages
Third-party developer entry: [../../../sdk/README.md](/docs/sdk-overview)
| Package | Path | Use |
|---|---|---|
| Overview + contract + skeleton | sdk/ | Enough to start |
| Go | sdk/platform-plugin/go | Plugin backend: session, knowledge sync, middleware, CSP, fill_open_form |
| TypeScript | sdk/platform-plugin/js (@mindlink/plugin-sdk) | Plugin frontend: API client, iframe session, report/fill draft, mount embed assistant |
| Skeleton | sdk/platform-plugin/templates/starter/ | Copy-and-run minimal plugin |
| API contract | sdk/platform-plugin/contracts/API.md | REST detail |
Example: plugins/hr/ (official HR plugin + knowledge sync + plugin_invoke). The old org-chart example was merged into this plugin. User notes: help/product-features/platform-hr.md; knowledge: plugins/hr/knowledge/.
5. Workspace data namespace (shared Cadau DB)
Official recommendation: plugin business data lives in the same Cadau database, isolated by workspace:
| Backend | Isolation | session field |
|---|---|---|
| PostgreSQL | CREATE SCHEMA ws_<workspace_id> | data_namespace.schema |
| SQLite | {RUNTIME_DIR}/workspaces/{id}/plugin_ns.db | data_namespace.path / dsn |
- Cadau Ensures this idempotently on workspace create and launch/session.
- The plugin reads
data_namespacefrom theGET /platform-plugins/sessionresponse and migrates its own business tables. - The plugin process must reach the same
DATABASE_URL(Postgres) or the SQLite path on the same host (for Docker development, mountRUNTIME_DIRor switch to Postgres).
Third parties may still use a separate DB (row isolation by workspace_id only); that can coexist with the namespace approach.
6. Agents operating the plugin (plugin_invoke)
Every platform plugin should provide:
operations.json(or configureoperations_url): operation name, description,mutates,min_role,dangerous, parameter schema.POST /api/agent/invoke: verify the short-lived plugin_invoke JWT Cadau issued (notlaunch_token), run the business, return JSON.- When there is a fill dialog:
fill_open_form(mutates: false) + iframe reports the current UI (§6.1). Read-only apps with no dialog may omit this.
The help assistant and work assistants can both call the tool plugin_invoke (on the help side, this is the only tool). Writes default to min_role=admin; dangerous=true requires user confirmation then confirm=true. Calls are written to plugin_invoke_audit.
6.1 Recognize the open form (required if you have one)
When the user opens a fill dialog / drawer / draft in the app, the app assistant on the Cadau shell must:
- When asked “what is on screen”, name this form, not only the tab.
- When the user adds fields in natural language, write them back into the current draft and show them immediately.
- Until the user says “save / create / submit / complete”, do not call create/update operations that write the system immediately.
This is a platform convention, independent of HR. Field-level contract: [contracts/API.md §3.2](/docs/sdk-platform-api).
| Who | Does |
|---|---|
| Plugin UI | On open/close/change draft, postPluginUiChanged (or equivalent mindlink:plugin_ui_changed); listen listenHostPluginFormFill and write back into inputs |
| Plugin invoke | Provide fill_open_form (mutates: false), return { ui_action: "fill_form", form, fields }; may resolve names to ids, but do not write the DB |
| Cadau shell | Puts the current dialog into conversation client_context; after ui_action=fill_form, pushes fields back to the iframe |
| Language | Ready-made symbols |
|---|---|
| TypeScript | postPluginUiChanged, listenHostPluginFormFill, fillOpenFormPassthrough, collectFillFormArgs |
| Go | FillOpenFormPassthrough, CollectFillFormArgs, FillOpenFormResult, InvokeBody |
React reference: plugins/hr/app/PluginUiSurface.tsx (copy, then change moduleId). Minimal runnable: templates/starter/ (includes a draft example).
When closing the dialog, pass dialog as an empty string. View-only dialogs set readonly: true; the assistant names the title and does not fill.
Checklist:
- [ ] Open/change/close draft all
postPluginUiChanged(close withdialog="") - [ ]
operations.jsonincludesfill_open_form(mutates: false) - [ ] invoke returns
ui_action: "fill_form"and does not write business tables - [ ]
listenHostPluginFormFillwritesfieldsinto the currently open inputs - [ ] Knowledge documents say: telling the app assistant field values writes the draft; nothing is stored until the user says save
6.2 Extra workflow steps (workflow_steps)
A plugin can declare workflow_steps in the same operations.json, hanging domain auto-nodes on the process designer (they appear in the catalog only when that plugin is enabled):
{
"module_id": "compliance",
"operations": [{ "name": "workflow_load_check_items", "mutates": false, "min_role": "member" }],
"workflow_steps": [
{
"type": "compliance.load_check_items",
"label": "Load check standards",
"description": "Read scoring criteria from compliance check items",
"color": "#0891b2",
"kind": "auto",
"operation": "workflow_load_check_items",
"config_hint": "Optional phases; check items come from the survey bound in process settings"
}
]
}
| Field | Meaning |
|---|---|
type | Step type (prefer {module_id}.{action}) |
kind | Currently only auto: on arrival InvokeOperation, then advance on always after success |
operation | Matching operation name in operations |
label / description / color | Designer display |
Auto-node agreed response:
{ "ok": true, "rubric": [ /* RubricItem[] */ ], "variables": { } }
rubric: written as instance scoring/upload criteria; elements must match the platformRubricItemcontract. Required for load-style nodes.variables: merged into instance variables (flat keys). Write-back nodes (HR “confirm staffing request”) may return onlyvariables.- On
ok: falseyou may includeerrorcopy; do not return list items with the wrong shape.
Implementation source of truth (plugin SDK): field table and response convention: [sdk/platform-plugin/contracts/API.md §3.3](/docs/sdk-platform-api); Go/TS types RubricItem / WorkflowLoadRubricResult. Product/engine summary: workflow.json-v1.md §check-item list contract.
Reference: toRubric in plugins/compliance/lib/workflowOps.ts. Other domain load nodes should output the same shape, so they can plug into core “upload evidence” and “auto score”.
Cadau: GET /workspaces/{id}/workflows/step-catalog merges the core catalog with enabled plugins’ workflow_steps.
7. Knowledge documents: source in the plugin, retrieval in Cadau
Vs standards library: government/industry regulations and cross-plugin shared policy/theory live in product §3.2.2 and
Standards library. This section’s APIs only cover each plugin’s own how-to notes (plugin-{module_id}/). Do not sync a full regulation text into one plugin theme as workspace-wide source of truth.Read standards library (implemented):
GET /platform-plugins/standards,…/{id},…/{id}/content(Bearerlaunch_token, published and in-effect only); GoClient.ListStandards/ JSlistStandards.Vs asset library: shared table/form templates live in product §3.2.3 and
Asset library.Read asset library (implemented):
GET /platform-plugins/assets,…/{id},…/{id}/file(Bearerlaunch_token, published only); GoClient.ListAssets/ JSlistAssets; files are binary streams.Call the workspace model (implemented):
POST /platform-plugins/ai/json(Bearerlaunch_token) → structured JSON text from the workspace-configured model; GoClient.AIJSON/ JSclient.aiJSON; contract: [sdk/platform-plugin/contracts/API.md](/docs/sdk-platform-api) §2.2.3.
7.1 Principles
- Markdown files live in the plugin project (for example
knowledge/guide.md) and ship with the plugin. - Conversation retrieval still uses Cadau workspace knowledge (two-level
index.json+ match by question). Do not replace that with a plugin-owned vector store. - After sync, files land in runtime directory
plugin-{module_id}/, and a theme is registered in the workspace-rootindex.json. Workspace agents (including the in-plugin embed assistant) inject them automatically in conversation. - The plugin sync API syncs Markdown body only; images/short video and other rich media in workspace knowledge are managed on the Cadau product side, not uploaded via
knowledge/sync.
7.2 API (Bearer launch_token)
| Method | Path | Meaning |
|---|---|---|
| GET | /platform-plugins/knowledge/items | List synced documents |
| GET | /platform-plugins/knowledge/content?path= | Read body |
| PUT | /platform-plugins/knowledge/content | Write body and update index (owner/admin) |
| DELETE | /platform-plugins/knowledge/content?path= | Delete (owner/admin) |
| POST | /platform-plugins/knowledge/sync | Batch sync { documents: [...] } |
Go convenience:
synced, err := mindlinkplugin.SyncKnowledgeFromDir(ctx, client, "./knowledge")
7.3 Optional for local development: volume mount
Same-machine Docker can mount plugin knowledge/ onto the matching Cadau runtime path and skip sync; production and multi-machine still prefer API sync.
7.4 Workspace model (structured JSON, not conversation)
If an app-style plugin needs short-lived intelligence in its own business UI (for example generate a survey from a standard’s body, recommend staffing by department function), it should call Cadau:
POST /api/v1/platform-plugins/ai/json
Authorization: Bearer <launch_token>
- Model and keys: use the large model already configured for this workspace; the plugin does not talk to vendors or hold an API key.
- Input:
system+user(prompts); output:{ "text": "<JSON string>" }. - Vs in-plugin assistant: §8 embed is the user talking on the plugin page; this API is a one-shot structured generate on the plugin backend, with no chat conversation.
- Model not configured: returns unavailable; the plugin should fall back to rules/templates (see HR / compliance plugins).
- SDK:
const text = await client.aiJSON(
"You are……output JSON only: {\"items\":[...]}",
JSON.stringify({ context: "…" }),
);
const data = JSON.parse(text);
text, err := client.AIJSON(ctx, system, user)
Detail and length limits (Unicode characters: system ≤ 32,000, user ≤ 400,000): [sdk/platform-plugin/contracts/API.md](/docs/sdk-platform-api) §2.2.3. Business (for example compliance surveys) may set a stricter combined-body cap and ask for batches.
7.5 Skills shipped with the plugin (workspace skill center)
Besides knowledge/ (background), an app-style plugin can ship a skill pack in the repo: teach agents how to do a class of plugin-domain operations (steps, mappings, calling plugin_invoke / standards library / asset library, and similar). Skill body contract: Skill composition spec.
7.5.1 Vs knowledge and internet skills
| | Plugin knowledge (§7) | Skills shipped with the plugin (this section) | §1.1 internet skills | |--|-------------------|-------------------------|-------------------| | In user language | How-to notes, domain background | Repeatable operating handbook | Handbook for calling an external HTTP API | | In the repo | knowledge/*.md | skills/{slug}/SKILL.md (+ references/ and similar) | Anywhere; often only maintained in the skill center | | Into the workspace | knowledge/sync → plugin-{module_id}/ | Install into the skill center (idempotent by slug) | Create by hand / import / directory install | | In conversation | Retrieval injects background | Trigger-description recall / @skill | Same as left + http_request |
7.5.2 Repo directory convention
plugins/{module_id}/
knowledge/ # §7 sync
skills/
{slug}/
SKILL.md # required: frontmatter name/description + operating body
references/ # optional: long notes, column maps, sample pointers
seed-fingerprint.txt # recommended: content fingerprint for upgrade compare
samples/ # optional: sample files paired with the skill (need not go in the skill zip)
slug: stable id (lowercase hyphen); install and upgrade are idempotent on this; do not rename casually.- Author mirror:
skills/in the plugin repo is convenient for release and review; runtime source of truth may be a Cadau backend embedded pack (common for official plugins), or byte-level sync with the repo directory. - Do not put skill body only in
knowledge/and expect sync — knowledge retrieval does not replace skill-center recall.
7.5.3 Install and upgrade (required product voice)
- Scope: install target is the workspace skill center; agents in the workspace can recall by default.
- Forbidden: silently writing the skill into one agent’s “available skills” narrow list; do not guide copy as “bind to such-and-such agent” as an install step.
- First install: prefer when the user opens that plugin (enters the module from the apps desktop), after a member who can manage confirms seed; “not now” only skips the prompt, and does not block later prompts or upgrades.
- Content upgrade: compare a content fingerprint (for example
references/seed-fingerprint.txt); if installed and the fingerprint is behind, opening the plugin again can silently upgrade skill body and attachments (no browser clear, no reinstall). - Permissions: seed / upgrade require a workspace member who can manage; ordinary members can open the plugin but cannot install skills for the whole workspace.
7.5.4 Host-side APIs (generic)
User sign-in auth (same user-state as launch in §1; not launch_token):
| Method | Path | Meaning |
|---|---|---|
| GET | /workspaces/{id}/platform-plugins/{moduleId}/skills | This module’s registered shipped skills + install/upgrade status, can_manage_members |
| POST | /workspaces/{id}/platform-plugins/{moduleId}/skills/seed | Install or upgrade by fingerprint; optional body { "slug" }; omit to process all for that module |
Backend registration: official plugins in init call skillfromchat.RegisterPluginSkillBundle(...), embedding skills/{slug}/ into backend/internal/skillfromchat/bundled/. First example: module_id=compliance → practice-checklist-to-pack.
Response and fields: [sdk/platform-plugin/contracts/API.md](/docs/sdk-platform-api) §1.3; mechanism draft Compliance practice checklist to machine-readable pack.
Until a third-party plugin embeds the pack in Cadau, you can:
- Provide a downloadable skill zip and guide admins to import in the skill center; or
- Contribute embed registration to Cadau’s release side (same path as compliance).
7.5.5 Checklist
- [ ]
skills/{slug}/SKILL.mdhas clear when to use / when not trigger notes - [ ] Stable
slug; upgrades use a content fingerprint, not a slug rename - [ ] On open plugin: not installed → confirm install; installed but behind → silent upgrade
- [ ] Success copy says “skill center / agents in the workspace can recall”, not “bound to such-and-such agent”
- [ ] Do not mix duties with
knowledge/,plugin_invoke, or read-only standards/asset APIs
8. In-plugin agent Q&A
- In the workspace, create or pick a My agent, note
user_agent_id. - The plugin calls
POST /platform-plugins/embed-token(body:user_agent_id). - Frontend loads
{Cadau site}/embed/mindlink-widget.min.js, passingauth.token,workspace_id,user_agent_id(see host-embed/Embed contract).
The assistant uses workspace knowledge (including synced plugin knowledge). The plugin need not implement RAG itself.
9. Security and CSP
- Plugin responses must set
frame-ancestorsso the Cadau Web origin can iframe them (for examplehttp://localhost:8080). launch_tokenlasts about 15 minutes; about 2 minutes before expiry the Cadau shell silently re-issues andpostMessages the iframe{ type: "mindlink:launch_token", launch_token, expires_at }(also refreshes the gateway cookie). The plugin must writesessionStorage(see JS SDKlistenHostLaunchToken). The user need not go back to the apps desktop.- If host sign-in has expired or renew failed, plugin APIs 401; then open again from the apps desktop.
- Knowledge writes are limited to workspace owner/admin (same as the plugin business “can manage” role).
- Agent writes go through
plugin_invokeJWT +min_role/dangerousconfirmation, and are written to the audit table.
9.1 Follow host theme
When Cadau Web opens a platform plugin:
- Append
?theme=light|darkon the entry URL (the effective theme after resolving “system”). - When the host theme changes,
postMessagethe iframe:
{ "source": "mindlink", "type": "mindlink:theme", "theme": "dark" }
The plugin should set document.documentElement.dataset.theme and use the same CSS variables as the host (see client/web --bg / --card / --primary and similar). Official example: plugins/hr/app/HostThemeSync.tsx.
The host also pushes when the launch token is about to expire:
{ "source": "mindlink", "type": "mindlink:launch_token", "launch_token": "…", "expires_at": "…" }
The plugin must update the locally stored launch token (listenHostLaunchToken / sessionStorage), or requests with the old Bearer are rejected before the new cookie.
On hot reload or API 401, the plugin can ask for a renew (source must be mindlink-plugin):
{ "source": "mindlink-plugin", "type": "mindlink:request_launch_token" }
JS SDK: requestHostLaunchToken(). The host also pushes:
type | Meaning |
|---|---|
mindlink:plugin_tab | { tab } — help deep-links and similar switch plugin tabs (listenHostPluginTab) |
mindlink:plugin_data_changed | { module_id?, operation? } — suggest refreshing lists (listenHostPluginDataChanged) |
mindlink:plugin_form_fill | { form, fields } — write fields the assistant parsed back into the current draft (listenHostPluginFormFill) |
Plugin → host (fill dialog, §6.1):
{ "source": "mindlink-plugin", "type": "mindlink:plugin_ui_changed", "module_id": "my-plugin", "dialog": "draft", "dialog_title": "Example draft", "form": {}, "field_hints": "Name", "readonly": false }
Do not mix cookies / storage: gateway cookie ml_plugin_gw, plugin-origin cookie ml_plugin_launch, frontend sessionStorage key ml_plugin_launch_token (see [sdk/platform-plugin/contracts/API.md](../contracts/API.md) §3.4).
10. Local testing
See deploy/plugin-dev-platform/README.md (Cadau Docker 8080/8082) and plugins/hr/README.md.
Typical steps:
- Start the Cadau Docker stack.
- Configure
plugins/hr.env(optionalMINDLINK_USER_AGENT_ID,MINDLINK_DATABASE_URL/MINDLINK_JWT_SECRET). run.cmdstarts the plugin (local 3011, reverse-proxied via Cadau/p/hr/).- Sign in on 8080 → workspace → Apps → open Human Resources → sync knowledge → use the in-plugin assistant or
plugin_invokein conversation.
Plugin gateway (recommended)
| Config | Meaning |
|---|---|
entry_base_url | Browser iframe entry; prefer /p/{module_id}/ |
upstream_url | Plugin internal URL (for example http://127.0.0.1:3011); do not expose the plugin port on the public internet |
operations_url | Optional; default {upstream}/operations.json |
| Implementation | backend/internal/platformplugin/proxy.go; Web Nginx location /p/ → backend |
Public auth (C+D):
| Path | Gateway requirement |
|---|---|
| Static assets / entry page | Valid launch_token: URL query (first screen only), gateway cookie ml_plugin_gw, or Authorization: Bearer |
/p/{module_id}/api/* | Must cookie or Bearer; query alone is forbidden (log leak) |
/p/hr/take/*, /p/hr/api/exam-public/* | Exception (HR only): candidate answering; launch credential not required |
| Launch | POST …/launch response writes an HttpOnly cookie with Path=/p/{module_id}/ |
Plugin frontend calls to its own APIs must use a relative path or JS SDK resolvePluginAPI() + createAuthorizedFetch() (Bearer and credentials: "include" automatically).
11. Implementation map
| In user language | Implementation |
|---|---|
| Launch token | JWT plugin_launch, authx/plugin_launch.go |
| Agent-call token | JWT plugin_invoke, authx/plugin_invoke.go; tool plugin_invoke |
| Workspace data namespace | workspacens.Ensure; table workspace_data_namespaces |
| Plugin entry gateway | /p/{module_id}/ → upstream_url; gateway cookie ml_plugin_gw + API requires Bearer/Cookie (platformplugin/proxy.go, gateway_auth.go) |
| Plugin knowledge directory | {RuntimeDir}/workspaces/{id}/knowledge/plugin-{module_id}/ |
| Index maintenance | platformplugin/knowledge.go → UpsertPluginKnowledgeDocument |
| Embed token | Same registration logic as POST /user-agents/{id}/embed-token, issued via platform-plugins/embed-token |
| Official HR plugin | plugins/hr/ (README + knowledge/; user help help/product-features/platform-hr*.md) |
| Official email plugin | plugins/email/ (IMAP receive / SMTP send; user help help/product-features/platform-email.md) |
| Official camera plugin | plugins/camera/ (face-recognition cameras: access, attendance, security, recording playback; user help help/product-features/platform-camera.md) |
| Skills shipped with the plugin | plugins/{module}/skills/{slug}/ → workspace skill center; example plugins/compliance/skills/practice-checklist-to-pack/ (§7.5) |