@lotics/app-sdk 0.124.8 → 0.124.9

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/index.js CHANGED
@@ -7613,7 +7613,11 @@ var TOOL_INPUT_SEMANTICS = {
7613
7613
  // Each entry's {field, op, value} carries a field-key position, but the
7614
7614
  // field is a sibling-dependent runtime value, not a single `table_from`
7615
7615
  // kind — the tool's own resolveSurgicalKeys validates it at execute time.
7616
- field_edits: { kind: "dynamic" }
7616
+ field_edits: { kind: "dynamic" },
7617
+ // The lock the call leaves, the deleted records it brings back, and the approval it files for a locked one: none holds a field key.
7618
+ locked: { kind: "scalar" },
7619
+ restore: { kind: "scalar" },
7620
+ request_change: { kind: "scalar" }
7617
7621
  },
7618
7622
  create_records: {
7619
7623
  table_id: { kind: "table_ref", rows: "change" },
@@ -7674,21 +7678,11 @@ var TOOL_INPUT_SEMANTICS = {
7674
7678
  as_member: { kind: "scalar" },
7675
7679
  removal: { kind: "table_field_array" }
7676
7680
  },
7677
- restore_records: {
7678
- table_id: { kind: "table_ref", rows: "change" },
7679
- record_ids: { kind: "record_refs" },
7680
- filters: { kind: "filter_tree", table_from: "table_id" }
7681
- },
7682
7681
  lock_records: {
7683
7682
  table_id: { kind: "table_ref", rows: "change" },
7684
7683
  record_ids: { kind: "record_refs" },
7685
7684
  filters: { kind: "filter_tree", table_from: "table_id" }
7686
7685
  },
7687
- unlock_records: {
7688
- table_id: { kind: "table_ref", rows: "change" },
7689
- record_ids: { kind: "record_refs" },
7690
- filters: { kind: "filter_tree", table_from: "table_id" }
7691
- },
7692
7686
  request_locked_record_change: {
7693
7687
  table_id: { kind: "table_ref", rows: "none" },
7694
7688
  record_id: { kind: "record_ref" },
@@ -7697,18 +7691,6 @@ var TOOL_INPUT_SEMANTICS = {
7697
7691
  changes: { kind: "dynamic" },
7698
7692
  reason: { kind: "text" }
7699
7693
  },
7700
- // The record names its own table; an entry's id is the trail's.
7701
- undo_record_change: {
7702
- record_id: { kind: "record_ref" },
7703
- entry_id: { kind: "scalar" }
7704
- },
7705
- // `table_id` names the table whose rows the undo changes, moves to the trash and brings back; a run's id is the
7706
- // trail's.
7707
- undo_record_changes: {
7708
- table_id: { kind: "table_ref", rows: "remove" },
7709
- record_ids: { kind: "record_refs" },
7710
- workflow_execution_id: { kind: "scalar" }
7711
- },
7712
7694
  // ─── Query surfaces ────────────────────────────────────────────────────
7713
7695
  query_records: {
7714
7696
  table_id: { kind: "table_ref", rows: "none" },
@@ -7724,6 +7706,10 @@ var TOOL_INPUT_SEMANTICS = {
7724
7706
  archived_only: { kind: "scalar" },
7725
7707
  include_timestamps: { kind: "scalar" },
7726
7708
  as_member: { kind: "scalar" },
7709
+ comments: { kind: "scalar" },
7710
+ record_ids: { kind: "record_refs" },
7711
+ // Nested {field_key, operation} shapes and a group_by key: resolved at execute time, as the aggregate tool did.
7712
+ aggregate: { kind: "dynamic" },
7727
7713
  // Each entry pairs its OWN table_id with its OWN filter tree, so the table a
7728
7714
  // filter resolves against is per-element rather than a sibling kwarg — no
7729
7715
  // top-level kind can say that. Opaque to the save-time walker, which is the
@@ -7775,7 +7761,8 @@ var TOOL_INPUT_SEMANTICS = {
7775
7761
  // Keys on the record's own table, which `table_id` names when supplied and
7776
7762
  // the record resolves otherwise — the same position it holds on a query.
7777
7763
  field_keys: { kind: "field_key_refs", table_from: "table_id" },
7778
- as_member: { kind: "scalar" }
7764
+ as_member: { kind: "scalar" },
7765
+ comments: { kind: "scalar" }
7779
7766
  },
7780
7767
  // ─── Buttons (table-scoped field with action) ─────────────────────────
7781
7768
  press_button: {
@@ -9682,7 +9669,8 @@ async function tableOf(walk, input) {
9682
9669
  async function predictUpdate(walk, input) {
9683
9670
  const table = await tableOf(walk, input);
9684
9671
  const ids = input.record_ids === void 0 ? null : asIds(await attempt(walk, input.record_ids));
9685
- if (table === null || ids === null || input.filters !== void 0 || input.rows !== void 0 || input.field_edits !== void 0) {
9672
+ const serverDecided = input.restore !== void 0 || input.locked !== void 0 || input.request_change !== void 0;
9673
+ if (table === null || ids === null || serverDecided || input.filters !== void 0 || input.rows !== void 0 || input.field_edits !== void 0) {
9686
9674
  note(walk, { kind: "unknown", table_id: table });
9687
9675
  return;
9688
9676
  }
package/docs/ai.md CHANGED
@@ -13,7 +13,7 @@ Don't run a structured extraction through `askAi` (the result is stranded in a c
13
13
 
14
14
  ## Declared agents — what `useAgentRun` runs
15
15
 
16
- `useAgentRun` runs an agent already bound to the app; none can be added or changed any more, and `remove_app_binding` with `kind: "agent"` unbinds one. A new job for an agent is a declared Query or Workflow node of the app, which an outside agent runs with `run_app_query` and `run_app_workflow`. `.lotics/app_agents.d.ts`, generated from the app's live bindings by `lotics app create --custom` and `lotics app deploy`, types `useAgentRun("<alias>")` by the agent's input and output shapes.
16
+ `useAgentRun` runs an agent already bound to the app; none can be added, changed or removed any more. A new job for an agent is a declared Query or Workflow node of the app, which an outside agent runs with `run_app_query` and `run_app_workflow`. `.lotics/app_agents.d.ts`, generated from the app's live bindings by `lotics app create --custom` and `lotics app deploy`, types `useAgentRun("<alias>")` by the agent's input and output shapes.
17
17
 
18
18
  ---
19
19
 
@@ -194,10 +194,10 @@ three gates are observable as an `error` on the hook:
194
194
  resolves to `{ members: [] }`.
195
195
 
196
196
  Name the group by the `grp_…` id the workflow input's `group` declares — the binding below, set
197
- with `set_app_workflow`:
197
+ with `set_workflow`:
198
198
 
199
199
  ```jsonc
200
- // set_app_workflow — the declaration that unlocks useMembers
200
+ // the header of a set_workflow source — the declaration that unlocks useMembers
201
201
  {
202
202
  "alias": "assignOrder",
203
203
  "inputs": {
package/docs/mutations.md CHANGED
@@ -24,7 +24,7 @@ const result = await createOrder({ customer_id, quantity: 3 });
24
24
  against the alias's declared input schema, and executes the workflow under the **app
25
25
  owner's** authority (never the viewer's — see [security](./security.md) for attribution,
26
26
  privilege gates, and public-app semantics).
27
- - Binding is server-side and live: `set_app_workflow` binds an alias as a new version of the app,
27
+ - Binding is server-side and live: `set_workflow` binds an alias as a new version of the app,
28
28
  and a deploy never touches it. The declaration also types `useWorkflow` (below).
29
29
  - Invoking an alias that is not bound resolves with `status: "error"` and a message naming
30
30
  the missing binding.
@@ -170,7 +170,7 @@ envelope's grammar — `message` is required, `field_errors` never reaches the a
170
170
 
171
171
  - **Typed for free — persisted at `set`.** The alias's `outputs` schema is derived at save
172
172
  time from the inferred type of the body's `return({ data })`. When the binding declares
173
- **no** `outputs`, `set_app_workflow` stores the derived schema as the alias's `outputs`. An
173
+ **no** `outputs`, `set_workflow` stores the derived schema as the alias's `outputs`. An
174
174
  **explicitly declared** `outputs` is authoritative and never overwritten;
175
175
  declare one only to narrow beyond what's inferred. A shape the checker can't pin down degrades
176
176
  to untyped `json`, never to a wrong schema.
@@ -214,7 +214,7 @@ if (r.status === "success" && r.data) {
214
214
  An alias's declaration is `{ inputs?, outputs?, workflow_id? }` — `workflow_id` is the server's
215
215
  half, stamped by the first bind, so an alias an author has only just written carries none.
216
216
  `inputs` maps each input name to a typed declaration; it is authored beside the body and
217
- travels with it (`set_app_workflow` takes both), and the schema the body was verified against is
217
+ travels with it (`set_workflow` takes both), and the schema the body was verified against is
218
218
  canonical. It drives three things at once: compile-time typing of the `useWorkflow` payload,
219
219
  compile-time typing of `trigger.app_workflow.inputs.*` inside the body, and runtime payload
220
220
  validation at the execute boundary.
@@ -283,7 +283,7 @@ When the alias declares `inputs`, the server validates the payload before the wo
283
283
  added after authoring is accepted, a removed one rejected
284
284
  (`select input "<path>" value "<v>" is not one of its current options (<label>=<value>, …)`) with no
285
285
  redeploy. A `field` that doesn't exist or names a non-select field is rejected at bind time
286
- (`set_app_workflow`) with
286
+ (`set_workflow`) with
287
287
  `select input references field "<key>", which does not exist in this workspace` /
288
288
  `… which is a <type> field, not a select`. Prefer `field` for any select backed by a real
289
289
  field; keep `options` for a fixed enum the app owns. Populate pickers from `useFieldOptions`
@@ -460,7 +460,7 @@ return({ status: "success", message: "Updated." });
460
460
  ```
461
461
 
462
462
  `update_records` has no bypass flag — a locked record's values change only through the
463
- approval flow below (or the table's owner or an admin explicitly unlocking the record first; `unlock_records` is
463
+ approval flow below (or the table's owner or an admin explicitly unlocking the record first; `update_records` with `locked: false` is
464
464
  owner- or admin-gated — in an app's run, judged as the member who pressed, never the app's owner — so don't build an unlock→edit→relock workflow around the approval flow — the
465
465
  lock exists to route changes through review).
466
466
 
@@ -553,7 +553,7 @@ What the steps cannot tell before they run is never guessed:
553
553
  looks for a row to reuse draws nothing until the server says which it did. A `catch` block and a
554
554
  `while` or counted `for` loop wait too.
555
555
  - A pause (`wait`, `wait_for_event`, `wait_for_approval`): every write after it waits for the answer.
556
- - A step that changes rows it cannot draw — `lock_records`, `restore_records`, a pressed button, an
556
+ - A step that changes rows it cannot draw — `lock_records`, `update_records` with `restore` or `locked`, a pressed button, an
557
557
  agent whose tools can change rows, a `create_records` matching on a field, a filtered update or
558
558
  delete: every read over the tables it may touch (every read, where it names none) is `pending`
559
559
  until the answer lands.
package/docs/recipes.md CHANGED
@@ -121,11 +121,7 @@ Two **separate** layers; set both.
121
121
 
122
122
  **Launcher chrome** — `lotics run set_app '{"app_id":"…","body":{"icon":"truck","color":"blue"}}'`.
123
123
  `icon` is any Lucide name (the full library, validated at write time — a wrong name is rejected, not
124
- silently boxed). Don't guess the kebab spelling:
125
-
126
- ```
127
- lotics run search_app_icons '{"query":"shipping"}' → ["truck","ship","package",…]
128
- ```
124
+ silently boxed). Don't guess the kebab spelling: a wrong name is refused with the nearest Lucide names.
129
125
 
130
126
  `color` is a named palette colour: `red orange amber yellow lime green emerald teal cyan sky
131
127
  blue indigo violet purple fuchsia pink rose slate gray zinc neutral stone`. `null` in `body` clears
package/docs/workflows.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Workflows — the body-authoring reference
2
2
 
3
- The grammar of a **workflow body**: what `set_app_workflow` takes as an alias's `source`, the
3
+ The grammar of a **workflow body**: what `set_workflow` takes as a `source` bound to an app's alias, the
4
4
  JS subset that is accepted, and the rules that decide whether a body saves.
5
5
  [mutations](./mutations.md) owns the *call* side; this document owns everything between the
6
6
  braces.
@@ -17,7 +17,7 @@ engine, no `eval`, no sandbox. Four consequences shape everything below:
17
17
  - **A successful save may still return `warnings[]`** — advisory lint that does not block.
18
18
  Read them; they are the failures that only show up in production.
19
19
  - **One canonical form per concept.** Several JS spellings are accepted and *lowered* to one
20
- stored form. The stored tree renders back to source when it is read (`get_app_workflow`), so a
20
+ stored form. The stored tree renders back to source when it is read (`get_workflow`), so a
21
21
  read body shows the canonical spelling, not the sugar you typed (see *Round-tripping* below).
22
22
  - **Opaque keys, never display names.** Fields are `fld_*`, select options are `opt_*`, tables
23
23
  are `tbl_*`, groups are `grp_*`. A display name is rejected at save with an error pointing at
@@ -31,10 +31,10 @@ if (order.data["fld_status"] == "opt_open") { … }
31
31
 
32
32
  ## Where a body lives
33
33
 
34
- A body is the app's, stored on the server and bound to an alias: `set_app_workflow` takes the
35
- alias, the body's `source`, and the alias's `inputs` (leave `outputs` off to let the body's
36
- `return({ data })` derive it). The server verifies the body and the write lands live, as a new
37
- version of the app; `get_app_workflow` reads it back, and an earlier body is restored by writing it again. Input declaration vocabulary and validation:
34
+ A body is the app's, stored on the server and bound to an alias: `set_workflow` takes a
35
+ `source` whose `on({ type: "app", app_id, alias, inputs?, outputs? })` header names the alias and its `inputs`
36
+ (leave `outputs` off to let the body's `return({ data })` derive it), then the body. The server verifies the body and the write lands live, as a new
37
+ version of the app; `get_workflow` reads it back, and an earlier body is restored by writing it again. Input declaration vocabulary and validation:
38
38
  [mutations](./mutations.md#declaring-workflow-inputs).
39
39
 
40
40
  An app workflow body carries **no trigger declaration** — the binding supplies the trigger
@@ -250,7 +250,7 @@ only the dialog. What it may name depends on who renders the refusal:
250
250
 
251
251
  An app workflow whose alias declares no inputs leaves input-shaped keys unchecked, so a body can
252
252
  be written before its declaration. A key outside its row is refused when the body is SAVED — by
253
- `set_app_workflow`, by an agent binding one — and by a `verify_only` call before that. `return({ field_errors })` keys are not checked.
253
+ `set_workflow`, by an agent binding one — and by a `dry_run` call before that. `return({ field_errors })` keys are not checked.
254
254
 
255
255
  ### `wait_for_approval` — the one wait worth binding
256
256
 
@@ -460,7 +460,7 @@ A single-expression helper, **top level only**, inlined at every call site.
460
460
  value: `lineTotal({ qty: 1, price: 2 })` is rejected because inlining would splice a path onto
461
461
  an object literal. Bind it first (`const l = { … };` then `lineTotal(l)`), or pass a row.
462
462
 
463
- **A declared `function` does not survive a read.** Inlining erases it: `get_app_workflow` returns
463
+ **A declared `function` does not survive a read.** Inlining erases it: `get_workflow` returns
464
464
  the body repeated at each call site and no `function` at all. Reach for one when the *saved* logic
465
465
  is what matters, and expect to re-extract it if you edit the source read back.
466
466
 
@@ -834,7 +834,7 @@ genuinely recursive.
834
834
 
835
835
  ## The check loop
836
836
 
837
- `set_app_workflow` with `verify_only: true` gives the verdict a save would, and writes nothing. The
837
+ `set_workflow` with `dry_run: true` gives the verdict a save would, and writes nothing. The
838
838
  server runs every check a save runs — the subset parse, the type pass against the alias's
839
839
  declared inputs, tool and kwarg resolution, field, option, table and group resolution, `switch`
840
840
  case validation, the literal `formatDate` probe, waits in loops, table reach, the `field_key` rule and the published-API guard. **Green means the save will take
@@ -849,8 +849,8 @@ fix-stating messages before you see them.
849
849
 
850
850
  `check` and `set` prove parse, types, name resolution, structure, and lint — **none of them
851
851
  evaluates an expression**. A body that saves clean can still take the wrong branch, hand a tool a filter it
852
- rejects, or read a path that is null on real data. The rehearsal for that is `set_app_workflow` with
853
- `verify_only: true` and a `record_id` (the row an Act would run on) or `preview_inputs` (the values of a
852
+ rejects, or read a path that is null on real data. The rehearsal for that is `set_workflow` with
853
+ `dry_run: true` and a `record_id` (the row an Act would run on) or `preview_inputs` (the values of a
854
854
  workflow that takes inputs and no row): the draft is walked by the engine an Act's preview uses, under the
855
855
  app's authority, with the production expression evaluator. Reads are real; writes and service sends are
856
856
  said, not made, and a document a step renders is made as a paper no listing shows. It answers `preview`
@@ -865,7 +865,7 @@ A workflow that files a recording ([mutations](./mutations.md#recording-into-a-w
865
865
  declares the platform's `recording` input in exactly this shape, beside its own inputs:
866
866
 
867
867
  ```jsonc
868
- // set_app_workflow { "alias": "log_visit", "inputs": … }
868
+ // the header of a set_workflow source: on({ type: "app", app_id, alias: "log_visit", inputs: … })
869
869
  "inputs": {
870
870
  "site": { "type": "record_link", "table_id": "tbl_sites" },
871
871
  "recording": { "type": "object", "fields": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lotics/app-sdk",
3
- "version": "0.124.8",
3
+ "version": "0.124.9",
4
4
  "description": "The SDK a Lotics custom-code app reads and writes through — typed hooks over the host bridge, cell readers, mount() and AppRouter",
5
5
  "type": "module",
6
6
  "exports": {