@proveanything/smartlinks 2.1.2 → 2.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/docs/API_SUMMARY.md +24 -1
- package/dist/docs/agent-tools.md +6 -1
- package/dist/docs/app-manifest.md +43 -2
- package/dist/docs/assistant-apps.md +168 -0
- package/dist/docs/deep-link-discovery.md +12 -0
- package/dist/docs/iframe-responder.md +1 -0
- package/dist/docs/websites.md +26 -0
- package/dist/iframeResponder.d.ts +2 -0
- package/dist/iframeResponder.js +18 -8
- package/dist/openapi.yaml +43 -0
- package/dist/types/appManifest.d.ts +67 -4
- package/dist/types/iframeResponder.d.ts +6 -0
- package/docs/API_SUMMARY.md +24 -1
- package/docs/agent-tools.md +6 -1
- package/docs/app-manifest.md +43 -2
- package/docs/assistant-apps.md +168 -0
- package/docs/deep-link-discovery.md +12 -0
- package/docs/iframe-responder.md +1 -0
- package/docs/websites.md +26 -0
- package/openapi.yaml +43 -0
- package/package.json +1 -1
package/dist/docs/API_SUMMARY.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Smartlinks API Summary
|
|
2
2
|
|
|
3
|
-
Version: 2.1.
|
|
3
|
+
Version: 2.1.4 | Generated: 2026-10-28T12:35:09.709Z
|
|
4
4
|
|
|
5
5
|
This is a concise summary of all available API functions and types.
|
|
6
6
|
|
|
@@ -2585,9 +2585,11 @@ interface AppContainerComponent {
|
|
|
2585
2585
|
**DeepLinkEntry** (interface)
|
|
2586
2586
|
```typescript
|
|
2587
2587
|
interface DeepLinkEntry {
|
|
2588
|
+
id?: string;
|
|
2588
2589
|
title: string;
|
|
2589
2590
|
path?: string;
|
|
2590
2591
|
params?: Record<string, string>;
|
|
2592
|
+
accepts?: PublicViewParams;
|
|
2591
2593
|
widget?: true;
|
|
2592
2594
|
}
|
|
2593
2595
|
```
|
|
@@ -2711,6 +2713,7 @@ interface AppFunctionAgentExposure {
|
|
|
2711
2713
|
description?: string;
|
|
2712
2714
|
input?: Record<string, any>;
|
|
2713
2715
|
approval?: 'auto' | 'require';
|
|
2716
|
+
audience?: Array<'app' | 'public' | 'assistant'>;
|
|
2714
2717
|
}
|
|
2715
2718
|
```
|
|
2716
2719
|
|
|
@@ -3049,10 +3052,29 @@ interface AppManifest {
|
|
|
3049
3052
|
data?: AppDataDeclaration;
|
|
3050
3053
|
headless?: AppHeadlessDeclaration;
|
|
3051
3054
|
connector?: AppConnectorDeclaration;
|
|
3055
|
+
assistant?: AppAssistantDeclaration;
|
|
3052
3056
|
[key: string]: any;
|
|
3053
3057
|
}
|
|
3054
3058
|
```
|
|
3055
3059
|
|
|
3060
|
+
**AppAssistantFile** (interface)
|
|
3061
|
+
```typescript
|
|
3062
|
+
interface AppAssistantFile {
|
|
3063
|
+
name: string;
|
|
3064
|
+
description?: string;
|
|
3065
|
+
file: string;
|
|
3066
|
+
}
|
|
3067
|
+
```
|
|
3068
|
+
|
|
3069
|
+
**AppAssistantDeclaration** (interface)
|
|
3070
|
+
```typescript
|
|
3071
|
+
interface AppAssistantDeclaration {
|
|
3072
|
+
skills?: AppAssistantFile[];
|
|
3073
|
+
knowledge?: AppAssistantFile[];
|
|
3074
|
+
preview?: string;
|
|
3075
|
+
}
|
|
3076
|
+
```
|
|
3077
|
+
|
|
3056
3078
|
**ConnectorConnectionField** (interface)
|
|
3057
3079
|
```typescript
|
|
3058
3080
|
interface ConnectorConnectionField {
|
|
@@ -7479,6 +7501,7 @@ interface IframeResponderOptions {
|
|
|
7479
7501
|
version?: string;
|
|
7480
7502
|
appUrl?: string;
|
|
7481
7503
|
initialPath?: string;
|
|
7504
|
+
params?: Record<string, string>;
|
|
7482
7505
|
isAdmin?: boolean;
|
|
7483
7506
|
cache?: CachedData;
|
|
7484
7507
|
onAuthLogin?: (
|
package/dist/docs/agent-tools.md
CHANGED
|
@@ -44,7 +44,8 @@ needs to call it — a description and an input schema:
|
|
|
44
44
|
"title": "Create an FAQ entry",
|
|
45
45
|
"description": "Add a question/answer to this collection's FAQ. Use when the user asks to add or draft an FAQ.",
|
|
46
46
|
"input": { /* JSON Schema for the arguments the agent supplies as event.body */ },
|
|
47
|
-
"approval": "auto"
|
|
47
|
+
"approval": "auto", // auto | require (require = human confirmation before the call)
|
|
48
|
+
"audience": ["app"] // optional: app | public | assistant — where it is offered (absent = everywhere)
|
|
48
49
|
}
|
|
49
50
|
}
|
|
50
51
|
]
|
|
@@ -103,6 +104,10 @@ Identical to server functions — nothing new to reason about:
|
|
|
103
104
|
- **`visibility`** — who may call (admin/public).
|
|
104
105
|
- **`authority`** — whose identity `ctx.sl` carries (`caller` vs `collection`).
|
|
105
106
|
- **`capabilities`** — least-privilege, capped even when elevated.
|
|
107
|
+
- **`agent.audience`** — where the tool is offered: `'app'` (the app's own agent runs, admin surface),
|
|
108
|
+
`'public'` (public assistants), `'assistant'` (the Studio assistant, the admin's agent —
|
|
109
|
+
[assistant-apps.md](assistant-apps.md)). Absent = everywhere the function's visibility allows. An
|
|
110
|
+
assistant-only tool declares `["assistant"]` and never joins an in-app or public run.
|
|
106
111
|
- **`agent.approval: "require"`** — the agent must get human confirmation before invoking (use for
|
|
107
112
|
destructive or public-facing actions).
|
|
108
113
|
|
|
@@ -326,11 +326,28 @@ Static deep-linkable states built into the app — fixed routes that exist regar
|
|
|
326
326
|
|
|
327
327
|
See the [Deep Link Discovery guide](deep-link-discovery.md) for the full dual-source pattern (static manifest routes + dynamic `appConfig.linkable`).
|
|
328
328
|
|
|
329
|
+
These are the app's **pages that a host opens**: portal menus and tabs, and websites that embed the app
|
|
330
|
+
(websites.md → "Using another app's pages"). A host opens them with its own signed-in session, so one
|
|
331
|
+
app can offer several pages that each know the visitor: book, upcoming bookings, past bookings, change a
|
|
332
|
+
booking.
|
|
333
|
+
|
|
334
|
+
```json
|
|
335
|
+
"linkable": [
|
|
336
|
+
{ "id": "book", "title": "Book an appointment", "path": "/",
|
|
337
|
+
"accepts": { "optional": ["service", "staff", "date"] } },
|
|
338
|
+
{ "id": "upcoming", "title": "Your upcoming bookings", "path": "/my-bookings" },
|
|
339
|
+
{ "id": "change", "title": "Change a booking", "path": "/booking",
|
|
340
|
+
"accepts": { "required": ["id"] } }
|
|
341
|
+
]
|
|
342
|
+
```
|
|
343
|
+
|
|
329
344
|
| Field | Type | Required | Description |
|
|
330
345
|
|-------|------|----------|-------------|
|
|
346
|
+
| `id` | string | ❌ | Stable id, unique within the app: hosts and sites refer to the page by it |
|
|
331
347
|
| `title` | string | ✅ | Human-readable label shown in menus and offered to AI agents |
|
|
332
348
|
| `path` | string | ❌ | Hash route within the app (defaults to `"/"` if omitted) |
|
|
333
|
-
| `params` | object | ❌ |
|
|
349
|
+
| `params` | object | ❌ | Params this entry PINS, appended to the URL — do **not** include platform params (`collectionId`, `productId`, etc.) |
|
|
350
|
+
| `accepts` | `{ required?: string[]; optional?: string[] }` | ❌ | The params a host may supply when it opens the page (e.g. which service to start on) |
|
|
334
351
|
|
|
335
352
|
#### `publicViews`
|
|
336
353
|
|
|
@@ -371,7 +388,7 @@ An app can have any combination of three setup styles. Only public views are ext
|
|
|
371
388
|
| Style | Declared by | Lives | Context |
|
|
372
389
|
|-------|-------------|-------|---------|
|
|
373
390
|
| **Portal components** — where the app renders inside portal (collection / product / batch / variant / proof) | module registry `components.*` (set at publish) | Inside portal | Physical twin (QR/NFC scan) |
|
|
374
|
-
| **
|
|
391
|
+
| **App pages** — the entry points portal menus/tabs link to and websites embed (e.g. list vs calendar; book vs my bookings) | `linkable` / `DeepLinkEntry` | Inside a host: portal or a website | The host: its signed-in session, plus the page's `accepts` params |
|
|
375
392
|
| **Public views** — standalone external screens | `publicViews` (this block) | Outside portal | URL params only |
|
|
376
393
|
|
|
377
394
|
#### `records`
|
|
@@ -443,6 +460,30 @@ lists it and draws its connect form and settings, and the integration engine cal
|
|
|
443
460
|
functions (`testConnection`, `describeTarget`, `sample`, `fetch`, `shape`, `serialize`, `send`). It adapts the provider's format; core does each customer's mapping. The full
|
|
444
461
|
contract is in the **[Connectors guide](connectors.md)**.
|
|
445
462
|
|
|
463
|
+
#### `assistant`
|
|
464
|
+
|
|
465
|
+
What the **Studio assistant** (the admin's own agent) knows about this app: skills and knowledge files
|
|
466
|
+
shipped with the build and loaded into its box on demand, and an optional page that renders a planned
|
|
467
|
+
change set before it is applied.
|
|
468
|
+
|
|
469
|
+
```jsonc
|
|
470
|
+
"assistant": {
|
|
471
|
+
"skills": [
|
|
472
|
+
{ "name": "theme", "description": "Changing the Hub's colours, fonts or logo", "file": "assistant/skills/theme.md" },
|
|
473
|
+
{ "name": "pages", "description": "Adding, ordering and editing pages and the menu", "file": "assistant/skills/pages.md" }
|
|
474
|
+
],
|
|
475
|
+
"knowledge": [
|
|
476
|
+
{ "name": "settings-schema", "description": "JSON Schema of the hub settings group", "file": "assistant/knowledge/settings.schema.json" }
|
|
477
|
+
],
|
|
478
|
+
"preview": "/admin/preview?changeSet={id}"
|
|
479
|
+
}
|
|
480
|
+
```
|
|
481
|
+
|
|
482
|
+
The files live under `public/assistant/` and ship with every release like any other static file. The
|
|
483
|
+
assistant sees one line per skill (its description) and reads the file only when the job calls for it,
|
|
484
|
+
so a big app can carry many skills at no cost to the conversation. Full guide, including what a skill
|
|
485
|
+
should say and how the assistant changes config with undo: **[assistant-apps.md](assistant-apps.md)**.
|
|
486
|
+
|
|
446
487
|
---
|
|
447
488
|
|
|
448
489
|
## `app.admin.json`
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
# The Studio assistant and your app
|
|
2
|
+
|
|
3
|
+
SmartLinks admins have **one assistant**: an agent that works for the signed-in admin on one
|
|
4
|
+
collection, with the admin's own authority, from a chat in Studio. It reads and changes data through
|
|
5
|
+
the SmartLinks API, every write is a **change set** the admin can see and undo, and the things that
|
|
6
|
+
can't be undone (deleting, sending, calling out, publishing, spending) wait for the admin's "Allow".
|
|
7
|
+
|
|
8
|
+
This page is for app authors: how the assistant learns what your app is, how it is set up, and what it
|
|
9
|
+
can do for the admin — without you building a chat of your own. Your app declares three things:
|
|
10
|
+
|
|
11
|
+
| What | Where | Status |
|
|
12
|
+
|---|---|---|
|
|
13
|
+
| **Skills and knowledge** — plain-words procedures for the admin side, and the facts they need | `assistant` block in `app.manifest.json`, files under `public/assistant/` | this page |
|
|
14
|
+
| **Tools** — logic the assistant should call rather than reimplement | server functions with an `agent` block (`audience: ["assistant"]` for assistant-only) | [agent-tools.md](agent-tools.md) |
|
|
15
|
+
| **Setup** — questions, config schema, how to save | `app.admin.json` + `ai-guide.md` | [app-manifest.md](app-manifest.md#appadminjson) |
|
|
16
|
+
|
|
17
|
+
An app with none of these still works with the assistant: it can read the app's records and config
|
|
18
|
+
and change them through change sets. The declarations make it *good* at your app.
|
|
19
|
+
|
|
20
|
+
## The `assistant` block
|
|
21
|
+
|
|
22
|
+
```jsonc
|
|
23
|
+
// app.manifest.json
|
|
24
|
+
"assistant": {
|
|
25
|
+
"skills": [
|
|
26
|
+
{ "name": "setup", "description": "First-time setup of the FAQ app: categories, tone, who answers", "file": "assistant/skills/setup.md" },
|
|
27
|
+
{ "name": "content", "description": "Adding, editing, ordering and retiring questions and answers", "file": "assistant/skills/content.md" }
|
|
28
|
+
],
|
|
29
|
+
"knowledge": [
|
|
30
|
+
{ "name": "config-schema", "description": "JSON Schema of the app's config document", "file": "assistant/knowledge/config.schema.json" },
|
|
31
|
+
{ "name": "examples", "description": "Three worked examples of good FAQ content", "file": "assistant/knowledge/examples.md" }
|
|
32
|
+
],
|
|
33
|
+
"preview": "/admin/preview?changeSet={id}"
|
|
34
|
+
}
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
The files sit in `public/assistant/` in your project and ship with every release like any other static
|
|
38
|
+
file (they are read from the release's CDN folder, on the channel the collection runs your app on).
|
|
39
|
+
|
|
40
|
+
### How they are loaded
|
|
41
|
+
|
|
42
|
+
When an admin opens a chat, the assistant's box gets the **index** of every installed app's skills: one
|
|
43
|
+
line each, the description. Nothing else. A skill's body, and the knowledge files it names, are read
|
|
44
|
+
only when the assistant decides the job needs them. Twenty apps with three skills each cost the
|
|
45
|
+
conversation about sixty lines. So: write several focused skills rather than one long one, and spend
|
|
46
|
+
care on the description — it is how the assistant decides to open the file.
|
|
47
|
+
|
|
48
|
+
### Writing a skill
|
|
49
|
+
|
|
50
|
+
A skill is a markdown file: what this area of your app is for, what each setting means, how to read and
|
|
51
|
+
change it, what a good result looks like, what to check afterwards. Write it for a capable colleague who
|
|
52
|
+
has never seen your app. The assistant works through the SmartLinks API with a small CLI (`sl`), so say
|
|
53
|
+
things in those terms:
|
|
54
|
+
|
|
55
|
+
```markdown
|
|
56
|
+
---
|
|
57
|
+
name: faq-content
|
|
58
|
+
description: Adding, editing, ordering and retiring questions and answers in the FAQ app
|
|
59
|
+
---
|
|
60
|
+
# FAQ content
|
|
61
|
+
|
|
62
|
+
Questions live as app records of type `question` (`sl get app/faq/records recordType=question`).
|
|
63
|
+
Fields: `data.question`, `data.answer` (markdown), `data.category` (one of the categories in the app's
|
|
64
|
+
config, `sl get app/faq` → `categories[]`), `data.order` (ascending), `status` (`published` | `draft`).
|
|
65
|
+
|
|
66
|
+
## Adding questions
|
|
67
|
+
Plan one change set per batch: items `{ "op": "create", "entity": "record", "appId": "faq",
|
|
68
|
+
"data": { "recordType": "question", "data": { … }, "status": "draft" } }`. Match on `data.question`
|
|
69
|
+
(case-insensitive) before creating, so a re-run updates instead of duplicating.
|
|
70
|
+
|
|
71
|
+
## A good answer
|
|
72
|
+
Two to five sentences, plain words, no marketing. Link to a product page when one exists
|
|
73
|
+
(`/p/<collectionId>/<productId>`). Never invent policy: if the answer depends on something the admin
|
|
74
|
+
hasn't said (returns window, delivery areas), ask.
|
|
75
|
+
|
|
76
|
+
## Check afterwards
|
|
77
|
+
`sl get app/faq/records recordType=question status=draft` — read three back and make sure the
|
|
78
|
+
category is one that exists.
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
The frontmatter is optional; when it is missing, the manifest entry's `name` and `description` are used.
|
|
82
|
+
Keep a skill under a few hundred lines; put long reference material in knowledge files and name them.
|
|
83
|
+
|
|
84
|
+
### Knowledge
|
|
85
|
+
|
|
86
|
+
Knowledge files are what the skills refer to: the schema of your config document, record shapes, worked
|
|
87
|
+
examples, a glossary of your app's words. The assistant reads them when a skill points at them (each
|
|
88
|
+
skill is told where the app's knowledge files are).
|
|
89
|
+
|
|
90
|
+
**Generate the schema, don't write it.** If your config is typed (a TypeScript interface, a zod schema),
|
|
91
|
+
produce the JSON Schema at build time (`ts-json-schema-generator`, `zod-to-json-schema`) into
|
|
92
|
+
`public/assistant/knowledge/`. A hand-written schema drifts the first week.
|
|
93
|
+
|
|
94
|
+
## Tools: when a skill is not enough
|
|
95
|
+
|
|
96
|
+
Nearly every admin action is a read or a patch of config or records, and the assistant does those
|
|
97
|
+
itself. Give it a **tool** only when a step can't be an API call it makes on its own:
|
|
98
|
+
|
|
99
|
+
- logic that lives in your code (validate a layout, render a page from a design direction, compute
|
|
100
|
+
something from your data model),
|
|
101
|
+
- a write that must be atomic across several objects,
|
|
102
|
+
- anything that needs your app's secrets.
|
|
103
|
+
|
|
104
|
+
A tool is an http server function with an `agent` block
|
|
105
|
+
([agent-tools.md](agent-tools.md)); the assistant calls it as
|
|
106
|
+
`sl post app/<appId>/functions/<name> '{ … }'`, under the function's own visibility, authority and
|
|
107
|
+
capabilities. Set `"audience": ["assistant"]` to keep an assistant-only tool out of your in-app and
|
|
108
|
+
public agent runs:
|
|
109
|
+
|
|
110
|
+
```jsonc
|
|
111
|
+
{
|
|
112
|
+
"name": "applyDesignDirection",
|
|
113
|
+
"trigger": { "type": "http" },
|
|
114
|
+
"visibility": "admin",
|
|
115
|
+
"authority": "caller",
|
|
116
|
+
"capabilities": ["sl:settings:write"],
|
|
117
|
+
"agent": {
|
|
118
|
+
"tool": true,
|
|
119
|
+
"title": "Apply a design direction",
|
|
120
|
+
"description": "Rewrites the theme, fonts and layout of the hub to match a named direction.",
|
|
121
|
+
"input": { "type": "object", "properties": { "direction": { "type": "string" } }, "required": ["direction"] },
|
|
122
|
+
"audience": ["assistant"]
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
A tool that changes config should **return the patch, not apply it**, so the assistant can put it in a
|
|
128
|
+
change set and the admin keeps undo. Tools that act directly are fine for reads and computations.
|
|
129
|
+
|
|
130
|
+
## How the assistant changes your config
|
|
131
|
+
|
|
132
|
+
Your config document (`SL.appConfiguration`, `sl get app/<appId>`) and the collection's settings
|
|
133
|
+
groups (`sl get settings/<group>`) are changed through change-set items:
|
|
134
|
+
|
|
135
|
+
```jsonc
|
|
136
|
+
{ "op": "update", "entity": "appConfig", "id": "<appId>", "data": { "theme": { "accent": "#0a7" } } }
|
|
137
|
+
{ "op": "update", "entity": "appConfig", "id": "<appId>", "scope": { "productId": "p1" }, "data": { … } }
|
|
138
|
+
{ "op": "update", "entity": "settings", "id": "hub", "data": { "pages": { "about": { … } } } }
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
Saves deep-merge, as your own admin UI's saves do (nested objects merge, arrays replace). The whole
|
|
142
|
+
document before the change is kept, so **undo puts it back exactly**. Say in your skills which keys
|
|
143
|
+
are arrays, so the assistant sends the whole array when it changes one element.
|
|
144
|
+
|
|
145
|
+
### Preview before apply
|
|
146
|
+
|
|
147
|
+
A planned change set can be rendered before it is applied. The API gives, for every document a set
|
|
148
|
+
touches, the live document and the document as it would become:
|
|
149
|
+
|
|
150
|
+
```
|
|
151
|
+
GET /admin/collection/:collectionId/assistant/changesets/:id/preview
|
|
152
|
+
→ { id, status, targets: [ { entity, id, scope?, before, after, ops } ] }
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
Declare `assistant.preview` — a page of your admin surface that takes `{id}`, calls that endpoint
|
|
156
|
+
with the admin's session, and renders `targets[].after` as your app would render the live config. The
|
|
157
|
+
Studio panel then shows "Preview" on the change set, and the admin sees the result before saying yes.
|
|
158
|
+
|
|
159
|
+
## Checklist
|
|
160
|
+
|
|
161
|
+
- `assistant.skills[]`: several focused skills with descriptions that say when they apply.
|
|
162
|
+
- `assistant.knowledge[]`: the config schema generated from your types; record shapes; examples.
|
|
163
|
+
- Every skill says which fields are arrays, what to match on before creating, what to check after.
|
|
164
|
+
- Logic the assistant can't reproduce: a server function with `agent.tool` and `audience: ["assistant"]`,
|
|
165
|
+
returning a patch for config changes.
|
|
166
|
+
- `assistant.preview` when your app has a visual surface worth seeing before a change lands.
|
|
167
|
+
- `ai-guide.md` still describes first-time setup for the setup wizard; the assistant reads it too when
|
|
168
|
+
an app declares no skills.
|
|
@@ -172,6 +172,9 @@ Both sources use the same entry shape:
|
|
|
172
172
|
|
|
173
173
|
```typescript
|
|
174
174
|
interface DeepLinkEntry {
|
|
175
|
+
/** Stable id, unique within the app (optional): hosts and websites refer to the page by it */
|
|
176
|
+
id?: string;
|
|
177
|
+
|
|
175
178
|
/** Human-readable label for this state (required) */
|
|
176
179
|
title: string;
|
|
177
180
|
|
|
@@ -193,6 +196,13 @@ interface DeepLinkEntry {
|
|
|
193
196
|
*/
|
|
194
197
|
params?: Record<string, string>;
|
|
195
198
|
|
|
199
|
+
/**
|
|
200
|
+
* The params a host may supply when it opens this page (optional), e.g. a booking page that can
|
|
201
|
+
* start on one service: `{ optional: ['service', 'staff', 'date'] }`. `params` are pinned by the
|
|
202
|
+
* entry; `accepts` are filled in by whoever opens it.
|
|
203
|
+
*/
|
|
204
|
+
accepts?: { required?: string[]; optional?: string[] };
|
|
205
|
+
|
|
196
206
|
/**
|
|
197
207
|
* When `true`, this entry is also available as a dynamic data context for widgets.
|
|
198
208
|
*
|
|
@@ -211,7 +221,9 @@ interface DeepLinkEntry {
|
|
|
211
221
|
|-------|----------|-------|
|
|
212
222
|
| `title` | ✅ | Displayed in menus and offered to AI agents. Should be concise and human-readable. |
|
|
213
223
|
| `path` | ❌ | Hash route within the app. Defaults to `/` if omitted. |
|
|
224
|
+
| `id` | ❌ | Stable id for hosts and websites to refer to the page by. |
|
|
214
225
|
| `params` | ❌ | App-specific query params only. Platform params are injected automatically. |
|
|
226
|
+
| `accepts` | ❌ | The params whoever opens the page may supply (`required` / `optional`). |
|
|
215
227
|
| `widget` | ❌ | Set to `true` to make this entry selectable in the widget config picker as a dynamic data context. |
|
|
216
228
|
|
|
217
229
|
> **Tip:** Different entries can share the same `path` if they differ by `params` — for example, two entries pointing at `/settings` with different `tab` params.
|
|
@@ -103,6 +103,7 @@ interface IframeResponderOptions {
|
|
|
103
103
|
version?: 'stable' | 'development'; // App version (default: 'stable')
|
|
104
104
|
appUrl?: string; // Override URL (for local dev)
|
|
105
105
|
initialPath?: string; // Initial hash path (e.g., '/settings')
|
|
106
|
+
params?: Record<string, string>; // The page's own params (e.g. { service: 'svc_123' }); context params win
|
|
106
107
|
|
|
107
108
|
// Data
|
|
108
109
|
cache?: CachedData; // Pre-cached data
|
package/dist/docs/websites.md
CHANGED
|
@@ -66,6 +66,32 @@ bytes. `_server/` is never served to visitors.
|
|
|
66
66
|
doing work that belongs in a server function or a scheduled job.
|
|
67
67
|
- **No secrets** in the server bundle and no Node built-ins beyond `nodejs_compat`.
|
|
68
68
|
|
|
69
|
+
## Using another app's pages
|
|
70
|
+
|
|
71
|
+
Some apps do a job a site shouldn't rebuild: a booking journey holds the time rules, the clash
|
|
72
|
+
handling and the confirmation; "my bookings" holds who may change what. Those apps declare their pages
|
|
73
|
+
in `linkable` (app-manifest.md), each with a stable `id`, a `path` and the params it `accepts`. A site
|
|
74
|
+
embeds one with **`IframeResponder`** (iframe-responder.md), from the browser:
|
|
75
|
+
|
|
76
|
+
```typescript
|
|
77
|
+
const site = SL.site.current()
|
|
78
|
+
const responder = new SL.IframeResponder({
|
|
79
|
+
collectionId: site.collectionId,
|
|
80
|
+
appId: 'appointments', // the app's id on this collection
|
|
81
|
+
initialPath: '/my-bookings', // the page's `path`
|
|
82
|
+
params: { service: 'svc_123' }, // what the page `accepts`
|
|
83
|
+
onResize: (h) => { iframe.style.height = `${h}px` },
|
|
84
|
+
})
|
|
85
|
+
iframe.src = await responder.attach(iframe)
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
- **The visitor's session goes with it.** The page's API calls are proxied through the site, so it
|
|
89
|
+
acts as whoever is signed in to the site (or as a guest). Never pass a token in the URL.
|
|
90
|
+
- **The app must be installed** on the site's collection; its address comes from there.
|
|
91
|
+
- **Show the app's content natively where you can** (its `headless` data: services, staff, events)
|
|
92
|
+
for your own design and SEO, and embed the page only for the part that does the work.
|
|
93
|
+
- Pages render on the client, after load: they aren't part of the server-rendered HTML.
|
|
94
|
+
|
|
69
95
|
## Caching
|
|
70
96
|
|
|
71
97
|
The router keeps a rendered page at the edge when your response says it may:
|
package/dist/iframeResponder.js
CHANGED
|
@@ -155,11 +155,16 @@ export class IframeResponder {
|
|
|
155
155
|
// URL Building
|
|
156
156
|
// ===========================================================================
|
|
157
157
|
buildIframeSrc() {
|
|
158
|
-
var _a, _b;
|
|
158
|
+
var _a, _b, _c;
|
|
159
159
|
if (!this.appUrl) {
|
|
160
160
|
throw new Error('App URL not resolved');
|
|
161
161
|
}
|
|
162
162
|
const params = new URLSearchParams();
|
|
163
|
+
// The page's own params first: the platform's context below always wins.
|
|
164
|
+
for (const [key, value] of Object.entries((_a = this.options.params) !== null && _a !== void 0 ? _a : {})) {
|
|
165
|
+
if (value != null && value !== '')
|
|
166
|
+
params.set(key, String(value));
|
|
167
|
+
}
|
|
163
168
|
// Required context
|
|
164
169
|
params.set('collectionId', this.options.collectionId);
|
|
165
170
|
params.set('appId', this.options.appId);
|
|
@@ -171,13 +176,13 @@ export class IframeResponder {
|
|
|
171
176
|
if (this.options.isAdmin)
|
|
172
177
|
params.set('isAdmin', 'true');
|
|
173
178
|
// Dark mode from collection
|
|
174
|
-
const isDark = (
|
|
179
|
+
const isDark = (_c = (_b = this.cache.collection) === null || _b === void 0 ? void 0 : _b.dark) !== null && _c !== void 0 ? _c : false;
|
|
175
180
|
params.set('dark', isDark ? '1' : '0');
|
|
176
181
|
// Parent URL for redirects
|
|
177
182
|
try {
|
|
178
183
|
params.set('parentUrl', window.location.href);
|
|
179
184
|
}
|
|
180
|
-
catch (
|
|
185
|
+
catch (_d) {
|
|
181
186
|
// Ignore if can't access location
|
|
182
187
|
}
|
|
183
188
|
// Encode theme from collection
|
|
@@ -192,7 +197,7 @@ export class IframeResponder {
|
|
|
192
197
|
params.set('theme', btoa(JSON.stringify(themeData)));
|
|
193
198
|
}
|
|
194
199
|
}
|
|
195
|
-
catch (
|
|
200
|
+
catch (_e) {
|
|
196
201
|
// Ignore encoding errors
|
|
197
202
|
}
|
|
198
203
|
}
|
|
@@ -210,7 +215,8 @@ export class IframeResponder {
|
|
|
210
215
|
const path = this.options.initialPath.startsWith('/') ? this.options.initialPath : '/' + this.options.initialPath;
|
|
211
216
|
// Insert path before the ?
|
|
212
217
|
const [beforeHash, afterHash] = baseWithHash.split('#');
|
|
213
|
-
|
|
218
|
+
// App addresses end in "#/": join without doubling the slash ("#//my-bookings" matches no route).
|
|
219
|
+
finalUrl = `${beforeHash}#${(afterHash || '').replace(/\/+$/, '')}${path}?${existingParams.toString()}`;
|
|
214
220
|
}
|
|
215
221
|
else {
|
|
216
222
|
finalUrl = `${baseWithHash}?${existingParams.toString()}`;
|
|
@@ -765,8 +771,12 @@ export function isAdminFromRoles(user, collection, proof) {
|
|
|
765
771
|
* Standalone helper for cases where you don't need the full IframeResponder.
|
|
766
772
|
*/
|
|
767
773
|
export function buildIframeSrc(options) {
|
|
768
|
-
var _a, _b;
|
|
774
|
+
var _a, _b, _c;
|
|
769
775
|
const params = new URLSearchParams();
|
|
776
|
+
for (const [key, value] of Object.entries((_a = options.params) !== null && _a !== void 0 ? _a : {})) {
|
|
777
|
+
if (value != null && value !== '')
|
|
778
|
+
params.set(key, String(value));
|
|
779
|
+
}
|
|
770
780
|
params.set('collectionId', options.collectionId);
|
|
771
781
|
params.set('appId', options.appId);
|
|
772
782
|
if (options.productId)
|
|
@@ -776,14 +786,14 @@ export function buildIframeSrc(options) {
|
|
|
776
786
|
if (options.isAdmin)
|
|
777
787
|
params.set('isAdmin', 'true');
|
|
778
788
|
params.set('dark', options.dark ? '1' : '0');
|
|
779
|
-
if (((
|
|
789
|
+
if (((_b = options.theme) === null || _b === void 0 ? void 0 : _b.primary) || ((_c = options.theme) === null || _c === void 0 ? void 0 : _c.secondary)) {
|
|
780
790
|
const themeData = { p: options.theme.primary, s: options.theme.secondary };
|
|
781
791
|
params.set('theme', btoa(JSON.stringify(themeData)));
|
|
782
792
|
}
|
|
783
793
|
try {
|
|
784
794
|
params.set('parentUrl', window.location.href);
|
|
785
795
|
}
|
|
786
|
-
catch (
|
|
796
|
+
catch (_d) {
|
|
787
797
|
// Ignore
|
|
788
798
|
}
|
|
789
799
|
let base = options.appUrl.replace(/#\/?$/, '').replace(/\/$/, '');
|
package/dist/openapi.yaml
CHANGED
|
@@ -19909,6 +19909,8 @@ components:
|
|
|
19909
19909
|
DeepLinkEntry:
|
|
19910
19910
|
type: object
|
|
19911
19911
|
properties:
|
|
19912
|
+
id:
|
|
19913
|
+
type: string
|
|
19912
19914
|
title:
|
|
19913
19915
|
type: string
|
|
19914
19916
|
path:
|
|
@@ -19917,6 +19919,8 @@ components:
|
|
|
19917
19919
|
type: object
|
|
19918
19920
|
additionalProperties:
|
|
19919
19921
|
type: string
|
|
19922
|
+
accepts:
|
|
19923
|
+
$ref: "#/components/schemas/PublicViewParams"
|
|
19920
19924
|
widget:
|
|
19921
19925
|
type: object
|
|
19922
19926
|
additionalProperties: true
|
|
@@ -20134,6 +20138,14 @@ components:
|
|
|
20134
20138
|
enum:
|
|
20135
20139
|
- auto
|
|
20136
20140
|
- require
|
|
20141
|
+
audience:
|
|
20142
|
+
type: array
|
|
20143
|
+
items:
|
|
20144
|
+
type: string
|
|
20145
|
+
enum:
|
|
20146
|
+
- app
|
|
20147
|
+
- public
|
|
20148
|
+
- assistant
|
|
20137
20149
|
required:
|
|
20138
20150
|
- tool
|
|
20139
20151
|
AppManifestFunctions:
|
|
@@ -20631,6 +20643,8 @@ components:
|
|
|
20631
20643
|
$ref: "#/components/schemas/AppHeadlessDeclaration"
|
|
20632
20644
|
connector:
|
|
20633
20645
|
$ref: "#/components/schemas/AppConnectorDeclaration"
|
|
20646
|
+
assistant:
|
|
20647
|
+
$ref: "#/components/schemas/AppAssistantDeclaration"
|
|
20634
20648
|
required:
|
|
20635
20649
|
- name
|
|
20636
20650
|
- version
|
|
@@ -20638,6 +20652,31 @@ components:
|
|
|
20638
20652
|
- function
|
|
20639
20653
|
- files
|
|
20640
20654
|
- components
|
|
20655
|
+
AppAssistantFile:
|
|
20656
|
+
type: object
|
|
20657
|
+
properties:
|
|
20658
|
+
name:
|
|
20659
|
+
type: string
|
|
20660
|
+
description:
|
|
20661
|
+
type: string
|
|
20662
|
+
file:
|
|
20663
|
+
type: string
|
|
20664
|
+
required:
|
|
20665
|
+
- name
|
|
20666
|
+
- file
|
|
20667
|
+
AppAssistantDeclaration:
|
|
20668
|
+
type: object
|
|
20669
|
+
properties:
|
|
20670
|
+
skills:
|
|
20671
|
+
type: array
|
|
20672
|
+
items:
|
|
20673
|
+
$ref: "#/components/schemas/AppAssistantFile"
|
|
20674
|
+
knowledge:
|
|
20675
|
+
type: array
|
|
20676
|
+
items:
|
|
20677
|
+
$ref: "#/components/schemas/AppAssistantFile"
|
|
20678
|
+
preview:
|
|
20679
|
+
type: string
|
|
20641
20680
|
ConnectorConnectionField:
|
|
20642
20681
|
type: object
|
|
20643
20682
|
properties:
|
|
@@ -27652,6 +27691,10 @@ components:
|
|
|
27652
27691
|
type: string
|
|
27653
27692
|
initialPath:
|
|
27654
27693
|
type: string
|
|
27694
|
+
params:
|
|
27695
|
+
type: object
|
|
27696
|
+
additionalProperties:
|
|
27697
|
+
type: string
|
|
27655
27698
|
isAdmin:
|
|
27656
27699
|
type: boolean
|
|
27657
27700
|
cache:
|
|
@@ -109,6 +109,12 @@ export interface AppContainerComponent {
|
|
|
109
109
|
* { "title": "About Us", "params": { "pageId": "about-us" } }
|
|
110
110
|
*/
|
|
111
111
|
export interface DeepLinkEntry {
|
|
112
|
+
/**
|
|
113
|
+
* Stable id for this page, unique within the app (optional). Hosts and websites refer to the page
|
|
114
|
+
* by it (e.g. a site embeds `{ app: 'appointments', page: 'book' }`), so a renamed `title` or a moved
|
|
115
|
+
* `path` doesn't break them.
|
|
116
|
+
*/
|
|
117
|
+
id?: string;
|
|
112
118
|
/** Human-readable label shown in menus and offered to AI agents */
|
|
113
119
|
title: string;
|
|
114
120
|
/**
|
|
@@ -123,6 +129,12 @@ export interface DeepLinkEntry {
|
|
|
123
129
|
* those are injected by the platform automatically.
|
|
124
130
|
*/
|
|
125
131
|
params?: Record<string, string>;
|
|
132
|
+
/**
|
|
133
|
+
* The params a host may supply when it opens this page (in addition to the platform's own), e.g. a
|
|
134
|
+
* booking page that can start on one service: `{ optional: ['service', 'staff', 'date'] }`. `params`
|
|
135
|
+
* above are the values this entry PINS; `accepts` are the ones the caller fills in.
|
|
136
|
+
*/
|
|
137
|
+
accepts?: PublicViewParams;
|
|
126
138
|
/**
|
|
127
139
|
* When `true`, this entry is also available as a dynamic data context for widgets
|
|
128
140
|
* (in addition to being a navigable page / container route).
|
|
@@ -337,6 +349,13 @@ export interface AppFunctionAgentExposure {
|
|
|
337
349
|
* the autonomous server-side loop (they'd otherwise run unconfirmed).
|
|
338
350
|
*/
|
|
339
351
|
approval?: 'auto' | 'require';
|
|
352
|
+
/**
|
|
353
|
+
* Where the tool is offered. `'app'` = the app's own agent runs (admin surface), `'public'` = public
|
|
354
|
+
* assistants, `'assistant'` = the Studio assistant (the admin's agent, docs/assistant-apps.md).
|
|
355
|
+
* Absent = everywhere the function's visibility allows. An assistant-only tool (logic the assistant
|
|
356
|
+
* needs that lives in your code) declares `['assistant']` and never shows in an in-app or public run.
|
|
357
|
+
*/
|
|
358
|
+
audience?: Array<'app' | 'public' | 'assistant'>;
|
|
340
359
|
}
|
|
341
360
|
/** The `functions` block in `app.manifest.json`. Presence means the app ships server functions. */
|
|
342
361
|
export interface AppManifestFunctions {
|
|
@@ -856,10 +875,10 @@ export interface AppManifest {
|
|
|
856
875
|
components: AppContainerComponent[];
|
|
857
876
|
};
|
|
858
877
|
/**
|
|
859
|
-
*
|
|
860
|
-
*
|
|
861
|
-
*
|
|
862
|
-
*
|
|
878
|
+
* The app's PAGES that a host opens — portal menus (bottom menu, side menus, tabs) and WEBSITES that
|
|
879
|
+
* embed the app (IframeResponder). Use these when one app has several ways in (a list view and a
|
|
880
|
+
* calendar view; book, upcoming bookings, change a booking). A host opens them with its own signed-in
|
|
881
|
+
* session: the page's API calls go through the host, so it acts as the person signed in there.
|
|
863
882
|
* Fixed routes are declared here at build time; dynamic content entries (e.g. CMS pages) are stored
|
|
864
883
|
* separately in `appConfig.linkable` — merge both for the full navigable set.
|
|
865
884
|
* @see DeepLinkEntry
|
|
@@ -929,8 +948,52 @@ export interface AppManifest {
|
|
|
929
948
|
* @see AppConnectorDeclaration · docs/connectors.md
|
|
930
949
|
*/
|
|
931
950
|
connector?: AppConnectorDeclaration;
|
|
951
|
+
/**
|
|
952
|
+
* What the Studio assistant (the admin's agent) knows about this app: skills and knowledge files
|
|
953
|
+
* shipped with the build and loaded into its box on demand, and an optional page that renders a
|
|
954
|
+
* planned change set. See docs/assistant-apps.md. @see AppAssistantDeclaration
|
|
955
|
+
*/
|
|
956
|
+
assistant?: AppAssistantDeclaration;
|
|
932
957
|
[key: string]: any;
|
|
933
958
|
}
|
|
959
|
+
/** One skill or knowledge file the app ships for the assistant. */
|
|
960
|
+
export interface AppAssistantFile {
|
|
961
|
+
/** Short id, unique within the app (`theme`, `pages`, `settings-schema`). */
|
|
962
|
+
name: string;
|
|
963
|
+
/**
|
|
964
|
+
* One line: what it covers and when to reach for it. For a skill this is what the assistant sees
|
|
965
|
+
* before deciding to read the file, so say when it applies ("Changing the Hub's colours, fonts or
|
|
966
|
+
* logo"). A knowledge file's description says what is in it.
|
|
967
|
+
*/
|
|
968
|
+
description?: string;
|
|
969
|
+
/**
|
|
970
|
+
* Path of the file in the published build, relative to the app's root (`public/` in the project):
|
|
971
|
+
* `assistant/skills/theme.md`. Markdown for skills; markdown or JSON (a JSON Schema) for knowledge.
|
|
972
|
+
*/
|
|
973
|
+
file: string;
|
|
974
|
+
}
|
|
975
|
+
/** The `assistant` block in `app.manifest.json`. */
|
|
976
|
+
export interface AppAssistantDeclaration {
|
|
977
|
+
/**
|
|
978
|
+
* Skills: plain-words procedures for one area of the app's admin side — what the settings mean,
|
|
979
|
+
* which calls read and change them, what a good result looks like, what to check. Several for a big
|
|
980
|
+
* app (identity and theme; pages and navigation; domains and SEO). A skill file may start with
|
|
981
|
+
* YAML frontmatter (`name`, `description`); if it doesn't, these entries supply it.
|
|
982
|
+
*/
|
|
983
|
+
skills?: AppAssistantFile[];
|
|
984
|
+
/**
|
|
985
|
+
* Knowledge the skills refer to: the settings schema (generate it from your config types — never
|
|
986
|
+
* by hand), object definitions, worked examples, a glossary. Loaded only when a skill reaches for it.
|
|
987
|
+
*/
|
|
988
|
+
knowledge?: AppAssistantFile[];
|
|
989
|
+
/**
|
|
990
|
+
* A page of the app that renders a PLANNED change set over the live config, so the admin can see a
|
|
991
|
+
* change before it is applied. `{id}` is replaced with the change-set id; the page reads
|
|
992
|
+
* `GET /admin/collection/:cid/assistant/changesets/:id/preview` and renders `targets[].after`.
|
|
993
|
+
* Relative to the app's admin surface, e.g. `"/admin/preview?changeSet={id}"`.
|
|
994
|
+
*/
|
|
995
|
+
preview?: string;
|
|
996
|
+
}
|
|
934
997
|
/** One field of a connector's "Connect" form. */
|
|
935
998
|
export interface ConnectorConnectionField {
|
|
936
999
|
/** Key in the installation's connection settings (non-secret fields) — `name` for a secret too. */
|
|
@@ -24,6 +24,12 @@ export interface IframeResponderOptions {
|
|
|
24
24
|
appUrl?: string;
|
|
25
25
|
/** Initial hash path (e.g., '/settings') */
|
|
26
26
|
initialPath?: string;
|
|
27
|
+
/**
|
|
28
|
+
* The page's own params (a `linkable` entry's pinned `params` plus what it `accepts`), e.g.
|
|
29
|
+
* `{ service: 'svc_123' }`. Added to the app's URL alongside the platform's context params, which
|
|
30
|
+
* always win (collectionId, appId, productId, proofId, isAdmin, dark, theme, parentUrl).
|
|
31
|
+
*/
|
|
32
|
+
params?: Record<string, string>;
|
|
27
33
|
/** Is user an admin of this collection */
|
|
28
34
|
isAdmin?: boolean;
|
|
29
35
|
cache?: CachedData;
|
package/docs/API_SUMMARY.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Smartlinks API Summary
|
|
2
2
|
|
|
3
|
-
Version: 2.1.
|
|
3
|
+
Version: 2.1.4 | Generated: 2026-10-28T12:35:09.709Z
|
|
4
4
|
|
|
5
5
|
This is a concise summary of all available API functions and types.
|
|
6
6
|
|
|
@@ -2585,9 +2585,11 @@ interface AppContainerComponent {
|
|
|
2585
2585
|
**DeepLinkEntry** (interface)
|
|
2586
2586
|
```typescript
|
|
2587
2587
|
interface DeepLinkEntry {
|
|
2588
|
+
id?: string;
|
|
2588
2589
|
title: string;
|
|
2589
2590
|
path?: string;
|
|
2590
2591
|
params?: Record<string, string>;
|
|
2592
|
+
accepts?: PublicViewParams;
|
|
2591
2593
|
widget?: true;
|
|
2592
2594
|
}
|
|
2593
2595
|
```
|
|
@@ -2711,6 +2713,7 @@ interface AppFunctionAgentExposure {
|
|
|
2711
2713
|
description?: string;
|
|
2712
2714
|
input?: Record<string, any>;
|
|
2713
2715
|
approval?: 'auto' | 'require';
|
|
2716
|
+
audience?: Array<'app' | 'public' | 'assistant'>;
|
|
2714
2717
|
}
|
|
2715
2718
|
```
|
|
2716
2719
|
|
|
@@ -3049,10 +3052,29 @@ interface AppManifest {
|
|
|
3049
3052
|
data?: AppDataDeclaration;
|
|
3050
3053
|
headless?: AppHeadlessDeclaration;
|
|
3051
3054
|
connector?: AppConnectorDeclaration;
|
|
3055
|
+
assistant?: AppAssistantDeclaration;
|
|
3052
3056
|
[key: string]: any;
|
|
3053
3057
|
}
|
|
3054
3058
|
```
|
|
3055
3059
|
|
|
3060
|
+
**AppAssistantFile** (interface)
|
|
3061
|
+
```typescript
|
|
3062
|
+
interface AppAssistantFile {
|
|
3063
|
+
name: string;
|
|
3064
|
+
description?: string;
|
|
3065
|
+
file: string;
|
|
3066
|
+
}
|
|
3067
|
+
```
|
|
3068
|
+
|
|
3069
|
+
**AppAssistantDeclaration** (interface)
|
|
3070
|
+
```typescript
|
|
3071
|
+
interface AppAssistantDeclaration {
|
|
3072
|
+
skills?: AppAssistantFile[];
|
|
3073
|
+
knowledge?: AppAssistantFile[];
|
|
3074
|
+
preview?: string;
|
|
3075
|
+
}
|
|
3076
|
+
```
|
|
3077
|
+
|
|
3056
3078
|
**ConnectorConnectionField** (interface)
|
|
3057
3079
|
```typescript
|
|
3058
3080
|
interface ConnectorConnectionField {
|
|
@@ -7479,6 +7501,7 @@ interface IframeResponderOptions {
|
|
|
7479
7501
|
version?: string;
|
|
7480
7502
|
appUrl?: string;
|
|
7481
7503
|
initialPath?: string;
|
|
7504
|
+
params?: Record<string, string>;
|
|
7482
7505
|
isAdmin?: boolean;
|
|
7483
7506
|
cache?: CachedData;
|
|
7484
7507
|
onAuthLogin?: (
|
package/docs/agent-tools.md
CHANGED
|
@@ -44,7 +44,8 @@ needs to call it — a description and an input schema:
|
|
|
44
44
|
"title": "Create an FAQ entry",
|
|
45
45
|
"description": "Add a question/answer to this collection's FAQ. Use when the user asks to add or draft an FAQ.",
|
|
46
46
|
"input": { /* JSON Schema for the arguments the agent supplies as event.body */ },
|
|
47
|
-
"approval": "auto"
|
|
47
|
+
"approval": "auto", // auto | require (require = human confirmation before the call)
|
|
48
|
+
"audience": ["app"] // optional: app | public | assistant — where it is offered (absent = everywhere)
|
|
48
49
|
}
|
|
49
50
|
}
|
|
50
51
|
]
|
|
@@ -103,6 +104,10 @@ Identical to server functions — nothing new to reason about:
|
|
|
103
104
|
- **`visibility`** — who may call (admin/public).
|
|
104
105
|
- **`authority`** — whose identity `ctx.sl` carries (`caller` vs `collection`).
|
|
105
106
|
- **`capabilities`** — least-privilege, capped even when elevated.
|
|
107
|
+
- **`agent.audience`** — where the tool is offered: `'app'` (the app's own agent runs, admin surface),
|
|
108
|
+
`'public'` (public assistants), `'assistant'` (the Studio assistant, the admin's agent —
|
|
109
|
+
[assistant-apps.md](assistant-apps.md)). Absent = everywhere the function's visibility allows. An
|
|
110
|
+
assistant-only tool declares `["assistant"]` and never joins an in-app or public run.
|
|
106
111
|
- **`agent.approval: "require"`** — the agent must get human confirmation before invoking (use for
|
|
107
112
|
destructive or public-facing actions).
|
|
108
113
|
|
package/docs/app-manifest.md
CHANGED
|
@@ -326,11 +326,28 @@ Static deep-linkable states built into the app — fixed routes that exist regar
|
|
|
326
326
|
|
|
327
327
|
See the [Deep Link Discovery guide](deep-link-discovery.md) for the full dual-source pattern (static manifest routes + dynamic `appConfig.linkable`).
|
|
328
328
|
|
|
329
|
+
These are the app's **pages that a host opens**: portal menus and tabs, and websites that embed the app
|
|
330
|
+
(websites.md → "Using another app's pages"). A host opens them with its own signed-in session, so one
|
|
331
|
+
app can offer several pages that each know the visitor: book, upcoming bookings, past bookings, change a
|
|
332
|
+
booking.
|
|
333
|
+
|
|
334
|
+
```json
|
|
335
|
+
"linkable": [
|
|
336
|
+
{ "id": "book", "title": "Book an appointment", "path": "/",
|
|
337
|
+
"accepts": { "optional": ["service", "staff", "date"] } },
|
|
338
|
+
{ "id": "upcoming", "title": "Your upcoming bookings", "path": "/my-bookings" },
|
|
339
|
+
{ "id": "change", "title": "Change a booking", "path": "/booking",
|
|
340
|
+
"accepts": { "required": ["id"] } }
|
|
341
|
+
]
|
|
342
|
+
```
|
|
343
|
+
|
|
329
344
|
| Field | Type | Required | Description |
|
|
330
345
|
|-------|------|----------|-------------|
|
|
346
|
+
| `id` | string | ❌ | Stable id, unique within the app: hosts and sites refer to the page by it |
|
|
331
347
|
| `title` | string | ✅ | Human-readable label shown in menus and offered to AI agents |
|
|
332
348
|
| `path` | string | ❌ | Hash route within the app (defaults to `"/"` if omitted) |
|
|
333
|
-
| `params` | object | ❌ |
|
|
349
|
+
| `params` | object | ❌ | Params this entry PINS, appended to the URL — do **not** include platform params (`collectionId`, `productId`, etc.) |
|
|
350
|
+
| `accepts` | `{ required?: string[]; optional?: string[] }` | ❌ | The params a host may supply when it opens the page (e.g. which service to start on) |
|
|
334
351
|
|
|
335
352
|
#### `publicViews`
|
|
336
353
|
|
|
@@ -371,7 +388,7 @@ An app can have any combination of three setup styles. Only public views are ext
|
|
|
371
388
|
| Style | Declared by | Lives | Context |
|
|
372
389
|
|-------|-------------|-------|---------|
|
|
373
390
|
| **Portal components** — where the app renders inside portal (collection / product / batch / variant / proof) | module registry `components.*` (set at publish) | Inside portal | Physical twin (QR/NFC scan) |
|
|
374
|
-
| **
|
|
391
|
+
| **App pages** — the entry points portal menus/tabs link to and websites embed (e.g. list vs calendar; book vs my bookings) | `linkable` / `DeepLinkEntry` | Inside a host: portal or a website | The host: its signed-in session, plus the page's `accepts` params |
|
|
375
392
|
| **Public views** — standalone external screens | `publicViews` (this block) | Outside portal | URL params only |
|
|
376
393
|
|
|
377
394
|
#### `records`
|
|
@@ -443,6 +460,30 @@ lists it and draws its connect form and settings, and the integration engine cal
|
|
|
443
460
|
functions (`testConnection`, `describeTarget`, `sample`, `fetch`, `shape`, `serialize`, `send`). It adapts the provider's format; core does each customer's mapping. The full
|
|
444
461
|
contract is in the **[Connectors guide](connectors.md)**.
|
|
445
462
|
|
|
463
|
+
#### `assistant`
|
|
464
|
+
|
|
465
|
+
What the **Studio assistant** (the admin's own agent) knows about this app: skills and knowledge files
|
|
466
|
+
shipped with the build and loaded into its box on demand, and an optional page that renders a planned
|
|
467
|
+
change set before it is applied.
|
|
468
|
+
|
|
469
|
+
```jsonc
|
|
470
|
+
"assistant": {
|
|
471
|
+
"skills": [
|
|
472
|
+
{ "name": "theme", "description": "Changing the Hub's colours, fonts or logo", "file": "assistant/skills/theme.md" },
|
|
473
|
+
{ "name": "pages", "description": "Adding, ordering and editing pages and the menu", "file": "assistant/skills/pages.md" }
|
|
474
|
+
],
|
|
475
|
+
"knowledge": [
|
|
476
|
+
{ "name": "settings-schema", "description": "JSON Schema of the hub settings group", "file": "assistant/knowledge/settings.schema.json" }
|
|
477
|
+
],
|
|
478
|
+
"preview": "/admin/preview?changeSet={id}"
|
|
479
|
+
}
|
|
480
|
+
```
|
|
481
|
+
|
|
482
|
+
The files live under `public/assistant/` and ship with every release like any other static file. The
|
|
483
|
+
assistant sees one line per skill (its description) and reads the file only when the job calls for it,
|
|
484
|
+
so a big app can carry many skills at no cost to the conversation. Full guide, including what a skill
|
|
485
|
+
should say and how the assistant changes config with undo: **[assistant-apps.md](assistant-apps.md)**.
|
|
486
|
+
|
|
446
487
|
---
|
|
447
488
|
|
|
448
489
|
## `app.admin.json`
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
# The Studio assistant and your app
|
|
2
|
+
|
|
3
|
+
SmartLinks admins have **one assistant**: an agent that works for the signed-in admin on one
|
|
4
|
+
collection, with the admin's own authority, from a chat in Studio. It reads and changes data through
|
|
5
|
+
the SmartLinks API, every write is a **change set** the admin can see and undo, and the things that
|
|
6
|
+
can't be undone (deleting, sending, calling out, publishing, spending) wait for the admin's "Allow".
|
|
7
|
+
|
|
8
|
+
This page is for app authors: how the assistant learns what your app is, how it is set up, and what it
|
|
9
|
+
can do for the admin — without you building a chat of your own. Your app declares three things:
|
|
10
|
+
|
|
11
|
+
| What | Where | Status |
|
|
12
|
+
|---|---|---|
|
|
13
|
+
| **Skills and knowledge** — plain-words procedures for the admin side, and the facts they need | `assistant` block in `app.manifest.json`, files under `public/assistant/` | this page |
|
|
14
|
+
| **Tools** — logic the assistant should call rather than reimplement | server functions with an `agent` block (`audience: ["assistant"]` for assistant-only) | [agent-tools.md](agent-tools.md) |
|
|
15
|
+
| **Setup** — questions, config schema, how to save | `app.admin.json` + `ai-guide.md` | [app-manifest.md](app-manifest.md#appadminjson) |
|
|
16
|
+
|
|
17
|
+
An app with none of these still works with the assistant: it can read the app's records and config
|
|
18
|
+
and change them through change sets. The declarations make it *good* at your app.
|
|
19
|
+
|
|
20
|
+
## The `assistant` block
|
|
21
|
+
|
|
22
|
+
```jsonc
|
|
23
|
+
// app.manifest.json
|
|
24
|
+
"assistant": {
|
|
25
|
+
"skills": [
|
|
26
|
+
{ "name": "setup", "description": "First-time setup of the FAQ app: categories, tone, who answers", "file": "assistant/skills/setup.md" },
|
|
27
|
+
{ "name": "content", "description": "Adding, editing, ordering and retiring questions and answers", "file": "assistant/skills/content.md" }
|
|
28
|
+
],
|
|
29
|
+
"knowledge": [
|
|
30
|
+
{ "name": "config-schema", "description": "JSON Schema of the app's config document", "file": "assistant/knowledge/config.schema.json" },
|
|
31
|
+
{ "name": "examples", "description": "Three worked examples of good FAQ content", "file": "assistant/knowledge/examples.md" }
|
|
32
|
+
],
|
|
33
|
+
"preview": "/admin/preview?changeSet={id}"
|
|
34
|
+
}
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
The files sit in `public/assistant/` in your project and ship with every release like any other static
|
|
38
|
+
file (they are read from the release's CDN folder, on the channel the collection runs your app on).
|
|
39
|
+
|
|
40
|
+
### How they are loaded
|
|
41
|
+
|
|
42
|
+
When an admin opens a chat, the assistant's box gets the **index** of every installed app's skills: one
|
|
43
|
+
line each, the description. Nothing else. A skill's body, and the knowledge files it names, are read
|
|
44
|
+
only when the assistant decides the job needs them. Twenty apps with three skills each cost the
|
|
45
|
+
conversation about sixty lines. So: write several focused skills rather than one long one, and spend
|
|
46
|
+
care on the description — it is how the assistant decides to open the file.
|
|
47
|
+
|
|
48
|
+
### Writing a skill
|
|
49
|
+
|
|
50
|
+
A skill is a markdown file: what this area of your app is for, what each setting means, how to read and
|
|
51
|
+
change it, what a good result looks like, what to check afterwards. Write it for a capable colleague who
|
|
52
|
+
has never seen your app. The assistant works through the SmartLinks API with a small CLI (`sl`), so say
|
|
53
|
+
things in those terms:
|
|
54
|
+
|
|
55
|
+
```markdown
|
|
56
|
+
---
|
|
57
|
+
name: faq-content
|
|
58
|
+
description: Adding, editing, ordering and retiring questions and answers in the FAQ app
|
|
59
|
+
---
|
|
60
|
+
# FAQ content
|
|
61
|
+
|
|
62
|
+
Questions live as app records of type `question` (`sl get app/faq/records recordType=question`).
|
|
63
|
+
Fields: `data.question`, `data.answer` (markdown), `data.category` (one of the categories in the app's
|
|
64
|
+
config, `sl get app/faq` → `categories[]`), `data.order` (ascending), `status` (`published` | `draft`).
|
|
65
|
+
|
|
66
|
+
## Adding questions
|
|
67
|
+
Plan one change set per batch: items `{ "op": "create", "entity": "record", "appId": "faq",
|
|
68
|
+
"data": { "recordType": "question", "data": { … }, "status": "draft" } }`. Match on `data.question`
|
|
69
|
+
(case-insensitive) before creating, so a re-run updates instead of duplicating.
|
|
70
|
+
|
|
71
|
+
## A good answer
|
|
72
|
+
Two to five sentences, plain words, no marketing. Link to a product page when one exists
|
|
73
|
+
(`/p/<collectionId>/<productId>`). Never invent policy: if the answer depends on something the admin
|
|
74
|
+
hasn't said (returns window, delivery areas), ask.
|
|
75
|
+
|
|
76
|
+
## Check afterwards
|
|
77
|
+
`sl get app/faq/records recordType=question status=draft` — read three back and make sure the
|
|
78
|
+
category is one that exists.
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
The frontmatter is optional; when it is missing, the manifest entry's `name` and `description` are used.
|
|
82
|
+
Keep a skill under a few hundred lines; put long reference material in knowledge files and name them.
|
|
83
|
+
|
|
84
|
+
### Knowledge
|
|
85
|
+
|
|
86
|
+
Knowledge files are what the skills refer to: the schema of your config document, record shapes, worked
|
|
87
|
+
examples, a glossary of your app's words. The assistant reads them when a skill points at them (each
|
|
88
|
+
skill is told where the app's knowledge files are).
|
|
89
|
+
|
|
90
|
+
**Generate the schema, don't write it.** If your config is typed (a TypeScript interface, a zod schema),
|
|
91
|
+
produce the JSON Schema at build time (`ts-json-schema-generator`, `zod-to-json-schema`) into
|
|
92
|
+
`public/assistant/knowledge/`. A hand-written schema drifts the first week.
|
|
93
|
+
|
|
94
|
+
## Tools: when a skill is not enough
|
|
95
|
+
|
|
96
|
+
Nearly every admin action is a read or a patch of config or records, and the assistant does those
|
|
97
|
+
itself. Give it a **tool** only when a step can't be an API call it makes on its own:
|
|
98
|
+
|
|
99
|
+
- logic that lives in your code (validate a layout, render a page from a design direction, compute
|
|
100
|
+
something from your data model),
|
|
101
|
+
- a write that must be atomic across several objects,
|
|
102
|
+
- anything that needs your app's secrets.
|
|
103
|
+
|
|
104
|
+
A tool is an http server function with an `agent` block
|
|
105
|
+
([agent-tools.md](agent-tools.md)); the assistant calls it as
|
|
106
|
+
`sl post app/<appId>/functions/<name> '{ … }'`, under the function's own visibility, authority and
|
|
107
|
+
capabilities. Set `"audience": ["assistant"]` to keep an assistant-only tool out of your in-app and
|
|
108
|
+
public agent runs:
|
|
109
|
+
|
|
110
|
+
```jsonc
|
|
111
|
+
{
|
|
112
|
+
"name": "applyDesignDirection",
|
|
113
|
+
"trigger": { "type": "http" },
|
|
114
|
+
"visibility": "admin",
|
|
115
|
+
"authority": "caller",
|
|
116
|
+
"capabilities": ["sl:settings:write"],
|
|
117
|
+
"agent": {
|
|
118
|
+
"tool": true,
|
|
119
|
+
"title": "Apply a design direction",
|
|
120
|
+
"description": "Rewrites the theme, fonts and layout of the hub to match a named direction.",
|
|
121
|
+
"input": { "type": "object", "properties": { "direction": { "type": "string" } }, "required": ["direction"] },
|
|
122
|
+
"audience": ["assistant"]
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
A tool that changes config should **return the patch, not apply it**, so the assistant can put it in a
|
|
128
|
+
change set and the admin keeps undo. Tools that act directly are fine for reads and computations.
|
|
129
|
+
|
|
130
|
+
## How the assistant changes your config
|
|
131
|
+
|
|
132
|
+
Your config document (`SL.appConfiguration`, `sl get app/<appId>`) and the collection's settings
|
|
133
|
+
groups (`sl get settings/<group>`) are changed through change-set items:
|
|
134
|
+
|
|
135
|
+
```jsonc
|
|
136
|
+
{ "op": "update", "entity": "appConfig", "id": "<appId>", "data": { "theme": { "accent": "#0a7" } } }
|
|
137
|
+
{ "op": "update", "entity": "appConfig", "id": "<appId>", "scope": { "productId": "p1" }, "data": { … } }
|
|
138
|
+
{ "op": "update", "entity": "settings", "id": "hub", "data": { "pages": { "about": { … } } } }
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
Saves deep-merge, as your own admin UI's saves do (nested objects merge, arrays replace). The whole
|
|
142
|
+
document before the change is kept, so **undo puts it back exactly**. Say in your skills which keys
|
|
143
|
+
are arrays, so the assistant sends the whole array when it changes one element.
|
|
144
|
+
|
|
145
|
+
### Preview before apply
|
|
146
|
+
|
|
147
|
+
A planned change set can be rendered before it is applied. The API gives, for every document a set
|
|
148
|
+
touches, the live document and the document as it would become:
|
|
149
|
+
|
|
150
|
+
```
|
|
151
|
+
GET /admin/collection/:collectionId/assistant/changesets/:id/preview
|
|
152
|
+
→ { id, status, targets: [ { entity, id, scope?, before, after, ops } ] }
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
Declare `assistant.preview` — a page of your admin surface that takes `{id}`, calls that endpoint
|
|
156
|
+
with the admin's session, and renders `targets[].after` as your app would render the live config. The
|
|
157
|
+
Studio panel then shows "Preview" on the change set, and the admin sees the result before saying yes.
|
|
158
|
+
|
|
159
|
+
## Checklist
|
|
160
|
+
|
|
161
|
+
- `assistant.skills[]`: several focused skills with descriptions that say when they apply.
|
|
162
|
+
- `assistant.knowledge[]`: the config schema generated from your types; record shapes; examples.
|
|
163
|
+
- Every skill says which fields are arrays, what to match on before creating, what to check after.
|
|
164
|
+
- Logic the assistant can't reproduce: a server function with `agent.tool` and `audience: ["assistant"]`,
|
|
165
|
+
returning a patch for config changes.
|
|
166
|
+
- `assistant.preview` when your app has a visual surface worth seeing before a change lands.
|
|
167
|
+
- `ai-guide.md` still describes first-time setup for the setup wizard; the assistant reads it too when
|
|
168
|
+
an app declares no skills.
|
|
@@ -172,6 +172,9 @@ Both sources use the same entry shape:
|
|
|
172
172
|
|
|
173
173
|
```typescript
|
|
174
174
|
interface DeepLinkEntry {
|
|
175
|
+
/** Stable id, unique within the app (optional): hosts and websites refer to the page by it */
|
|
176
|
+
id?: string;
|
|
177
|
+
|
|
175
178
|
/** Human-readable label for this state (required) */
|
|
176
179
|
title: string;
|
|
177
180
|
|
|
@@ -193,6 +196,13 @@ interface DeepLinkEntry {
|
|
|
193
196
|
*/
|
|
194
197
|
params?: Record<string, string>;
|
|
195
198
|
|
|
199
|
+
/**
|
|
200
|
+
* The params a host may supply when it opens this page (optional), e.g. a booking page that can
|
|
201
|
+
* start on one service: `{ optional: ['service', 'staff', 'date'] }`. `params` are pinned by the
|
|
202
|
+
* entry; `accepts` are filled in by whoever opens it.
|
|
203
|
+
*/
|
|
204
|
+
accepts?: { required?: string[]; optional?: string[] };
|
|
205
|
+
|
|
196
206
|
/**
|
|
197
207
|
* When `true`, this entry is also available as a dynamic data context for widgets.
|
|
198
208
|
*
|
|
@@ -211,7 +221,9 @@ interface DeepLinkEntry {
|
|
|
211
221
|
|-------|----------|-------|
|
|
212
222
|
| `title` | ✅ | Displayed in menus and offered to AI agents. Should be concise and human-readable. |
|
|
213
223
|
| `path` | ❌ | Hash route within the app. Defaults to `/` if omitted. |
|
|
224
|
+
| `id` | ❌ | Stable id for hosts and websites to refer to the page by. |
|
|
214
225
|
| `params` | ❌ | App-specific query params only. Platform params are injected automatically. |
|
|
226
|
+
| `accepts` | ❌ | The params whoever opens the page may supply (`required` / `optional`). |
|
|
215
227
|
| `widget` | ❌ | Set to `true` to make this entry selectable in the widget config picker as a dynamic data context. |
|
|
216
228
|
|
|
217
229
|
> **Tip:** Different entries can share the same `path` if they differ by `params` — for example, two entries pointing at `/settings` with different `tab` params.
|
package/docs/iframe-responder.md
CHANGED
|
@@ -103,6 +103,7 @@ interface IframeResponderOptions {
|
|
|
103
103
|
version?: 'stable' | 'development'; // App version (default: 'stable')
|
|
104
104
|
appUrl?: string; // Override URL (for local dev)
|
|
105
105
|
initialPath?: string; // Initial hash path (e.g., '/settings')
|
|
106
|
+
params?: Record<string, string>; // The page's own params (e.g. { service: 'svc_123' }); context params win
|
|
106
107
|
|
|
107
108
|
// Data
|
|
108
109
|
cache?: CachedData; // Pre-cached data
|
package/docs/websites.md
CHANGED
|
@@ -66,6 +66,32 @@ bytes. `_server/` is never served to visitors.
|
|
|
66
66
|
doing work that belongs in a server function or a scheduled job.
|
|
67
67
|
- **No secrets** in the server bundle and no Node built-ins beyond `nodejs_compat`.
|
|
68
68
|
|
|
69
|
+
## Using another app's pages
|
|
70
|
+
|
|
71
|
+
Some apps do a job a site shouldn't rebuild: a booking journey holds the time rules, the clash
|
|
72
|
+
handling and the confirmation; "my bookings" holds who may change what. Those apps declare their pages
|
|
73
|
+
in `linkable` (app-manifest.md), each with a stable `id`, a `path` and the params it `accepts`. A site
|
|
74
|
+
embeds one with **`IframeResponder`** (iframe-responder.md), from the browser:
|
|
75
|
+
|
|
76
|
+
```typescript
|
|
77
|
+
const site = SL.site.current()
|
|
78
|
+
const responder = new SL.IframeResponder({
|
|
79
|
+
collectionId: site.collectionId,
|
|
80
|
+
appId: 'appointments', // the app's id on this collection
|
|
81
|
+
initialPath: '/my-bookings', // the page's `path`
|
|
82
|
+
params: { service: 'svc_123' }, // what the page `accepts`
|
|
83
|
+
onResize: (h) => { iframe.style.height = `${h}px` },
|
|
84
|
+
})
|
|
85
|
+
iframe.src = await responder.attach(iframe)
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
- **The visitor's session goes with it.** The page's API calls are proxied through the site, so it
|
|
89
|
+
acts as whoever is signed in to the site (or as a guest). Never pass a token in the URL.
|
|
90
|
+
- **The app must be installed** on the site's collection; its address comes from there.
|
|
91
|
+
- **Show the app's content natively where you can** (its `headless` data: services, staff, events)
|
|
92
|
+
for your own design and SEO, and embed the page only for the part that does the work.
|
|
93
|
+
- Pages render on the client, after load: they aren't part of the server-rendered HTML.
|
|
94
|
+
|
|
69
95
|
## Caching
|
|
70
96
|
|
|
71
97
|
The router keeps a rendered page at the edge when your response says it may:
|
package/openapi.yaml
CHANGED
|
@@ -19909,6 +19909,8 @@ components:
|
|
|
19909
19909
|
DeepLinkEntry:
|
|
19910
19910
|
type: object
|
|
19911
19911
|
properties:
|
|
19912
|
+
id:
|
|
19913
|
+
type: string
|
|
19912
19914
|
title:
|
|
19913
19915
|
type: string
|
|
19914
19916
|
path:
|
|
@@ -19917,6 +19919,8 @@ components:
|
|
|
19917
19919
|
type: object
|
|
19918
19920
|
additionalProperties:
|
|
19919
19921
|
type: string
|
|
19922
|
+
accepts:
|
|
19923
|
+
$ref: "#/components/schemas/PublicViewParams"
|
|
19920
19924
|
widget:
|
|
19921
19925
|
type: object
|
|
19922
19926
|
additionalProperties: true
|
|
@@ -20134,6 +20138,14 @@ components:
|
|
|
20134
20138
|
enum:
|
|
20135
20139
|
- auto
|
|
20136
20140
|
- require
|
|
20141
|
+
audience:
|
|
20142
|
+
type: array
|
|
20143
|
+
items:
|
|
20144
|
+
type: string
|
|
20145
|
+
enum:
|
|
20146
|
+
- app
|
|
20147
|
+
- public
|
|
20148
|
+
- assistant
|
|
20137
20149
|
required:
|
|
20138
20150
|
- tool
|
|
20139
20151
|
AppManifestFunctions:
|
|
@@ -20631,6 +20643,8 @@ components:
|
|
|
20631
20643
|
$ref: "#/components/schemas/AppHeadlessDeclaration"
|
|
20632
20644
|
connector:
|
|
20633
20645
|
$ref: "#/components/schemas/AppConnectorDeclaration"
|
|
20646
|
+
assistant:
|
|
20647
|
+
$ref: "#/components/schemas/AppAssistantDeclaration"
|
|
20634
20648
|
required:
|
|
20635
20649
|
- name
|
|
20636
20650
|
- version
|
|
@@ -20638,6 +20652,31 @@ components:
|
|
|
20638
20652
|
- function
|
|
20639
20653
|
- files
|
|
20640
20654
|
- components
|
|
20655
|
+
AppAssistantFile:
|
|
20656
|
+
type: object
|
|
20657
|
+
properties:
|
|
20658
|
+
name:
|
|
20659
|
+
type: string
|
|
20660
|
+
description:
|
|
20661
|
+
type: string
|
|
20662
|
+
file:
|
|
20663
|
+
type: string
|
|
20664
|
+
required:
|
|
20665
|
+
- name
|
|
20666
|
+
- file
|
|
20667
|
+
AppAssistantDeclaration:
|
|
20668
|
+
type: object
|
|
20669
|
+
properties:
|
|
20670
|
+
skills:
|
|
20671
|
+
type: array
|
|
20672
|
+
items:
|
|
20673
|
+
$ref: "#/components/schemas/AppAssistantFile"
|
|
20674
|
+
knowledge:
|
|
20675
|
+
type: array
|
|
20676
|
+
items:
|
|
20677
|
+
$ref: "#/components/schemas/AppAssistantFile"
|
|
20678
|
+
preview:
|
|
20679
|
+
type: string
|
|
20641
20680
|
ConnectorConnectionField:
|
|
20642
20681
|
type: object
|
|
20643
20682
|
properties:
|
|
@@ -27652,6 +27691,10 @@ components:
|
|
|
27652
27691
|
type: string
|
|
27653
27692
|
initialPath:
|
|
27654
27693
|
type: string
|
|
27694
|
+
params:
|
|
27695
|
+
type: object
|
|
27696
|
+
additionalProperties:
|
|
27697
|
+
type: string
|
|
27655
27698
|
isAdmin:
|
|
27656
27699
|
type: boolean
|
|
27657
27700
|
cache:
|