@lotics/app-sdk 0.102.3 → 0.104.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/{chunk-ZOFKWF6V.js → chunk-CRJQYSDI.js} +6 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.js +59 -18
- package/dist/queries.d.ts +7 -0
- package/dist/recording.d.ts +18 -0
- package/dist/recording_state.d.ts +1 -0
- package/dist/router.js +1 -1
- package/docs/ai.md +1 -0
- package/docs/data_fetching.md +1 -1
- package/docs/mutations.md +5 -0
- package/docs/workflows.md +17 -0
- package/package.json +1 -1
package/AGENTS.md
CHANGED
|
@@ -17,7 +17,7 @@ This file is the index. The **exact type** of anything is its shipped declaratio
|
|
|
17
17
|
|---|---|
|
|
18
18
|
| [docs/data_fetching.md](./docs/data_fetching.md) | The reads: `useQuery(alias, params, opts)` — its rows (`limit`), numbered pages (`page`), a keyset feed (`more`), its `total` or the total alone, with `total: { by }` a count per value of one column — `useQueries` for reads known only at render, `queryAll` outside React, `exportQuery` for a file of them the server makes; every hook answers one `QueryState`. The ROW type (`RowOf` — the alias's projected columns and nothing else), runtime `sort`/`filter` keys, cell readers (`row.*`, `readSelect`, `readMembers`, `readLinks`, `readFiles`, `readLocked`, `readCreatedAt`/`readUpdatedAt`), the SDK's own cache — **arrival revalidates** — **realtime push**, a write drawn on every read from the press, a count as its own full scan, and the search-as-you-type and record-picker patterns. |
|
|
19
19
|
| [docs/queries.md](./docs/queries.md) | **The query engine reference** — AST node kinds, per-field-type operators, filters/params/pruning, free-text search, combining tables, shaping (aggregates, date buckets, windows), runtime refinement bounds, limits and the efficiency playbook. |
|
|
20
|
-
| [docs/mutations.md](./docs/mutations.md) | `useWorkflow` (the ONLY write path; `useWorkflows` for writes that are data), the `WorkflowResult` resolve-never-throw contract (`field_errors`), typed inputs, every write drawn on every read from the press — predicted from the workflow's own steps, taken back on a refusal, `pending` meanwhile — the re-read a successful write triggers over the tables its body names, diff-before-update, locked records, `useNewRecord`, read-after-write ordering (`writesSettled()`), `useRecording
|
|
20
|
+
| [docs/mutations.md](./docs/mutations.md) | `useWorkflow` (the ONLY write path; `useWorkflows` for writes that are data), the `WorkflowResult` resolve-never-throw contract (`field_errors`), typed inputs, every write drawn on every read from the press — predicted from the workflow's own steps, taken back on a refusal, `pending` meanwhile — the re-read a successful write triggers over the tables its body names, diff-before-update, locked records, `useNewRecord`, read-after-write ordering (`writesSettled()`), `useRecording`, `useRecordings` over several aliases. |
|
|
21
21
|
| [docs/workflows.md](./docs/workflows.md) | **The workflow-BODY reference** — the JS subset a body may use, opaque `fld_*`/`opt_*` keys, every step form, the accepted sugar, helpers, record-write surfaces, the traps and the verify loop. |
|
|
22
22
|
| [docs/recipes.md](./docs/recipes.md) | Task-shaped how-tos — returning a generated file, returning structured data, parameterized lookups, composable optional filters, cell decoding, testing an AI action without spending credits. |
|
|
23
23
|
| [docs/files.md](./docs/files.md) | Files end to end — `useFileUpload` and its `fidelity`, `renameFile` (a new file over the same bytes), `useAttachments`/`useAttachmentPiles`, `readFiles` and presigned URLs (a bearer credential — never logged or persisted), workflow-generated files, naming a zip's entries, the delivery bounds. |
|
|
@@ -444,9 +444,14 @@ async function runUploadPipeline(file, rpc2, options = {}) {
|
|
|
444
444
|
var IDLE_RECORDING = { phase: "idle" };
|
|
445
445
|
var states = /* @__PURE__ */ new Map();
|
|
446
446
|
var listeners = /* @__PURE__ */ new Set();
|
|
447
|
+
var version = 0;
|
|
447
448
|
function emit() {
|
|
449
|
+
version += 1;
|
|
448
450
|
for (const listener of listeners) listener();
|
|
449
451
|
}
|
|
452
|
+
function recordingsVersion() {
|
|
453
|
+
return version;
|
|
454
|
+
}
|
|
450
455
|
function subscribeRecordings(listener) {
|
|
451
456
|
listeners.add(listener);
|
|
452
457
|
return () => {
|
|
@@ -1115,6 +1120,7 @@ async function standaloneUpload(file, fidelity) {
|
|
|
1115
1120
|
export {
|
|
1116
1121
|
__export,
|
|
1117
1122
|
DEFAULT_IMAGE_FIDELITY,
|
|
1123
|
+
recordingsVersion,
|
|
1118
1124
|
subscribeRecordings,
|
|
1119
1125
|
recordingStateOf,
|
|
1120
1126
|
anyRecordingLive,
|
package/dist/index.d.ts
CHANGED
|
@@ -11,8 +11,8 @@ export type { AttachedFile, AttachmentPiles, AttachmentPilesOptions, Attachments
|
|
|
11
11
|
export { useComments, useCommentCounts } from "./comments.js";
|
|
12
12
|
export type { AppComment, AppCommentAuthor, AppCommentFile, CommentsState, UseCommentsArgs, CommentCountsState, UseCommentCountsArgs, } from "./comments.js";
|
|
13
13
|
export { useAppContext, useViewer, useWorkspaceCurrency, useWorkspaceTimezone } from "./viewer.js";
|
|
14
|
-
export { useRecording } from "./recording.js";
|
|
15
|
-
export type { UseRecording } from "./recording.js";
|
|
14
|
+
export { useRecording, useRecordings } from "./recording.js";
|
|
15
|
+
export type { UseRecording, UseRecordings } from "./recording.js";
|
|
16
16
|
export type { RecordingState, RecordingInputs } from "./recording_state.js";
|
|
17
17
|
export { requestGeofencedLocation, isWithinZone } from "./geolocation.js";
|
|
18
18
|
export type { GeofenceZone, GeoCoords, GeofenceOutcome, GeofenceOptions } from "./geolocation.js";
|
package/dist/index.js
CHANGED
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
postHostNotification,
|
|
13
13
|
readRecordingSnapshot,
|
|
14
14
|
recordingStateOf,
|
|
15
|
+
recordingsVersion,
|
|
15
16
|
replaceRecordings,
|
|
16
17
|
rpc,
|
|
17
18
|
rpcAgentRun,
|
|
@@ -21,7 +22,7 @@ import {
|
|
|
21
22
|
subscribeRecordings,
|
|
22
23
|
subscribeUrlParams,
|
|
23
24
|
urlParam
|
|
24
|
-
} from "./chunk-
|
|
25
|
+
} from "./chunk-CRJQYSDI.js";
|
|
25
26
|
|
|
26
27
|
// src/mount.tsx
|
|
27
28
|
import { createRoot } from "react-dom/client";
|
|
@@ -24213,7 +24214,7 @@ var appWorkflowContractSchema = zod_default.object({
|
|
|
24213
24214
|
inputs: zod_default.record(zod_default.string(), appWorkflowInputSchema).optional(),
|
|
24214
24215
|
outputs: zod_default.record(zod_default.string(), appWorkflowOutputSchema).optional()
|
|
24215
24216
|
});
|
|
24216
|
-
var APP_EXPORT_REPORT_KEYS = ["title", "lines", "readings"];
|
|
24217
|
+
var APP_EXPORT_REPORT_KEYS = ["title", "lines", "readings", "at", "dates"];
|
|
24217
24218
|
var appExportTemplateSchema = zod_default.object({
|
|
24218
24219
|
template_id: zod_default.string().min(1).describe("The document template (`dtl_\u2026`) filled: an Excel workbook, or an HTML page made a PDF."),
|
|
24219
24220
|
rows: zod_default.string().regex(APP_ALIAS_REGEX).refine((key) => !APP_EXPORT_REPORT_KEYS.some((taken) => taken === key), {
|
|
@@ -24234,6 +24235,11 @@ var appQueryDeclarationSchema = zod_default.object({
|
|
|
24234
24235
|
"Document templates an export of this query's rows may fill, by the name the export asks for. An export names one of these or none; a template it does not declare is refused."
|
|
24235
24236
|
)
|
|
24236
24237
|
});
|
|
24238
|
+
var appAgentWritesSchema = zod_default.object({
|
|
24239
|
+
table_id: zod_default.string().min(1).describe("The table of the row written"),
|
|
24240
|
+
row: zod_default.string().min(1).describe("The input naming that row: a single `record_link` input to `table_id`"),
|
|
24241
|
+
fields: zod_default.record(zod_default.string().min(1), zod_default.string().min(1)).refine((fields) => Object.keys(fields).length > 0, { message: "writes.fields maps at least one output" }).describe("Output name \u2192 the field key of `table_id` it is written to")
|
|
24242
|
+
}).strict();
|
|
24237
24243
|
var appAgentDeclarationSchema = zod_default.object({
|
|
24238
24244
|
instructions: zod_default.string().min(1).describe("System instructions for the agent \u2014 the task it performs per run."),
|
|
24239
24245
|
tool_names: zod_default.array(zod_default.string().min(1)).describe(
|
|
@@ -24265,6 +24271,9 @@ var appAgentDeclarationSchema = zod_default.object({
|
|
|
24265
24271
|
).refine(
|
|
24266
24272
|
(outputs) => outputs === void 0 || Object.values(outputs).every((o) => appWorkflowOutputDepth(o) <= MAX_APP_WORKFLOW_OUTPUT_DEPTH),
|
|
24267
24273
|
{ message: `output schema nesting exceeds the max depth of ${MAX_APP_WORKFLOW_OUTPUT_DEPTH}` }
|
|
24274
|
+
),
|
|
24275
|
+
writes: appAgentWritesSchema.optional().describe(
|
|
24276
|
+
"Where the run's outputs land: each named output written to a field of the one row an input names, under the app's authority, once the result validates \u2014 locks and hooks apply as to any update. Omit for an agent whose output is only returned."
|
|
24268
24277
|
)
|
|
24269
24278
|
});
|
|
24270
24279
|
var appThemeSchema = zod_default.object({
|
|
@@ -24396,6 +24405,12 @@ var agentStepSchema = stepBaseSchema.extend({
|
|
|
24396
24405
|
model_tier: zod_default.enum(MODEL_TIERS).optional(),
|
|
24397
24406
|
output: agentOutputSpecSchema
|
|
24398
24407
|
});
|
|
24408
|
+
var appAgentStepSchema = stepBaseSchema.extend({
|
|
24409
|
+
type: zod_default.literal("app_agent"),
|
|
24410
|
+
alias: zod_default.string().min(1),
|
|
24411
|
+
input: zod_default.record(zod_default.string(), toolInputValueSchema),
|
|
24412
|
+
wait: zod_default.literal(false).optional()
|
|
24413
|
+
});
|
|
24399
24414
|
var breakStepSchema = stepBaseSchema.extend({
|
|
24400
24415
|
type: zod_default.literal("break")
|
|
24401
24416
|
});
|
|
@@ -24422,6 +24437,7 @@ var workflowStepSchema = zod_default.lazy(
|
|
|
24422
24437
|
returnStepSchema,
|
|
24423
24438
|
validateStepSchema,
|
|
24424
24439
|
agentStepSchema,
|
|
24440
|
+
appAgentStepSchema,
|
|
24425
24441
|
breakStepSchema,
|
|
24426
24442
|
continueStepSchema,
|
|
24427
24443
|
letDeclareStepSchema,
|
|
@@ -25044,6 +25060,7 @@ function stepChildStepArrays(step) {
|
|
|
25044
25060
|
case "bind":
|
|
25045
25061
|
case "validate":
|
|
25046
25062
|
case "agent":
|
|
25063
|
+
case "app_agent":
|
|
25047
25064
|
case "break":
|
|
25048
25065
|
case "continue":
|
|
25049
25066
|
case "let_declare":
|
|
@@ -25076,6 +25093,7 @@ function changesRows(toolName) {
|
|
|
25076
25093
|
}
|
|
25077
25094
|
function rowWriteOf(step) {
|
|
25078
25095
|
if (step.type === "agent") return step.tool_names.some(changesRows) ? { table_id: null } : void 0;
|
|
25096
|
+
if (step.type === "app_agent") return { table_id: null };
|
|
25079
25097
|
if (step.type !== "tool_call") return void 0;
|
|
25080
25098
|
const rows = toolRows(step.tool_name);
|
|
25081
25099
|
if (rows === null) return void 0;
|
|
@@ -27204,7 +27222,8 @@ async function run(walk, step) {
|
|
|
27204
27222
|
if (write2 !== void 0) note(walk, { kind: "unknown", table_id: write2.table_id });
|
|
27205
27223
|
return unknownAt(walk.ctx.step_outputs, step.id);
|
|
27206
27224
|
}
|
|
27207
|
-
case "agent":
|
|
27225
|
+
case "agent":
|
|
27226
|
+
case "app_agent": {
|
|
27208
27227
|
const write2 = rowWriteOf(step);
|
|
27209
27228
|
if (write2 !== void 0) note(walk, { kind: "unknown", table_id: write2.table_id });
|
|
27210
27229
|
return unknownAt(walk.ctx.step_outputs, step.id);
|
|
@@ -31086,33 +31105,54 @@ function useCommentCounts(args) {
|
|
|
31086
31105
|
}
|
|
31087
31106
|
|
|
31088
31107
|
// src/recording.ts
|
|
31089
|
-
import { useCallback as useCallback7, useSyncExternalStore as useSyncExternalStore3 } from "react";
|
|
31108
|
+
import { useCallback as useCallback7, useMemo as useMemo4, useSyncExternalStore as useSyncExternalStore3 } from "react";
|
|
31090
31109
|
var NO_MOCKS = {};
|
|
31110
|
+
async function startRecording(alias, inputs) {
|
|
31111
|
+
if (getMockRecordings()?.[alias]) return { started: true };
|
|
31112
|
+
return rpc("recording.start", { alias, inputs });
|
|
31113
|
+
}
|
|
31114
|
+
async function stopRecording(alias) {
|
|
31115
|
+
if (getMockRecordings()?.[alias]) return;
|
|
31116
|
+
await rpc("recording.stop", { alias });
|
|
31117
|
+
}
|
|
31118
|
+
function busyWith(mocks, hostBusy) {
|
|
31119
|
+
return hostBusy || Object.values(mocks).some((state) => state.phase === "live");
|
|
31120
|
+
}
|
|
31091
31121
|
function useRecording(alias) {
|
|
31092
31122
|
const { recordingEnabled } = useAppContext();
|
|
31093
31123
|
const mocks = getMockRecordings() ?? NO_MOCKS;
|
|
31094
31124
|
const mocked = mocks[alias];
|
|
31095
31125
|
const hostState = useSyncExternalStore3(subscribeRecordings, () => recordingStateOf(alias));
|
|
31096
31126
|
const hostBusy = useSyncExternalStore3(subscribeRecordings, anyRecordingLive);
|
|
31097
|
-
const start = useCallback7(
|
|
31098
|
-
|
|
31099
|
-
if (getMockRecordings()?.[alias]) return { started: true };
|
|
31100
|
-
return rpc("recording.start", { alias, inputs: inputs ?? {} });
|
|
31101
|
-
},
|
|
31102
|
-
[alias]
|
|
31103
|
-
);
|
|
31104
|
-
const stop = useCallback7(async () => {
|
|
31105
|
-
if (getMockRecordings()?.[alias]) return;
|
|
31106
|
-
await rpc("recording.stop", { alias });
|
|
31107
|
-
}, [alias]);
|
|
31127
|
+
const start = useCallback7((inputs) => startRecording(alias, inputs ?? {}), [alias]);
|
|
31128
|
+
const stop = useCallback7(() => stopRecording(alias), [alias]);
|
|
31108
31129
|
return {
|
|
31109
31130
|
available: mocked !== void 0 || recordingEnabled,
|
|
31110
31131
|
state: mocked ?? hostState,
|
|
31111
|
-
busy:
|
|
31132
|
+
busy: busyWith(mocks, hostBusy),
|
|
31112
31133
|
start,
|
|
31113
31134
|
stop
|
|
31114
31135
|
};
|
|
31115
31136
|
}
|
|
31137
|
+
function useRecordings(aliases) {
|
|
31138
|
+
const { recordingEnabled } = useAppContext();
|
|
31139
|
+
const mocks = getMockRecordings() ?? NO_MOCKS;
|
|
31140
|
+
const version3 = useSyncExternalStore3(subscribeRecordings, recordingsVersion);
|
|
31141
|
+
const hostBusy = useSyncExternalStore3(subscribeRecordings, anyRecordingLive);
|
|
31142
|
+
const key = aliases.join("\n");
|
|
31143
|
+
const states = useMemo4(
|
|
31144
|
+
() => Object.fromEntries(key.split("\n").filter((alias) => alias !== "").map((alias) => [alias, mocks[alias] ?? recordingStateOf(alias)])),
|
|
31145
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
31146
|
+
[key, version3, mocks]
|
|
31147
|
+
);
|
|
31148
|
+
return {
|
|
31149
|
+
available: recordingEnabled || aliases.some((alias) => mocks[alias] !== void 0),
|
|
31150
|
+
states,
|
|
31151
|
+
busy: busyWith(mocks, hostBusy),
|
|
31152
|
+
start: startRecording,
|
|
31153
|
+
stop: stopRecording
|
|
31154
|
+
};
|
|
31155
|
+
}
|
|
31116
31156
|
|
|
31117
31157
|
// src/geolocation.ts
|
|
31118
31158
|
var EARTH_RADIUS_M = 6371e3;
|
|
@@ -31320,7 +31360,7 @@ function useRecents(key, options = {}) {
|
|
|
31320
31360
|
}
|
|
31321
31361
|
|
|
31322
31362
|
// src/use_url_state.ts
|
|
31323
|
-
import { useCallback as useCallback9, useEffect as useEffect6, useMemo as
|
|
31363
|
+
import { useCallback as useCallback9, useEffect as useEffect6, useMemo as useMemo5, useRef as useRef5, useState as useState5 } from "react";
|
|
31324
31364
|
function useUrlState(defs) {
|
|
31325
31365
|
const defsRef = useRef5(defs);
|
|
31326
31366
|
defsRef.current = defs;
|
|
@@ -31340,7 +31380,7 @@ function useUrlState(defs) {
|
|
|
31340
31380
|
unsubscribe();
|
|
31341
31381
|
};
|
|
31342
31382
|
}, []);
|
|
31343
|
-
const values2 =
|
|
31383
|
+
const values2 = useMemo5(() => decodeAll(defsRef.current, params), [params]);
|
|
31344
31384
|
const setValues = useCallback9(
|
|
31345
31385
|
(patch) => {
|
|
31346
31386
|
editedRef.current = true;
|
|
@@ -31424,6 +31464,7 @@ export {
|
|
|
31424
31464
|
useQuery,
|
|
31425
31465
|
useRecents,
|
|
31426
31466
|
useRecording,
|
|
31467
|
+
useRecordings,
|
|
31427
31468
|
useUrlState,
|
|
31428
31469
|
useViewer,
|
|
31429
31470
|
useWorkflow,
|
package/dist/queries.d.ts
CHANGED
|
@@ -185,6 +185,13 @@ export interface ExportReport {
|
|
|
185
185
|
title: string;
|
|
186
186
|
lines: string[];
|
|
187
187
|
readings: ReportReading[];
|
|
188
|
+
/** Each date filter narrowing the rows, by field alias: its bounds, a day or a minute of one, with no zone. */
|
|
189
|
+
dates?: Record<string, {
|
|
190
|
+
from?: string;
|
|
191
|
+
to?: string;
|
|
192
|
+
}>;
|
|
193
|
+
/** The file's name, in the template grammar over the report; absent, the template's name, or `title`. */
|
|
194
|
+
filename?: string;
|
|
188
195
|
}
|
|
189
196
|
/** The file an export makes: the default report workbook — its words in `language`, the rows under `columns` on the
|
|
190
197
|
* tab `sheet` — or a template the query declares under `templates`, by its name there. */
|
package/dist/recording.d.ts
CHANGED
|
@@ -44,4 +44,22 @@ export interface UseRecording<I> {
|
|
|
44
44
|
*/
|
|
45
45
|
export declare function useRecording<K extends keyof AppWorkflows & string>(alias: K): UseRecording<ActInputsOf<K>>;
|
|
46
46
|
export declare function useRecording(alias: string): UseRecording<Record<string, unknown>>;
|
|
47
|
+
export interface UseRecordings {
|
|
48
|
+
/** As {@link UseRecording}'s: whether the host records here at all. */
|
|
49
|
+
available: boolean;
|
|
50
|
+
/** The latest recording through each alias asked, idle where none ran. */
|
|
51
|
+
states: Readonly<Record<string, RecordingState>>;
|
|
52
|
+
/** Any of this app's recordings is live, through any alias. */
|
|
53
|
+
busy: boolean;
|
|
54
|
+
start: (alias: string, inputs: Record<string, unknown>) => Promise<{
|
|
55
|
+
started: boolean;
|
|
56
|
+
}>;
|
|
57
|
+
stop: (alias: string) => Promise<void>;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* {@link useRecording} over several aliases at once — for a screen drawing an
|
|
61
|
+
* unknown number of recording acts, which cannot call a hook per alias. One
|
|
62
|
+
* snapshot per change: the host's every-second push while live redraws once.
|
|
63
|
+
*/
|
|
64
|
+
export declare function useRecordings(aliases: readonly string[]): UseRecordings;
|
|
47
65
|
export {};
|
|
@@ -33,6 +33,7 @@ export type RecordingState = {
|
|
|
33
33
|
inputs: RecordingInputs;
|
|
34
34
|
};
|
|
35
35
|
export declare const IDLE_RECORDING: RecordingState;
|
|
36
|
+
export declare function recordingsVersion(): number;
|
|
36
37
|
export declare function subscribeRecordings(listener: () => void): () => void;
|
|
37
38
|
export declare function recordingStateOf(alias: string): RecordingState;
|
|
38
39
|
export declare function anyRecordingLive(): boolean;
|
package/dist/router.js
CHANGED
package/docs/ai.md
CHANGED
|
@@ -29,6 +29,7 @@ A declaration carries:
|
|
|
29
29
|
| `prefix_cache_ttl` | Optional prompt-cache window for the agent's stable prefix (tools + system). **Omit it** — the default `"5m"` is right for essentially every agent. `"1h"` writes at 2x the input rate (against 1.25x) to buy only the five-minute-to-one-hour band; set it only with measured cadence showing runs land there, such as a scheduled sweep |
|
|
30
30
|
| `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. **Every field defaults to `required: true`**, exactly as `outputs` does — the two extend the same base — so an input the caller may legitimately omit needs `"required": false`, or the run is rejected before the agent sees it |
|
|
31
31
|
| `outputs` | Optional typed output schema — the declaration vocabulary, including the two ways a `select` names its option set, is [mutations](./mutations.md#structured-results-return-data). Declared → **structured agent** (the run must emit a matching result); omitted → **free-text agent** (the answer is the final prose). **Every field defaults to `required: true`** (the same base schema as `inputs` above) — mark `"required": false` on anything the source may legitimately not carry. It matters most for `number`, which has no blank: text can answer `""`, but a required number leaves only a wrong value or a rejected submission |
|
|
32
|
+
| `writes` | Optional — where the result lands: `{ table_id, row, fields }`. `row` is a single `record_link` input to `table_id`; `fields` maps each output to a field key of that table. Once a run's result validates, each output it carries is written to that row under the app's authority, with the table's locks and hooks applied; an output the result omits is left alone. A write that fails fails the run. Requires `outputs` |
|
|
32
33
|
|
|
33
34
|
**Structured vs free-text is the load-bearing split.** A structured agent's result arrives in `run.output` (the server strictly validates the submitted result against the declared schema — see the `output` typing section for the exact guarantee); a free-text agent's answer is the transcript's prose (`run.text`) and its `output` stays `undefined` — never a stray string, so a consumer reading `output.<field>` can't crash on a free-text answer.
|
|
34
35
|
|
package/docs/data_fetching.md
CHANGED
|
@@ -24,7 +24,7 @@ and by `lotics app create --custom` and `lotics app deploy`. Every hook reads th
|
|
|
24
24
|
| `useQuery(alias, params?, { rows: false, total: true })` | `total` only, no rows (`total: { by: column }` adds `counts`) | a facet chip, a queue badge, an "N awaiting approval" tile — the size of a set you are not listing |
|
|
25
25
|
| `useQueries(calls, opts?)` | one state per `{ alias, params?, filter?, sort?, aggregate?, total?, rows? }`, in order — a `sort` orders that call's rows before the cap cuts them | reads that are DATA — a list known only at render, one read per item, or one count per stage (`rows: false, total: true`, each state's `.total`); the deploy's alias scan reads a list as dynamic, so every alias in it is still declared. With `aggregate` a call answers the groups of its filtered rows instead of the rows ([queries](./queries.md)) |
|
|
26
26
|
| `queryAll(alias, params?, { filter, sort })` | a promise of every row | outside React, for a job that must hold the whole narrowed set in the browser (the ids an act runs on): it asks page after page until the server says the set ended, never stopping at the 10,000-row cap. The server's rows as stored — no write of this app is drawn over them |
|
|
27
|
-
| `exportQuery(alias, { params, filter, sort, report, file })` | a promise of the file | every row the narrowed set holds, made into one file by the server — no row reaches the browser. `report` is `{ title, lines, readings }
|
|
27
|
+
| `exportQuery(alias, { params, filter, sort, report, file })` | a promise of the file | every row the narrowed set holds, made into one file by the server — no row reaches the browser. `report` is `{ title, lines, readings, dates?, filename? }` — `dates` each date filter's `{ from?, to? }` by field alias, each bound a day (`yyyy-MM-dd`) or a minute of one (`yyyy-MM-ddTHH:mm`) with no zone; `filename` the file's name in the template grammar over one value each — `title`, `at` (the moment the server made the file), `dates.<field>.from` or `.to`, `lines | lookup:<n>` — at most 200 bytes once filled (absent, the template's name, or `title`); `file` is `{ kind: "workbook", language, sheet, columns }` (the default report workbook, its head closing with the moment the server made it, each column `{ key, header, type }` an output column of the query — a `number` one on a number column, a `date` one on a date or datetime) or `{ kind: "template", template }`, one the query declares under `templates` by name ([queries](./queries.md)). Past 20,000 rows it rejects, never cuts; each caller makes at most 60 exports a minute. The file's `url` is signed for the caller: open it with `openExternal` |
|
|
28
28
|
|
|
29
29
|
Every hook answers the same `QueryState`: `rows`, `truncated`, `total`, `counts`, `loading`,
|
|
30
30
|
`isValidating`, `error`, `pending`, `refetch`, `page`, `setPage`, `pageCount`, `hasMore`,
|
package/docs/mutations.md
CHANGED
|
@@ -674,6 +674,11 @@ replaces the first's state):
|
|
|
674
674
|
| `filed` | `execution_id`, `inputs` | the workflow ran and succeeded |
|
|
675
675
|
| `failed` | `message`, `inputs` | transcription failed, or filing was refused or its run failed |
|
|
676
676
|
|
|
677
|
+
A screen drawing a number of recording acts it only learns at run time reads them together with
|
|
678
|
+
**`useRecordings(aliases)`**: `available` and `busy` as above, `states` (each alias's latest
|
|
679
|
+
recording, idle where none ran), and `start(alias, inputs)` / `stop(alias)` — the same calls, by
|
|
680
|
+
alias.
|
|
681
|
+
|
|
677
682
|
`inputs` is what `start` was passed, so a screen with one act per row can tell which row the
|
|
678
683
|
recording is about. **`busy`** is true while any recording this app started is live, through
|
|
679
684
|
any alias: stand the other acts down, since the host would refuse them. A recording in another
|
package/docs/workflows.md
CHANGED
|
@@ -150,6 +150,7 @@ The body is a sequence of statements, each of which maps 1:1 onto a stored step.
|
|
|
150
150
|
| Pause for an event | `await wait_for_event({ event_type: "payment" \| "webhook", event_ref, timeout_in_minutes? });` |
|
|
151
151
|
| Pause for a decision | `const a = await wait_for_approval({ approvers, prompt, timeout_in_minutes? });` |
|
|
152
152
|
| LLM reasoning | `const x = await agent({ instructions, input, tools, model?, output });` |
|
|
153
|
+
| Run a declared agent | `const x = await app_agent({ alias, input, wait? });` |
|
|
153
154
|
| Guard | `validate({ checks: [{ fail_when, field_key?, message }, …] });` |
|
|
154
155
|
| End the run | `return({ status, message, field_errors?, data? });` |
|
|
155
156
|
|
|
@@ -313,6 +314,22 @@ name" when broken:
|
|
|
313
314
|
`model` is an optional pin — omit it to follow the platform default. The step is atomic (no wait
|
|
314
315
|
or approval inside it).
|
|
315
316
|
|
|
317
|
+
### `app_agent` — run one of this app's agents as one step
|
|
318
|
+
|
|
319
|
+
```js
|
|
320
|
+
const scored = await app_agent({ alias: "scorer", input: { deal: trigger.app_workflow.inputs.deal } });
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
`alias` names an agent this app declares; `input` is an object literal checked against its
|
|
324
|
+
`inputs`. The step resolves to the agent's declared `outputs` — typed, so `scored.summary` is
|
|
325
|
+
checked at save — or to its final text when it declares none, and applies the agent's `writes`.
|
|
326
|
+
The run shows in the agent's history like any other. It exists only in an app's own workflows,
|
|
327
|
+
and a run an agent started (through `run_app_workflow`) refuses it: an agent never starts an agent.
|
|
328
|
+
|
|
329
|
+
`wait: false` starts the run and resolves at once to `{ run_id }`. The workflow goes on, and what
|
|
330
|
+
it wrote stands; the agent's result — its `writes` included — lands when the run settles, and a
|
|
331
|
+
run that fails fails in the agent's history, not the workflow.
|
|
332
|
+
|
|
316
333
|
### `try` / `catch`
|
|
317
334
|
|
|
318
335
|
```js
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lotics/app-sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.104.0",
|
|
4
4
|
"description": "The SDK a Lotics custom-code app reads and writes through \u2014 typed hooks over the host bridge, cell readers, mount() and AppRouter",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|