All docs

Workspace apps

Document version: 3.0

Source docs/en/site/mech-workspace-apps.md

Document version: 3.0 Status: Declarative metadata-driven (app.json + platform standard UI renderer) Voice: For product, operations, integrators, and admins; user-visible copy follows user language. Implementation names: Implementation mapping.

Related:


1. Up front

A workspace app is lightweight business software that can run on its own in this workspace: its own SQLite data file, a declarative UI description (app.json), optional Python custom actions, and auto-generated skill notes (for agents to call from conversation).

Cadau’s main database does not store business detail, only app metadata; business data lives at {RUNTIME_DIR}/workspaces/{workspaceId}/apps/{appSlug}/data/app.db.

The UI is not LLM-generated HTML. The Web standard renderer draws lists and forms from entities, views, and actions in app.json.

The platform provides:

CapabilityNote
App desktopShown by suite; published workflow apps + self-built business apps
invoke runtimeBuilt-in CRUD + the app’s handlers.py (script-class actions only)
Create from conversationappfromchat: describe the need → template or LLM generates app_spec
Member authWorkspace members may access list, detail, invoke
Import / exportZip app packs migrate UI and logic (no business data; may attach predefined-query dependencies)

2. What users feel

  1. Top-bar Apps opens the app desktop; published workflows appear together with self-built business apps.
  2. Workflows is in the top-bar feature menu, for creating a process from a template, publishing to the desktop, and viewing to-dos.
  3. New app: pick a name, suite, template (simple inbound / blank notes).
  4. Import an app pack: the manage sidebar can upload a zip; after opening an app you can export an app pack (migrate UI and logic across workspaces, no business data; if it depends on predefined queries, definitions are attached when possible and merged into the target data connection on import).
  5. In Messages, describe a lightweight need to a work assistant; it can create an app automatically and give an Open app link.
  6. UI buttons and (later) conversation actions share the same invoke contract.

3. Split from platform plugins

| | Workspace apps | Platform plugin modules (§4.2.4) | |--|-----------|------------------------| | Create | Template / conversation generates app.json | Independent project + manifest | | Data | One SQLite per app | Own DB (PostgreSQL and similar OK) | | UI | Platform standard renderer | Full web framework | | Fit | Department small tools, register-style scenes | Approval flows, compliance, cross-system, long-term ops | | Evolution | May export/upgrade to a plugin | May provide suite templates |

When creating from conversation, if approval flow / cross-workspace / ERP integration and similar complexity is detected, it suggests using a platform plugin instead.


4. App-pack structure (runtime)

RUNTIME_DIR/workspaces/{workspaceId}/apps/{appSlug}/
  manifest.json       # manifest: name, suite, ui_mode=metadata, intents
  app.json            # declarative spec: entities, views, actions
  data/app.db         # business SQLite (separate from mindlink.db)
  schema/001_init.sql # generated from entities
  logic/handlers.py   # custom actions with impl=script only
  skills/SKILL.md     # generated from spec (conversation-action notes)

Platform main DB table workspace_apps: id, workspace_id, app_slug, display_name, suite, icon, status, created_by_user_id, timestamps.


5. app.json contract (v1)

Full hand-written spec (field-level notes, validation checklist, employee-profile comparison): [../../sdk/appsdk/界面描述规范.md](/docs/sdk-appsdk-ui)

Directory notes: [../../sdk/appsdk/README.md](/docs/sdk-appsdk)

{
  "version": 1,
  "entities": [{
    "name": "inventory_items",
    "label": "库存项",
    "fields": [
      { "name": "name", "label": "商品名", "type": "text", "required": true },
      { "name": "quantity", "label": "数量", "type": "number", "required": true, "min": 1 }
    ]
  }],
  "views": [
    { "type": "action_form", "action": "inbound.create", "label": "入库登记" },
    { "type": "list", "entity": "inventory_items", "label": "库存列表", "columns": ["name", "quantity", "note"] }
  ],
  "actions": [
    { "id": "inbound.create", "label": "商品入库", "impl": "script", "params": [...] }
  ]
}

View types (summary):

typeNote
listCalls crud.list to show a table; optional allow_delete (omit/true shows delete, false hides); on delete the platform first shows a product-style confirm then runs crud.delete (not the browser native dialog)
formmode=create calls crud.create
action_formCalls a custom action (usually impl=script); form controls come from actions[].params, not views[].fields

Field types: text | number | textarea | datetime | select


6. HTTP API (contract)

All require sign-in; {id} in the path is the workspace id; workspace member is checked.

MethodPathNote
GET/api/v1/workspaces/{id}/appsList + suite_entities + desktop (suites ∪ pinned apps)
POST/api/v1/workspaces/{id}/appsCreate (template / suite_id / pinned_to_desktop)
PATCH/api/v1/workspaces/{id}/apps/{appId}Rename, icon, suite, pin to desktop
GET/api/v1/workspaces/{id}/apps/{appId}Detail + manifest + spec
DELETE/api/v1/workspaces/{id}/apps/{appId}Delete (creator or admin)
POST/api/v1/workspaces/{id}/apps/{appId}/invokeRun an action
GET/POST/api/v1/workspaces/{id}/app-suitesSuite list / create
GET/PATCH/DELETE/api/v1/workspaces/{id}/app-suites/{suiteId}Suite detail (including apps in the pack) / update / delete
GET/POST/api/v1/workspaces/{id}/app-templatesApp-template list (including system templates) / create (may from_app_id)
PATCH/api/v1/workspaces/{id}/app-templates/{templateId}Update template
POST.../publish / .../unpublishPublish / unpublish
DELETE.../app-templates/{templateId}Delete (must unpublish first)

invoke request body:

{
  "action": "inbound.create",
  "params": { "name": "A 商品", "quantity": 20 }
}

7. invoke actions

7.1 Built-in actions

actionparamsNote
pingHealth check
schema.tablesList user tables in app.db
crud.listtable, limit?, offset?List
crud.createtable, rowInsert
crud.updatetable, id, rowUpdate
crud.deletetable, idDelete

7.2 Custom actions (impl=script)

When built-in is not hit, load logic/handlers.py inside the app pack (and same-pack scripts/, assets/, and similar), then run in a sandbox after safety checks.

invoke injects a generic platform bridge (ctx["platform"]): scripts may query_run / query_list predefined queries and save_upload save attachments, without a hard-coded pipeline in the backend for one business — business logic stays in the Python written when the app was created.

The inventory template provides inbound.create; the employee_profile template provides a full generate script: on create it writes references/pipeline.json matching the skill plus assemble scripts; after submit it fetches data step by step by the pipeline and assembles HTML; if a fetch step fails, warnings are returned in the result (fields are no longer dropped silently).

When “make an employee-profile app from skill /xxx”, that skill’s scripts/, assets/, references/ overwrite template files so the app and the conversation skill share the same fetch steps.

When “make an HTML report app from a skill”: the skill’s compute script goes into the app pack, or the app’s own scripts/build_report.py is generated (rules inlined). Later report skills each carry their own fill rules; they do not share a platform generic compute engine.


8. Front end: app desktop and standard renderer

ItemNote
Route/apps, optional ?app={appId}
DesktopWorkspaceAppsPanel.tsx
RendererWorkspaceAppRenderer.tsx — reads spec, calls invoke API directly
Styles.wa-* in style.css

No longer uses iframe / custom HTML / postMessage bridge.


9. Create an app from conversation (appfromchat)

9.1 Flow

  • FastRoute: inbound keywords → inventory template; “employee profile” → employee_profile template
  • HTML report (reference skill): when the user wants to “generate an app” and an HTML/ECharts report skill can be resolved, skip a whole-app LLM:

- With no / pick, may inherit a skill just used from recent skill_read in this conversation - Compute always lives in the app pack: prefer syncing the skill’s own scripts/* (including build_report) - If the skill has no compute script (common: stats finished in the conversation LLM, only file_write lands HTML): an LLM generates this app’s scripts/build_report.py from the skill body + this conversation summary (classify/fill equivalent to conversation); handlers only fetch data and call that script - If LLM generate fails and a classify table in the body can be parsed, fall back to deterministic inline; otherwise fail and ask to retry with / skill - Only the non-faithful path falls back to the platform generic assemble_html_report.py

  • LLM: other lightweight scenes output app_spec + optional handlers_py
  • PersistApp: validate spec → generate schema/SKILL → write directory; sync /-picked/inherited skill scripts/assets/references

Complex needs (approvals, cross-system, and similar) return a hint to use a platform plugin.


10. Implementation mapping

User conceptImplementation
Declarative specworkspaceapp/spec.go, on-disk app.json
Schema generateworkspaceapp/schema_gen.go
SKILL generateworkspaceapp/skill_gen.go
Write diskworkspaceapp/write.go
Import / export zipworkspaceapp/bundle.go; GET .../apps/{id}/export, POST .../apps/import
Create from conversationinternal/appfromchat/
Web renderWorkspaceAppRenderer.tsx

App development assistant (improve an app from conversation): for full app development and iteration (UI/data model + fetch/logic/flow). Model context includes ../../sdk/appsdk runtime-rule summaries, plus extra chapters by intent; when improving, also attach a snapshot of the current app pack. The composer can type / to pick a workspace skill: the backend injects that skill’s scripts/, assets/, references/ as a “reference skill” summary (borrow ideas; default does not overwrite the whole current app). Generation (progress steps, collapsible reasoning and model output) shows inside the conversation timeline, with no separate process window. POST .../improve/draft/stream (SSE: start / progress / done / error) generates a draft; the request may carry skill_ids; POST .../improve/publish applies and returns a previous snapshot for rollback. Revisable paths use workspaceapp.ImproveCoreRelPaths as truth. Web supports progress heartbeat, stop, queue, attachments/paste screenshots, / to pick reference skills, a context-usage ring, and “roll back last version” for this conversation.

App-pack panel: after opening an app, top-bar “App pack” browses the in-pack file tree and preview; allowlisted paths can be edited and saved (GET/PUT .../package/file). app.json is read-only; UI changes still go through the assistant or the spec.


11. Later (P2+)

  • [ ] Work-assistant conversation invoke directly (recall SKILL + FastRoute intents)
  • [x] Suites as first-class objects + mixed desktop (suite icons ∪ pinned apps) + workspace app templates (create/publish/unpublish/delete)
  • [ ] Cross-workspace template marketplace, app versions and rollback
  • [x] App-pack zip import / export (UI and logic; no business data; may attach and merge predefined-query dependencies)
  • [ ] Export an app as a platform-plugin skeleton