@superpanel-sdk/runtime 0.5.0 → 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 +3 -3
- package/dist/builder/codegen/emit.d.ts +5 -10
- package/dist/builder/codegen/emit.js +100 -106
- package/dist/builder/codegen/generate.d.ts +3 -5
- package/dist/builder/codegen/generate.js +13 -32
- package/dist/builder/company-imports.d.ts +5 -0
- package/dist/builder/company-imports.js +9 -1
- package/dist/builder/compile/compile-sequence.d.ts +13 -11
- package/dist/builder/compile/compile-sequence.js +135 -83
- package/dist/builder/compile/function-code.d.ts +15 -9
- package/dist/builder/compile/function-code.js +59 -29
- package/dist/builder/compile/registry.d.ts +4 -4
- package/dist/builder/compile/registry.js +14 -12
- 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 +17 -17
- package/dist/builder/load.js +27 -25
- package/dist/builder/paths.d.ts +6 -1
- package/dist/builder/paths.js +16 -2
- package/dist/builder/schedules.d.ts +9 -7
- package/dist/builder/schedules.js +20 -21
- package/dist/builder/typecheck.d.ts +1 -1
- package/dist/builder/typecheck.js +3 -6
- package/dist/builder/types.d.ts +41 -5
- package/dist/builder/types.js +19 -0
- package/dist/cli/calls/index.d.ts +177 -0
- package/dist/cli/calls/index.js +96 -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 +84 -1
- 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 +40 -4
- package/dist/cli/serve/index.js +167 -28
- 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 +0 -1
- package/dist/compute-host/compute-host.d.ts +5 -6
- package/dist/compute-host/compute-host.js +4 -13
- package/dist/compute-host/source-compute-host.d.ts +5 -7
- package/dist/compute-host/source-compute-host.js +12 -33
- package/dist/core/case-orchestrator/contact-hours.d.ts +26 -6
- package/dist/core/case-orchestrator/contact-hours.js +27 -8
- package/dist/core/case-orchestrator/index.d.ts +1 -4
- package/dist/core/case-orchestrator/index.js +1 -4
- package/dist/core/case-orchestrator/render.d.ts +1 -1
- package/dist/core/case-orchestrator/render.js +15 -10
- package/dist/core/case-orchestrator/types.d.ts +83 -128
- package/dist/core/case-orchestrator/types.js +0 -4
- package/dist/core/context/email-message.d.ts +40 -1
- package/dist/core/context/email-message.js +27 -0
- package/dist/core/context/email.d.ts +13 -1
- package/dist/core/context/email.js +2 -3
- package/dist/core/context/exchanges.d.ts +25 -0
- package/dist/core/context/exchanges.js +25 -7
- package/dist/core/context/instances.d.ts +31 -2
- package/dist/core/context/instances.js +18 -4
- package/dist/core/context/platform-registry.d.ts +35 -0
- package/dist/core/context/sms.d.ts +30 -0
- package/dist/core/context/sms.js +20 -0
- package/dist/core/context/types.d.ts +29 -1
- package/dist/core/conversation/index.d.ts +58 -4
- package/dist/core/conversation/index.js +123 -4
- package/dist/core/functions/errors.d.ts +74 -0
- package/dist/core/functions/errors.js +26 -0
- package/dist/core/functions/events.d.ts +23 -78
- package/dist/core/functions/events.js +8 -17
- package/dist/core/functions/schedules.d.ts +9 -10
- package/dist/core/functions/schedules.js +2 -2
- package/dist/core/functions/types.d.ts +17 -72
- package/dist/core/functions/types.js +1 -9
- package/dist/core/page/define.d.ts +230 -160
- package/dist/core/page/define.js +145 -104
- 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/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 +67 -25
- package/dist/core/page/render.js +492 -272
- 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 +27 -0
- package/dist/core/page/time-format.js +84 -0
- package/dist/core/page/typed.d.ts +165 -159
- package/dist/core/page/workspace.d.ts +25 -1
- package/dist/core/page/workspace.js +48 -13
- package/dist/core/party/schema.d.ts +74 -3
- package/dist/core/party/schema.js +39 -6
- package/dist/core/party/types.d.ts +8 -13
- package/dist/core/sequence/define.d.ts +1 -2
- package/dist/core/slot/company-metadata.d.ts +11 -22
- package/dist/core/slot/company-metadata.js +12 -39
- package/dist/core/slot/compile-schema.js +6 -6
- package/dist/core/slot/field.d.ts +1 -2
- package/dist/core/slot/field.js +0 -2
- package/dist/core/slot/index.d.ts +3 -3
- package/dist/core/slot/index.js +3 -3
- package/dist/core/slot/naming.d.ts +1 -1
- package/dist/core/slot/naming.js +1 -1
- package/dist/core/slot/platform-fields.d.ts +4 -4
- package/dist/core/slot/platform-fields.js +5 -5
- package/dist/core/slot/schema-compat.d.ts +8 -7
- package/dist/core/slot/schema-compat.js +22 -31
- package/dist/core/slot/schema.d.ts +3 -59
- package/dist/core/slot/schema.js +0 -15
- package/dist/core/slot/types.d.ts +4 -10
- package/dist/core/slot/value.d.ts +2 -0
- package/dist/core/slot/value.js +6 -2
- package/dist/dev-overlay/protocol.d.ts +38 -5
- package/dist/fn-context/build-context.js +10 -4
- package/dist/fn-context/fake/index.d.ts +2 -0
- package/dist/fn-context/fake/index.js +5 -3
- package/dist/fn-context/fn-params.d.ts +2 -5
- package/dist/fn-context/fn-params.js +6 -9
- package/dist/fn-context/index.d.ts +1 -1
- package/dist/fn-context/make-ctx.js +5 -4
- package/dist/fn-context/rpc/case-orchestrator-tools.d.ts +11 -3
- package/dist/fn-context/rpc/case-orchestrator-tools.js +0 -1
- 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 +1 -1
- package/dist/fn-context/rpc/index.js +1 -1
- package/dist/fn-context/rpc/integration/index.d.ts +11 -1
- package/dist/fn-context/rpc/log.d.ts +11 -1
- package/dist/fn-context/rpc/platform.d.ts +11 -1
- package/dist/fn-context/rpc/records.d.ts +13 -6
- package/dist/fn-context/rpc/records.js +2 -4
- package/dist/fn-context/rpc/trpc-client.d.ts +42 -0
- package/dist/fn-context/rpc/trpc-client.js +151 -22
- package/dist/fn-context/rpc/trpc-init.d.ts +19 -1
- package/dist/fn-context/rpc/trpc-init.js +10 -4
- package/dist/fn-context/rpc/trpc-router-factory.d.ts +66 -8
- package/dist/fn-context/run-scope.js +1 -1
- package/dist/fn-context/types.d.ts +20 -6
- package/dist/index.d.ts +11 -12
- package/dist/index.js +5 -8
- package/dist/logger.d.ts +4 -0
- package/dist/logger.js +8 -0
- package/dist/platform.d.ts +32 -33
- package/dist/platform.js +23 -22
- 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 +12 -35
- 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 +16 -6
- package/dist/worker.d.ts +2 -1
- package/dist/worker.js +2 -1
- package/package.json +20 -16
- package/dist/core/case-orchestrator/follow-ups.d.ts +0 -31
- package/dist/core/case-orchestrator/follow-ups.js +0 -103
- package/dist/core/case-orchestrator/tools.d.ts +0 -29
- package/dist/core/case-orchestrator/tools.js +0 -31
- package/dist/core/case-orchestrator/validate.d.ts +0 -11
- package/dist/core/case-orchestrator/validate.js +0 -63
- package/dist/core/functions/define/bind.d.ts +0 -25
- package/dist/core/functions/define/bind.js +0 -14
- package/dist/core/functions/define/case.d.ts +0 -9
- package/dist/core/functions/define/case.js +0 -13
- package/dist/core/functions/define/events.d.ts +0 -20
- package/dist/core/functions/define/events.js +0 -21
- package/dist/core/functions/define/no-case.d.ts +0 -4
- package/dist/core/functions/define/no-case.js +0 -9
- package/dist/core/functions/func-ref.d.ts +0 -25
- package/dist/core/functions/func-ref.js +0 -20
package/dist/core/page/define.js
CHANGED
|
@@ -3,11 +3,13 @@
|
|
|
3
3
|
* inside the QuickJS sandbox, bundled into the page script, so it imports nothing at runtime and only
|
|
4
4
|
* uses `globalThis.__host` (the sandbox's async host call, services `page/sandbox.ts`).
|
|
5
5
|
*
|
|
6
|
-
* A page script reads with `db` and returns a
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
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
10
|
*/
|
|
11
|
+
import { checkSpoken } from '../conversation/index.js';
|
|
12
|
+
import { toFnPageResult } from './fns.js';
|
|
11
13
|
function runsOf(runs, key, scope) {
|
|
12
14
|
return (scope === 'case' ? runs?.caseActions : runs?.actions)?.[key];
|
|
13
15
|
}
|
|
@@ -19,21 +21,44 @@ export function tried(runs, key, scope = 'run') {
|
|
|
19
21
|
export function lastActionRun(runs, key, scope = 'run') {
|
|
20
22
|
return runsOf(runs, key, scope)?.at(-1) ?? null;
|
|
21
23
|
}
|
|
22
|
-
|
|
24
|
+
/** The next-step button (`scheduleFollowUp({ channels })`, 76). */
|
|
25
|
+
export function followUpAction(options) {
|
|
23
26
|
const channels = [...new Set(options?.channels ?? [])];
|
|
24
27
|
if (!channels.length || channels.some((c) => c !== 'call' && c !== 'sms' && c !== 'email')) {
|
|
25
|
-
throw new Error("
|
|
28
|
+
throw new Error("scheduleFollowUp(): name at least one of 'call', 'sms', 'email'");
|
|
26
29
|
}
|
|
27
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;
|
|
28
48
|
if (options.label !== undefined)
|
|
29
49
|
action.label = options.label;
|
|
30
50
|
if (options.description !== undefined)
|
|
31
51
|
action.description = options.description;
|
|
32
52
|
return action;
|
|
33
53
|
}
|
|
34
|
-
/** The
|
|
35
|
-
export function
|
|
36
|
-
|
|
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;
|
|
37
62
|
}
|
|
38
63
|
/** At most this many FAQ entries in a conversation (24 §7.2): the catalog is in every prompt. */
|
|
39
64
|
export const MAX_FAQ_ENTRIES = 100;
|
|
@@ -57,29 +82,11 @@ export function choose(rows, label) {
|
|
|
57
82
|
export function isChoose(value) {
|
|
58
83
|
return typeof value === 'object' && value !== null && value.$choose === true && Array.isArray(value.options);
|
|
59
84
|
}
|
|
60
|
-
|
|
61
|
-
if (!key || typeof key !== 'string')
|
|
62
|
-
throw new Error(`messages.${channel === 'sms' ? 'text' : 'email'}(): name it with a key`);
|
|
63
|
-
const action = { $action: 'message', channel, key };
|
|
64
|
-
if (options.to !== undefined) {
|
|
65
|
-
const to = typeof options.to === 'string' ? options.to : isRow(options.to) ? options.to.id : null;
|
|
66
|
-
if (!to)
|
|
67
|
-
throw new Error(`messages: ${key}: \`to\` must be a contact row read from db, or its id`);
|
|
68
|
-
action.to = to;
|
|
69
|
-
}
|
|
70
|
-
for (const name of ['body', 'subject', 'say', 'label', 'description']) {
|
|
71
|
-
const value = options[name];
|
|
72
|
-
if (value !== undefined)
|
|
73
|
-
action[name] = value;
|
|
74
|
-
}
|
|
75
|
-
return action;
|
|
76
|
-
}
|
|
77
|
-
export const messages = {
|
|
78
|
-
text: (key, options = {}) => message('sms', key, options),
|
|
79
|
-
email: (key, options = {}) => message('email', key, options),
|
|
80
|
-
};
|
|
85
|
+
export const PAGE_TOOLS = ['pressKeypad', 'convertTime'];
|
|
81
86
|
export function transferTo(options) {
|
|
82
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');
|
|
83
90
|
if (options.bridge !== undefined && mode !== 'background')
|
|
84
91
|
throw new Error(`transferTo: bridge is for a background transfer (mode '${mode}')`);
|
|
85
92
|
const action = { $action: 'channel', channel: 'transfer', to: options.to, mode };
|
|
@@ -91,8 +98,16 @@ export function transferTo(options) {
|
|
|
91
98
|
action.callerId = options.callerId;
|
|
92
99
|
if (options.briefing !== undefined)
|
|
93
100
|
action.briefing = options.briefing;
|
|
94
|
-
if (options.
|
|
101
|
+
if (options.whisper !== undefined)
|
|
102
|
+
action.whisper = options.whisper;
|
|
103
|
+
if (options.bridge !== undefined) {
|
|
95
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();
|
|
96
111
|
if (options.label !== undefined)
|
|
97
112
|
action.label = options.label;
|
|
98
113
|
if (options.description !== undefined)
|
|
@@ -103,10 +118,10 @@ export function transferTo(options) {
|
|
|
103
118
|
export function isSipUri(to) {
|
|
104
119
|
return /^sips?:/i.test(to.trim());
|
|
105
120
|
}
|
|
106
|
-
export function
|
|
121
|
+
export function keypadAction(key, options) {
|
|
107
122
|
if (!options.digits.length)
|
|
108
123
|
throw new Error(`keypad('${key}'): digits must name at least one key`);
|
|
109
|
-
const action = { $action: 'channel', channel: 'keypad', key, say: options.say, digits: [...options.digits], timeoutMs: options.timeoutMs ?? 8000 };
|
|
124
|
+
const action = { $action: 'channel', channel: 'keypad', key, say: checkSpoken(options.say, `keypad('${key}'): say`), digits: [...options.digits], timeoutMs: options.timeoutMs ?? 8000 };
|
|
110
125
|
if (options.write !== undefined)
|
|
111
126
|
action.write = isSlot(options.write) ? { ...options.write.ref } : { ...options.write };
|
|
112
127
|
if (options.map !== undefined)
|
|
@@ -127,7 +142,7 @@ export function runFunction(name, options = {}) {
|
|
|
127
142
|
if (options.description !== undefined)
|
|
128
143
|
action.description = options.description;
|
|
129
144
|
if (options.say !== undefined)
|
|
130
|
-
action.say = options.say;
|
|
145
|
+
action.say = checkSpoken(options.say, `runFunction('${name}'): say`);
|
|
131
146
|
if (options.parameters !== undefined)
|
|
132
147
|
action.parameters = options.parameters;
|
|
133
148
|
if (options.prefill !== undefined) {
|
|
@@ -139,20 +154,18 @@ export function runFunction(name, options = {}) {
|
|
|
139
154
|
}
|
|
140
155
|
return action;
|
|
141
156
|
}
|
|
142
|
-
export function
|
|
143
|
-
return { $action: 'channel', channel: 'announce', key, text };
|
|
157
|
+
export function announceAction(key, text, options = {}) {
|
|
158
|
+
return { $action: 'channel', channel: 'announce', key, text: checkSpoken(text, `announce('${key}'): text`), ...(options.answers ? { answers: true } : {}) };
|
|
144
159
|
}
|
|
145
|
-
export function
|
|
146
|
-
|
|
147
|
-
throw new Error('voicemail(text): the message to leave');
|
|
148
|
-
const action = { $action: 'channel', channel: 'voicemail', text };
|
|
160
|
+
export function voicemailAction(text, options = {}) {
|
|
161
|
+
const action = { $action: 'channel', channel: 'voicemail', text: checkSpoken(text, 'voicemail(text): the message to leave') };
|
|
149
162
|
if (options.label !== undefined)
|
|
150
163
|
action.label = options.label;
|
|
151
164
|
if (options.description !== undefined)
|
|
152
165
|
action.description = options.description;
|
|
153
166
|
return action;
|
|
154
167
|
}
|
|
155
|
-
export function
|
|
168
|
+
export function endAction(options = {}) {
|
|
156
169
|
const action = { $action: 'channel', channel: 'end' };
|
|
157
170
|
if (options.label !== undefined)
|
|
158
171
|
action.label = options.label;
|
|
@@ -160,17 +173,6 @@ export function end(options = {}) {
|
|
|
160
173
|
action.description = options.description;
|
|
161
174
|
return action;
|
|
162
175
|
}
|
|
163
|
-
export function perform(action, options = {}) {
|
|
164
|
-
const plain = typeof action.toJSON === 'function' && !('$action' in action) ? action.toJSON() : action;
|
|
165
|
-
if (plain.$action === 'channel' && options.wait === false)
|
|
166
|
-
throw new Error('perform(): a channel action cannot run in the background');
|
|
167
|
-
const out = { $perform: true, action: plain };
|
|
168
|
-
if (options.key !== undefined)
|
|
169
|
-
out.key = options.key;
|
|
170
|
-
if (options.wait !== undefined)
|
|
171
|
-
out.wait = options.wait;
|
|
172
|
-
return out;
|
|
173
|
-
}
|
|
174
176
|
// ---------------------------------------------------------------------------------------------
|
|
175
177
|
function isSlot(value) {
|
|
176
178
|
return (typeof value === 'object' &&
|
|
@@ -250,7 +252,7 @@ export function checkPageStart(raw) {
|
|
|
250
252
|
* (`options: [{ id, label }]`, or `choose(rows, label)`); read-only, `options` only labels the current
|
|
251
253
|
* value. Its definition is always on the page. `field()` refuses a ref field.
|
|
252
254
|
*/
|
|
253
|
-
export function
|
|
255
|
+
export function refFieldCell(slot, options = {}) {
|
|
254
256
|
if (!isSlot(slot))
|
|
255
257
|
throw new Error('refField(): the first argument must be a slot read from db');
|
|
256
258
|
const given = options.options ?? [];
|
|
@@ -270,7 +272,7 @@ export function refField(slot, options = {}) {
|
|
|
270
272
|
cell.readOnly = options.readOnly;
|
|
271
273
|
return cell;
|
|
272
274
|
}
|
|
273
|
-
export function
|
|
275
|
+
export function fieldCell(slot, options = {}) {
|
|
274
276
|
if (!isSlot(slot))
|
|
275
277
|
throw new Error('field(): the first argument must be a slot read from db');
|
|
276
278
|
if (options.hint !== undefined || options.extraction !== undefined) {
|
|
@@ -285,23 +287,12 @@ export function field(slot, options = {}) {
|
|
|
285
287
|
cell.expand = options.expand;
|
|
286
288
|
return cell;
|
|
287
289
|
}
|
|
288
|
-
/** A parameter taken from the schema: `schemaField('providers.name')`, `schemaField('injuries.id')` (a row). */
|
|
289
|
-
export function schemaField(path, options = {}) {
|
|
290
|
-
if (options.description !== undefined)
|
|
291
|
-
throw new Error(`schemaField('${path}'): \`description\` was renamed \`extraction\``);
|
|
292
|
-
const param = { $schemaField: path };
|
|
293
|
-
if (options.extraction !== undefined)
|
|
294
|
-
param.extraction = options.extraction;
|
|
295
|
-
return param;
|
|
296
|
-
}
|
|
297
290
|
function placeable(make) {
|
|
298
|
-
|
|
299
|
-
place.toJSON = () => make({});
|
|
300
|
-
return place;
|
|
291
|
+
return (prefill) => make(toPrefill((prefill ?? {})));
|
|
301
292
|
}
|
|
302
293
|
const CREATE_ROW_OPTIONS = new Set(['fields', 'required', 'label', 'description']);
|
|
303
294
|
/** Add a row (15 §10.6): `fields` are what the AI fills; call it to preset the rest (`({ status: 'active' })`). */
|
|
304
|
-
export function
|
|
295
|
+
export function createRowAction(table, options) {
|
|
305
296
|
const tableName = typeof table === 'string' ? table : table.$table;
|
|
306
297
|
// An option it does not know (a `prefill` here, say) would otherwise be dropped: preset values are
|
|
307
298
|
// given by calling it, `createRow(…)({ parent: row.id })` (26 §16.2 #5d).
|
|
@@ -325,7 +316,7 @@ export function createRow(table, options) {
|
|
|
325
316
|
});
|
|
326
317
|
}
|
|
327
318
|
/** Delete a row (cascading, 15 §10.6.1): a built-in action with the row prefilled and nothing to fill. */
|
|
328
|
-
export function
|
|
319
|
+
export function deleteRowAction(row, options = {}) {
|
|
329
320
|
const rowId = typeof row === 'string' ? row : isRow(row) ? row.id : null;
|
|
330
321
|
if (!rowId)
|
|
331
322
|
throw new Error('deleteRow(): the first argument must be a row read from db, or its id');
|
|
@@ -338,25 +329,6 @@ export function deleteRow(row, options = {}) {
|
|
|
338
329
|
action.description = options.description;
|
|
339
330
|
return action;
|
|
340
331
|
}
|
|
341
|
-
const ACTIONS = new Map();
|
|
342
|
-
/**
|
|
343
|
-
* A custom action, declared like an LLM function call: `parameters` are everything `run` takes. Where
|
|
344
|
-
* the page places it, some parameters may be prefilled; the AI fills the rest.
|
|
345
|
-
*/
|
|
346
|
-
export function defineAction(definition) {
|
|
347
|
-
if (ACTIONS.has(definition.name))
|
|
348
|
-
throw new Error(`defineAction(): two actions are named ${definition.name}`);
|
|
349
|
-
ACTIONS.set(definition.name, { run: definition.run });
|
|
350
|
-
const parameters = definition.parameters ?? { type: 'object', properties: {} };
|
|
351
|
-
const place = placeable((prefill) => {
|
|
352
|
-
for (const key of Object.keys(prefill)) {
|
|
353
|
-
if (!(key in parameters.properties))
|
|
354
|
-
throw new Error(`${definition.name}: ${key} is not one of its parameters`);
|
|
355
|
-
}
|
|
356
|
-
return { $action: 'custom', name: definition.name, description: definition.description, parameters, prefill };
|
|
357
|
-
});
|
|
358
|
-
return Object.assign(place, { actionName: definition.name, run: definition.run });
|
|
359
|
-
}
|
|
360
332
|
/** Prefilled values as given (ids and plain JSON, as `parameters` types them), without the unset ones. */
|
|
361
333
|
function toPrefill(prefill) {
|
|
362
334
|
const out = {};
|
|
@@ -401,6 +373,14 @@ function timeOf(at) {
|
|
|
401
373
|
today: at.today,
|
|
402
374
|
format: (when, options = {}) => call('time', [when, { timeZone: options.timeZone ?? at.zone, ...(options.style ? { style: options.style } : {}) }]),
|
|
403
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),
|
|
404
384
|
};
|
|
405
385
|
}
|
|
406
386
|
/** `history` inside the sandbox: each count is a `history` host call. */
|
|
@@ -416,14 +396,71 @@ function historyProxy() {
|
|
|
416
396
|
export function emptyRuns() {
|
|
417
397
|
return { actions: {}, caseActions: {} };
|
|
418
398
|
}
|
|
419
|
-
|
|
420
|
-
|
|
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) {
|
|
421
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);
|
|
422
458
|
return (arg) => {
|
|
423
459
|
if (arg.mode === 'channels') {
|
|
424
460
|
if (exported?.kind !== 'page')
|
|
425
461
|
throw new Error('context.page.ts must default-export definePage({ persona, faq, call, sms, email })');
|
|
426
|
-
|
|
462
|
+
// The page actions: the build checks it read each one's parameters.
|
|
463
|
+
return { channels: Object.keys(exported.channels ?? {}), actions: [...actions.keys()] };
|
|
427
464
|
}
|
|
428
465
|
if (arg.mode === 'render' || arg.mode === 'start') {
|
|
429
466
|
const channels = exported?.kind === 'page' ? (exported.channels ?? {}) : null;
|
|
@@ -434,7 +471,7 @@ export function pageEntry(mod) {
|
|
|
434
471
|
if (!channel || typeof main !== 'function')
|
|
435
472
|
throw new Error(`context.page.ts has no page function for ${arg.channel ?? 'any channel'}`);
|
|
436
473
|
const caps = channel === 'call'
|
|
437
|
-
? { call: {
|
|
474
|
+
? { call: { previous: arg.call?.previous ?? null, farEnd: arg.call?.farEnd ?? null, heard: arg.call?.heard ?? null, phase: arg.call?.phase ?? 'live' } }
|
|
438
475
|
: channel === 'sms'
|
|
439
476
|
? { sms: { inbound: arg.sms?.inbound ?? [] } }
|
|
440
477
|
: { email: { inbound: arg.email?.inbound ?? [], mailbox: arg.email?.mailbox ?? '' } };
|
|
@@ -443,35 +480,39 @@ export function pageEntry(mod) {
|
|
|
443
480
|
runs: arg.runs ?? emptyRuns(),
|
|
444
481
|
scheduledAction: arg.scheduledAction ?? null,
|
|
445
482
|
officeHours: arg.officeHours ?? null,
|
|
446
|
-
|
|
483
|
+
nextSteps: [...(arg.nextSteps ?? [])],
|
|
447
484
|
subject: arg.subject,
|
|
485
|
+
instanceId: arg.instanceId,
|
|
448
486
|
caller: arg.caller,
|
|
449
487
|
session: arg.session,
|
|
450
488
|
conversation: arg.conversation,
|
|
451
489
|
history: historyProxy(),
|
|
452
490
|
now: arg.now,
|
|
453
|
-
messages,
|
|
454
491
|
time: timeOf(arg.time ?? { zone: 'America/New_York', today: arg.now.slice(0, 10) }),
|
|
492
|
+
env: envOf(arg.env ?? {}),
|
|
455
493
|
};
|
|
456
494
|
const ctx = { ...base, ...caps, channel };
|
|
457
495
|
if (arg.mode === 'start') {
|
|
458
496
|
const page = exported;
|
|
459
497
|
return Promise.all([page.persona(ctx), page.faq ? page.faq(ctx) : []]).then(([persona, faq]) => ({ persona, faq }));
|
|
460
498
|
}
|
|
461
|
-
|
|
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
|
+
});
|
|
462
508
|
}
|
|
463
509
|
if (arg.mode !== 'action')
|
|
464
510
|
throw new Error(`unknown page mode ${String(arg.mode)}`);
|
|
465
|
-
const
|
|
466
|
-
if (!
|
|
467
|
-
throw new Error(`no action named ${arg.name}`);
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
subject: arg.subject,
|
|
471
|
-
caller: arg.caller,
|
|
472
|
-
session: arg.session ?? null,
|
|
473
|
-
now: arg.now,
|
|
474
|
-
});
|
|
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);
|
|
475
516
|
};
|
|
476
517
|
}
|
|
477
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
|