@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
@@ -2,7 +2,7 @@ import { spawn } from 'node:child_process';
2
2
  import { existsSync, readFileSync, realpathSync } from 'node:fs';
3
3
  import { createRequire } from 'node:module';
4
4
  import path from 'node:path';
5
- import { generateAll, generateCompany, listSequenceIds } from './codegen/generate.js';
5
+ import { generateAll } from './codegen/generate.js';
6
6
  import { RUNTIME_PACKAGE, isCompanyApiFile } from './company-imports.js';
7
7
  /**
8
8
  * The TypeScript this runtime pins: the check never depends on whether, or which, `typescript` the repo
@@ -71,7 +71,7 @@ async function tsconfigImportIssue(tsc, root) {
71
71
  return null;
72
72
  }
73
73
  /**
74
- * The repository's type check, as `pnpm typecheck` runs it locally: `type:gen`, then `tsc --noEmit`
74
+ * The repository's type check, as `npm run typecheck` runs it locally: `type:gen`, then `tsc --noEmit`
75
75
  * with the repo's own `tsconfig.json`. With no sequence it writes only the root `__gen` (the local
76
76
  * `type:gen` needs a sequence). The compile runs it itself, so a type error blocks a release in every
77
77
  * environment; the platform never calls the repo's `typecheck` script.
@@ -83,10 +83,7 @@ export async function typecheckRepo(repoRoot, onLog) {
83
83
  }
84
84
  // type:gen first. A repository with no sequence still releases its company code (22 §11): only the
85
85
  // root __gen is written then, which its root code's `ctx.api.db` is typed by.
86
- if (listSequenceIds(repoRoot).length > 0)
87
- await generateAll(repoRoot);
88
- else
89
- await generateCompany(repoRoot);
86
+ await generateAll(repoRoot);
90
87
  const { version, tsc } = pinnedTypescript();
91
88
  onLog?.(`Type checking with TypeScript ${version}...`);
92
89
  // tsc names files relative to its working directory, which is the real path (macOS's /var is a link):
@@ -2,6 +2,7 @@ import type { CaseOrchestratorManifest } from '../core/case-orchestrator/types.j
2
2
  import type { ScheduleManifest } from '../core/functions/schedules.js';
3
3
  import type { FunctionRegistry } from '../core/functions/types.js';
4
4
  import type { OfficeHours } from '../core/office-hours/index.js';
5
+ import type { ActionParameters } from '../core/page/define.js';
5
6
  import type { CompiledExperiments, CompiledVariables } from '../core/sequence/index.js';
6
7
  import type { CompiledSlotSchema } from '../core/slot/types.js';
7
8
  export type RuntimePackageSource = 'local-file' | 'npm';
@@ -61,12 +62,15 @@ export type SequenceBuild = {
61
62
  contextPage: {
62
63
  code: string;
63
64
  channels: string[];
65
+ /** The functions' parameters, from their `args` types (38 §2.4): what a function button has filled. */
66
+ functions?: Record<string, {
67
+ parameters: ActionParameters;
68
+ }>;
69
+ /** The page's actions' parameters, from their `args` types (38 §2.9): what a page action button has filled. */
70
+ actions?: Record<string, {
71
+ parameters: ActionParameters;
72
+ }>;
64
73
  };
65
- /**
66
- * The sequence's recurring jobs (`sequences/<id>/schedules.ts`, 25 §6.4), by name; each one's code is
67
- * `functions['schedule.<name>']`. Absent: none.
68
- */
69
- schedules?: ScheduleManifest;
70
74
  };
71
75
  /**
72
76
  * The company build (22-sequence-as-business-unit §4.1): one release compiles the whole repository
@@ -105,4 +109,36 @@ export type CompanyPart = {
105
109
  };
106
110
  /** Where company-level functions live, in place of a sequence's directory name. */
107
111
  export declare const COMPANY_SCOPE = "@company";
112
+ /** The page script's name among its sequence's functions (its `functionKey`): reserved, no function takes it. */
113
+ export declare const CONTEXT_PAGE_FUNCTION = "contextPage";
114
+ /**
115
+ * The name the platform knows a piece of compiled company code by, wherever it runs: its scope (the
116
+ * sequence's directory name, or {@link COMPANY_SCOPE}), then its name. A Worker function is invoked
117
+ * by it (the company Worker's registry is keyed by it); the page script, run in the page sandbox, is
118
+ * `<sequence>/contextPage`. Directory names never contain `/` or `@`, so names of different scopes
119
+ * cannot meet.
120
+ */
121
+ export declare function functionKey(scope: string, name: string): string;
122
+ /**
123
+ * The file a piece of code's stack frames name, by its {@link functionKey}: its `//# sourceURL` in the
124
+ * company Worker, the file name the page sandbox runs it as. Its source map is found by that key.
125
+ */
126
+ export declare function functionScriptFile(key: string): string;
127
+ /**
128
+ * The source map of one compiled function or page script (v3, no `sourcesContent`): where a position
129
+ * in its `code` was in the company's own files, by path from the repository root. Every other source
130
+ * (the compile's entry, the runtime, dependencies) is `null`, the platform's code. Kept out of the
131
+ * build, which is read on every run: a failure's stack is turned into the company's with it.
132
+ */
133
+ export type FunctionSourceMap = {
134
+ version: 3;
135
+ sources: Array<string | null>;
136
+ names: string[];
137
+ mappings: string;
138
+ };
139
+ /**
140
+ * Source maps of compiled code by its {@link functionKey}: a sequence's functions, events and page
141
+ * script (`<sequence>/contextPage`), the company's functions (`@company/<name>`).
142
+ */
143
+ export type SourceMaps = Record<string, FunctionSourceMap>;
108
144
  //# sourceMappingURL=types.d.ts.map
@@ -1,3 +1,22 @@
1
1
  /** Where company-level functions live, in place of a sequence's directory name. */
2
2
  export const COMPANY_SCOPE = '@company';
3
+ /** The page script's name among its sequence's functions (its `functionKey`): reserved, no function takes it. */
4
+ export const CONTEXT_PAGE_FUNCTION = 'contextPage';
5
+ /**
6
+ * The name the platform knows a piece of compiled company code by, wherever it runs: its scope (the
7
+ * sequence's directory name, or {@link COMPANY_SCOPE}), then its name. A Worker function is invoked
8
+ * by it (the company Worker's registry is keyed by it); the page script, run in the page sandbox, is
9
+ * `<sequence>/contextPage`. Directory names never contain `/` or `@`, so names of different scopes
10
+ * cannot meet.
11
+ */
12
+ export function functionKey(scope, name) {
13
+ return `${scope}/${name}`;
14
+ }
15
+ /**
16
+ * The file a piece of code's stack frames name, by its {@link functionKey}: its `//# sourceURL` in the
17
+ * company Worker, the file name the page sandbox runs it as. Its source map is found by that key.
18
+ */
19
+ export function functionScriptFile(key) {
20
+ return `fn/${key}.js`;
21
+ }
3
22
  //# sourceMappingURL=types.js.map
@@ -0,0 +1,177 @@
1
+ /**
2
+ * An integration test's calls in text (68 §4): a test customer calls one of the environment's simulated
3
+ * lines and talks in text, through the backend's `cli-auth/exec/call/*`. What the AI says comes back as
4
+ * text, what it does on the line (a keypad, a transfer, a voicemail) as an event the test answers. And the
5
+ * customer's side (68 §5.2): their texts and emails in, what the environment sent them, a call ringing them.
6
+ */
7
+ import type { TurnSummary } from '../turns/index.js';
8
+ export type TextCallEvent = {
9
+ kind: 'keypad';
10
+ key: string;
11
+ say: string;
12
+ digits: string[];
13
+ } | {
14
+ kind: 'transfer';
15
+ to: string;
16
+ mode: string;
17
+ say: string;
18
+ language?: string;
19
+ } | {
20
+ kind: 'voicemail';
21
+ text: string;
22
+ } | {
23
+ kind: 'language';
24
+ language: string;
25
+ } | {
26
+ kind: 'listen';
27
+ ms: number;
28
+ } | {
29
+ kind: 'far_end';
30
+ farEnd: string;
31
+ decision: string;
32
+ };
33
+ export type TextCallEnd = {
34
+ /** `ai`: the AI ended it; `hangup`: the test did; `transfer`: a person took it; `no-answer` / `busy`: a call to them not taken. */
35
+ by: 'ai' | 'hangup' | 'transfer' | 'no-answer' | 'busy';
36
+ /** The Orchestrator, run once the call's `call.ended` was handled: its status and the follow-ups it booked. */
37
+ orchestrator: {
38
+ status: string;
39
+ failureCode?: string;
40
+ scheduled: unknown[];
41
+ cancelled: unknown[];
42
+ } | null;
43
+ };
44
+ /** What a step of the call came to. */
45
+ export type TextCallStep = {
46
+ callId: string;
47
+ /** What the AI said, in order. */
48
+ said: string[];
49
+ /** What it did on the line that a voice service would have done. */
50
+ events: TextCallEvent[];
51
+ /** The turns this step ran: what was heard, said, pressed and written. */
52
+ turns: TurnSummary[];
53
+ /** How the call ended, when it ended in this step. */
54
+ ended: TextCallEnd | null;
55
+ };
56
+ /** An open call in text. */
57
+ export type TextCall = {
58
+ callId: string;
59
+ /** The opening: what the AI said first. */
60
+ opening: TextCallStep;
61
+ /** The customer says `text`; the AI's turn on it. */
62
+ say(text: string): Promise<TextCallStep>;
63
+ /** The customer presses `value` for the keypad asked for (`key`: the last `keypad` event's when left out). */
64
+ press(value: string, key?: string): Promise<TextCallStep>;
65
+ /** The transfer asked for: `answered` (the person took it; the call ends for the AI) or `failed` (the AI goes on). */
66
+ transfer(outcome: 'answered' | 'failed'): Promise<TextCallStep>;
67
+ /** The customer hangs up: the wrap-up, `call.ended` and the Orchestrator run. */
68
+ hangup(): Promise<TextCallStep>;
69
+ /** Whether the call has ended. */
70
+ readonly ended: boolean;
71
+ };
72
+ type Post = <T>(what: string, body: unknown) => Promise<T>;
73
+ /** A test customer's number: North America's fictional range, the half simulated lines never take. */
74
+ export declare function testCustomerPhone(): string;
75
+ /** A test customer's email address: on the reserved `.test` domain, where simulated mailboxes write. */
76
+ export declare function testCustomerEmail(): string;
77
+ /** Opens a call from `from` to the simulated line `to`, and the object to go on with it. */
78
+ export declare function openTextCall(post: Post, input: {
79
+ from: string;
80
+ to: string;
81
+ }): Promise<TextCall>;
82
+ /** What a simulated line or mailbox sent a test customer, or a call ringing them. */
83
+ export type InboxItem = {
84
+ kind: 'sms';
85
+ id: string;
86
+ from: string;
87
+ line: string | null;
88
+ body: string;
89
+ at: string;
90
+ } | {
91
+ kind: 'email';
92
+ id: string;
93
+ from: string;
94
+ mailbox: string | null;
95
+ subject: string;
96
+ body: string;
97
+ threadId: string | null;
98
+ at: string;
99
+ } | {
100
+ kind: 'call';
101
+ id: string;
102
+ from: string;
103
+ line: string | null;
104
+ at: string;
105
+ status: 'ringing';
106
+ };
107
+ /** What a text or an email from a test customer came to: the run's turns, and what came back to them. */
108
+ export type SentResult = {
109
+ /** `debounce: 'real'`: stored and queued; the worker runs it once the texts stop coming, the reply comes to the inbox. */
110
+ queued?: boolean;
111
+ turns: TurnSummary[];
112
+ replies: InboxItem[];
113
+ orchestrator: TextCallEnd['orchestrator'];
114
+ };
115
+ /**
116
+ * A test customer (68 §5.2): a number and an address no carrier or provider has. They text and email the
117
+ * environment's simulated lines and mailboxes, find what it sent them in their inbox, and answer or let
118
+ * ring the calls it makes to them, as a person would, whenever the environment gets to it.
119
+ */
120
+ export type TestCustomer = {
121
+ phone: string;
122
+ email: string;
123
+ /**
124
+ * They text the simulated line `to` (its name or number, `Main line` when left out); the run it started,
125
+ * now. `debounce: 'real'`: as a carrier's text, handled by the worker once their texts stop coming for
126
+ * 15 s (several in a row are one batch, one run): it returns at once (`queued`), the reply comes to
127
+ * `next('sms')`.
128
+ */
129
+ text(body: string, options?: {
130
+ to?: string;
131
+ orchestrator?: 'now' | 'debounced';
132
+ debounce?: 'skip' | 'real';
133
+ }): Promise<SentResult>;
134
+ /** They email the simulated mailbox `to` (its name or address, `Support` when left out); `threadId` (an inbox email's) replies in its thread. */
135
+ sendEmail(input: {
136
+ body: string;
137
+ subject?: string;
138
+ to?: string;
139
+ threadId?: string | null;
140
+ orchestrator?: 'now' | 'debounced';
141
+ }): Promise<SentResult>;
142
+ /** What was sent to them since `since` (ISO; since this object was made when left out), oldest first, ringing calls included. */
143
+ inbox(since?: string): Promise<InboxItem[]>;
144
+ /**
145
+ * Waits for the next thing of `kind` sent to them that this object has not handed out yet (a scheduled text
146
+ * coming due, a call ringing): checked every 2 s, until `timeoutMs` (60 s when left out), then it fails.
147
+ */
148
+ next<K extends InboxItem['kind']>(kind: K, options?: {
149
+ timeoutMs?: number;
150
+ match?: (item: Extract<InboxItem, {
151
+ kind: K;
152
+ }>) => boolean;
153
+ }): Promise<Extract<InboxItem, {
154
+ kind: K;
155
+ }>>;
156
+ /** They pick up a call ringing them: `as` a person (the AI opens), or their voicemail, a screener, a menu. */
157
+ answer(call: {
158
+ id: string;
159
+ } | string, options?: {
160
+ as?: 'human' | 'voicemail' | 'screening' | 'ivr';
161
+ said?: string;
162
+ orchestrator?: 'now' | 'debounced';
163
+ }): Promise<TextCall>;
164
+ /** They let it ring (`no-answer`) or are busy: the attempt ends as a carrier would end it, the Orchestrator runs. */
165
+ decline(call: {
166
+ id: string;
167
+ } | string, options?: {
168
+ status?: 'no-answer' | 'busy';
169
+ orchestrator?: 'now' | 'debounced';
170
+ }): Promise<TextCallStep>;
171
+ };
172
+ export declare function testCustomer(post: Post, input?: {
173
+ phone?: string;
174
+ email?: string;
175
+ }): TestCustomer;
176
+ export {};
177
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,96 @@
1
+ /** A test customer's number: North America's fictional range, the half simulated lines never take. */
2
+ export function testCustomerPhone() {
3
+ // Area codes in use: a number of an unassigned one (889) is not taken as a phone number at all.
4
+ const codes = ['206', '212', '213', '303', '305', '312', '404', '415', '503', '512', '602', '617', '702', '713', '808', '917'];
5
+ const npa = codes[Math.floor(Math.random() * codes.length)];
6
+ const xx = 50 + Math.floor(Math.random() * 50);
7
+ return `+1${npa}55501${xx}`;
8
+ }
9
+ /** A test customer's email address: on the reserved `.test` domain, where simulated mailboxes write. */
10
+ export function testCustomerEmail() {
11
+ return `c-${Math.random().toString(36).slice(2, 10)}@customers.sim.test`;
12
+ }
13
+ /** Opens a call from `from` to the simulated line `to`, and the object to go on with it. */
14
+ export async function openTextCall(post, input) {
15
+ const opening = await post('call/open', input);
16
+ if (!opening.callId) {
17
+ throw new Error(`call: the line did not take it (no route, or resolve declined); the caller heard: ${opening.said.join(' ') || 'nothing'}`);
18
+ }
19
+ return textCall(post, opening);
20
+ }
21
+ function textCall(post, opening) {
22
+ let ended = opening.ended !== null;
23
+ let keypad = [...opening.events].reverse().find((e) => e.kind === 'keypad')?.key;
24
+ const step = async (what, body) => {
25
+ if (ended)
26
+ throw new Error(`call ${opening.callId}: it has ended`);
27
+ const result = await post(`call/${what}`, { callId: opening.callId, ...body });
28
+ ended = result.ended !== null;
29
+ keypad = [...result.events].reverse().find((e) => e.kind === 'keypad')?.key ?? keypad;
30
+ return result;
31
+ };
32
+ return {
33
+ callId: opening.callId,
34
+ opening,
35
+ say: (text) => step('say', { text }),
36
+ press: (value, key) => {
37
+ const asked = key ?? keypad;
38
+ if (!asked)
39
+ throw new Error(`call ${opening.callId}: no keypad was asked for; name its key`);
40
+ return step('press', { key: asked, value });
41
+ },
42
+ transfer: (outcome) => step('transfer', { outcome }),
43
+ hangup: () => step('hangup', {}),
44
+ get ended() {
45
+ return ended;
46
+ },
47
+ };
48
+ }
49
+ export function testCustomer(post, input = {}) {
50
+ const phone = input.phone ?? testCustomerPhone();
51
+ const email = input.email ?? testCustomerEmail();
52
+ const madeAt = new Date(Date.now() - 1_000).toISOString();
53
+ const handedOut = new Set();
54
+ const inbox = async (since) => (await post('sim/inbox', { phone, email, since: since ?? madeAt })).items;
55
+ const idOf = (call) => (typeof call === 'string' ? call : call.id);
56
+ const customer = {
57
+ phone,
58
+ email,
59
+ text: (body, options = {}) => post('sim/sms', {
60
+ from: phone,
61
+ to: options.to ?? 'Main line',
62
+ body,
63
+ ...(options.orchestrator ? { orchestrator: options.orchestrator } : {}),
64
+ ...(options.debounce ? { debounce: options.debounce } : {}),
65
+ }),
66
+ sendEmail: (mail) => post('sim/email', {
67
+ from: email,
68
+ to: mail.to ?? 'Support',
69
+ body: mail.body,
70
+ ...(mail.subject !== undefined ? { subject: mail.subject } : {}),
71
+ ...(mail.threadId ? { threadId: mail.threadId } : {}),
72
+ ...(mail.orchestrator ? { orchestrator: mail.orchestrator } : {}),
73
+ }),
74
+ inbox,
75
+ async next(kind, options = {}) {
76
+ const deadline = Date.now() + (options.timeoutMs ?? 60_000);
77
+ for (;;) {
78
+ const found = (await inbox()).find((item) => item.kind === kind && !handedOut.has(item.id) && (!options.match || options.match(item)));
79
+ if (found) {
80
+ handedOut.add(found.id);
81
+ return found;
82
+ }
83
+ if (Date.now() >= deadline)
84
+ throw new Error(`${phone} / ${email}: no ${kind} came within ${options.timeoutMs ?? 60_000} ms`);
85
+ await new Promise((resolve) => setTimeout(resolve, 2_000));
86
+ }
87
+ },
88
+ async answer(call, options = {}) {
89
+ const opening = await post('call/answer', { callId: idOf(call), ...options });
90
+ return textCall(post, opening);
91
+ },
92
+ decline: (call, options = {}) => post('call/decline', { callId: idOf(call), ...options }),
93
+ };
94
+ return customer;
95
+ }
96
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,31 @@
1
+ import type { ApiCall } from '../exec/trace.js';
2
+ import type { LogLine } from '../logs/index.js';
3
+ import { type TurnSummary } from '../turns/index.js';
4
+ /** The case ids in a request or an answer: every `instanceId`, and a started instance's `id`. */
5
+ export declare function casesIn(value: unknown, into: Set<string>, depth?: number): void;
6
+ export type DebugSources = {
7
+ /** A case's timeline and turns (`exec/debug`). */
8
+ caseDebug(instanceId: string): Promise<{
9
+ timeline: Array<{
10
+ at: string;
11
+ kind: string;
12
+ text: string;
13
+ [key: string]: unknown;
14
+ }>;
15
+ turns: TurnSummary[];
16
+ }>;
17
+ /** One turn, with what it began with (`readTurn`). */
18
+ readTurn(turnId: string): Promise<unknown>;
19
+ /** The repository's log lines on a case since a time. */
20
+ logs(instanceId: string, since: string): Promise<LogLine[]>;
21
+ };
22
+ export type DebugTest = {
23
+ name: string;
24
+ error: unknown;
25
+ startedAt: string;
26
+ cases: string[];
27
+ calls: ApiCall[];
28
+ };
29
+ /** Writes the package; its directory. Never throws: a package that could not be read says so in its summary. */
30
+ export declare function writeDebugPackage(root: string, test: DebugTest, sources: DebugSources): Promise<string>;
31
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,119 @@
1
+ /**
2
+ * A test's debug package (68 §9): when an integration test fails (or every test, with `debug: 'always'`),
3
+ * everything that can be seen of the cases it touched is written next to the repository, under
4
+ * `.superpanel/tests/<run>/<test>/`, for a person or an agent to read with `grep` / `jq`:
5
+ *
6
+ * summary.md read this first: the failure, the cases, their turns, the commands to go on with
7
+ * timeline.jsonl the cases' timelines and turns, by time
8
+ * turns/<n>.json each turn: what was heard, said, pressed and written, and what it began with
9
+ * logs.jsonl what this repository's code logged on those cases
10
+ * calls.jsonl the `ctx.api` calls the test made
11
+ *
12
+ * The cases are the ones the test's requests named or were answered with (`instanceId`, a started
13
+ * instance): a test that makes its own contacts and cases (TESTING.md) finds all of its own.
14
+ */
15
+ import { mkdirSync, writeFileSync } from 'node:fs';
16
+ import path from 'node:path';
17
+ import { formatTurnLine } from '../turns/index.js';
18
+ const UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
19
+ /** The case ids in a request or an answer: every `instanceId`, and a started instance's `id`. */
20
+ export function casesIn(value, into, depth = 0) {
21
+ if (!value || typeof value !== 'object' || depth > 6)
22
+ return;
23
+ if (Array.isArray(value)) {
24
+ for (const item of value.slice(0, 200))
25
+ casesIn(item, into, depth + 1);
26
+ return;
27
+ }
28
+ for (const [key, item] of Object.entries(value)) {
29
+ if ((key === 'instanceId' || key === 'currentInstanceId') && typeof item === 'string' && UUID.test(item))
30
+ into.add(item);
31
+ else if (key === 'instance' && item && typeof item === 'object' && typeof item.id === 'string' && UUID.test(item.id))
32
+ into.add(item.id);
33
+ else
34
+ casesIn(item, into, depth + 1);
35
+ }
36
+ }
37
+ const slug = (text) => text.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '').slice(0, 80) || 'test';
38
+ function messageOf(error) {
39
+ if (!error)
40
+ return '';
41
+ if (error instanceof Error)
42
+ return error.message;
43
+ return String(error);
44
+ }
45
+ /** Writes the package; its directory. Never throws: a package that could not be read says so in its summary. */
46
+ export async function writeDebugPackage(root, test, sources) {
47
+ const dir = path.join(root, slug(test.name));
48
+ mkdirSync(path.join(dir, 'turns'), { recursive: true });
49
+ const problems = [];
50
+ const timeline = [];
51
+ const turns = [];
52
+ const logs = [];
53
+ for (const instanceId of test.cases) {
54
+ try {
55
+ const debug = await sources.caseDebug(instanceId);
56
+ for (const entry of debug.timeline)
57
+ timeline.push({ ...entry, source: 'timeline', instanceId });
58
+ for (const turn of debug.turns) {
59
+ turns.push(turn);
60
+ timeline.push({ at: turn.startedAt, source: 'turn', instanceId, kind: `turn:${turn.kind}`, text: formatTurnLine(turn) });
61
+ }
62
+ }
63
+ catch (error) {
64
+ problems.push(`case ${instanceId}: ${messageOf(error)}`);
65
+ }
66
+ try {
67
+ logs.push(...(await sources.logs(instanceId, test.startedAt)));
68
+ }
69
+ catch (error) {
70
+ problems.push(`logs of case ${instanceId}: ${messageOf(error)}`);
71
+ }
72
+ }
73
+ timeline.sort((a, b) => a.at.localeCompare(b.at));
74
+ for (const line of logs)
75
+ timeline.push({ at: line.at, source: 'log', instanceId: '', kind: `log:${line.level}`, text: line.message ?? '' });
76
+ timeline.sort((a, b) => a.at.localeCompare(b.at));
77
+ const ofTest = turns.filter((t) => t.startedAt >= test.startedAt);
78
+ for (const turn of ofTest.slice(-30)) {
79
+ try {
80
+ writeFileSync(path.join(dir, 'turns', `${String(turn.seq).padStart(3, '0')}-${turn.kind}-${turn.id.slice(0, 8)}.json`), `${JSON.stringify(await sources.readTurn(turn.id), null, 2)}\n`);
81
+ }
82
+ catch (error) {
83
+ problems.push(`turn ${turn.id}: ${messageOf(error)}`);
84
+ }
85
+ }
86
+ const jsonl = (rows) => rows.map((row) => JSON.stringify(row)).join('\n') + (rows.length ? '\n' : '');
87
+ writeFileSync(path.join(dir, 'timeline.jsonl'), jsonl(timeline));
88
+ writeFileSync(path.join(dir, 'logs.jsonl'), jsonl(logs));
89
+ writeFileSync(path.join(dir, 'calls.jsonl'), jsonl(test.calls));
90
+ const last = ofTest.at(-1) ?? turns.at(-1);
91
+ const summary = [
92
+ `# ${test.name}`,
93
+ '',
94
+ test.error ? `**Failed:** ${messageOf(test.error).trim()}` : '**Passed.**',
95
+ '',
96
+ `Started ${test.startedAt}. Cases: ${test.cases.length ? test.cases.map((id) => `\`${id}\``).join(', ') : 'none named by the test'}.`,
97
+ '',
98
+ '## Turns in this test',
99
+ '',
100
+ ...(ofTest.length ? ofTest.map((t) => `- ${formatTurnLine(t)}`) : ['- none']),
101
+ '',
102
+ '## Go on with',
103
+ '',
104
+ ...(last
105
+ ? [
106
+ `- the last turn, as it ran: \`npm run turns -- export ${last.id}\` (then \`renderPage(fromTurn(dir))\`)`,
107
+ `- again, on your code as it is now, 5 times, nothing kept: \`npm run turn -- --from ${last.id} --repeat 5\``,
108
+ ]
109
+ : []),
110
+ ...test.cases.map((id) => `- what the code logged on case ${id}: \`npm run logs -- --instance ${id} --since 2h\``),
111
+ '',
112
+ 'Files: `timeline.jsonl` (by time: the cases\' timelines, turns, log lines), `turns/` (each turn whole), `logs.jsonl`, `calls.jsonl` (the test\'s `ctx.api` calls).',
113
+ ...(problems.length ? ['', '## Not read', '', ...problems.map((p) => `- ${p}`)] : []),
114
+ '',
115
+ ].join('\n');
116
+ writeFileSync(path.join(dir, 'summary.md'), summary);
117
+ return dir;
118
+ }
119
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,39 @@
1
+ import { traceApi, type ApiCall } from './trace.js';
2
+ export { traceApi, type ApiCall };
3
+ export type ExecOptions = {
4
+ env?: string;
5
+ instance?: string;
6
+ args?: string;
7
+ url?: string;
8
+ /** Print only the result's JSON (for a script or an agent). */
9
+ json?: boolean;
10
+ };
11
+ /** The run's outcome, as `result.json` keeps it and `--json` prints it. */
12
+ export type ExecResult = {
13
+ ok: boolean;
14
+ file: string;
15
+ environment: {
16
+ id: string;
17
+ name: string;
18
+ };
19
+ instance: {
20
+ id: string;
21
+ sequence: string;
22
+ } | null;
23
+ startedAt: string;
24
+ durationMs: number;
25
+ /** `ctx.api` calls made, and how many of them failed. */
26
+ calls: {
27
+ total: number;
28
+ failed: number;
29
+ };
30
+ value?: unknown;
31
+ error?: {
32
+ message: string;
33
+ stack?: string;
34
+ };
35
+ /** Where the run's files are. */
36
+ dir: string;
37
+ };
38
+ export declare function runExec(repoRoot: string, file: string, options?: ExecOptions): Promise<ExecResult>;
39
+ //# sourceMappingURL=index.d.ts.map