@lotics/cli 0.316.20 → 0.316.22
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/AGENTS.md +4 -5
- package/README.md +3 -7
- package/dist/src/cli.js +63 -117
- package/dist/src/client.js +2 -2
- package/docs/agent_surface.md +5 -5
- package/docs/app_bindings.md +18 -17
- package/docs/building_an_app.md +5 -6
- package/docs/cli_reference.md +6 -7
- package/docs/data_model.md +2 -2
- package/docs/design.md +1 -1
- package/docs/document_templates.md +9 -8
- package/docs/filters.md +18 -1
- package/docs/first_build.md +2 -2
- package/docs/knowledge_docs.md +3 -3
- package/docs/migration.md +4 -4
- package/docs/workflow_steps.md +185 -6
- package/docs/workflows.md +71 -62
- package/docs/working_in_a_workspace.md +38 -7
- package/package.json +1 -1
package/docs/workflow_steps.md
CHANGED
|
@@ -12,6 +12,73 @@ answer the provider refuses — ends the run, and the run's error reads `[<step>
|
|
|
12
12
|
(e) { … }` the body handles it instead: `e` is `{ message, type, step_id?, detail? }`, `message` the reason as
|
|
13
13
|
above. A provider's refusal carries the provider's own words.
|
|
14
14
|
|
|
15
|
+
## aggregate
|
|
16
|
+
|
|
17
|
+
### aggregate_records (workflow step)
|
|
18
|
+
|
|
19
|
+
- Compute aggregate statistics over records.
|
|
20
|
+
- Fields and select options are named by their alias, as get_table shows them. (chat, MCP, CLI)
|
|
21
|
+
- A workspace admin's `as_member` computes over what that member would see.
|
|
22
|
+
- Pass either `aggregate_option` for a single statistic, or `aggregations` to compute several in one call.
|
|
23
|
+
- Example: { "table_id": "...", "aggregate_option": { "operation": "sum", "field_key": "amount" } } Operations by field type: No field: count — the rows matching `filters`.
|
|
24
|
+
- `filled` counts populated CELLS, which is a different number the moment any are empty.
|
|
25
|
+
- All fields: empty, filled, percent_empty, percent_filled, unique, percent_unique Number: sum, avg, median, min, max, range Date: earliest, latest, date_range Boolean: checked, unchecked, percent_checked, percent_unchecked `group_by` breaks every requested statistic down per distinct value of one field, computed over the same filters — one call in place of a call per bucket.
|
|
26
|
+
- Example: { "table_id": "...", "group_by": "status", "filters": {...}, "aggregations": [{ "operation": "filled", "field_key": "created_on" }, { "operation": "sum", "field_key": "amount" }] } Restrict the period through `filters` on a date field rather than grouping by that date — a raw date/datetime groups per distinct stored value.
|
|
27
|
+
- Filters: Operators: `is`, `is_not` on a field holding one value; `is_any_of`, `is_none_of` on text, number, date, select, member and link fields; `lt`, `lte`, `gt`, `gte` on number and date; `is_empty`, `is_not_empty` on every field but yes/no.
|
|
28
|
+
- A date is "YYYY-MM-DD" for that whole day.
|
|
29
|
+
- `docs path="filters"`. (chat, MCP, CLI)
|
|
30
|
+
|
|
31
|
+
**Inputs**
|
|
32
|
+
|
|
33
|
+
| Name | Type | Required | What it is |
|
|
34
|
+
| --- | --- | --- | --- |
|
|
35
|
+
| `table_id` | string | yes | |
|
|
36
|
+
| `as_member` | string | | For a workspace admin: reads as this member would — their row rules and access. Anyone else is refused. |
|
|
37
|
+
| `filters` | any | | |
|
|
38
|
+
| `aggregate_option` | any | | |
|
|
39
|
+
| `aggregations` | any[] | | At most 25 entries. |
|
|
40
|
+
| `group_by` | string | | Field alias to break the statistics down by. Rows with no value in it are reported separately, not folded into a group. |
|
|
41
|
+
|
|
42
|
+
**Answer**
|
|
43
|
+
|
|
44
|
+
| Name | Type | Required | What it is |
|
|
45
|
+
| --- | --- | --- | --- |
|
|
46
|
+
| `result` | number \\| string | | Result of a single `aggregate_option` call — a number, or a string for dates. |
|
|
47
|
+
| `_units` | object[] | | One answer per unit or currency the rows hold, most rows first; unit null for rows naming none. `result` is the first. |
|
|
48
|
+
| `_units[].unit` | string \\| null | yes | |
|
|
49
|
+
| `_units[].value` | number | yes | |
|
|
50
|
+
| `results` | object[] | | One entry per aggregation when `aggregations` is used. |
|
|
51
|
+
| `results[].operation` | string | yes | |
|
|
52
|
+
| `results[].field_key` | string | yes | |
|
|
53
|
+
| `results[].field_name` | string | yes | |
|
|
54
|
+
| `results[].result` | number \\| string | yes | |
|
|
55
|
+
| `results[].units` | object[] | | One answer per unit or currency the rows hold, most rows first; unit null for rows naming none. `result` is the first. |
|
|
56
|
+
| `results[].units[].unit` | string \\| null | yes | |
|
|
57
|
+
| `results[].units[].value` | number | yes | |
|
|
58
|
+
| `groups` | object[] | | One entry per distinct value of `group_by`, each carrying every requested statistic. |
|
|
59
|
+
| `groups[].key` | string | yes | The group's identity. `label` is a spelling of it, and two rows can spell one identity differently. |
|
|
60
|
+
| `groups[].label` | string | | Readable name for `key` where one is known: an option's name, a linked record's display text, a filename. Absent means no name was available, never that the group is empty. |
|
|
61
|
+
| `groups[].results` | object[] | yes | |
|
|
62
|
+
| `groups[].results[].operation` | string | yes | |
|
|
63
|
+
| `groups[].results[].field_key` | string | yes | |
|
|
64
|
+
| `groups[].results[].field_name` | string | yes | |
|
|
65
|
+
| `groups[].results[].result` | number \\| string | yes | |
|
|
66
|
+
| `groups[].results[].units` | object[] | | One answer per unit or currency the rows hold, most rows first; unit null for rows naming none. `result` is the first. |
|
|
67
|
+
| `groups[].results[].units[].unit` | string \\| null | yes | |
|
|
68
|
+
| `groups[].results[].units[].value` | number | yes | |
|
|
69
|
+
| `_ungrouped_rows` | number | | Matching rows with no value in the `group_by` field — counted in no group. |
|
|
70
|
+
| `_group_field_name` | string | | |
|
|
71
|
+
| `_operation` | string | | |
|
|
72
|
+
| `_field_key` | string | | |
|
|
73
|
+
| `_field_name` | string | | |
|
|
74
|
+
| `_value_coverage` | object | | |
|
|
75
|
+
| `_value_coverage.requested` | number | yes | |
|
|
76
|
+
| `_value_coverage.field_name` | string | yes | |
|
|
77
|
+
| `_value_coverage.composite` | boolean | | |
|
|
78
|
+
| `_value_coverage.per_value` | object | | |
|
|
79
|
+
| `_value_coverage.per_value.matched` | number | yes | |
|
|
80
|
+
| `_value_coverage.per_value.missing` | string[] | yes | |
|
|
81
|
+
|
|
15
82
|
## bkav
|
|
16
83
|
|
|
17
84
|
### bkav_attach_files (workflow step)
|
|
@@ -739,6 +806,28 @@ above. A provider's refusal carries the provider's own words.
|
|
|
739
806
|
| `results[].MessLog` | string | yes | |
|
|
740
807
|
| `failed` | number | yes | Invoices whose own Status is not 0 |
|
|
741
808
|
|
|
809
|
+
## delete
|
|
810
|
+
|
|
811
|
+
### delete_file (workflow step)
|
|
812
|
+
|
|
813
|
+
- Soft-delete a file by its file_id — archives it (never a hard delete).
|
|
814
|
+
- Deleting an original file deletes every version of it; a file's current version cannot be deleted on its own.
|
|
815
|
+
- Refuses while the file or a version it takes along is still referenced by a record, comment, knowledge doc, document template, voice session, or pending change request, and names the referents so you can detach them first.
|
|
816
|
+
|
|
817
|
+
**Inputs**
|
|
818
|
+
|
|
819
|
+
| Name | Type | Required | What it is |
|
|
820
|
+
| --- | --- | --- | --- |
|
|
821
|
+
| `file_id` | string | yes | |
|
|
822
|
+
|
|
823
|
+
**Answer**
|
|
824
|
+
|
|
825
|
+
| Name | Type | Required | What it is |
|
|
826
|
+
| --- | --- | --- | --- |
|
|
827
|
+
| `id` | string | yes | |
|
|
828
|
+
| `filename` | string | yes | |
|
|
829
|
+
| `archived` | boolean | yes | |
|
|
830
|
+
|
|
742
831
|
## drive
|
|
743
832
|
|
|
744
833
|
### drive_export_file (workflow step)
|
|
@@ -766,7 +855,7 @@ above. A provider's refusal carries the provider's own words.
|
|
|
766
855
|
### drive_import_file (workflow step)
|
|
767
856
|
|
|
768
857
|
- Import a Google Drive file into the workspace as a Lotics file, and return its file id.
|
|
769
|
-
- Google Docs arrive as .docx, Sheets as .xlsx, Slides as .pptx; everything else transfers unchanged. **This is also how you READ a Drive file.** There is no separate Drive read tool: import the file, then use `view_files` on the returned id. (chat)
|
|
858
|
+
- Google Docs arrive as .docx, Sheets as .xlsx, Slides as .pptx; everything else transfers unchanged. **This is also how you READ a Drive file.** There is no separate Drive read tool: import the file, then use `view_files` on the returned id. (chat, MCP, CLI)
|
|
770
859
|
- That path handles scanned documents — a photographed B/L or a stamped invoice inside a .docx reaches the model as a picture.
|
|
771
860
|
- Exporting Drive content as plain text would return an empty document for exactly those files.
|
|
772
861
|
- Folders, Forms, shortcuts and other Drive items with no file form cannot be imported.
|
|
@@ -782,7 +871,7 @@ above. A provider's refusal carries the provider's own words.
|
|
|
782
871
|
|
|
783
872
|
| Name | Type | Required | What it is |
|
|
784
873
|
| --- | --- | --- | --- |
|
|
785
|
-
| `id` | string | yes | Lotics file ID — pass this to view_files to read the contents (chat) |
|
|
874
|
+
| `id` | string | yes | Lotics file ID — pass this to view_files to read the contents (chat, MCP, CLI) |
|
|
786
875
|
| `filename` | string | yes | |
|
|
787
876
|
| `mime_type` | string | yes | |
|
|
788
877
|
| `url` | string | yes | |
|
|
@@ -1330,6 +1419,49 @@ above. A provider's refusal carries the provider's own words.
|
|
|
1330
1419
|
| `mime_type` | string | yes | |
|
|
1331
1420
|
| `url` | string | yes | |
|
|
1332
1421
|
|
|
1422
|
+
## get
|
|
1423
|
+
|
|
1424
|
+
### get_record (workflow step)
|
|
1425
|
+
|
|
1426
|
+
- Fetch a single record with all field values.
|
|
1427
|
+
- Returns data (raw values) and display (human-readable labels).
|
|
1428
|
+
- A workspace admin's `as_member` fetches it as that member would see it.
|
|
1429
|
+
- Cell shapes in `data`, keyed by field alias: select / member / link, and a formula with options, read back as arrays — a single-select as ["open"], the option's alias; one member as ["mbr_x"].
|
|
1430
|
+
- A workflow body reads the same single-select, single-member or option-formula cell as the bare value.
|
|
1431
|
+
|
|
1432
|
+
**Inputs**
|
|
1433
|
+
|
|
1434
|
+
| Name | Type | Required | What it is |
|
|
1435
|
+
| --- | --- | --- | --- |
|
|
1436
|
+
| `record_id` | string | yes | |
|
|
1437
|
+
| `table_id` | string | | |
|
|
1438
|
+
| `as_member` | string | | For a workspace admin: reads as this member would — their row rules and access. Anyone else is refused. |
|
|
1439
|
+
| `field_keys` | string[] | | Field aliases to return. Omit to return all fields. |
|
|
1440
|
+
| `comments` | boolean | | Return the record's newest comments, 20 at most; `comment_count` is the whole thread. |
|
|
1441
|
+
|
|
1442
|
+
**Answer**
|
|
1443
|
+
|
|
1444
|
+
| Name | Type | Required | What it is |
|
|
1445
|
+
| --- | --- | --- | --- |
|
|
1446
|
+
| `id` | string | yes | |
|
|
1447
|
+
| `data` | object | yes | |
|
|
1448
|
+
| `display` | object | yes | Human-readable display values for select, member, and record link fields. |
|
|
1449
|
+
| `links` | object | | Each link field's linked rows with the title the cell carries, by field alias; `data` holds their ids. |
|
|
1450
|
+
| `locked` | boolean | | |
|
|
1451
|
+
| `comment_count` | number | yes | How many comments the record holds. |
|
|
1452
|
+
| `comments` | object[] | | With `comments: true`: the newest first. |
|
|
1453
|
+
| `comments[].id` | string | yes | Unique identifier for the comment |
|
|
1454
|
+
| `comments[].member_id` | string | yes | ID of the member who created this comment |
|
|
1455
|
+
| `comments[].content` | string | yes | Comment text content (0-10,000 characters) |
|
|
1456
|
+
| `comments[].files` | object[] \\| null | | Array of files attached to this comment |
|
|
1457
|
+
| `comments[].created_at` | string | yes | Timestamp when comment was created |
|
|
1458
|
+
| `comments[].updated_at` | string | yes | Timestamp of last update |
|
|
1459
|
+
| `archived_at` | string \\| null | | |
|
|
1460
|
+
| `created_at` | string | | |
|
|
1461
|
+
| `updated_at` | string | | |
|
|
1462
|
+
| `_field_names` | object | | |
|
|
1463
|
+
| `units` | object | | The unit or currency this row reads a figure in, by the figure's field alias, where each row states its own. |
|
|
1464
|
+
|
|
1333
1465
|
## gmail
|
|
1334
1466
|
|
|
1335
1467
|
### gmail_get_thread (workflow step)
|
|
@@ -1415,7 +1547,7 @@ above. A provider's refusal carries the provider's own words.
|
|
|
1415
1547
|
### gmail_read_email (workflow step)
|
|
1416
1548
|
|
|
1417
1549
|
- Fetch a Gmail message's body and the list of its attachments.
|
|
1418
|
-
- Attachments are stored as workspace files — the result names each one's file_id; read one with view_files, attach one to a record with update_records. (chat)
|
|
1550
|
+
- Attachments are stored as workspace files — the result names each one's file_id; read one with view_files, attach one to a record with update_records. (chat, MCP, CLI)
|
|
1419
1551
|
|
|
1420
1552
|
**Inputs**
|
|
1421
1553
|
|
|
@@ -1450,7 +1582,7 @@ above. A provider's refusal carries the provider's own words.
|
|
|
1450
1582
|
| `message.attachments[].filename` | string | yes | Attachment filename as stored, which may differ from the sender's |
|
|
1451
1583
|
| `message.attachments[].mimeType` | string | yes | MIME type as stored, recovered from the filename when the sender declared a generic one |
|
|
1452
1584
|
| `message.attachments[].attachmentId` | string | yes | Gmail attachment ID |
|
|
1453
|
-
| `message.attachments[].file_id` | string | | Stored file ID — pass to view_files to read it, or to update_records to attach it (chat) |
|
|
1585
|
+
| `message.attachments[].file_id` | string | | Stored file ID — pass to view_files to read it, or to update_records to attach it (chat, MCP, CLI) |
|
|
1454
1586
|
| `message.attachments[].size` | number | | Stored size in bytes |
|
|
1455
1587
|
|
|
1456
1588
|
### gmail_reply_email (workflow step)
|
|
@@ -2427,6 +2559,28 @@ above. A provider's refusal carries the provider's own words.
|
|
|
2427
2559
|
| `banks[].short_name` | string | yes | |
|
|
2428
2560
|
| `banks[].full_name` | string | yes | |
|
|
2429
2561
|
|
|
2562
|
+
## lock
|
|
2563
|
+
|
|
2564
|
+
### lock_records (workflow step)
|
|
2565
|
+
|
|
2566
|
+
- Lock records to prevent editing.
|
|
2567
|
+
- Returns `{ locked, record_ids }`.
|
|
2568
|
+
|
|
2569
|
+
**Inputs**
|
|
2570
|
+
|
|
2571
|
+
| Name | Type | Required | What it is |
|
|
2572
|
+
| --- | --- | --- | --- |
|
|
2573
|
+
| `table_id` | string | yes | |
|
|
2574
|
+
| `record_ids` | string[] | | |
|
|
2575
|
+
| `filters` | any | | |
|
|
2576
|
+
|
|
2577
|
+
**Answer**
|
|
2578
|
+
|
|
2579
|
+
| Name | Type | Required | What it is |
|
|
2580
|
+
| --- | --- | --- | --- |
|
|
2581
|
+
| `locked` | number | yes | |
|
|
2582
|
+
| `record_ids` | string[] | yes | |
|
|
2583
|
+
|
|
2430
2584
|
## lookup
|
|
2431
2585
|
|
|
2432
2586
|
### lookup_business (workflow step)
|
|
@@ -2856,7 +3010,7 @@ above. A provider's refusal carries the provider's own words.
|
|
|
2856
3010
|
### outlook_read_email (workflow step)
|
|
2857
3011
|
|
|
2858
3012
|
- Fetch an Outlook email's body and the list of its attachments.
|
|
2859
|
-
- Attachments are stored as workspace files — the result names each one's file_id; read one with view_files, attach one to a record with update_records. (chat)
|
|
3013
|
+
- Attachments are stored as workspace files — the result names each one's file_id; read one with view_files, attach one to a record with update_records. (chat, MCP, CLI)
|
|
2860
3014
|
|
|
2861
3015
|
**Inputs**
|
|
2862
3016
|
|
|
@@ -2894,7 +3048,7 @@ above. A provider's refusal carries the provider's own words.
|
|
|
2894
3048
|
| `message.attachments[].fileName` | string | | Attachment filename as stored, which may differ from the sender's |
|
|
2895
3049
|
| `message.attachments[].contentType` | string | | MIME type as stored, recovered from the filename when Graph declared a generic one |
|
|
2896
3050
|
| `message.attachments[].size` | number | | Stored size in bytes |
|
|
2897
|
-
| `message.attachments[].file_id` | string | | Stored file ID — pass to view_files to read it, or to update_records to attach it (chat) |
|
|
3051
|
+
| `message.attachments[].file_id` | string | | Stored file ID — pass to view_files to read it, or to update_records to attach it (chat, MCP, CLI) |
|
|
2898
3052
|
|
|
2899
3053
|
### outlook_reply_email (workflow step)
|
|
2900
3054
|
|
|
@@ -3007,6 +3161,31 @@ above. A provider's refusal carries the provider's own words.
|
|
|
3007
3161
|
| `filename` | string | yes | The filename of the generated file, including extension. |
|
|
3008
3162
|
| `mime_type` | string | yes | The MIME type of the generated file. |
|
|
3009
3163
|
|
|
3164
|
+
## request
|
|
3165
|
+
|
|
3166
|
+
### request_locked_record_change (workflow step)
|
|
3167
|
+
|
|
3168
|
+
- Submit one approval request to change locked field values on a record.
|
|
3169
|
+
- Include every blocked field in the same call — the approver accepts or rejects the change set as a whole.
|
|
3170
|
+
- Values are NOT changed immediately; an approver must accept the request first.
|
|
3171
|
+
- Returns `{ approval_request_id, status }`.
|
|
3172
|
+
|
|
3173
|
+
**Inputs**
|
|
3174
|
+
|
|
3175
|
+
| Name | Type | Required | What it is |
|
|
3176
|
+
| --- | --- | --- | --- |
|
|
3177
|
+
| `table_id` | string | yes | |
|
|
3178
|
+
| `record_id` | string | yes | |
|
|
3179
|
+
| `changes` | object | yes | Map of field_key to proposed new value. Every key must be locked on this record. |
|
|
3180
|
+
| `reason` | string | yes | |
|
|
3181
|
+
|
|
3182
|
+
**Answer**
|
|
3183
|
+
|
|
3184
|
+
| Name | Type | Required | What it is |
|
|
3185
|
+
| --- | --- | --- | --- |
|
|
3186
|
+
| `approval_request_id` | string | yes | |
|
|
3187
|
+
| `status` | "pending" | yes | |
|
|
3188
|
+
|
|
3010
3189
|
## save
|
|
3011
3190
|
|
|
3012
3191
|
### save_file (workflow step)
|
package/docs/workflows.md
CHANGED
|
@@ -7,25 +7,50 @@ what to write instead. A save that succeeds may still return `warnings` — advi
|
|
|
7
7
|
loop that may never end. Read them.
|
|
8
8
|
|
|
9
9
|
A workflow changes records, calls integrations, notifies and makes papers. A tool for anything else — views, history,
|
|
10
|
-
tables and apps, members, knowledge, skills, editing a file — is an agent's,
|
|
10
|
+
tables and apps, members, knowledge, skills, editing a file — is an agent's, and a body that
|
|
11
11
|
calls one is refused at save with that reason. One saved before is refused at run, before its first step.
|
|
12
12
|
|
|
13
|
-
The same grammar serves an app's workflow, which `
|
|
14
|
-
|
|
15
|
-
declaration.
|
|
13
|
+
The same grammar serves a table's workflow, an automation and an app's workflow, which `set_workflow` saves, and the
|
|
14
|
+
expressions of a table check.
|
|
16
15
|
|
|
17
16
|
## Triggers
|
|
18
17
|
|
|
18
|
+
Every workflow's source opens with `on({ ... })`, which states what starts it. `set_workflow` saves the source, and
|
|
19
|
+
`get_workflow` reads it back with whether it is on and how its latest runs ended. `get_workflow` without a
|
|
20
|
+
`workflow_id` lists the workflows you may read, a page at a time (`limit` 1 to 100, `offset`, `total`), each with its
|
|
21
|
+
`kind`, its trigger in a line, whether it is on and its failed runs of the last 7 days; `table_id` and `app_id` keep the
|
|
22
|
+
ones that follow or name them.
|
|
23
|
+
|
|
24
|
+
| kind | header |
|
|
25
|
+
|---|---|
|
|
26
|
+
| table | `on({ type: "create" \| "update" \| "delete", table: "<alias>", changed?: ["<alias>", ...], when?: "<expression>" })` |
|
|
27
|
+
| automation | `on({ type: "recurring_schedule", cron_expression })`, `receive_webhook`, `receive_gmail_email` or `receive_outlook_email` — see **Automations** |
|
|
28
|
+
| app | `on({ type: "app", app_id, alias, inputs?, outputs? })` — `inputs` and `outputs` only for a custom-code app |
|
|
29
|
+
|
|
30
|
+
What starts a workflow is fixed once it is made: a table workflow keeps its trigger type and its table, an app workflow its
|
|
31
|
+
app and alias. To change either, delete the workflow and make another. A replace states the whole trigger, so a key the
|
|
32
|
+
header leaves out is removed (an app workflow keeps the inputs it leaves out, and derives the outputs it leaves out from
|
|
33
|
+
what the body returns). `expected_version` refuses a replace when the workflow has moved since the `version` you read.
|
|
34
|
+
`dry_run` checks the source and stores nothing; given the trial inputs its trigger supplies, it also walks the steps once
|
|
35
|
+
and returns the calls planned, the return value, the failures and the papers rendered. A table trigger takes `record_id`,
|
|
36
|
+
`prev_data` and `changes`; a webhook or email trigger `trigger_payload`; an app trigger `record_id` and `preview_inputs`; a
|
|
37
|
+
table or automation trigger also `live_reads` (an app's rehearsal always reads real data). A trial input its trigger does
|
|
38
|
+
not supply is refused, naming the ones it takes. A rehearsal needs write scope: a key that reads data alone cannot make one.
|
|
39
|
+
|
|
40
|
+
`delete: true` with a `workflow_id` removes a workflow, and is the one call that asks for approval; a replace asks for none,
|
|
41
|
+
a step it drops included. `even_if_invoked` and `acknowledge_breaking_api_change` belong to an app workflow and are
|
|
42
|
+
refused for another kind.
|
|
43
|
+
|
|
19
44
|
An app workflow reads its call as `trigger.app_workflow.inputs.<name>`, or `inputs.<name>`, which is the same read. A Workflow node receives the inputs it declares;
|
|
20
45
|
an Act's `workflow` receives `record_id` (`record_ids` on an act over several rows) and what the Act asks.
|
|
21
46
|
|
|
22
47
|
### Automations
|
|
23
48
|
|
|
24
|
-
An automation answers an event outside the records: a schedule, a webhook, an inbound email. `
|
|
25
|
-
from its source — the `on({...})` declaration, then its steps — and, given its `
|
|
49
|
+
An automation answers an event outside the records: a schedule, a webhook, an inbound email. `set_workflow` saves one
|
|
50
|
+
from its source — the `on({...})` declaration, then its steps — and, given its `workflow_id`, replaces it: the
|
|
26
51
|
trigger config is the one the header states, a key it leaves out removed, and steps left as it runs them are checked again
|
|
27
|
-
only when the call turns it on, so one a later rule refuses can still be switched off.
|
|
28
|
-
|
|
52
|
+
only when the call turns it on (`enabled`), so one a later rule refuses can still be switched off. An admin names the owner at creation with
|
|
53
|
+
`owner_member_id`. It runs with its owner's current permissions, a connected service it names is its owner's account, and only its
|
|
29
54
|
owner or an admin reads, changes or deletes it.
|
|
30
55
|
|
|
31
56
|
```
|
|
@@ -137,13 +162,13 @@ Only `x` comes back: in text mode `x` is the string itself, in object mode `x.fi
|
|
|
137
162
|
|
|
138
163
|
## Naming tables and templates
|
|
139
164
|
|
|
140
|
-
A body names a table or document template by its alias (`query_records({ table_id: "orders" })`); ids still work. `
|
|
165
|
+
A body names a table or document template by its alias (`query_records({ table_id: "orders" })`); ids still work. `get_workflow` shows aliases. An app's workflow reaches only the tables the app uses.
|
|
141
166
|
|
|
142
167
|
## Fields and options
|
|
143
168
|
|
|
144
169
|
1. **A body names a table's fields and select options by alias**, the names the table's document states: a field reads
|
|
145
170
|
as `record["status"]`, an option as `"done"`, a linked field as `linked(record["customer"])[0]["name"]`, in the
|
|
146
|
-
body of a Workflow node, of an Act's own `workflow`, of an automation
|
|
171
|
+
body of a Workflow node, of an Act's own `workflow`, of an automation and of a table's workflow (`set_workflow`). It is stored by key and `get_workflow` shows it by alias
|
|
147
172
|
again. A field's or option's key is accepted where its alias is and is kept as written; a display name is refused with the
|
|
148
173
|
aliases there are, and a row that names one field by both its alias and its key is refused. A row written to a table is
|
|
149
174
|
stored by field key wherever the save can follow it to the record tool that writes it: a row or list written in the call,
|
|
@@ -186,7 +211,7 @@ A body names a table or document template by its alias (`query_records({ table_i
|
|
|
186
211
|
## Paths
|
|
187
212
|
|
|
188
213
|
- `record.x` and `record["x"]` read a field; `[n]` reads a list's n-th item.
|
|
189
|
-
- A path may start at any expression: an awaited call `(await
|
|
214
|
+
- A path may start at any expression: an awaited call `(await query_records({ table_id: "orders", record_ids: [id] })).records[0].data["x"]`, a helper's
|
|
190
215
|
result `first(found.records).id`, a filter `rows.filter((r) => r.ok)[0].n`, a `(a ?? b).c`. `?.` reads
|
|
191
216
|
through a null as usual, except on a value drawn at random (`sample(xs)?.id` is refused: bind it first).
|
|
192
217
|
- A record reads in its declared shape wherever it comes from — the trigger, a tool result, a `let`, a
|
|
@@ -375,7 +400,7 @@ const found = await query_records({
|
|
|
375
400
|
filters: { node_type: "condition", field_key: "name", operator: "equals", value: "ACME Corp" },
|
|
376
401
|
});
|
|
377
402
|
const customer = first(found.records);
|
|
378
|
-
const order = await
|
|
403
|
+
const order = (await query_records({ table_id: "orders", record_ids: [trigger.app_workflow.inputs.order_id] })).records[0];
|
|
379
404
|
|
|
380
405
|
if (customer && includes(order.data["customer"], customer.id)) {
|
|
381
406
|
// …
|
|
@@ -433,14 +458,11 @@ by the same fields take turns, so the second matches the rows the first added. T
|
|
|
433
458
|
`workflow_steps` states its bounds.
|
|
434
459
|
|
|
435
460
|
A workflow does not put an import back: that reads the history of what the run wrote, which a member's agent reads
|
|
436
|
-
under the member's own access to the table and no workflow does.
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
trash comes back where nothing has touched it since. A row the run brought back from the trash stays out of it. What the
|
|
442
|
-
tool leaves as it stands is answered in `kept`, one entry a record and reason. `undo_record_change` puts back one edit
|
|
443
|
-
named by its audit-log entry, the same way.
|
|
461
|
+
under the member's own access to the table and no workflow does. A member's agent puts an import back with
|
|
462
|
+
`update_records`: `query_audit_logs` on the table shows what the run wrote — the run is what `runtime.execution_id` held in
|
|
463
|
+
it, which a body keeps on a row of its own — and each field's value before it, `update_records` writes that value back as
|
|
464
|
+
the member's own write, `update_records` with `restore: true` brings back a row the run moved to the trash, and
|
|
465
|
+
`delete_records` removes a row the run created. Write a field back only where the row still holds what the run wrote.
|
|
444
466
|
|
|
445
467
|
What a business keeps of an import — the file, its counts, the lines waiting for a person — is rows of
|
|
446
468
|
its own tables, written by the workflow that ran the step. A garden centre brings its grower's price
|
|
@@ -524,7 +546,7 @@ lets it write over what the plant holds. A cell the line holds none of is left o
|
|
|
524
546
|
never empties a cell of the plant:
|
|
525
547
|
|
|
526
548
|
```
|
|
527
|
-
const line = await
|
|
549
|
+
const line = (await query_records({ table_id: "sheet_lines", record_ids: [trigger.app_workflow.inputs.record_id] })).records[0];
|
|
528
550
|
const values = {
|
|
529
551
|
code: line.data["code"],
|
|
530
552
|
...(isEmpty(line.data["name"]) ? {} : { name: line.data["name"] }),
|
|
@@ -545,8 +567,8 @@ await update_records({ table_id: "sheet_lines", record_ids: [line.id], set: { ou
|
|
|
545
567
|
return({ status: "success", message: "Applied." });
|
|
546
568
|
```
|
|
547
569
|
|
|
548
|
-
A sheet is put back by a member's agent
|
|
549
|
-
|
|
570
|
+
A sheet is put back by a member's agent with `update_records` over the plants its lines name, 50 at a time, from the values
|
|
571
|
+
`query_audit_logs` shows for the run the sheet kept.
|
|
550
572
|
|
|
551
573
|
A shop matches a bank statement to the notes it is owed by an act that states `review`. That act's workflow only
|
|
552
574
|
proposes: its body calls steps that read and no other, and runs no agent — either is refused when the body is saved —
|
|
@@ -681,10 +703,10 @@ nothing to an app's run:
|
|
|
681
703
|
}
|
|
682
704
|
```
|
|
683
705
|
|
|
684
|
-
3. `
|
|
706
|
+
3. `set_workflow` with a `source` that opens with `on({ type: "app", app_id: "app_…", alias: "invoice_order" });` and this body (the step's entry is in `workflow_steps`):
|
|
685
707
|
|
|
686
708
|
```
|
|
687
|
-
const order = await
|
|
709
|
+
const order = (await query_records({ table_id: "orders", record_ids: [trigger.app_workflow.inputs.order] })).records[0];
|
|
688
710
|
const made = await kiotviet_create_invoice({ // a workflow step
|
|
689
711
|
connected_account_id: "cac_…",
|
|
690
712
|
branch_id: 1,
|
|
@@ -798,40 +820,25 @@ any app. An effect that must hold for all of them belongs to the table. A button
|
|
|
798
820
|
when a row is made is `default_from`, and a rule that refuses a write is a check; a table workflow runs after the write has
|
|
799
821
|
committed and cannot refuse it.
|
|
800
822
|
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
823
|
+
`set_workflow` writes it: the `source` opens with `on({ type, table, ... })` and carries the steps, and `enabled: false`
|
|
824
|
+
keeps the workflow without running it (absent, it runs). `get_table` lists a table's workflows to whoever manages the
|
|
825
|
+
table — `workflow_id`, name, trigger, whether it is on and its failed runs of the last 7 days — and `get_workflow` reads one
|
|
826
|
+
back. The table and the trigger type (`create`, `update` or `delete`) are fixed once the workflow is made: to change
|
|
827
|
+
either, delete it and make another. A replace that changes only the gate, the trigger config or `enabled` moves the
|
|
828
|
+
workflow's version. `dry_run` with a `record_id` (and `prev_data` and `changes` for an update) says what the workflow would
|
|
829
|
+
write on that row; reads are real and nothing is written.
|
|
805
830
|
|
|
806
|
-
| part | what it holds |
|
|
831
|
+
| part of the header | what it holds |
|
|
807
832
|
|---|---|
|
|
808
|
-
| `
|
|
833
|
+
| `type` | the write it follows: `create`, `update` or `delete` |
|
|
834
|
+
| `table` | the table's alias |
|
|
809
835
|
| `when` | optional expression over `record`, `prev_record` and `changes`; the workflow runs only after a write where it is truthy |
|
|
810
|
-
| `changed` | optional, with `
|
|
811
|
-
| `body` | the steps, written as an app workflow's body is |
|
|
812
|
-
| `enabled` | `false` keeps the workflow without running it; absent, it runs |
|
|
836
|
+
| `changed` | optional, with `type: "update"`: field aliases; the workflow runs only after an update that changes any one of them |
|
|
813
837
|
|
|
814
|
-
```
|
|
815
|
-
{
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
"ref": "repairs",
|
|
819
|
-
"label": "Repairs",
|
|
820
|
-
"fields": {
|
|
821
|
-
"status": { "type": "select", "label": "Status", "options": [{ "alias": "queued", "label": "Queued" }, { "alias": "fixed", "label": "Fixed" }] },
|
|
822
|
-
"fixed_on": { "type": "date", "label": "Fixed on" }
|
|
823
|
-
},
|
|
824
|
-
"workflows": {
|
|
825
|
-
"stamp_fixed": {
|
|
826
|
-
"on": "update",
|
|
827
|
-
"changed": ["status"],
|
|
828
|
-
"when": "record.status == \"fixed\"",
|
|
829
|
-
"body": "await update_records({ table_id: \"repairs\", record_ids: [trigger.record_id], set: { fixed_on: formatDate(now(), \"yyyy-MM-dd\") } });"
|
|
830
|
-
}
|
|
831
|
-
}
|
|
832
|
-
}
|
|
833
|
-
]
|
|
834
|
-
}
|
|
838
|
+
```
|
|
839
|
+
on({ type: "update", table: "repairs", changed: ["status"], when: "record.status == \"fixed\"" });
|
|
840
|
+
|
|
841
|
+
await update_records({ table_id: "repairs", record_ids: [trigger.record_id], set: { fixed_on: formatDate(now(), "yyyy-MM-dd") } });
|
|
835
842
|
```
|
|
836
843
|
|
|
837
844
|
The body names the table's fields, select options and other tables by alias, and reads the write as `record`,
|
|
@@ -839,27 +846,27 @@ The body names the table's fields, select options and other tables by alias, and
|
|
|
839
846
|
that calls a tool changing a table, an app or any other structure is refused when it is saved. It runs as the table's owner, so
|
|
840
847
|
it reaches the tables the owner reaches, and its writes meet the table's checks like any other write; a write refused by a
|
|
841
848
|
check fails that run and leaves the row as it was. A body names a field by alias where it can tell which table's field it is: a
|
|
842
|
-
read of `record`, `prev_record` or `changes`, a row from `
|
|
849
|
+
read of `record`, `prev_record` or `changes`, a row from `query_records` called with a literal `table_id`, and a
|
|
843
850
|
loop over such rows. A name it cannot tie to a table, and an option compared to a value of unknown type (not a read of a field, and
|
|
844
851
|
not a text, number or other value the type proves holds no option), are refused with what to change. A link reads as the list of the row ids it holds, a one-row link too: compare `first(record.customer) == id`. A lookup reads as one flat list of what it looks up: `record.x != prev_record.x` on one is refused, so compare `first(record.x)` with `first(prev_record.x)`. A single member, a single select, a number or a text reads as the value itself: `first(record.technician)` is refused at save, naming the read.
|
|
845
852
|
|
|
846
853
|
A write the workflow itself makes sets off the workflows of its table in turn, and a chain of them stops after five. A workflow
|
|
847
854
|
that writes the row it follows is gated by a `changed` or a `when` that its own write does not satisfy. One run is queued for a
|
|
848
855
|
row and a workflow at a time, so a burst of writes to one row runs it once more after the run in flight. A field a workflow
|
|
849
|
-
reads cannot be deleted or retyped until the workflow is changed or
|
|
856
|
+
reads cannot be deleted or retyped until the workflow is changed or deleted with `set_workflow` before. Cloning a table copies its
|
|
850
857
|
workflows for whoever manages it (`include_workflows: false` leaves them), each with a body of its own, and is refused when a
|
|
851
858
|
body names a table the cloner cannot reach. A workflow runs as the table's owner, so whoever saves or rehearses one must manage the
|
|
852
859
|
table and read every table it reads in full: the body's, the gate's links', the written table. An option is written where
|
|
853
860
|
it is compared to, listed against or set on its field; a constant holding one is refused.
|
|
854
861
|
|
|
855
|
-
A lock is the table's: a table workflow
|
|
856
|
-
the row's writes until whoever manages the table
|
|
862
|
+
A lock is the table's: a table workflow locks a row with `update_records` and `locked: true` (an app's act may too), and every door then refuses
|
|
863
|
+
the row's writes until whoever manages the table unlocks it with `update_records` and `locked: false` — in an app's run, judged as the member who pressed.
|
|
857
864
|
A locked row reads locked in every app. An app's own Check or `edit: false` only makes a row read-only in that app.
|
|
858
865
|
|
|
859
866
|
## Runs
|
|
860
867
|
|
|
861
868
|
Each start of a workflow is a run: an app's workflow or act, a table workflow, a button, an automation. An agent finds,
|
|
862
|
-
reads, retries and stops runs
|
|
869
|
+
reads, retries and stops runs. A run is read by whoever manages what its workflow belongs to: its
|
|
863
870
|
automation or its table, and for an app's workflow the app's owner or an admin, never a manager of the app.
|
|
864
871
|
|
|
865
872
|
- **Find.** `query_workflow_executions` lists one workflow's runs (`workflow_id`), every run started for one row
|
|
@@ -871,7 +878,7 @@ automation or its table, and for an app's workflow the app's owner or an admin,
|
|
|
871
878
|
returned, how many rows it wrote (a row's own history is in `query_audit_logs`) and the runs its writes started. Ninety days
|
|
872
879
|
after a run starts, its trigger and its steps' input and output are pruned; the read says so and gives the rest.
|
|
873
880
|
`run_app_workflow` answers each run it makes with its `execution_id`.
|
|
874
|
-
- **Retry.** `
|
|
881
|
+
- **Retry.** `set_workflow_execution` with `action: "retry"` runs a failed, refused or cancelled run again as a new run linked to the first
|
|
875
882
|
(`retry_of`): the first run's trigger and row, the workflow's current steps and its owner's current access, with the
|
|
876
883
|
member who retries it as the one who started it. A run is retried once; a retry that fails is retried in turn. It asks
|
|
877
884
|
first when the run, or a run it retried, already wrote rows or called a service no delete reverses. An automation's or a
|
|
@@ -879,7 +886,9 @@ automation or its table, and for an app's workflow the app's owner or an admin,
|
|
|
879
886
|
the call waits. An app's call is checked again against the workflow's current inputs. A pruned run, a run of a workflow
|
|
880
887
|
that is off, a reviewed act's run (call the act again) and a recording's filing (the recording retries its delivery)
|
|
881
888
|
are not run again.
|
|
882
|
-
- **Stop.** `
|
|
889
|
+
- **Stop.** `set_workflow_execution` with `action: "cancel"` stops a waiting run and withdraws its pending approvals. Both answer
|
|
890
|
+
`execution_id`, `action`, `status`, `retry_of`, `error` and, for a retry, `original_wrote`: the rows and the irreversible
|
|
891
|
+
calls the first run wrote, which the retry makes again.
|
|
883
892
|
|
|
884
893
|
Off means off: a waiting run whose workflow was turned off, deleted, or taken off its automation, table, button or app ends
|
|
885
894
|
`cancelled` when it would resume, saying why, and runs no further step.
|
|
@@ -7,13 +7,13 @@ Once tables and apps exist, an agent does the team's work through them. The mean
|
|
|
7
7
|
| A business operation: move an order on, issue a paper, notify someone | `run_app_workflow` on the app's act, `act_<key>` or the `workflow` it names | The outcome equals a person's press: status, stamps, documents, notifications. `update_records` on an act's field skips all of them. |
|
|
8
8
|
| Data entry, a correction, loading the owner's lists | `create_records`, `update_records` | The table's rules hold for every writer. |
|
|
9
9
|
| A rule or policy the business wrote | `get_workspace` with `search`, then `read_knowledge` | The business's own words, not a guess. |
|
|
10
|
-
| A file someone hands over | `
|
|
10
|
+
| A file someone hands over | `view_files`; `upload_file` to keep it. A picture reaches a CLI as a note naming `download_file` and `lotics file download <file_id>` | Reads its content; the upload files it in the workspace. When your host passes no bytes, `upload_file` with `person: true` gives the person a link to drop it on. |
|
|
11
11
|
| A paper an app makes | the act that makes it, which files it on the record | `generate_document` makes an unattached file, for a paper no app makes. |
|
|
12
12
|
| A connected service (KiotViet, MISA, a carrier) | a workflow step, reached through a Workflow node | No tool calls a service directly (`workflows/reaching-a-connected-service`). |
|
|
13
13
|
| A figure or list the business defines, or an operation it repeats | the app's declared Query or Workflow node: `run_app_query`, `run_app_workflow` | The node is the business's own definition, found by `get_workspace` with `search` and read in `get_app`. |
|
|
14
|
-
| A count or sum no app answers | `
|
|
14
|
+
| A count or sum no app answers | `query_records` with `aggregate` (grouped by `group_by`), or its rows; `record_ids` reads those records by id | |
|
|
15
15
|
|
|
16
|
-
Find the means in this order: `get_workspace` maps the workspace; `search` it by what is asked ("overdue"), which matches an app's declared Query and Workflow keys and descriptions and names the matching keys under the app; `get_app` lists that app's queries and workflows with their inputs. Run the declared node. Only a question no node answers goes to `
|
|
16
|
+
Find the means in this order: `get_workspace` maps the workspace; `search` it by what is asked ("overdue"), which matches an app's declared Query and Workflow keys and descriptions and names the matching keys under the app; `get_app` lists that app's queries and workflows with their inputs. Run the declared node. Only a question no node answers goes to `query_records`.
|
|
17
17
|
|
|
18
18
|
## Workspace settings and standing instructions
|
|
19
19
|
|
|
@@ -21,7 +21,38 @@ Find the means in this order: `get_workspace` maps the workspace; `search` it by
|
|
|
21
21
|
|
|
22
22
|
## People
|
|
23
23
|
|
|
24
|
-
`get_workspace` with kind `member` lists
|
|
24
|
+
`get_workspace` with kind `member` lists every member's name and id; an admin also sees each one's email, role, `managed` and `sign_in_name`, and a `unavailable` list (kind `knowledge` or `template`) of the docs and templates the admin cannot open, while usable ones carry `access`. An admin also reads the credit limits there.
|
|
25
|
+
|
|
26
|
+
`set_members` is an admin's: `members` is a map keyed by an email address, a member id or a managed member's sign-in name, and `groups` a map keyed by a group id or the name of a group to create. In `members`, an email no member holds invites that person (`role` is `member` or `admin`; the invitation email goes out), a current member's entry sets `name` and `role`, a removed member's entry restores them and takes only `name`, and `null` removes a member or cancels the invitation pending to that address. A managed member's entry takes a `password` of 8 to 128 characters (sign-in names are compared in lowercase and checked for uniqueness across Lotics before any write), and the result carries the `sign_in_name`. The organization keeps at least one admin: the last one cannot be demoted or removed. In `groups`, an entry sets `name`, `description` and the roster with `member_ids`, or with `add_member_ids` and `remove_member_ids` that leave the rest in place; a roster entry may be a key of `members`, so a person invited in the same call can join; `null` deletes the group. The whole input is checked before anything is written or sent; members are applied before groups, in the order given, and the caller's own step-down last. A member's tool list does not carry `set_members` or `set_workspace`.
|
|
27
|
+
|
|
28
|
+
`set_access` gives members, groups or the whole organization a role on resources. Role `owner`, on one member, hands every resource listed to that member, whom only the resource's owner or an admin may name; a call cannot share an app and hand it over together, an app handed over loses its sharing, and an automation is turned off until its new owner turns it on. Handovers are applied one resource at a time, and a failure part-way names what was already applied. `set_workspace` takes `credit_limits` (`workspace`, `apps` by app id, `members` by member id; a number is the daily ceiling in credits, `null` removes it and asks for approval).
|
|
29
|
+
|
|
30
|
+
## Comments
|
|
31
|
+
|
|
32
|
+
Every record read carries `comment_count`. `query_records` takes `comments: true` to return the thread (`limit` defaults to 20, 20 at most). `set_record_comments` writes in one transaction, 1 to 20 entries: an entry without `comment_id` creates, one with it edits, and `content: null` deletes. Only a comment's author edits or deletes it.
|
|
33
|
+
|
|
34
|
+
## Deleting and restoring
|
|
35
|
+
|
|
36
|
+
A keyed entry is removed by `null`: a field, check, view, option, comment, instruction, query alias, a member or a group. A resource addressed by its id is deleted with `delete: true` and brought back with `restore: true`, beside its id: a table in a `set_tables` item (`{ table_id, delete: true }`; `cascade` deletes the links that point at the deleted tables and is refused unless the call deletes; `force` goes with a patch that loses stored values and is refused on a call that only deletes or restores), a knowledge doc (`set_knowledge` with `knowledge_doc_id`), a template (`set_template` with `template_id`), an app (`set_app` with `app_id`) and a workflow (`set_workflow` with `workflow_id`; a deletion only). Records are deleted by `delete_records` and brought back by `update_records` with `restore: true` and `record_ids` or `filters`. `delete` takes no other key. A call that deletes asks for approval; one that does not never does.
|
|
37
|
+
|
|
38
|
+
## Locking and changing a locked record
|
|
39
|
+
|
|
40
|
+
`update_records` with `locked: true` locks the targets (write access) and `locked: false` unlocks them (the table's manager). Beside field edits, a record that is locked before and after the call is refused into `blocked`; an unlock lands before the edit and a lock after it. `request_change: { reason }` files one approval request per blocked record instead, carrying the values the call resolves, and lists them in `requested` as `{ record_id, approval_request_id }`; the unlocked targets of the same call are written, and the call and its requests are one transaction, so a request that cannot be filed leaves them unwritten too.
|
|
41
|
+
|
|
42
|
+
## Undoing a write
|
|
43
|
+
|
|
44
|
+
A write is put back by writing the value `query_audit_logs` shows before the change: `update_records` for a field, `update_records` with `restore: true` for a deleted record, `delete_records` for a record a run created.
|
|
45
|
+
|
|
46
|
+
## Files
|
|
47
|
+
|
|
48
|
+
`view_files` reads what a file says (up to 100 by id, or the files of a record's file field with `source`). Inside an app's agent only the `file_ids` a query returned may be opened, and `source` is refused. `download_file` returns a `download_url` and, for a caller on a key or a connector, the bytes (up to 512 KB; `bytes_omitted` says why they are absent). In the in-product chat the `download_url` is the stable address a signed-in member opens, so it is for the person, not a link a tool fetches.
|
|
49
|
+
|
|
50
|
+
`upload_file` names one source:
|
|
51
|
+
|
|
52
|
+
- `url`: a public http(s) address the server fetches.
|
|
53
|
+
- `file`: the `{download_url, file_id}` reference a host hands over for a member's attachment (a ChatGPT file parameter arrives hydrated into this shape). Never construct one.
|
|
54
|
+
- `files`: bytes you hold locally. Each entry gets an `upload_url`, valid for 5 minutes, on the same host as your other calls; PUT the bytes to it and the response is the `file_id`. Where your sandbox allows no outbound request to that host (in Claude, a Team or Enterprise owner lists it under Admin settings → Capabilities), the PUT fails before it leaves: give the person the file with `person: true` instead.
|
|
55
|
+
- `person: true`: returns a `drop_url`, valid for an hour, to give the person, and a `ticket_id` per file; once they say they dropped the files, call again with each `ticket_id` for its `file_id`.
|
|
25
56
|
|
|
26
57
|
## Doing an operation
|
|
27
58
|
|
|
@@ -43,8 +74,8 @@ tables, loaded this way after `set_tables`.
|
|
|
43
74
|
`params` returns it; `filters`, `fields`, `sort` and `count` narrow the result.
|
|
44
75
|
2. A period's total is one call per period, each with `from` and `to` or a date filter. `aggregate` groups a result by
|
|
45
76
|
a column (`by`), a number's bands (`bands`) or a date column's day (`day`), and sums one (`sum`).
|
|
46
|
-
3. A question no app answers is `
|
|
47
|
-
|
|
77
|
+
3. A question no app answers is `query_records` over one table: its rows, or with `aggregate` counts and sums grouped by a
|
|
78
|
+
field; `filters` is the grammar of both.
|
|
48
79
|
|
|
49
80
|
## A worked day
|
|
50
81
|
|
|
@@ -56,5 +87,5 @@ get_app app_id → aliases: order_list,
|
|
|
56
87
|
run_app_query app_id, alias "order_list", params {"search": "1042"}
|
|
57
88
|
run_app_workflow app_id, alias "act_ship", inputs {"record_id": "rec_…"}, dry_run true
|
|
58
89
|
run_app_workflow the same call without dry_run
|
|
59
|
-
|
|
90
|
+
query_records table_id, aggregate {group_by "stage", count} → how many orders stand at each stage now
|
|
60
91
|
```
|