@superpanel-sdk/runtime 0.2.3 → 0.7.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/README.md +30 -10
- package/dist/builder/codegen/emit.d.ts +25 -12
- package/dist/builder/codegen/emit.js +339 -78
- package/dist/builder/codegen/generate.d.ts +21 -2
- package/dist/builder/codegen/generate.js +147 -34
- package/dist/builder/company-import-graph.d.ts +2 -0
- package/dist/builder/company-import-graph.js +40 -8
- package/dist/builder/company-imports.d.ts +29 -0
- package/dist/builder/company-imports.js +122 -0
- package/dist/builder/compile/compile-sequence.d.ts +61 -8
- package/dist/builder/compile/compile-sequence.js +334 -80
- package/dist/builder/compile/function-code.d.ts +19 -3
- package/dist/builder/compile/function-code.js +123 -45
- package/dist/builder/compile/registry.d.ts +7 -3
- package/dist/builder/compile/registry.js +30 -27
- package/dist/builder/errors.d.ts +9 -0
- package/dist/builder/errors.js +17 -0
- package/dist/builder/function-params.d.ts +10 -0
- package/dist/builder/function-params.js +203 -0
- package/dist/builder/load.d.ts +70 -10
- package/dist/builder/load.js +258 -67
- package/dist/builder/paths.d.ts +9 -1
- package/dist/builder/paths.js +32 -2
- package/dist/builder/schedules.d.ts +25 -0
- package/dist/builder/schedules.js +101 -0
- package/dist/builder/slot-schema-validation.d.ts +4 -0
- package/dist/builder/slot-schema-validation.js +10 -0
- package/dist/builder/typecheck.d.ts +8 -0
- package/dist/builder/typecheck.js +102 -0
- package/dist/builder/types.d.ts +127 -19
- package/dist/builder/types.js +21 -1
- package/dist/cli/auth/auth.d.ts +37 -0
- package/dist/cli/auth/auth.js +211 -0
- package/dist/cli/auth/backend-url.d.ts +5 -0
- package/dist/cli/auth/backend-url.js +25 -0
- package/dist/cli/auth/clipboard.d.ts +9 -0
- package/dist/cli/auth/clipboard.js +38 -0
- package/dist/cli/auth/http.d.ts +20 -0
- package/dist/cli/auth/http.js +27 -0
- package/dist/cli/auth/index.d.ts +10 -0
- package/dist/cli/auth/index.js +27 -0
- package/dist/cli/auth/progress.d.ts +12 -0
- package/dist/cli/auth/progress.js +15 -0
- package/dist/cli/auth/serve-login.d.ts +8 -0
- package/dist/cli/auth/serve-login.js +24 -0
- package/dist/cli/calls/index.d.ts +177 -0
- package/dist/cli/calls/index.js +96 -0
- package/dist/cli/compile/index.js +3 -0
- package/dist/cli/config.d.ts +10 -0
- package/dist/cli/config.js +52 -0
- package/dist/cli/debug/index.d.ts +31 -0
- package/dist/cli/debug/index.js +119 -0
- package/dist/cli/exec/index.d.ts +39 -0
- package/dist/cli/exec/index.js +138 -0
- package/dist/cli/exec/session.d.ts +48 -0
- package/dist/cli/exec/session.js +82 -0
- package/dist/cli/exec/trace.d.ts +18 -0
- package/dist/cli/exec/trace.js +52 -0
- package/dist/cli/index.js +112 -9
- package/dist/cli/logs/index.d.ts +47 -0
- package/dist/cli/logs/index.js +113 -0
- package/dist/cli/mobile/index.d.ts +12 -0
- package/dist/cli/mobile/index.js +405 -0
- package/dist/cli/serve/index.d.ts +109 -3
- package/dist/cli/serve/index.js +506 -118
- package/dist/cli/turns/index.d.ts +105 -0
- package/dist/cli/turns/index.js +128 -0
- package/dist/cli/turns/run.d.ts +13 -0
- package/dist/cli/turns/run.js +57 -0
- package/dist/cli/turns/shadow.d.ts +112 -0
- package/dist/cli/turns/shadow.js +54 -0
- package/dist/cli/type-gen/index.js +9 -9
- package/dist/compiler.d.ts +7 -0
- package/dist/compiler.js +7 -0
- package/dist/compute-host/compute-host.d.ts +14 -23
- package/dist/compute-host/compute-host.js +18 -30
- package/dist/compute-host/index.d.ts +2 -4
- package/dist/compute-host/index.js +1 -2
- package/dist/compute-host/source-compute-host.d.ts +10 -10
- package/dist/compute-host/source-compute-host.js +33 -40
- package/dist/core/case-orchestrator/contact-hours.d.ts +70 -0
- package/dist/core/case-orchestrator/contact-hours.js +127 -0
- package/dist/core/case-orchestrator/index.d.ts +3 -0
- package/dist/core/case-orchestrator/index.js +3 -0
- package/dist/core/case-orchestrator/render.d.ts +75 -0
- package/dist/core/case-orchestrator/render.js +244 -0
- package/dist/core/case-orchestrator/time.d.ts +11 -0
- package/dist/core/case-orchestrator/time.js +29 -0
- package/dist/core/case-orchestrator/types.d.ts +708 -0
- package/dist/core/case-orchestrator/types.js +42 -0
- package/dist/core/context/call.d.ts +291 -0
- package/dist/core/context/call.js +94 -0
- package/dist/core/context/channels.d.ts +60 -0
- package/dist/core/context/channels.js +35 -0
- package/dist/core/context/contact.d.ts +96 -6
- package/dist/core/context/contact.js +48 -2
- package/dist/core/context/email-message.d.ts +198 -0
- package/dist/core/context/email-message.js +86 -0
- package/dist/core/context/email.d.ts +109 -0
- package/dist/core/context/email.js +90 -0
- package/dist/core/context/exchanges.d.ts +167 -0
- package/dist/core/context/exchanges.js +81 -0
- package/dist/core/context/instances.d.ts +307 -0
- package/dist/core/context/instances.js +137 -0
- package/dist/core/context/kv.d.ts +113 -0
- package/dist/core/context/kv.js +60 -0
- package/dist/core/context/platform-registry.d.ts +544 -4
- package/dist/core/context/platform-registry.js +14 -2
- package/dist/core/context/platform.d.ts +2 -1
- package/dist/core/context/sms.d.ts +177 -0
- package/dist/core/context/sms.js +77 -0
- package/dist/core/context/types.d.ts +79 -48
- package/dist/core/context/types.js +1 -7
- package/dist/core/conversation/index.d.ts +85 -0
- package/dist/core/conversation/index.js +133 -0
- package/dist/core/functions/errors.d.ts +74 -0
- package/dist/core/functions/errors.js +26 -0
- package/dist/core/functions/events.d.ts +192 -0
- package/dist/core/functions/events.js +23 -0
- package/dist/core/functions/schedules.d.ts +50 -0
- package/dist/core/functions/schedules.js +20 -0
- package/dist/core/functions/types.d.ts +29 -73
- package/dist/core/functions/types.js +1 -4
- package/dist/core/office-hours/index.d.ts +39 -0
- package/dist/core/office-hours/index.js +187 -0
- package/dist/core/page/define.d.ts +904 -0
- package/dist/core/page/define.js +518 -0
- package/dist/core/page/envelope.d.ts +55 -0
- package/dist/core/page/envelope.js +4 -0
- package/dist/core/page/fns.d.ts +448 -0
- package/dist/core/page/fns.js +539 -0
- package/dist/core/page/memory-reader.d.ts +27 -0
- package/dist/core/page/memory-reader.js +299 -0
- package/dist/core/page/ops.d.ts +58 -0
- package/dist/core/page/ops.js +220 -0
- package/dist/core/page/page-host.d.ts +20 -0
- package/dist/core/page/page-host.js +57 -0
- package/dist/core/page/records-query.d.ts +234 -0
- package/dist/core/page/records-query.js +780 -0
- package/dist/core/page/render.d.ts +295 -0
- package/dist/core/page/render.js +1060 -0
- package/dist/core/page/sandbox.d.ts +85 -0
- package/dist/core/page/sandbox.js +357 -0
- package/dist/core/page/time-format.d.ts +46 -0
- package/dist/core/page/time-format.js +125 -0
- package/dist/core/page/timeline.d.ts +40 -0
- package/dist/core/page/timeline.js +2 -0
- package/dist/core/page/typed.d.ts +648 -0
- package/dist/core/page/typed.js +2 -0
- package/dist/core/page/workspace.d.ts +57 -0
- package/dist/core/page/workspace.js +266 -0
- package/dist/core/party/index.d.ts +3 -0
- package/dist/core/party/index.js +3 -0
- package/dist/core/party/schema.d.ts +208 -0
- package/dist/core/party/schema.js +113 -0
- package/dist/core/party/types.d.ts +35 -0
- package/dist/core/party/types.js +15 -0
- package/dist/core/sequence/define.d.ts +56 -0
- package/dist/core/sequence/define.js +10 -0
- package/dist/core/sequence/index.d.ts +63 -0
- package/dist/core/sequence/index.js +231 -0
- package/dist/core/slot/binding.d.ts +40 -0
- package/dist/core/slot/binding.js +2 -0
- package/dist/core/slot/company-metadata.d.ts +102 -0
- package/dist/core/slot/company-metadata.js +536 -0
- package/dist/core/slot/compile-schema.d.ts +19 -13
- package/dist/core/slot/compile-schema.js +51 -248
- package/dist/core/slot/contact-timezone.d.ts +110 -0
- package/dist/core/slot/contact-timezone.js +186 -0
- package/dist/core/slot/field.d.ts +2 -25
- package/dist/core/slot/field.js +0 -71
- package/dist/core/slot/id-ref.d.ts +17 -2
- package/dist/core/slot/id-ref.js +16 -8
- package/dist/core/slot/index.d.ts +12 -13
- package/dist/core/slot/index.js +11 -12
- package/dist/core/slot/naming.d.ts +15 -0
- package/dist/core/slot/naming.js +96 -0
- package/dist/core/slot/platform-fields.d.ts +119 -0
- package/dist/core/slot/platform-fields.js +121 -0
- package/dist/core/slot/ref.d.ts +16 -0
- package/dist/core/slot/ref.js +14 -0
- package/dist/core/slot/row.d.ts +2 -9
- package/dist/core/slot/row.js +13 -23
- package/dist/core/slot/schema-compat.d.ts +17 -0
- package/dist/core/slot/schema-compat.js +103 -0
- package/dist/core/slot/schema.d.ts +353 -76
- package/dist/core/slot/schema.js +163 -10
- package/dist/core/slot/types.d.ts +30 -103
- package/dist/core/slot/value.d.ts +28 -3
- package/dist/core/slot/value.js +93 -57
- package/dist/dev-overlay/protocol.d.ts +74 -8
- package/dist/fn-context/build-context.d.ts +3 -1
- package/dist/fn-context/build-context.js +26 -10
- package/dist/fn-context/fake/empty-api.js +9 -28
- package/dist/fn-context/fake/index.d.ts +13 -4
- package/dist/fn-context/fake/index.js +29 -2
- package/dist/fn-context/fn-params.d.ts +3 -0
- package/dist/fn-context/fn-params.js +10 -0
- package/dist/fn-context/index.d.ts +5 -5
- package/dist/fn-context/index.js +4 -4
- package/dist/fn-context/log.js +2 -2
- package/dist/fn-context/make-ctx.d.ts +11 -0
- package/dist/fn-context/make-ctx.js +20 -9
- package/dist/fn-context/rpc/case-orchestrator-tools.d.ts +32 -0
- package/dist/fn-context/rpc/case-orchestrator-tools.js +12 -0
- package/dist/fn-context/rpc/errors.d.ts +13 -0
- package/dist/fn-context/rpc/errors.js +22 -0
- package/dist/fn-context/rpc/index.d.ts +4 -2
- package/dist/fn-context/rpc/index.js +4 -2
- package/dist/fn-context/rpc/integration/index.d.ts +15 -5
- package/dist/fn-context/rpc/integration/index.js +1 -1
- package/dist/fn-context/rpc/integration/list-surfaces.d.ts +21 -0
- package/dist/fn-context/rpc/integration/{list-bindings.js → list-surfaces.js} +4 -4
- package/dist/fn-context/rpc/log.d.ts +11 -1
- package/dist/fn-context/rpc/platform.d.ts +13 -3
- package/dist/fn-context/rpc/platform.js +1 -1
- package/dist/fn-context/rpc/records.d.ts +85 -0
- package/dist/fn-context/rpc/records.js +24 -0
- package/dist/fn-context/rpc/trpc-client.d.ts +44 -2
- package/dist/fn-context/rpc/trpc-client.js +222 -12
- package/dist/fn-context/rpc/trpc-init.d.ts +21 -3
- package/dist/fn-context/rpc/trpc-init.js +10 -4
- package/dist/fn-context/rpc/trpc-router-factory.d.ts +111 -7
- package/dist/fn-context/rpc/trpc-router-factory.js +2 -0
- package/dist/fn-context/run-scope.d.ts +4 -0
- package/dist/fn-context/run-scope.js +9 -0
- package/dist/fn-context/types.d.ts +52 -5
- package/dist/index.d.ts +36 -46
- package/dist/index.js +30 -33
- package/dist/logger.d.ts +11 -0
- package/dist/logger.js +49 -0
- package/dist/platform.d.ts +75 -0
- package/dist/platform.js +57 -0
- package/dist/testing-compile-child.d.ts +2 -0
- package/dist/testing-compile-child.js +20 -0
- package/dist/testing.d.ts +368 -0
- package/dist/testing.js +439 -0
- package/dist/voice-service/actions.d.ts +50 -6
- package/dist/voice-service/index.d.ts +2 -0
- package/dist/voice-service/index.js +2 -0
- package/dist/voice-service/protocol.d.ts +604 -0
- package/dist/voice-service/protocol.js +67 -0
- package/dist/voice-service/types.d.ts +55 -8
- package/dist/worker.d.ts +11 -0
- package/dist/worker.js +11 -0
- package/package.json +38 -15
- package/dist/builder/compile/compile-agenda.d.ts +0 -15
- package/dist/builder/compile/compile-agenda.js +0 -64
- package/dist/builder/merge.d.ts +0 -4
- package/dist/builder/merge.js +0 -19
- package/dist/compute-host/read-only-cache.d.ts +0 -20
- package/dist/compute-host/read-only-cache.js +0 -61
- package/dist/core/context/db.d.ts +0 -69
- package/dist/core/context/db.js +0 -44
- package/dist/core/context/docusign.d.ts +0 -134
- package/dist/core/context/docusign.js +0 -108
- package/dist/core/context/filevine.d.ts +0 -323
- package/dist/core/context/filevine.js +0 -254
- package/dist/core/context/http.d.ts +0 -9
- package/dist/core/context/http.js +0 -4
- package/dist/core/context/hubspot.d.ts +0 -250
- package/dist/core/context/hubspot.js +0 -191
- package/dist/core/context/twilio.d.ts +0 -50
- package/dist/core/context/twilio.js +0 -42
- package/dist/core/functions/define/case-context.d.ts +0 -14
- package/dist/core/functions/define/case-context.js +0 -15
- package/dist/core/functions/define/compute.d.ts +0 -7
- package/dist/core/functions/define/compute.js +0 -17
- package/dist/core/functions/define/custom.d.ts +0 -4
- package/dist/core/functions/define/custom.js +0 -8
- package/dist/core/functions/define/system.d.ts +0 -7
- package/dist/core/functions/define/system.js +0 -12
- package/dist/core/functions/func-ref.d.ts +0 -16
- package/dist/core/functions/func-ref.js +0 -20
- package/dist/core/slot/deps.d.ts +0 -15
- package/dist/core/slot/deps.js +0 -88
- package/dist/core/slot/derived.d.ts +0 -32
- package/dist/core/slot/derived.js +0 -115
- package/dist/core/slot/platform-session-fields.d.ts +0 -38
- package/dist/core/slot/platform-session-fields.js +0 -48
- package/dist/core/slot/sf-schema.d.ts +0 -23
- package/dist/core/slot/sf-schema.js +0 -125
- package/dist/core/slot/sf-validate.d.ts +0 -14
- package/dist/core/slot/sf-validate.js +0 -132
- package/dist/core/slot/snapshot.d.ts +0 -18
- package/dist/core/slot/snapshot.js +0 -138
- package/dist/core/slot/write.d.ts +0 -48
- package/dist/core/slot/write.js +0 -426
- package/dist/core/stage/builders.d.ts +0 -54
- package/dist/core/stage/builders.js +0 -31
- package/dist/core/stage/condition.d.ts +0 -17
- package/dist/core/stage/condition.js +0 -198
- package/dist/core/stage/drive.d.ts +0 -43
- package/dist/core/stage/drive.js +0 -78
- package/dist/core/stage/finish-when.d.ts +0 -20
- package/dist/core/stage/finish-when.js +0 -82
- package/dist/core/stage/flatten.d.ts +0 -27
- package/dist/core/stage/flatten.js +0 -466
- package/dist/core/stage/index.d.ts +0 -15
- package/dist/core/stage/index.js +0 -13
- package/dist/core/stage/interrupts.d.ts +0 -28
- package/dist/core/stage/interrupts.js +0 -120
- package/dist/core/stage/ivr-fields.d.ts +0 -9
- package/dist/core/stage/ivr-fields.js +0 -75
- package/dist/core/stage/simulate.d.ts +0 -24
- package/dist/core/stage/simulate.js +0 -33
- package/dist/core/stage/transfer-fields.d.ts +0 -19
- package/dist/core/stage/transfer-fields.js +0 -129
- package/dist/core/stage/transfer-spec.d.ts +0 -48
- package/dist/core/stage/transfer-spec.js +0 -113
- package/dist/core/stage/types.d.ts +0 -208
- package/dist/core/stage/types.js +0 -2
- package/dist/core/stage/validate.d.ts +0 -11
- package/dist/core/stage/validate.js +0 -330
- package/dist/core/stage/walk.d.ts +0 -39
- package/dist/core/stage/walk.js +0 -119
- package/dist/fn-context/rpc/integration/list-bindings.d.ts +0 -21
- package/dist/fn-context/system-args.d.ts +0 -6
- package/dist/fn-context/system-args.js +0 -14
|
@@ -0,0 +1,518 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The page module (15 §10.2–§10.6): what a sequence's `context.page.ts` is written with. It runs
|
|
3
|
+
* inside the QuickJS sandbox, bundled into the page script, so it imports nothing at runtime and only
|
|
4
|
+
* uses `globalThis.__host` (the sandbox's async host call, services `page/sandbox.ts`).
|
|
5
|
+
*
|
|
6
|
+
* A page script reads with `db` and returns a page written with functions (76): `page(…)`, or
|
|
7
|
+
* `doNow(…)` to do something first. The page functions (`fns.ts`) are built from what is here (a
|
|
8
|
+
* field's cell, a button's action) and cross the sandbox as an envelope; the platform turns it into a
|
|
9
|
+
* `RenderedPage` (`render.ts`).
|
|
10
|
+
*/
|
|
11
|
+
import { checkSpoken } from '../conversation/index.js';
|
|
12
|
+
import { toFnPageResult } from './fns.js';
|
|
13
|
+
function runsOf(runs, key, scope) {
|
|
14
|
+
return (scope === 'case' ? runs?.caseActions : runs?.actions)?.[key];
|
|
15
|
+
}
|
|
16
|
+
/** Whether the action `key` ran at least once (whatever its result), in this run or (`'case'`) on the case. */
|
|
17
|
+
export function tried(runs, key, scope = 'run') {
|
|
18
|
+
return (runsOf(runs, key, scope)?.length ?? 0) > 0;
|
|
19
|
+
}
|
|
20
|
+
/** The latest run of the action `key`, in this run or (`'case'`) on the case, if any. */
|
|
21
|
+
export function lastActionRun(runs, key, scope = 'run') {
|
|
22
|
+
return runsOf(runs, key, scope)?.at(-1) ?? null;
|
|
23
|
+
}
|
|
24
|
+
/** The next-step button (`scheduleFollowUp({ channels })`, 76). */
|
|
25
|
+
export function followUpAction(options) {
|
|
26
|
+
const channels = [...new Set(options?.channels ?? [])];
|
|
27
|
+
if (!channels.length || channels.some((c) => c !== 'call' && c !== 'sms' && c !== 'email')) {
|
|
28
|
+
throw new Error("scheduleFollowUp(): name at least one of 'call', 'sms', 'email'");
|
|
29
|
+
}
|
|
30
|
+
const action = { $action: 'followUp', key: options.key ?? 'followUp', channels };
|
|
31
|
+
const prefill = options.prefill ?? {};
|
|
32
|
+
const unknown = Object.keys(prefill).filter((key) => key !== 'channel' && key !== 'contactId' && prefill[key] !== undefined);
|
|
33
|
+
if (unknown.length)
|
|
34
|
+
throw new Error(`scheduleFollowUp(): the page fixes only channel and contactId, not ${unknown.join(', ')}`);
|
|
35
|
+
const fixed = {};
|
|
36
|
+
if (prefill.channel !== undefined) {
|
|
37
|
+
if (!channels.includes(prefill.channel))
|
|
38
|
+
throw new Error(`scheduleFollowUp(): prefill.channel ${JSON.stringify(prefill.channel)} is not one of ${channels.join(', ')}`);
|
|
39
|
+
fixed.channel = prefill.channel;
|
|
40
|
+
}
|
|
41
|
+
if (prefill.contactId !== undefined) {
|
|
42
|
+
if (typeof prefill.contactId !== 'string' && !isChoose(prefill.contactId))
|
|
43
|
+
throw new Error('scheduleFollowUp(): contactId is a contact row\'s id, or choose(contacts, label) in options');
|
|
44
|
+
fixed.contactId = prefill.contactId;
|
|
45
|
+
}
|
|
46
|
+
if (Object.keys(fixed).length)
|
|
47
|
+
action.prefill = fixed;
|
|
48
|
+
if (options.label !== undefined)
|
|
49
|
+
action.label = options.label;
|
|
50
|
+
if (options.description !== undefined)
|
|
51
|
+
action.description = options.description;
|
|
52
|
+
return action;
|
|
53
|
+
}
|
|
54
|
+
/** The cancel-next-step button (`cancelFollowUp()`, 76). */
|
|
55
|
+
export function cancelFollowUpAction(options = {}) {
|
|
56
|
+
const action = { $action: 'cancelFollowUp', key: options.key ?? 'cancelFollowUp' };
|
|
57
|
+
if (options.label !== undefined)
|
|
58
|
+
action.label = options.label;
|
|
59
|
+
if (options.description !== undefined)
|
|
60
|
+
action.description = options.description;
|
|
61
|
+
return action;
|
|
62
|
+
}
|
|
63
|
+
/** At most this many FAQ entries in a conversation (24 §7.2): the catalog is in every prompt. */
|
|
64
|
+
export const MAX_FAQ_ENTRIES = 100;
|
|
65
|
+
export function choose(rows, label) {
|
|
66
|
+
if (!Array.isArray(rows))
|
|
67
|
+
throw new Error('choose(rows, label): rows must be a list of rows read from db');
|
|
68
|
+
let table = null;
|
|
69
|
+
const options = rows.map((row) => {
|
|
70
|
+
if (!isRow(row))
|
|
71
|
+
throw new Error('choose(rows, label): each row must be a row read from db');
|
|
72
|
+
const ref = Object.values(row).find(isSlot)?.ref;
|
|
73
|
+
if (ref) {
|
|
74
|
+
if (table !== null && table !== ref.table)
|
|
75
|
+
throw new Error(`choose(): rows of ${table} and ${ref.table} together`);
|
|
76
|
+
table = ref.table;
|
|
77
|
+
}
|
|
78
|
+
return { id: row.id, label: String(label(row)) };
|
|
79
|
+
});
|
|
80
|
+
return { $choose: true, table, options };
|
|
81
|
+
}
|
|
82
|
+
export function isChoose(value) {
|
|
83
|
+
return typeof value === 'object' && value !== null && value.$choose === true && Array.isArray(value.options);
|
|
84
|
+
}
|
|
85
|
+
export const PAGE_TOOLS = ['pressKeypad', 'convertTime'];
|
|
86
|
+
export function transferTo(options) {
|
|
87
|
+
const mode = options.mode ?? 'cold';
|
|
88
|
+
if (options.whisper !== undefined && mode === 'cold')
|
|
89
|
+
throw new Error('transferTo: whisper is for a transfer the person confirms (warm or background); a cold one has no confirmation');
|
|
90
|
+
if (options.bridge !== undefined && mode !== 'background')
|
|
91
|
+
throw new Error(`transferTo: bridge is for a background transfer (mode '${mode}')`);
|
|
92
|
+
const action = { $action: 'channel', channel: 'transfer', to: options.to, mode };
|
|
93
|
+
if (options.sipAuth !== undefined && !isSipUri(options.to))
|
|
94
|
+
throw new Error(`transferTo: sipAuth is for a SIP URI, not ${options.to}`);
|
|
95
|
+
if (options.sipAuth !== undefined)
|
|
96
|
+
action.sipAuth = { ...options.sipAuth };
|
|
97
|
+
if (options.callerId !== undefined)
|
|
98
|
+
action.callerId = options.callerId;
|
|
99
|
+
if (options.briefing !== undefined)
|
|
100
|
+
action.briefing = options.briefing;
|
|
101
|
+
if (options.whisper !== undefined)
|
|
102
|
+
action.whisper = options.whisper;
|
|
103
|
+
if (options.bridge !== undefined) {
|
|
104
|
+
action.bridge = { ...options.bridge };
|
|
105
|
+
if (options.bridge.customer !== undefined)
|
|
106
|
+
action.bridge.customer = checkSpoken(options.bridge.customer, 'transferTo: bridge.customer');
|
|
107
|
+
}
|
|
108
|
+
// Checked and made canonical where the transfer runs (the page sandbox has no Intl).
|
|
109
|
+
if (options.language !== undefined)
|
|
110
|
+
action.language = options.language.trim();
|
|
111
|
+
if (options.label !== undefined)
|
|
112
|
+
action.label = options.label;
|
|
113
|
+
if (options.description !== undefined)
|
|
114
|
+
action.description = options.description;
|
|
115
|
+
return action;
|
|
116
|
+
}
|
|
117
|
+
/** Whether a transfer's `to` is a SIP URI rather than a phone number. */
|
|
118
|
+
export function isSipUri(to) {
|
|
119
|
+
return /^sips?:/i.test(to.trim());
|
|
120
|
+
}
|
|
121
|
+
export function keypadAction(key, options) {
|
|
122
|
+
if (!options.digits.length)
|
|
123
|
+
throw new Error(`keypad('${key}'): digits must name at least one key`);
|
|
124
|
+
const action = { $action: 'channel', channel: 'keypad', key, say: checkSpoken(options.say, `keypad('${key}'): say`), digits: [...options.digits], timeoutMs: options.timeoutMs ?? 8000 };
|
|
125
|
+
if (options.write !== undefined)
|
|
126
|
+
action.write = isSlot(options.write) ? { ...options.write.ref } : { ...options.write };
|
|
127
|
+
if (options.map !== undefined)
|
|
128
|
+
action.map = { ...options.map };
|
|
129
|
+
if (options.label !== undefined)
|
|
130
|
+
action.label = options.label;
|
|
131
|
+
if (options.description !== undefined)
|
|
132
|
+
action.description = options.description;
|
|
133
|
+
return action;
|
|
134
|
+
}
|
|
135
|
+
/** A company function as a button (20 §6.2): `runFunction('importOrders', { label, say, parameters })`. */
|
|
136
|
+
export function runFunction(name, options = {}) {
|
|
137
|
+
if (!name || typeof name !== 'string')
|
|
138
|
+
throw new Error('runFunction(): name the function (one of functions/)');
|
|
139
|
+
const action = { $action: 'function', name };
|
|
140
|
+
if (options.label !== undefined)
|
|
141
|
+
action.label = options.label;
|
|
142
|
+
if (options.description !== undefined)
|
|
143
|
+
action.description = options.description;
|
|
144
|
+
if (options.say !== undefined)
|
|
145
|
+
action.say = checkSpoken(options.say, `runFunction('${name}'): say`);
|
|
146
|
+
if (options.parameters !== undefined)
|
|
147
|
+
action.parameters = options.parameters;
|
|
148
|
+
if (options.prefill !== undefined) {
|
|
149
|
+
for (const key of Object.keys(options.prefill)) {
|
|
150
|
+
if (!(key in (options.parameters?.properties ?? {})))
|
|
151
|
+
throw new Error(`runFunction('${name}'): ${key} is not one of its parameters`);
|
|
152
|
+
}
|
|
153
|
+
action.prefill = toPrefill(options.prefill);
|
|
154
|
+
}
|
|
155
|
+
return action;
|
|
156
|
+
}
|
|
157
|
+
export function announceAction(key, text, options = {}) {
|
|
158
|
+
return { $action: 'channel', channel: 'announce', key, text: checkSpoken(text, `announce('${key}'): text`), ...(options.answers ? { answers: true } : {}) };
|
|
159
|
+
}
|
|
160
|
+
export function voicemailAction(text, options = {}) {
|
|
161
|
+
const action = { $action: 'channel', channel: 'voicemail', text: checkSpoken(text, 'voicemail(text): the message to leave') };
|
|
162
|
+
if (options.label !== undefined)
|
|
163
|
+
action.label = options.label;
|
|
164
|
+
if (options.description !== undefined)
|
|
165
|
+
action.description = options.description;
|
|
166
|
+
return action;
|
|
167
|
+
}
|
|
168
|
+
export function endAction(options = {}) {
|
|
169
|
+
const action = { $action: 'channel', channel: 'end' };
|
|
170
|
+
if (options.label !== undefined)
|
|
171
|
+
action.label = options.label;
|
|
172
|
+
if (options.description !== undefined)
|
|
173
|
+
action.description = options.description;
|
|
174
|
+
return action;
|
|
175
|
+
}
|
|
176
|
+
// ---------------------------------------------------------------------------------------------
|
|
177
|
+
function isSlot(value) {
|
|
178
|
+
return (typeof value === 'object' &&
|
|
179
|
+
value !== null &&
|
|
180
|
+
'ref' in value &&
|
|
181
|
+
'state' in value &&
|
|
182
|
+
typeof value.ref === 'object' &&
|
|
183
|
+
value.ref !== null);
|
|
184
|
+
}
|
|
185
|
+
function isRow(value) {
|
|
186
|
+
return typeof value === 'object' && value !== null && typeof value.id === 'string';
|
|
187
|
+
}
|
|
188
|
+
/** Every channel a page may be written for, in order. */
|
|
189
|
+
export const PAGE_CHANNELS = ['call', 'sms', 'email'];
|
|
190
|
+
/**
|
|
191
|
+
* `context.page.ts` (17b §9.1, 24 §4): `definePage({ persona: (ctx) => '…', faq: (ctx) => [...], call:
|
|
192
|
+
* async (ctx) => page, sms: … })`. One page function per channel; what they share is the company code's
|
|
193
|
+
* own business.
|
|
194
|
+
*/
|
|
195
|
+
export function definePage(definition) {
|
|
196
|
+
if (!definition || typeof definition !== 'object' || typeof definition === 'function') {
|
|
197
|
+
throw new Error('definePage({ persona, faq, call, sms, email }): one page function per channel');
|
|
198
|
+
}
|
|
199
|
+
const channels = {};
|
|
200
|
+
for (const channel of PAGE_CHANNELS) {
|
|
201
|
+
const fn = definition[channel];
|
|
202
|
+
if (fn === undefined)
|
|
203
|
+
continue;
|
|
204
|
+
if (typeof fn !== 'function')
|
|
205
|
+
throw new Error(`definePage(): ${channel} must be a page function`);
|
|
206
|
+
channels[channel] = fn;
|
|
207
|
+
}
|
|
208
|
+
if (Object.keys(channels).length === 0)
|
|
209
|
+
throw new Error('definePage(): no page function (call, sms, email)');
|
|
210
|
+
if (typeof definition.persona !== 'function') {
|
|
211
|
+
throw new Error('definePage(): persona must be a function, called once when the conversation starts: persona: (ctx) => \'You are …\' (24 §4)');
|
|
212
|
+
}
|
|
213
|
+
if (definition.faq !== undefined && typeof definition.faq !== 'function') {
|
|
214
|
+
throw new Error('definePage(): faq must be a function, called once when the conversation starts: faq: (ctx) => [{ id, description, content }] (24 §4)');
|
|
215
|
+
}
|
|
216
|
+
return { kind: 'page', persona: definition.persona, faq: definition.faq ?? null, channels };
|
|
217
|
+
}
|
|
218
|
+
/**
|
|
219
|
+
* What `persona` and `faq` returned, checked (24 §4): a persona's text; at most `MAX_FAQ_ENTRIES` FAQ
|
|
220
|
+
* entries, each with an id of its own, a description and an answer. Throws what is wrong.
|
|
221
|
+
*/
|
|
222
|
+
export function checkPageStart(raw) {
|
|
223
|
+
const value = raw;
|
|
224
|
+
if (typeof value?.persona !== 'string' || !value.persona.trim())
|
|
225
|
+
throw new Error('persona(ctx) must return the persona\'s text');
|
|
226
|
+
const faq = value.faq ?? [];
|
|
227
|
+
if (!Array.isArray(faq))
|
|
228
|
+
throw new Error('faq(ctx) must return a list of { id, description, content }');
|
|
229
|
+
if (faq.length > MAX_FAQ_ENTRIES)
|
|
230
|
+
throw new Error(`faq(ctx) returned ${faq.length} entries; at most ${MAX_FAQ_ENTRIES} (24 §7.2)`);
|
|
231
|
+
const seen = new Set();
|
|
232
|
+
const entries = faq.map((entry, i) => {
|
|
233
|
+
const e = entry;
|
|
234
|
+
for (const key of ['id', 'description', 'content']) {
|
|
235
|
+
if (typeof e?.[key] !== 'string' || !e[key].trim())
|
|
236
|
+
throw new Error(`faq(ctx)[${i}]: ${key} must be text`);
|
|
237
|
+
}
|
|
238
|
+
if (seen.has(e.id))
|
|
239
|
+
throw new Error(`faq(ctx): the id ${e.id} is used twice`);
|
|
240
|
+
seen.add(e.id);
|
|
241
|
+
return { id: e.id, description: e.description, content: e.content };
|
|
242
|
+
});
|
|
243
|
+
return { persona: value.persona, faq: entries };
|
|
244
|
+
}
|
|
245
|
+
/**
|
|
246
|
+
* A cell the AI may write (19-page-writable-cells): a slot placed on a page as is is read-only;
|
|
247
|
+
* `field(slot)` opens it, unless `readOnly: true`. The row id and the parent never open; a lookup
|
|
248
|
+
* chooses among its table's rows on the page.
|
|
249
|
+
*/
|
|
250
|
+
/**
|
|
251
|
+
* A ref field (a lookup) on the page (26 §16.2 #7): the rows the AI may set it to, each with its label
|
|
252
|
+
* (`options: [{ id, label }]`, or `choose(rows, label)`); read-only, `options` only labels the current
|
|
253
|
+
* value. Its definition is always on the page. `field()` refuses a ref field.
|
|
254
|
+
*/
|
|
255
|
+
export function refFieldCell(slot, options = {}) {
|
|
256
|
+
if (!isSlot(slot))
|
|
257
|
+
throw new Error('refField(): the first argument must be a slot read from db');
|
|
258
|
+
const given = options.options ?? [];
|
|
259
|
+
const list = isChoose(given) ? given.options : given;
|
|
260
|
+
if (!Array.isArray(list))
|
|
261
|
+
throw new Error('refField(): options must be a list of { id, label } or choose(rows, label)');
|
|
262
|
+
const cell = {
|
|
263
|
+
$refField: true,
|
|
264
|
+
slot,
|
|
265
|
+
options: list.map((option) => {
|
|
266
|
+
if (!option || typeof option.id !== 'string')
|
|
267
|
+
throw new Error('refField(): each option is { id, label }, id a row id');
|
|
268
|
+
return { id: option.id, label: String(option.label) };
|
|
269
|
+
}),
|
|
270
|
+
};
|
|
271
|
+
if (options.readOnly !== undefined)
|
|
272
|
+
cell.readOnly = options.readOnly;
|
|
273
|
+
return cell;
|
|
274
|
+
}
|
|
275
|
+
export function fieldCell(slot, options = {}) {
|
|
276
|
+
if (!isSlot(slot))
|
|
277
|
+
throw new Error('field(): the first argument must be a slot read from db');
|
|
278
|
+
if (options.hint !== undefined || options.extraction !== undefined) {
|
|
279
|
+
throw new Error("field(): how to fill a field is the page's: write it in $extraction on the page's root, by table.field (26 §15.3)");
|
|
280
|
+
}
|
|
281
|
+
const cell = { $field: true, slot };
|
|
282
|
+
if (options.readOnly !== undefined)
|
|
283
|
+
cell.readOnly = options.readOnly;
|
|
284
|
+
if (options.required !== undefined)
|
|
285
|
+
throw new Error('field(): `required` was removed (24 §2.3): say what is still needed in $guide');
|
|
286
|
+
if (options.expand !== undefined)
|
|
287
|
+
cell.expand = options.expand;
|
|
288
|
+
return cell;
|
|
289
|
+
}
|
|
290
|
+
function placeable(make) {
|
|
291
|
+
return (prefill) => make(toPrefill((prefill ?? {})));
|
|
292
|
+
}
|
|
293
|
+
const CREATE_ROW_OPTIONS = new Set(['fields', 'required', 'label', 'description']);
|
|
294
|
+
/** Add a row (15 §10.6): `fields` are what the AI fills; call it to preset the rest (`({ status: 'active' })`). */
|
|
295
|
+
export function createRowAction(table, options) {
|
|
296
|
+
const tableName = typeof table === 'string' ? table : table.$table;
|
|
297
|
+
// An option it does not know (a `prefill` here, say) would otherwise be dropped: preset values are
|
|
298
|
+
// given by calling it, `createRow(…)({ parent: row.id })` (26 §16.2 #5d).
|
|
299
|
+
const unknown = Object.keys(options).filter((key) => !CREATE_ROW_OPTIONS.has(key));
|
|
300
|
+
if (unknown.length) {
|
|
301
|
+
throw new Error(`createRow('${tableName}'): unknown option ${unknown.join(', ')} (it takes ${[...CREATE_ROW_OPTIONS].join(', ')}); preset values by calling it: createRow('${tableName}', { … })({ field: value })`);
|
|
302
|
+
}
|
|
303
|
+
return placeable((prefill) => {
|
|
304
|
+
const action = {
|
|
305
|
+
$action: 'createRow',
|
|
306
|
+
table: tableName,
|
|
307
|
+
fields: [...options.fields],
|
|
308
|
+
required: [...(options.required ?? [])],
|
|
309
|
+
prefill,
|
|
310
|
+
};
|
|
311
|
+
if (options.label !== undefined)
|
|
312
|
+
action.label = options.label;
|
|
313
|
+
if (options.description !== undefined)
|
|
314
|
+
action.description = options.description;
|
|
315
|
+
return action;
|
|
316
|
+
});
|
|
317
|
+
}
|
|
318
|
+
/** Delete a row (cascading, 15 §10.6.1): a built-in action with the row prefilled and nothing to fill. */
|
|
319
|
+
export function deleteRowAction(row, options = {}) {
|
|
320
|
+
const rowId = typeof row === 'string' ? row : isRow(row) ? row.id : null;
|
|
321
|
+
if (!rowId)
|
|
322
|
+
throw new Error('deleteRow(): the first argument must be a row read from db, or its id');
|
|
323
|
+
const action = { $action: 'deleteRow', rowId };
|
|
324
|
+
if (options.reason !== undefined)
|
|
325
|
+
action.reason = options.reason;
|
|
326
|
+
if (options.label !== undefined)
|
|
327
|
+
action.label = options.label;
|
|
328
|
+
if (options.description !== undefined)
|
|
329
|
+
action.description = options.description;
|
|
330
|
+
return action;
|
|
331
|
+
}
|
|
332
|
+
/** Prefilled values as given (ids and plain JSON, as `parameters` types them), without the unset ones. */
|
|
333
|
+
function toPrefill(prefill) {
|
|
334
|
+
const out = {};
|
|
335
|
+
for (const [key, value] of Object.entries(prefill)) {
|
|
336
|
+
if (value !== undefined)
|
|
337
|
+
out[key] = value;
|
|
338
|
+
}
|
|
339
|
+
return out;
|
|
340
|
+
}
|
|
341
|
+
function host() {
|
|
342
|
+
const call = globalThis.__host;
|
|
343
|
+
if (!call)
|
|
344
|
+
throw new Error('the page module runs inside the page sandbox only');
|
|
345
|
+
return call;
|
|
346
|
+
}
|
|
347
|
+
function dbProxy(mode) {
|
|
348
|
+
const call = host();
|
|
349
|
+
return new Proxy({}, {
|
|
350
|
+
get(_, table) {
|
|
351
|
+
if (typeof table !== 'string')
|
|
352
|
+
return undefined;
|
|
353
|
+
const read = (method) => (args) => call('records', [table, method, args ?? {}]);
|
|
354
|
+
const reads = {
|
|
355
|
+
$table: table,
|
|
356
|
+
findUnique: read('findUnique'),
|
|
357
|
+
findFirst: read('findFirst'),
|
|
358
|
+
findMany: read('findMany'),
|
|
359
|
+
count: read('count'),
|
|
360
|
+
};
|
|
361
|
+
if (mode === 'read')
|
|
362
|
+
return reads;
|
|
363
|
+
const write = (method) => (args) => call('write', [table, method, args ?? {}]);
|
|
364
|
+
return { ...reads, create: write('create'), update: write('update'), upsert: write('upsert'), delete: write('delete') };
|
|
365
|
+
},
|
|
366
|
+
});
|
|
367
|
+
}
|
|
368
|
+
/** `ctx.time` inside the sandbox: `format` and `day` are host calls (the sandbox has no `Intl`). */
|
|
369
|
+
function timeOf(at) {
|
|
370
|
+
const call = host();
|
|
371
|
+
return {
|
|
372
|
+
zone: at.zone,
|
|
373
|
+
today: at.today,
|
|
374
|
+
format: (when, options = {}) => call('time', [when, { timeZone: options.timeZone ?? at.zone, ...(options.style ? { style: options.style } : {}) }]),
|
|
375
|
+
day: (when, timeZone) => call('day', [when, timeZone ?? at.zone]),
|
|
376
|
+
at: (date, time, timeZone) => call('at', [date, time, timeZone ?? at.zone]),
|
|
377
|
+
parts: (when, timeZone) => call('parts', [when, timeZone ?? at.zone]),
|
|
378
|
+
};
|
|
379
|
+
}
|
|
380
|
+
/** `ctx.env` (76 §2.19): read only, the environment's configuration as the platform loaded it for this run. */
|
|
381
|
+
function envOf(values) {
|
|
382
|
+
return {
|
|
383
|
+
get: (key) => (Object.hasOwn(values, key) && values[key] !== undefined ? values[key] : null),
|
|
384
|
+
};
|
|
385
|
+
}
|
|
386
|
+
/** `history` inside the sandbox: each count is a `history` host call. */
|
|
387
|
+
function historyProxy() {
|
|
388
|
+
const call = host();
|
|
389
|
+
return {
|
|
390
|
+
sessionCount: (where) => call('history', ['sessionCount', where ?? {}]),
|
|
391
|
+
exchangeCount: (where) => call('history', ['exchangeCount', where ?? {}]),
|
|
392
|
+
timeline: (where) => call('history', ['timeline', where ?? {}]),
|
|
393
|
+
};
|
|
394
|
+
}
|
|
395
|
+
/** No runs yet. */
|
|
396
|
+
export function emptyRuns() {
|
|
397
|
+
return { actions: {}, caseActions: {} };
|
|
398
|
+
}
|
|
399
|
+
const ACTION_NAME = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
400
|
+
/**
|
|
401
|
+
* The page actions of `actions.ts` (76 §2.11), by name: its `actions` export, a plain object of
|
|
402
|
+
* `(ctx, args)` functions. No module, no actions. Read as its own keys only: `toString` is no action.
|
|
403
|
+
*/
|
|
404
|
+
function pageActionsOf(actionsModule) {
|
|
405
|
+
const out = new Map();
|
|
406
|
+
if (!actionsModule)
|
|
407
|
+
return out;
|
|
408
|
+
const actions = Object.hasOwn(actionsModule, 'actions') ? actionsModule.actions : undefined;
|
|
409
|
+
if (typeof actions !== 'object' || actions === null || Array.isArray(actions)) {
|
|
410
|
+
throw new Error('actions.ts must export const actions = { name: async (ctx: ActionCtx, args) => … } (76 §2.11)');
|
|
411
|
+
}
|
|
412
|
+
for (const name of Object.keys(actions)) {
|
|
413
|
+
const fn = actions[name];
|
|
414
|
+
if (name === '__proto__')
|
|
415
|
+
throw new Error("actions.ts: actions.__proto__: pick another name (JavaScript keeps it for an object's prototype)");
|
|
416
|
+
if (!ACTION_NAME.test(name))
|
|
417
|
+
throw new Error(`actions.ts: actions['${name}']: an action's name is an identifier (letters, digits, _): it is the run key`);
|
|
418
|
+
if (typeof fn !== 'function')
|
|
419
|
+
throw new Error(`actions.ts: actions.${name} is not a function: an action is async (ctx: ActionCtx, args) => …`);
|
|
420
|
+
out.set(name, fn);
|
|
421
|
+
}
|
|
422
|
+
return out;
|
|
423
|
+
}
|
|
424
|
+
/**
|
|
425
|
+
* The page actions a rendered page runs that `actions.ts` does not have (a stale or misspelled `act.x`):
|
|
426
|
+
* read where actions are, its buttons and its `doNow()`, never inside `data()` (data stays data, 31 §8).
|
|
427
|
+
*/
|
|
428
|
+
function unknownActions(envelope, actions) {
|
|
429
|
+
const missing = new Set();
|
|
430
|
+
const check = (action) => {
|
|
431
|
+
if (action.$action === 'custom' && !actions.has(action.name))
|
|
432
|
+
missing.add(action.name);
|
|
433
|
+
};
|
|
434
|
+
const visit = (nodes) => {
|
|
435
|
+
for (const node of nodes) {
|
|
436
|
+
if (node.k === 'button')
|
|
437
|
+
check(node.action);
|
|
438
|
+
else if (node.k === 'block')
|
|
439
|
+
visit(node.children);
|
|
440
|
+
}
|
|
441
|
+
};
|
|
442
|
+
if ('do' in envelope)
|
|
443
|
+
check(envelope.do.action);
|
|
444
|
+
else
|
|
445
|
+
visit(envelope.page.children);
|
|
446
|
+
return [...missing];
|
|
447
|
+
}
|
|
448
|
+
/**
|
|
449
|
+
* What the compiled page script evaluates to: renders the page, or runs one of its actions. `actionsModule`
|
|
450
|
+
* is the sequence's `actions.ts` (76 §2.11), null without one.
|
|
451
|
+
*/
|
|
452
|
+
export function pageEntry(mod, actionsModule = null) {
|
|
453
|
+
const exported = (mod.default ?? mod.page);
|
|
454
|
+
if (Object.hasOwn(mod, 'actions')) {
|
|
455
|
+
throw new Error('context.page.ts exports actions: page actions go in actions.ts (export const actions = { … }), run with run(act.name, …) (76 §2.11)');
|
|
456
|
+
}
|
|
457
|
+
const actions = pageActionsOf(actionsModule);
|
|
458
|
+
return (arg) => {
|
|
459
|
+
if (arg.mode === 'channels') {
|
|
460
|
+
if (exported?.kind !== 'page')
|
|
461
|
+
throw new Error('context.page.ts must default-export definePage({ persona, faq, call, sms, email })');
|
|
462
|
+
// The page actions: the build checks it read each one's parameters.
|
|
463
|
+
return { channels: Object.keys(exported.channels ?? {}), actions: [...actions.keys()] };
|
|
464
|
+
}
|
|
465
|
+
if (arg.mode === 'render' || arg.mode === 'start') {
|
|
466
|
+
const channels = exported?.kind === 'page' ? (exported.channels ?? {}) : null;
|
|
467
|
+
if (!channels)
|
|
468
|
+
throw new Error('context.page.ts must default-export definePage({ persona, faq, call, sms, email })');
|
|
469
|
+
const channel = arg.channel ?? Object.keys(channels)[0] ?? null;
|
|
470
|
+
const main = channel ? channels[channel] : undefined;
|
|
471
|
+
if (!channel || typeof main !== 'function')
|
|
472
|
+
throw new Error(`context.page.ts has no page function for ${arg.channel ?? 'any channel'}`);
|
|
473
|
+
const caps = channel === 'call'
|
|
474
|
+
? { call: { previous: arg.call?.previous ?? null, farEnd: arg.call?.farEnd ?? null, heard: arg.call?.heard ?? null, phase: arg.call?.phase ?? 'live' } }
|
|
475
|
+
: channel === 'sms'
|
|
476
|
+
? { sms: { inbound: arg.sms?.inbound ?? [] } }
|
|
477
|
+
: { email: { inbound: arg.email?.inbound ?? [], mailbox: arg.email?.mailbox ?? '' } };
|
|
478
|
+
const base = {
|
|
479
|
+
db: dbProxy('read'),
|
|
480
|
+
runs: arg.runs ?? emptyRuns(),
|
|
481
|
+
scheduledAction: arg.scheduledAction ?? null,
|
|
482
|
+
officeHours: arg.officeHours ?? null,
|
|
483
|
+
nextSteps: [...(arg.nextSteps ?? [])],
|
|
484
|
+
subject: arg.subject,
|
|
485
|
+
instanceId: arg.instanceId,
|
|
486
|
+
caller: arg.caller,
|
|
487
|
+
session: arg.session,
|
|
488
|
+
conversation: arg.conversation,
|
|
489
|
+
history: historyProxy(),
|
|
490
|
+
now: arg.now,
|
|
491
|
+
time: timeOf(arg.time ?? { zone: 'America/New_York', today: arg.now.slice(0, 10) }),
|
|
492
|
+
env: envOf(arg.env ?? {}),
|
|
493
|
+
};
|
|
494
|
+
const ctx = { ...base, ...caps, channel };
|
|
495
|
+
if (arg.mode === 'start') {
|
|
496
|
+
const page = exported;
|
|
497
|
+
return Promise.all([page.persona(ctx), page.faq ? page.faq(ctx) : []]).then(([persona, faq]) => ({ persona, faq }));
|
|
498
|
+
}
|
|
499
|
+
// A page returns nodes (76): the platform reads their envelope (31 §8).
|
|
500
|
+
return Promise.resolve(main(ctx))
|
|
501
|
+
.then(toFnPageResult)
|
|
502
|
+
.then((envelope) => {
|
|
503
|
+
const missing = unknownActions(envelope, actions);
|
|
504
|
+
if (missing.length)
|
|
505
|
+
throw new Error(`the page runs ${missing.map((name) => `act.${name}`).join(', ')}, not in actions.ts (76 §2.11)`);
|
|
506
|
+
return envelope;
|
|
507
|
+
});
|
|
508
|
+
}
|
|
509
|
+
if (arg.mode !== 'action')
|
|
510
|
+
throw new Error(`unknown page mode ${String(arg.mode)}`);
|
|
511
|
+
const run = actions.get(arg.name);
|
|
512
|
+
if (!run)
|
|
513
|
+
throw new Error(`actions.ts has no action named ${arg.name}`);
|
|
514
|
+
// A page action is a function like any other (38 §2.9): `(ctx, args)`.
|
|
515
|
+
return run({ db: dbProxy('readWrite'), subject: arg.subject, caller: arg.caller, session: arg.session ?? null, now: arg.now }, arg.args);
|
|
516
|
+
};
|
|
517
|
+
}
|
|
518
|
+
//# sourceMappingURL=define.js.map
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The envelope (31 §8): what crosses out of the page's sandbox. The page module turns the node tree a
|
|
3
|
+
* page function returned (`fns.ts`, 76) into it; nodes carry a mark no JSON can carry, so only what the
|
|
4
|
+
* page module marked is a block, a field, a button or a toolbox here, and the platform reads only this
|
|
5
|
+
* (`render.ts`).
|
|
6
|
+
*/
|
|
7
|
+
import type { FieldCell, PageAction, RefFieldCell } from './define.js';
|
|
8
|
+
/** `key`: the node's name, a label for the AI (46 §2.2); `''` when it has none (a button never has one). */
|
|
9
|
+
export type EnvelopeNode = {
|
|
10
|
+
k: 'block';
|
|
11
|
+
key: string;
|
|
12
|
+
title?: string;
|
|
13
|
+
description?: string;
|
|
14
|
+
list: boolean;
|
|
15
|
+
children: EnvelopeNode[];
|
|
16
|
+
} | {
|
|
17
|
+
k: 'field';
|
|
18
|
+
key: string;
|
|
19
|
+
cell: FieldCell | RefFieldCell;
|
|
20
|
+
} | {
|
|
21
|
+
k: 'button';
|
|
22
|
+
key: string;
|
|
23
|
+
action: PageAction;
|
|
24
|
+
} | {
|
|
25
|
+
k: 'data';
|
|
26
|
+
key: string;
|
|
27
|
+
value: unknown;
|
|
28
|
+
}
|
|
29
|
+
/** The platform's tools the page lets the AI use (76 §2.18), by name. */
|
|
30
|
+
| {
|
|
31
|
+
k: 'toolbox';
|
|
32
|
+
key: string;
|
|
33
|
+
tools: string[];
|
|
34
|
+
};
|
|
35
|
+
export type EnvelopePage = {
|
|
36
|
+
title?: string;
|
|
37
|
+
description?: string;
|
|
38
|
+
guide?: string;
|
|
39
|
+
extraction?: Record<string, string>;
|
|
40
|
+
voice?: unknown;
|
|
41
|
+
children: EnvelopeNode[];
|
|
42
|
+
};
|
|
43
|
+
export type Envelope = {
|
|
44
|
+
$envelope: 1;
|
|
45
|
+
page: EnvelopePage;
|
|
46
|
+
} | {
|
|
47
|
+
$envelope: 1;
|
|
48
|
+
do: {
|
|
49
|
+
action: PageAction;
|
|
50
|
+
key?: string;
|
|
51
|
+
wait?: boolean;
|
|
52
|
+
};
|
|
53
|
+
};
|
|
54
|
+
export declare function isEnvelope(value: unknown): value is Envelope;
|
|
55
|
+
//# sourceMappingURL=envelope.d.ts.map
|