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:
- 产品规格.md §4.2.4 (platform plugin modules — complementary to this mechanism)
- 工作区能力包.md
- 技能组成规范.md
- 帮助动作链接.md
- User-side notes: help/product-features/workspace-apps.md
- Report-scene special: 技能对话生成报表应用对齐指南.md
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:
| Capability | Note |
|---|---|
| App desktop | Shown by suite; published workflow apps + self-built business apps |
| invoke runtime | Built-in CRUD + the app’s handlers.py (script-class actions only) |
| Create from conversation | appfromchat: describe the need → template or LLM generates app_spec |
| Member auth | Workspace members may access list, detail, invoke |
| Import / export | Zip app packs migrate UI and logic (no business data; may attach predefined-query dependencies) |
2. What users feel
- Top-bar Apps opens the app desktop; published workflows appear together with self-built business apps.
- Workflows is in the top-bar feature menu, for creating a process from a template, publishing to the desktop, and viewing to-dos.
- New app: pick a name, suite, template (simple inbound / blank notes).
- 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).
- In Messages, describe a lightweight need to a work assistant; it can create an app automatically and give an
Open applink. - 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):
| type | Note |
|---|---|
list | Calls 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) |
form | mode=create calls crud.create |
action_form | Calls 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.
| Method | Path | Note |
|---|---|---|
| GET | /api/v1/workspaces/{id}/apps | List + suite_entities + desktop (suites ∪ pinned apps) |
| POST | /api/v1/workspaces/{id}/apps | Create (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}/invoke | Run an action |
| GET/POST | /api/v1/workspaces/{id}/app-suites | Suite 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-templates | App-template list (including system templates) / create (may from_app_id) |
| PATCH | /api/v1/workspaces/{id}/app-templates/{templateId} | Update template |
| POST | .../publish / .../unpublish | Publish / 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
| action | params | Note |
|---|---|---|
ping | — | Health check |
schema.tables | — | List user tables in app.db |
crud.list | table, limit?, offset? | List |
crud.create | table, row | Insert |
crud.update | table, id, row | Update |
crud.delete | table, id | Delete |
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
| Item | Note |
|---|---|
| Route | /apps, optional ?app={appId} |
| Desktop | WorkspaceAppsPanel.tsx |
| Renderer | WorkspaceAppRenderer.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 →
inventorytemplate; “employee profile” →employee_profiletemplate - 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+ optionalhandlers_py - PersistApp: validate spec → generate schema/SKILL → write directory; sync
/-picked/inherited skillscripts/assets/references
Complex needs (approvals, cross-system, and similar) return a hint to use a platform plugin.
10. Implementation mapping
| User concept | Implementation |
|---|---|
| Declarative spec | workspaceapp/spec.go, on-disk app.json |
| Schema generate | workspaceapp/schema_gen.go |
| SKILL generate | workspaceapp/skill_gen.go |
| Write disk | workspaceapp/write.go |
| Import / export zip | workspaceapp/bundle.go; GET .../apps/{id}/export, POST .../apps/import |
| Create from conversation | internal/appfromchat/ |
| Web render | WorkspaceAppRenderer.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