@lotics/app-sdk 0.58.4 → 0.59.0
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 +1 -1
- package/dist/src/analytics.js +11 -8
- package/docs/ai.md +8 -1
- package/docs/queries.md +31 -3
- package/docs/security.md +1 -1
- package/package.json +1 -1
package/AGENTS.md
CHANGED
|
@@ -21,7 +21,7 @@ signature; open the file.**
|
|
|
21
21
|
| [docs/files.md](./docs/files.md) | Files end to end — `useFileUpload`, `useAttachments`, `readFiles`/presigned URLs, workflow-generated files, preview pairing, filter operators, the server-side delivery bounds. **Uploads declare a `fidelity`** (`standard` / `high` / `original`) — the app picks how much of the image survives storage; use `high` whenever text must stay legible. |
|
|
22
22
|
| [docs/members_and_options.md](./docs/members_and_options.md) | People + select options + comments — `useMembers`, `useFieldOptions`, `useViewer`, `useComments`, and the `@lotics/ui` components they feed. |
|
|
23
23
|
| [docs/navigation_and_state.md](./docs/navigation_and_state.md) | `AppRouter` (embedded/standalone URL model), `useUrlState` + `urlParam` codecs, `useRecents`. |
|
|
24
|
-
| [docs/ai.md](./docs/ai.md) | `useAgentRun` (structured vs free-text, streaming ai-sdk `parts` → `AgentRun`, the agent's ask-back — `pendingChoice`/`answerChoice` over the parked `awaiting_input` state), `askAi` — plus the fields-vs-file razor for choosing between them — and `useAiContext` (push the current screen's view state to the member's ambient chat agent; caps, push-only semantics, auto query-refetch on chat mutation). **A `file` input carries its own content** — images/PDFs are perceived natively, Word/Excel/CSV/text are materialized into the run; no reader tool to declare. |
|
|
24
|
+
| [docs/ai.md](./docs/ai.md) | `useAgentRun` (structured vs free-text, streaming ai-sdk `parts` → `AgentRun`, the agent's ask-back — `pendingChoice`/`answerChoice` over the parked `awaiting_input` state), `askAi` — plus the fields-vs-file razor for choosing between them — and `useAiContext` (push the current screen's view state to the member's ambient chat agent; caps, push-only semantics, auto query-refetch on chat mutation). **A `file` input carries its own content** — images/PDFs are perceived natively, Word/Excel/CSV/text are materialized into the run; no reader tool to declare. **An agent reaches record DATA only through its declared `query_aliases` / `workflow_aliases`** (`run_app_query` / `run_app_workflow`); the raw record read/write tools are rejected. |
|
|
25
25
|
| [docs/security.md](./docs/security.md) | **Read before shipping** — the owner-principal model, `is_current_member` scoping, write attribution, group gates, public-app bounds, what runtime refinement cannot widen. |
|
|
26
26
|
| [docs/runtime.md](./docs/runtime.md) | `mount()`, the two transports, `rpc()`, `openExternal`/`downloadFile`, geofencing, analytics, `useConfig` (App-Packages installation config), `getAppBinding` (package apps' runtime `F`/`OPT`/`ROLE` resolution via the generated `.lotics/app_fields.ts`), and the publish chain for package contributors. |
|
|
27
27
|
|
package/dist/src/analytics.js
CHANGED
|
@@ -4,11 +4,13 @@
|
|
|
4
4
|
* Apps are a separate cross-origin bundle at `<slug>.lotics.app`, invisible to
|
|
5
5
|
* the product's PostHog — so `mount()` boots a PostHog instance per app.
|
|
6
6
|
*
|
|
7
|
-
* **Why explicit events, not autocapture:**
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
7
|
+
* **Why explicit events, not autocapture:** DOM autocapture ships `$el_text` —
|
|
8
|
+
* the visible text of whatever was clicked, i.e. the customer's own record
|
|
9
|
+
* values — and Điều 3.2 of the service contract warrants that our telemetry
|
|
10
|
+
* carries no record or document content. The flags below are what enforce
|
|
11
|
+
* that; the project-level setting is NOT a backstop, since it is server-side
|
|
12
|
+
* and fails open when the config fetch fails. The SDK emits explicit events
|
|
13
|
+
* for genuine user actions only: `app_opened`, `app_file_uploaded`, and
|
|
12
14
|
* `app_comment_*`. System signals — data-read mechanics (a `useQuery` refetch)
|
|
13
15
|
* and workflow/agent run outcomes — are not events: the backend already logs
|
|
14
16
|
* and persists every run (`workflow_executions`, `app_agent_runs`), so a client
|
|
@@ -88,10 +90,11 @@ export async function bootstrapAnalytics() {
|
|
|
88
90
|
// Pin the modern posthog-js init contract (the product uses the same).
|
|
89
91
|
defaults: "2025-05-24",
|
|
90
92
|
api_host: POSTHOG_HOST,
|
|
91
|
-
//
|
|
92
|
-
//
|
|
93
|
-
//
|
|
93
|
+
// App analytics is explicit events — not DOM autocapture or pageviews.
|
|
94
|
+
// Set here rather than relied on project-wide: the server-side setting
|
|
95
|
+
// fails open, and an app screen renders the customer's own records.
|
|
94
96
|
autocapture: false,
|
|
97
|
+
mask_all_text: true,
|
|
95
98
|
capture_pageview: false,
|
|
96
99
|
capture_pageleave: false,
|
|
97
100
|
// Dead-click autocapture (enabled by the defaults preset) is a UX-research
|
package/docs/ai.md
CHANGED
|
@@ -20,7 +20,10 @@ A declaration carries:
|
|
|
20
20
|
| Field | Meaning |
|
|
21
21
|
|---|---|
|
|
22
22
|
| `instructions` | System instructions — the task the agent performs per run |
|
|
23
|
-
| `tool_names` | The tools the agent may call, resolved against the platform's automation tool registry.
|
|
23
|
+
| `tool_names` | The tools the agent may call, resolved against the platform's automation tool registry. The capability boundary for everything EXCEPT workspace data — the run can use nothing else. May be empty — including for an agent that reads documents, since a [`file` input carries its own content](#file-inputs--what-the-agent-can-actually-see) |
|
|
24
|
+
| `knowledge_doc_ids` | The knowledge docs the agent may read. Small docs are materialized into the run's system context; a doc too large to inline is read by staging it into a code run, which requires `code_exec` in `tool_names` |
|
|
25
|
+
| `query_aliases` | The app's own named queries the agent may run via `run_app_query` — its **entire read surface** over records |
|
|
26
|
+
| `workflow_aliases` | The app's own workflows the agent may invoke via `run_app_workflow` — its **entire write surface** |
|
|
24
27
|
| `model_id` | Optional chat model pin. Omit (preferred) to follow the platform default chat model, resolved at run time — the agent tracks model generations with no rewrite. Pin only a deliberate, tested choice |
|
|
25
28
|
| `effort_level` | Optional reasoning depth for adaptive-thinking models. Requires an explicit `model_id` pin — effort is tuned per model |
|
|
26
29
|
| `inputs` | Optional typed input schema for one run — the same vocabulary as workflow inputs (`text`, `number`, `file`, `member`, `record_link`, `select`, …). The server validates every run payload against it |
|
|
@@ -30,6 +33,10 @@ A declaration carries:
|
|
|
30
33
|
|
|
31
34
|
**Input validation and tenant bounds.** Run inputs get the same server-side enforcement as workflow inputs: `record_link` ids must live in the declared table, `member` ids in the declared group, `file` ids in the app's workspace (see [the caller boundary](./security.md)). The run executes under the **app owner's** authority in the app's own workspace.
|
|
32
35
|
|
|
36
|
+
**Record data is reached only through declared aliases.** Every tool that reads or writes record VALUES by table id is rejected in `tool_names` — there is no `query_records`, `get_record`, `create_records`, or `update_records` for an agent. The agent reads with `run_app_query(alias, params)` and writes with `run_app_workflow(alias, inputs)`, both refused for any alias outside `query_aliases` / `workflow_aliases`. This is stricter than a list of tables: a query template fixes its own tables, joins, filters AND projection, so it bounds which **rows and columns** the agent sees; writes travel the app's declared mutation path, so workflow validation and table hooks apply. It matters because a run carries the OWNER's authority while any member can invoke it — raw table access would let a caller read or write, under that authority, anything the owner can. `is_current_member` inside a declared query resolves to the **invoking** member, so a self-scoped query scopes to the person using the agent, exactly as it does in the app's UI.
|
|
37
|
+
|
|
38
|
+
So an agent that reads or writes records needs a named query or workflow for each thing it touches — the same declaration the app's own UI uses. Declare only what that agent needs; a second agent in the same app can declare a narrower set.
|
|
39
|
+
|
|
33
40
|
**Typing.** `lotics app pull` / `lotics app codegen` emit `.lotics/app_agents.d.ts`, which augments the SDK's `AppAgents` (alias → input shape) and `AppAgentResults` (alias → declared output shape) interfaces — `useAgentRun("recognize")` then types both `run(input)` and `output`. An alias with no codegen falls back to `Record<string, unknown>` input / `unknown` output.
|
|
34
41
|
|
|
35
42
|
---
|
package/docs/queries.md
CHANGED
|
@@ -583,7 +583,7 @@ the table.
|
|
|
583
583
|
|
|
584
584
|
## 8. Shaping: group, buckets, window, fan-out
|
|
585
585
|
|
|
586
|
-
### The
|
|
586
|
+
### The 21 aggregate operations
|
|
587
587
|
|
|
588
588
|
`group` and `window` share one operation vocabulary. `count` is `COUNT(*)` (no `input_column`);
|
|
589
589
|
everything else requires an `input_column` whose type must be compatible — checked at deploy:
|
|
@@ -599,10 +599,38 @@ everything else requires an `input_column` whose type must be compatible — che
|
|
|
599
599
|
| `percent_filled`, `percent_empty` | any except boolean | number | **fraction 0–1**, not 0–100; NULL for an empty group |
|
|
600
600
|
| `unique`, `percent_unique` | text, number, date/datetime, select, select_member, select_record_link, files, json¹ | number | distinct **present** values; array cells compare as whole arrays |
|
|
601
601
|
| `checked`, `unchecked`, `percent_checked`, `percent_unchecked` | boolean | number | `unchecked` counts false **or** empty |
|
|
602
|
+
| `string_agg` | text, select | **text** | the distinct present values joined — the only operation returning values rather than a count. `group` only |
|
|
602
603
|
|
|
603
604
|
¹ opaque `json` columns support only the presence-counting six (`empty`/`filled`/`unique` and
|
|
604
605
|
their `percent_*` forms).
|
|
605
606
|
|
|
607
|
+
**`string_agg` — a summary column, not a dataset.** Every other operation counts or reduces to a
|
|
608
|
+
number; this one joins the values, so a child set answers "which ones?" in the parent row (the
|
|
609
|
+
sizes on a shipment, the tags on a ticket) without a second query.
|
|
610
|
+
|
|
611
|
+
```jsonc
|
|
612
|
+
{ "output": "sizes", "type": "text", "operation": "string_agg", "input_column": "size",
|
|
613
|
+
"distinct": true, "separator": ", ", "max_values": 20 }
|
|
614
|
+
```
|
|
615
|
+
|
|
616
|
+
- **`type` must be `text`** — declaring anything else is rejected at deploy.
|
|
617
|
+
- **`distinct`** defaults to **true**: three containers sized 40HC/40HC/20DC give `20DC, 40HC`.
|
|
618
|
+
Pass `false` to keep every occurrence. Values are always sorted, so the column doesn't
|
|
619
|
+
reshuffle between reads.
|
|
620
|
+
- **`max_values`** (default 20, max 100) caps the emitted values so an unbounded child set can't
|
|
621
|
+
produce a giant cell. It is a silent cap — pair it with a `unique` aggregate over the same
|
|
622
|
+
column to render an honest `+N more`.
|
|
623
|
+
- **`separator`** defaults to `", "` (max 8 chars).
|
|
624
|
+
- Empty values are dropped by the same emptiness contract below, so a partly-blank column
|
|
625
|
+
doesn't emit empty slots. A group with nothing present yields NULL.
|
|
626
|
+
- **`group` only** — it is not in the OVER-legal set, because deduplication and window frames
|
|
627
|
+
are mutually exclusive in SQL. Setting `distinct` / `separator` / `max_values` on any other
|
|
628
|
+
operation is rejected rather than ignored.
|
|
629
|
+
|
|
630
|
+
It is deliberately **not** a rollup field type: a rollup persists its value into record data and
|
|
631
|
+
rewrites it on every child change, and an unbounded concatenation does not belong in a stored
|
|
632
|
+
cell. Query-time only.
|
|
633
|
+
|
|
606
634
|
**The one emptiness contract.** `filled`/`empty`/`unique`/`percent_*` use the same definition
|
|
607
635
|
of "present" as the filter layer's `is_empty` and the `isEmpty` source: array-valued cells are
|
|
608
636
|
empty at NULL / JSON `null` / `[]`; text at NULL or blank (whitespace-only); opaque json at
|
|
@@ -643,8 +671,8 @@ A `window` node carries two independent column lists — **`aggregates`** (frame
|
|
|
643
671
|
**`aggregates` — the OVER-legal aggregate subset.** Only operations that compile to a single
|
|
644
672
|
legal SQL window call are accepted: **`count`, `sum`, `avg`, `min`, `max`, `earliest`, `latest`,
|
|
645
673
|
`filled`, `checked`, `unchecked`.** The rest cannot take an OVER clause (`median` is an
|
|
646
|
-
ordered-set aggregate; `unique`/`percent_unique` need DISTINCT;
|
|
647
|
-
`percent_*` compose multiple calls) — rejected at deploy. The optional `frame` applies **only**
|
|
674
|
+
ordered-set aggregate; `unique`/`percent_unique`/`string_agg` need DISTINCT;
|
|
675
|
+
`range`/`empty`/`date_range`/`percent_*` compose multiple calls) — rejected at deploy. The optional `frame` applies **only**
|
|
648
676
|
to these; a `frame` on a window with no `aggregates` is rejected as dead config.
|
|
649
677
|
|
|
650
678
|
**`functions` — ranking / navigation.** Each is `{ "output", "fn", … }` (its own arg shape, no
|
package/docs/security.md
CHANGED
|
@@ -30,7 +30,7 @@ Callers never submit query ASTs or workflow definitions — the server holds the
|
|
|
30
30
|
| `record_link` | Every submitted record id must live in the declared table |
|
|
31
31
|
| `file` | Every submitted file id must live in the app's workspace — a foreign tenant's `file_id` is rejected |
|
|
32
32
|
|
|
33
|
-
Workflows and agent runs additionally execute behind a **workspace tenant floor**: every table a run touches must live in the app's own workspace, so no input value can steer a run at another workspace's data even under an admin owner's authority.
|
|
33
|
+
Workflows and agent runs additionally execute behind a **workspace tenant floor**: every table a run touches must live in the app's own workspace, so no input value can steer a run at another workspace's data even under an admin owner's authority. An **agent** run is bounded harder still: it reaches records only through the queries and workflows its declaration names (`query_aliases` / `workflow_aliases`), never a raw table — so the ceiling is what you declared for that agent, not what the owner can see. See [ai](./ai.md).
|
|
34
34
|
|
|
35
35
|
What the platform does **not** constrain is the *meaning* of your params. A query param is a value hole in the template (a filter value, a search term); the server checks its type only — the group/table bindings above apply to workflow and agent-run inputs, **not** to query params — never whether the value should have been derived from the caller's identity. That's the next section.
|
|
36
36
|
|