@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.
Files changed (189) hide show
  1. package/README.md +3 -3
  2. package/dist/builder/codegen/emit.d.ts +5 -10
  3. package/dist/builder/codegen/emit.js +100 -106
  4. package/dist/builder/codegen/generate.d.ts +3 -5
  5. package/dist/builder/codegen/generate.js +13 -32
  6. package/dist/builder/company-imports.d.ts +5 -0
  7. package/dist/builder/company-imports.js +9 -1
  8. package/dist/builder/compile/compile-sequence.d.ts +13 -11
  9. package/dist/builder/compile/compile-sequence.js +135 -83
  10. package/dist/builder/compile/function-code.d.ts +15 -9
  11. package/dist/builder/compile/function-code.js +59 -29
  12. package/dist/builder/compile/registry.d.ts +4 -4
  13. package/dist/builder/compile/registry.js +14 -12
  14. package/dist/builder/errors.d.ts +9 -0
  15. package/dist/builder/errors.js +17 -0
  16. package/dist/builder/function-params.d.ts +10 -0
  17. package/dist/builder/function-params.js +203 -0
  18. package/dist/builder/load.d.ts +17 -17
  19. package/dist/builder/load.js +27 -25
  20. package/dist/builder/paths.d.ts +6 -1
  21. package/dist/builder/paths.js +16 -2
  22. package/dist/builder/schedules.d.ts +9 -7
  23. package/dist/builder/schedules.js +20 -21
  24. package/dist/builder/typecheck.d.ts +1 -1
  25. package/dist/builder/typecheck.js +3 -6
  26. package/dist/builder/types.d.ts +41 -5
  27. package/dist/builder/types.js +19 -0
  28. package/dist/cli/calls/index.d.ts +177 -0
  29. package/dist/cli/calls/index.js +96 -0
  30. package/dist/cli/debug/index.d.ts +31 -0
  31. package/dist/cli/debug/index.js +119 -0
  32. package/dist/cli/exec/index.d.ts +39 -0
  33. package/dist/cli/exec/index.js +138 -0
  34. package/dist/cli/exec/session.d.ts +48 -0
  35. package/dist/cli/exec/session.js +82 -0
  36. package/dist/cli/exec/trace.d.ts +18 -0
  37. package/dist/cli/exec/trace.js +52 -0
  38. package/dist/cli/index.js +84 -1
  39. package/dist/cli/logs/index.d.ts +47 -0
  40. package/dist/cli/logs/index.js +113 -0
  41. package/dist/cli/mobile/index.d.ts +12 -0
  42. package/dist/cli/mobile/index.js +405 -0
  43. package/dist/cli/serve/index.d.ts +40 -4
  44. package/dist/cli/serve/index.js +167 -28
  45. package/dist/cli/turns/index.d.ts +105 -0
  46. package/dist/cli/turns/index.js +128 -0
  47. package/dist/cli/turns/run.d.ts +13 -0
  48. package/dist/cli/turns/run.js +57 -0
  49. package/dist/cli/turns/shadow.d.ts +112 -0
  50. package/dist/cli/turns/shadow.js +54 -0
  51. package/dist/cli/type-gen/index.js +0 -1
  52. package/dist/compute-host/compute-host.d.ts +5 -6
  53. package/dist/compute-host/compute-host.js +4 -13
  54. package/dist/compute-host/source-compute-host.d.ts +5 -7
  55. package/dist/compute-host/source-compute-host.js +12 -33
  56. package/dist/core/case-orchestrator/contact-hours.d.ts +26 -6
  57. package/dist/core/case-orchestrator/contact-hours.js +27 -8
  58. package/dist/core/case-orchestrator/index.d.ts +1 -4
  59. package/dist/core/case-orchestrator/index.js +1 -4
  60. package/dist/core/case-orchestrator/render.d.ts +1 -1
  61. package/dist/core/case-orchestrator/render.js +15 -10
  62. package/dist/core/case-orchestrator/types.d.ts +83 -128
  63. package/dist/core/case-orchestrator/types.js +0 -4
  64. package/dist/core/context/email-message.d.ts +40 -1
  65. package/dist/core/context/email-message.js +27 -0
  66. package/dist/core/context/email.d.ts +13 -1
  67. package/dist/core/context/email.js +2 -3
  68. package/dist/core/context/exchanges.d.ts +25 -0
  69. package/dist/core/context/exchanges.js +25 -7
  70. package/dist/core/context/instances.d.ts +31 -2
  71. package/dist/core/context/instances.js +18 -4
  72. package/dist/core/context/platform-registry.d.ts +35 -0
  73. package/dist/core/context/sms.d.ts +30 -0
  74. package/dist/core/context/sms.js +20 -0
  75. package/dist/core/context/types.d.ts +29 -1
  76. package/dist/core/conversation/index.d.ts +58 -4
  77. package/dist/core/conversation/index.js +123 -4
  78. package/dist/core/functions/errors.d.ts +74 -0
  79. package/dist/core/functions/errors.js +26 -0
  80. package/dist/core/functions/events.d.ts +23 -78
  81. package/dist/core/functions/events.js +8 -17
  82. package/dist/core/functions/schedules.d.ts +9 -10
  83. package/dist/core/functions/schedules.js +2 -2
  84. package/dist/core/functions/types.d.ts +17 -72
  85. package/dist/core/functions/types.js +1 -9
  86. package/dist/core/page/define.d.ts +230 -160
  87. package/dist/core/page/define.js +145 -104
  88. package/dist/core/page/envelope.d.ts +55 -0
  89. package/dist/core/page/envelope.js +4 -0
  90. package/dist/core/page/fns.d.ts +448 -0
  91. package/dist/core/page/fns.js +539 -0
  92. package/dist/core/page/memory-reader.d.ts +27 -0
  93. package/dist/core/page/memory-reader.js +299 -0
  94. package/dist/core/page/page-host.d.ts +20 -0
  95. package/dist/core/page/page-host.js +57 -0
  96. package/dist/core/page/records-query.d.ts +234 -0
  97. package/dist/core/page/records-query.js +780 -0
  98. package/dist/core/page/render.d.ts +67 -25
  99. package/dist/core/page/render.js +492 -272
  100. package/dist/core/page/sandbox.d.ts +85 -0
  101. package/dist/core/page/sandbox.js +357 -0
  102. package/dist/core/page/time-format.d.ts +27 -0
  103. package/dist/core/page/time-format.js +84 -0
  104. package/dist/core/page/typed.d.ts +165 -159
  105. package/dist/core/page/workspace.d.ts +25 -1
  106. package/dist/core/page/workspace.js +48 -13
  107. package/dist/core/party/schema.d.ts +74 -3
  108. package/dist/core/party/schema.js +39 -6
  109. package/dist/core/party/types.d.ts +8 -13
  110. package/dist/core/sequence/define.d.ts +1 -2
  111. package/dist/core/slot/company-metadata.d.ts +11 -22
  112. package/dist/core/slot/company-metadata.js +12 -39
  113. package/dist/core/slot/compile-schema.js +6 -6
  114. package/dist/core/slot/field.d.ts +1 -2
  115. package/dist/core/slot/field.js +0 -2
  116. package/dist/core/slot/index.d.ts +3 -3
  117. package/dist/core/slot/index.js +3 -3
  118. package/dist/core/slot/naming.d.ts +1 -1
  119. package/dist/core/slot/naming.js +1 -1
  120. package/dist/core/slot/platform-fields.d.ts +4 -4
  121. package/dist/core/slot/platform-fields.js +5 -5
  122. package/dist/core/slot/schema-compat.d.ts +8 -7
  123. package/dist/core/slot/schema-compat.js +22 -31
  124. package/dist/core/slot/schema.d.ts +3 -59
  125. package/dist/core/slot/schema.js +0 -15
  126. package/dist/core/slot/types.d.ts +4 -10
  127. package/dist/core/slot/value.d.ts +2 -0
  128. package/dist/core/slot/value.js +6 -2
  129. package/dist/dev-overlay/protocol.d.ts +38 -5
  130. package/dist/fn-context/build-context.js +10 -4
  131. package/dist/fn-context/fake/index.d.ts +2 -0
  132. package/dist/fn-context/fake/index.js +5 -3
  133. package/dist/fn-context/fn-params.d.ts +2 -5
  134. package/dist/fn-context/fn-params.js +6 -9
  135. package/dist/fn-context/index.d.ts +1 -1
  136. package/dist/fn-context/make-ctx.js +5 -4
  137. package/dist/fn-context/rpc/case-orchestrator-tools.d.ts +11 -3
  138. package/dist/fn-context/rpc/case-orchestrator-tools.js +0 -1
  139. package/dist/fn-context/rpc/errors.d.ts +13 -0
  140. package/dist/fn-context/rpc/errors.js +22 -0
  141. package/dist/fn-context/rpc/index.d.ts +1 -1
  142. package/dist/fn-context/rpc/index.js +1 -1
  143. package/dist/fn-context/rpc/integration/index.d.ts +11 -1
  144. package/dist/fn-context/rpc/log.d.ts +11 -1
  145. package/dist/fn-context/rpc/platform.d.ts +11 -1
  146. package/dist/fn-context/rpc/records.d.ts +13 -6
  147. package/dist/fn-context/rpc/records.js +2 -4
  148. package/dist/fn-context/rpc/trpc-client.d.ts +42 -0
  149. package/dist/fn-context/rpc/trpc-client.js +151 -22
  150. package/dist/fn-context/rpc/trpc-init.d.ts +19 -1
  151. package/dist/fn-context/rpc/trpc-init.js +10 -4
  152. package/dist/fn-context/rpc/trpc-router-factory.d.ts +66 -8
  153. package/dist/fn-context/run-scope.js +1 -1
  154. package/dist/fn-context/types.d.ts +20 -6
  155. package/dist/index.d.ts +11 -12
  156. package/dist/index.js +5 -8
  157. package/dist/logger.d.ts +4 -0
  158. package/dist/logger.js +8 -0
  159. package/dist/platform.d.ts +32 -33
  160. package/dist/platform.js +23 -22
  161. package/dist/testing-compile-child.d.ts +2 -0
  162. package/dist/testing-compile-child.js +20 -0
  163. package/dist/testing.d.ts +368 -0
  164. package/dist/testing.js +439 -0
  165. package/dist/voice-service/actions.d.ts +12 -35
  166. package/dist/voice-service/index.d.ts +2 -0
  167. package/dist/voice-service/index.js +2 -0
  168. package/dist/voice-service/protocol.d.ts +604 -0
  169. package/dist/voice-service/protocol.js +67 -0
  170. package/dist/voice-service/types.d.ts +16 -6
  171. package/dist/worker.d.ts +2 -1
  172. package/dist/worker.js +2 -1
  173. package/package.json +20 -16
  174. package/dist/core/case-orchestrator/follow-ups.d.ts +0 -31
  175. package/dist/core/case-orchestrator/follow-ups.js +0 -103
  176. package/dist/core/case-orchestrator/tools.d.ts +0 -29
  177. package/dist/core/case-orchestrator/tools.js +0 -31
  178. package/dist/core/case-orchestrator/validate.d.ts +0 -11
  179. package/dist/core/case-orchestrator/validate.js +0 -63
  180. package/dist/core/functions/define/bind.d.ts +0 -25
  181. package/dist/core/functions/define/bind.js +0 -14
  182. package/dist/core/functions/define/case.d.ts +0 -9
  183. package/dist/core/functions/define/case.js +0 -13
  184. package/dist/core/functions/define/events.d.ts +0 -20
  185. package/dist/core/functions/define/events.js +0 -21
  186. package/dist/core/functions/define/no-case.d.ts +0 -4
  187. package/dist/core/functions/define/no-case.js +0 -9
  188. package/dist/core/functions/func-ref.d.ts +0 -25
  189. package/dist/core/functions/func-ref.js +0 -20
@@ -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 free-form document: objects are blocks, arrays are
7
- * lists, a slot is a cell, `field(slot, …)` a cell with page settings, other plain values read-only
8
- * content, and actions (`createRow`, `deleteRow`, `defineAction`) buttons. The platform turns the
9
- * document into a `RenderedPage` (`render.ts`).
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
- function agree(options) {
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("followUps.agree({ channels }): name at least one of 'call', 'sms', 'email'");
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 agreed follow-ups with the button that books one. */
35
- export function pageFollowUps(list) {
36
- return Object.assign([...list], { agree });
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
- function message(channel, key, options) {
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.bridge !== undefined)
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 keypad(key, options) {
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 announce(key, text) {
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 voicemail(text, options = {}) {
146
- if (typeof text !== 'string' || !text.trim())
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 end(options = {}) {
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 refField(slot, options = {}) {
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 field(slot, options = {}) {
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
- const place = ((prefill) => make(toPrefill((prefill ?? {}))));
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 createRow(table, options) {
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 deleteRow(row, options = {}) {
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
- /** What the compiled page script evaluates to: renders the page, or runs one of its actions. */
420
- export function pageEntry(mod) {
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
- return { channels: Object.keys(exported.channels ?? {}) };
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: { transferTo, keypad, announce, voicemail, previous: arg.call?.previous ?? null, farEnd: arg.call?.farEnd ?? null } }
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
- followUps: pageFollowUps(arg.followUps ?? []),
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
- return main(ctx);
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 action = ACTIONS.get(arg.name);
466
- if (!action)
467
- throw new Error(`no action named ${arg.name}`);
468
- return action.run(arg.args, {
469
- db: dbProxy('readWrite'),
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
@@ -0,0 +1,4 @@
1
+ export function isEnvelope(value) {
2
+ return typeof value === 'object' && value !== null && value.$envelope === 1;
3
+ }
4
+ //# sourceMappingURL=envelope.js.map