All docs

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:


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)

  1. Register platform_plugins[] in mindlink.json (entry_base_url, upstream_url, and similar).
  2. The user opens it from the apps desktop; Cadau issues a launch_token; the iframe loads your web.
  3. 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:

CapabilityPath
UI and human operationsApp-style platform plugin (this SDK)
Agents understand plugin backgroundPlugin knowledge sync → workspace knowledge plugin-{module_id}/ (§7)
Step-by-step plugin-domain work from MessagesPlugin 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 Messagesagent-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)

WhoDoes
CadauSign-in, workspace, apps-desktop iframe shell, launch_token, session check, workspace knowledge retrieval, embed assistant script
PluginOwn UI, business APIs, database; Markdown source of truth lives in the plugin repo; sync to Cadau as needed
SDKsdk/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)
  1. Register platform_plugins[] in mindlink.json (module_id, entry_base_url, upstream_url, and similar).
  2. The plugin entry reads launch_token and calls GET /api/v1/platform-plugins/session to verify identity.
  3. The plugin syncs knowledge/*.md to Cadau (see §5).
  4. If there is a fill dialog: report plugin_ui_changed and implement fill_open_form (see §6.1).
  5. 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)

PackagePathUse
Overview + contract + skeletonsdk/Enough to start
Gosdk/platform-plugin/goPlugin backend: session, knowledge sync, middleware, CSP, fill_open_form
TypeScriptsdk/platform-plugin/js (@mindlink/plugin-sdk)Plugin frontend: API client, iframe session, report/fill draft, mount embed assistant
Skeletonsdk/platform-plugin/templates/starter/Copy-and-run minimal plugin
API contractsdk/platform-plugin/contracts/API.mdREST 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:

BackendIsolationsession field
PostgreSQLCREATE SCHEMA ws_<workspace_id>data_namespace.schema
SQLite{RUNTIME_DIR}/workspaces/{id}/plugin_ns.dbdata_namespace.path / dsn
  • Cadau Ensures this idempotently on workspace create and launch/session.
  • The plugin reads data_namespace from the GET /platform-plugins/session response 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, mount RUNTIME_DIR or 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:

  1. operations.json (or configure operations_url): operation name, description, mutates, min_role, dangerous, parameter schema.
  2. POST /api/agent/invoke: verify the short-lived plugin_invoke JWT Cadau issued (not launch_token), run the business, return JSON.
  3. 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:

  1. When asked “what is on screen”, name this form, not only the tab.
  2. Unless they name another record, natural-language operations default to the current screen (open app, tab, fill dialog). They do not need to name “this form”. Write fields back into the current draft and show them immediately. form may be omitted; the host fills the open dialog.
  3. 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).

WhoDoes
Plugin UIOn open/close/change draft, postPluginUiChanged (or equivalent mindlink:plugin_ui_changed); listen listenHostPluginFormFill and write back into inputs
Plugin invokeProvide fill_open_form (mutates: false), return { ui_action: "fill_form", form, fields }; may resolve names to ids, but do not write the DB
Cadau shellPuts the current dialog into conversation client_context; after ui_action=fill_form, pushes fields back to the iframe
LanguageReady-made symbols
TypeScriptpostPluginUiChanged, listenHostPluginFormFill, fillOpenFormPassthrough, collectFillFormArgs
GoFillOpenFormPassthrough, 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 with dialog="")
  • [ ] operations.json includes fill_open_form (mutates: false)
  • [ ] invoke returns ui_action: "fill_form" and does not write business tables
  • [ ] listenHostPluginFormFill writes fields into the currently open inputs
  • [ ] Knowledge documents say: telling the app assistant field values writes the current draft (they need not name the form); 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"
    }
  ]
}
FieldMeaning
typeStep type (prefer {module_id}.{action})
kindCurrently only auto: on arrival InvokeOperation, then advance on always after success
operationMatching operation name in operations
label / description / colorDesigner display

Auto-node agreed response:

{ "ok": true, "rubric": [ /* RubricItem[] */ ], "variables": { } }
  • rubric: written as instance scoring/upload criteria; elements must match the platform RubricItem contract. Required for load-style nodes.
  • variables: merged into instance variables (flat keys). Write-back nodes (HR “confirm staffing request”) may return only variables.
  • On ok: false you may include error copy; 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.

6.3 Start an approval: write the document snapshot onto the run

When a plugin submits then starts approval (staffing request, hire/leave/transfer, expense, and similar), call POST /platform-plugins/workflows/start and put the full text the handler must see in variables.approval_doc. The engine lifts it onto the instance payload and the approval page renders it; it does not fetch the document from the plugin again. Do not send only an id / title and send people back to the app.

import { withApprovalDoc } from "@mindlink/plugin-sdk";

await client.startWorkflow({
  definition_id,
  title: "Submit staffing request · YR-2026-0001",
  variables: withApprovalDoc(
    { staffing_req_id },
    {
      title: "Staffing request YR-2026-0001",
      fields: [
        { label: "Applicant", value: "Chen Chen" },
        { label: "Department", value: "Corporate support" },
      ],
      tables: [{ title: "Lines", columns: ["Department", "Position", "Headcount"], rows: [["Corporate support", "Finance specialist", "1"]] }],
    },
  ),
});

Short documents (no line table) can use fieldListApprovalDoc("Onboarding", [["Name", "Chen Chen"], ["Employee no.", "E001"]]). Go: Client.StartWorkflow, WithApprovalDoc, FieldListApprovalDoc.

Auto-node invoke may also return top-level approval_doc (a document created mid-flow). Field table: [contracts/API.md §2.4](/docs/sdk-platform-api).


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 (Bearer launch_token, published and in-effect only); Go Client.ListStandards / JS listStandards.

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 (Bearer launch_token, published only); Go Client.ListAssets / JS listAssets; files are binary streams.

Call the workspace model (implemented): POST /platform-plugins/ai/json (Bearer launch_token) → structured JSON text from the workspace-configured model; Go Client.AIJSON / JS client.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-root index.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)

MethodPathMeaning
GET/platform-plugins/knowledge/itemsList synced documents
GET/platform-plugins/knowledge/content?path=Read body
PUT/platform-plugins/knowledge/contentWrite body and update index (owner/admin)
DELETE/platform-plugins/knowledge/content?path=Delete (owner/admin)
POST/platform-plugins/knowledge/syncBatch 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/syncplugin-{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)

  1. Scope: install target is the workspace skill center; agents in the workspace can recall by default.
  2. 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.
  3. 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.
  4. 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).
  5. 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):

MethodPathMeaning
GET/workspaces/{id}/platform-plugins/{moduleId}/skillsThis module’s registered shipped skills + install/upgrade status, can_manage_members
POST/workspaces/{id}/platform-plugins/{moduleId}/skills/seedInstall 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=compliancepractice-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.md has 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

  1. In the workspace, create or pick a My agent, note user_agent_id.
  2. The plugin calls POST /platform-plugins/embed-token (body: user_agent_id).
  3. Frontend loads {Cadau site}/embed/mindlink-widget.min.js, passing auth.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-ancestors so the Cadau Web origin can iframe them (for example http://localhost:8080).
  • launch_token lasts about 15 minutes; about 2 minutes before expiry the Cadau shell silently re-issues and postMessages the iframe { type: "mindlink:launch_token", launch_token, expires_at } (also refreshes the gateway cookie). The plugin must write sessionStorage (see JS SDK listenHostLaunchToken). 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_invoke JWT + min_role / dangerous confirmation, and are written to the audit table.

9.1 Follow host theme

When Cadau Web opens a platform plugin:

  1. Append ?theme=light|dark on the entry URL (the effective theme after resolving “system”).
  2. When the host theme changes, postMessage the 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:

typeMeaning
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:

  1. Start the Cadau Docker stack.
  2. Configure plugins/hr .env (optional MINDLINK_USER_AGENT_ID, MINDLINK_DATABASE_URL / MINDLINK_JWT_SECRET).
  3. run.cmd starts the plugin (local 3011, reverse-proxied via Cadau /p/hr/).
  4. Sign in on 8080 → workspace → Apps → open Human Resources → sync knowledge → use the in-plugin assistant or plugin_invoke in conversation.

Plugin gateway (recommended)

ConfigMeaning
entry_base_urlBrowser iframe entry; prefer /p/{module_id}/
upstream_urlPlugin internal URL (for example http://127.0.0.1:3011); do not expose the plugin port on the public internet
operations_urlOptional; default {upstream}/operations.json
Implementationbackend/internal/platformplugin/proxy.go; Web Nginx location /p/ → backend

Public auth (C+D):

PathGateway requirement
Static assets / entry pageValid 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
LaunchPOST …/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 languageImplementation
Launch tokenJWT plugin_launch, authx/plugin_launch.go
Agent-call tokenJWT plugin_invoke, authx/plugin_invoke.go; tool plugin_invoke
Workspace data namespaceworkspacens.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 maintenanceplatformplugin/knowledge.goUpsertPluginKnowledgeDocument
Embed tokenSame registration logic as POST /user-agents/{id}/embed-token, issued via platform-plugins/embed-token
Official HR pluginplugins/hr/ (README + knowledge/; user help help/product-features/platform-hr*.md)
Official email pluginplugins/email/ (IMAP receive / SMTP send; user help help/product-features/platform-email.md)
Official camera pluginplugins/camera/ (face-recognition cameras: access, attendance, security, recording playback; user help help/product-features/platform-camera.md)
Skills shipped with the pluginplugins/{module}/skills/{slug}/ → workspace skill center; example plugins/compliance/skills/practice-checklist-to-pack/ (§7.5)