@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
@@ -1,15 +1,18 @@
1
1
  import path from 'node:path';
2
+ import { fileURLToPath } from 'node:url';
2
3
  import { cancel, isCancel, select } from '@clack/prompts';
3
4
  import chokidar from 'chokidar';
4
- import { compileAll, compileCompanyMetadataFromRepo, compileCompanyPart, runtimePackageSource, runtimePackageVersion, } from '../../builder/compile/compile-sequence.js';
5
+ import { compileAll, compileCompanyPart, runtimePackageSource, runtimePackageVersion, } from '../../builder/compile/compile-sequence.js';
5
6
  import { withCompanyImportGraph } from '../../builder/company-import-graph.js';
6
7
  import { findTsModule, requireCompanyResolveModule, requireResolveModule } from '../../builder/paths.js';
7
8
  import { listSequenceIds, loadCompanyWebhook, loadSequenceCaseOrchestrator, WEBHOOK_MODULE, loadCompanyResolver, loadSequenceResolver, loadSequenceEvents, loadSequenceFunctions, } from '../../builder/load.js';
8
9
  import { loadSchedules } from '../../builder/schedules.js';
9
- import { ORCHESTRATOR_CONTACTS_FUNCTION, ORCHESTRATOR_FUNCTION } from '../../core/case-orchestrator/types.js';
10
+ import { loadOnError } from '../../builder/errors.js';
11
+ import { ORCHESTRATOR_FUNCTION } from '../../core/case-orchestrator/types.js';
10
12
  import { WEBHOOK_RECEIVED_FUNCTION } from '../../core/functions/events.js';
11
13
  import { COMPANY_RESOLVE_FUNCTION, RESOLVE_FUNCTION } from '../../core/party/types.js';
12
- import { COMPANY_SCOPE } from '../../builder/types.js';
14
+ import { COMPANY_SCOPE, } from '../../builder/types.js';
15
+ import { realPath } from '../../builder/company-imports.js';
13
16
  import { createSourceComputeHost } from '../../compute-host/source-compute-host.js';
14
17
  import { formatRuntimeVersion } from '../../fn-context/rpc/request-id.js';
15
18
  import { encodeOverlayMessage, OVERLAY_REPLACED_CLOSE_CODE, parseOverlayMessage, } from '../../dev-overlay/protocol.js';
@@ -67,22 +70,18 @@ async function loadOne(repoRoot, sequenceId, generation) {
67
70
  throw new Error(`Failed to compile sequence ${sequenceId}`);
68
71
  }
69
72
  const sequenceDir = path.join(repoRoot, 'sequences', sequenceId);
73
+ // A sequence without `functions/` has none (76 §5.5), as the compile above takes it.
70
74
  const functionsPath = findTsModule(sequenceDir, 'functions');
71
- if (!functionsPath) {
72
- throw new Error(`No functions module for sequence ${sequenceId}`);
73
- }
74
75
  const caseOrchestratorPath = findTsModule(sequenceDir, 'orchestrator');
75
- const authored = await loadSequenceFunctions(functionsPath);
76
+ const authored = functionsPath ? await loadSequenceFunctions(functionsPath) : {};
76
77
  const functions = {
77
78
  ...authored,
78
- [RESOLVE_FUNCTION]: await loadSequenceResolver(requireResolveModule(sequenceDir, sequenceId)),
79
- ...(caseOrchestratorPath ? await caseOrchestratorFunctions(caseOrchestratorPath) : {}),
80
- // The jobs of its schedules.ts run here too while serving (25 §6.4).
81
- ...(await loadSchedules(sequenceDir, `sequences/${sequenceId}/schedules.ts`))?.functions,
79
+ [RESOLVE_FUNCTION]: (await loadSequenceResolver(requireResolveModule(sequenceDir, sequenceId))),
80
+ ...(caseOrchestratorPath ? { [ORCHESTRATOR_FUNCTION]: await loadSequenceCaseOrchestrator(caseOrchestratorPath) } : {}),
82
81
  };
83
82
  const eventsPath = findTsModule(sequenceDir, 'events');
84
83
  const events = eventsPath ? await loadSequenceEvents(eventsPath) : {};
85
- return { build: compiled.build, functions, events, generation };
84
+ return { build: compiled.build, sourceMaps: compiled.sourceMaps, functions, events, generation };
86
85
  });
87
86
  }
88
87
  export async function loadCompany(repoRoot) {
@@ -90,23 +89,15 @@ export async function loadCompany(repoRoot) {
90
89
  const webhookPath = findTsModule(repoRoot, WEBHOOK_MODULE);
91
90
  return {
92
91
  part: await compileCompanyPart(repoRoot, { includeCode: false }),
93
- companyMetadata: await compileCompanyMetadataFromRepo(repoRoot),
94
92
  functions: {
95
- [COMPANY_RESOLVE_FUNCTION]: await loadCompanyResolver(requireCompanyResolveModule(repoRoot)),
96
- ...(webhookPath ? { [WEBHOOK_RECEIVED_FUNCTION]: await loadCompanyWebhook(webhookPath) } : {}),
97
- ...(await loadSchedules(repoRoot, 'schedules.ts'))?.functions,
93
+ [COMPANY_RESOLVE_FUNCTION]: (await loadCompanyResolver(requireCompanyResolveModule(repoRoot))),
94
+ ...(webhookPath ? { [WEBHOOK_RECEIVED_FUNCTION]: (await loadCompanyWebhook(webhookPath)) } : {}),
95
+ ...(await loadSchedules(repoRoot))?.functions,
96
+ ...(await loadOnError(repoRoot))?.functions,
98
97
  },
99
98
  };
100
99
  });
101
100
  }
102
- /** orchestrator.ts's two functions, as the build names them. */
103
- async function caseOrchestratorFunctions(path) {
104
- const { orchestrator, contacts } = await loadSequenceCaseOrchestrator(path);
105
- return {
106
- [ORCHESTRATOR_FUNCTION]: { kind: 'orchestrator', main: orchestrator },
107
- [ORCHESTRATOR_CONTACTS_FUNCTION]: { kind: 'orchestrator', main: contacts },
108
- };
109
- }
110
101
  /** The repository's sequences, compiled; a listed sequence missing from the repository is left out. */
111
102
  async function loadSequences(repoRoot, sequenceIds, generation) {
112
103
  return withCompanyImportGraph(repoRoot, async () => {
@@ -209,7 +200,54 @@ export function pushOverlay(ws, company, sequences, generation) {
209
200
  company: company.part,
210
201
  sequences: Object.fromEntries([...sequences].map(([sequenceId, sequence]) => [sequenceId, sequence.build])),
211
202
  };
212
- send(ws, { type: 'overlay', generation, build, companyMetadata: company.companyMetadata });
203
+ const sourceMaps = Object.assign({}, ...[...sequences.values()].map((sequence) => sequence.sourceMaps));
204
+ send(ws, { type: 'overlay', generation, build, sourceMaps });
205
+ }
206
+ /** A stack frame as V8 prints it: `at name (file:line:col)` or `at file:line:col`. */
207
+ const STACK_FRAME = /^\s*at (?:(.*?) \()?(.+?):(\d+):(\d+)\)?$/;
208
+ /**
209
+ * What the company is shown of a failed function's stack (the backend's company logs): the error's first
210
+ * line and the frames of the repository's own files, by their path from its root. tsx runs the original
211
+ * files, so their lines are already the company's. Undefined when no frame is the repository's.
212
+ */
213
+ export function repositoryStack(error, repoRoot) {
214
+ if (!(error instanceof Error) || !error.stack)
215
+ return undefined;
216
+ const lines = error.stack.split('\n');
217
+ const firstFrame = lines.findIndex((line) => /^\s*at /.test(line));
218
+ if (firstFrame === -1)
219
+ return undefined;
220
+ const roots = [...new Set([path.resolve(repoRoot), realPath(repoRoot)])];
221
+ const frames = lines.slice(firstFrame).flatMap((line) => {
222
+ const frame = STACK_FRAME.exec(line);
223
+ const file = frame ? repositoryFile(frame[2], roots) : null;
224
+ if (!frame || !file)
225
+ return [];
226
+ return [` at ${frame[1] ? `${frame[1]} (` : ''}${file}:${frame[3]}:${frame[4]}${frame[1] ? ')' : ''}`];
227
+ });
228
+ return frames.length > 0 ? [...lines.slice(0, firstFrame), ...frames].join('\n') : undefined;
229
+ }
230
+ /** A frame's file as a path from the repository root; null outside its own files (node_modules included). */
231
+ function repositoryFile(location, roots) {
232
+ let file = location;
233
+ if (file.startsWith('file://')) {
234
+ try {
235
+ // The import graph stamps the URLs it loads (`?spgen=…`).
236
+ file = fileURLToPath(file.replace(/[?#].*$/, ''));
237
+ }
238
+ catch {
239
+ return null;
240
+ }
241
+ }
242
+ if (!path.isAbsolute(file))
243
+ return null;
244
+ for (const root of roots) {
245
+ const relative = path.relative(root, file);
246
+ if (relative && !relative.startsWith('..') && !path.isAbsolute(relative) && !relative.split(path.sep).includes('node_modules')) {
247
+ return relative.split(path.sep).join('/');
248
+ }
249
+ }
250
+ return null;
213
251
  }
214
252
  function closeDetail(event) {
215
253
  return event.reason ? `${event.code} ${event.reason}` : String(event.code);
@@ -318,7 +356,12 @@ function waitAuthOk(ws) {
318
356
  ws.addEventListener('close', onClose);
319
357
  });
320
358
  }
321
- function attachExecuteHandler(ws, find, rpcUrl) {
359
+ /**
360
+ * Runs each `execute` the backend sends, unless its deadline has passed: the backend has then reported
361
+ * it as failed (a paused process, a laptop that slept), and running it would repeat its effects when
362
+ * the call is retried. One already started is not interrupted.
363
+ */
364
+ export function attachExecuteHandler(ws, find, rpcUrl, repoRoot) {
322
365
  ws.addEventListener('message', (event) => {
323
366
  void (async () => {
324
367
  let message;
@@ -331,6 +374,22 @@ function attachExecuteHandler(ws, find, rpcUrl) {
331
374
  }
332
375
  if (message.type !== 'execute')
333
376
  return;
377
+ const lateMs = Date.now() - message.deadline;
378
+ if (lateMs > 0) {
379
+ send(ws, {
380
+ type: 'execute.result',
381
+ requestId: message.requestId,
382
+ ok: false,
383
+ error: `expired: the backend stopped waiting ${lateMs} ms ago, so it was not run`,
384
+ notStarted: true,
385
+ });
386
+ logger.warn('execute skipped: it arrived after the backend stopped waiting', {
387
+ sequenceId: message.sequenceId,
388
+ name: message.name,
389
+ lateMs,
390
+ });
391
+ return;
392
+ }
334
393
  const loaded = find(message.sequenceId);
335
394
  if (!loaded) {
336
395
  const error = `unknown sequence ${message.sequenceId}`;
@@ -339,6 +398,7 @@ function attachExecuteHandler(ws, find, rpcUrl) {
339
398
  requestId: message.requestId,
340
399
  ok: false,
341
400
  error,
401
+ notStarted: true,
342
402
  });
343
403
  logger.error('execute failed', { sequenceId: message.sequenceId, name: message.name, error });
344
404
  return;
@@ -350,18 +410,21 @@ function attachExecuteHandler(ws, find, rpcUrl) {
350
410
  }
351
411
  catch (error) {
352
412
  const text = error instanceof Error ? error.message : String(error);
413
+ const stack = repositoryStack(error, repoRoot);
353
414
  send(ws, {
354
415
  type: 'execute.result',
355
416
  requestId: message.requestId,
356
417
  ok: false,
357
418
  error: text,
419
+ ...(stack ? { stack } : {}),
420
+ ...(error instanceof Error && error.name === 'PlatformUnavailableError' ? { platform: true } : {}),
358
421
  });
359
422
  logger.error('execute failed', {
360
423
  sequenceId: message.sequenceId,
361
424
  name: message.name,
362
425
  durationMs: Date.now() - started,
363
426
  error: text,
364
- });
427
+ }, ...(stack ? [`\n${stack}`] : []));
365
428
  return;
366
429
  }
367
430
  send(ws, { type: 'execute.result', requestId: message.requestId, ok: true, result });
@@ -611,7 +674,7 @@ export async function runServe(repoRoot, options = {}) {
611
674
  const authOk = waitAuthOk(ws);
612
675
  send(ws, { type: 'auth', token: authorization.token, environment });
613
676
  await authOk;
614
- attachExecuteHandler(ws, find, rpcUrl);
677
+ attachExecuteHandler(ws, find, rpcUrl, repoRoot);
615
678
  attachNoticeHandler(ws);
616
679
  pushOverlay(ws, company, sequences, generation);
617
680
  delayMs = 500;
@@ -647,4 +710,80 @@ export async function runServe(repoRoot, options = {}) {
647
710
  delayMs = Math.min(delayMs * 2, 5_000);
648
711
  }
649
712
  }
713
+ /**
714
+ * The repository served on `environmentId` as `serve` serves it, once (no file watch, no reconnect): its
715
+ * code loaded through the company import graph (an import the compile refuses fails here), pushed, and
716
+ * every function the environment runs meanwhile run here. Resolves once the environment took it.
717
+ */
718
+ export async function openOverlay(repoRoot, options) {
719
+ if (listSequenceIds(repoRoot).length === 0)
720
+ throw new Error(`No sequences found under ${path.join(repoRoot, 'sequences')}`);
721
+ const company = await loadCompany(repoRoot);
722
+ const sequences = await loadSequences(repoRoot, listSequenceIds(repoRoot), 1);
723
+ const runtimeSource = runtimePackageSource(repoRoot);
724
+ const find = (scope) => {
725
+ if (scope === COMPANY_SCOPE)
726
+ return { functions: company.functions, events: {}, runtimeVersion: runtimePackageVersion(), runtimeSource };
727
+ const loaded = sequences.get(scope);
728
+ return loaded
729
+ ? { functions: loaded.functions, events: loaded.events, runtimeVersion: loaded.build.engineVersion, runtimeSource: loaded.build.runtimeSource }
730
+ : undefined;
731
+ };
732
+ const ws = new WebSocket(overlayWebSocketUrl(options.backendUrl));
733
+ try {
734
+ await waitOpen(ws);
735
+ const authOk = waitAuthOk(ws);
736
+ send(ws, { type: 'auth', token: options.token, environment: options.environmentId });
737
+ await authOk;
738
+ }
739
+ catch (error) {
740
+ ws.close();
741
+ throw error;
742
+ }
743
+ attachExecuteHandler(ws, find, overlayRpcUrl(options.backendUrl), repoRoot);
744
+ attachNoticeHandler(ws);
745
+ // Served once the backend says so (`overlay.ok`): a call made before could still run the release.
746
+ const served = new Promise((resolve, reject) => {
747
+ const timer = setTimeout(() => reject(new Error('the environment did not take the code within 60 s')), 60_000);
748
+ ws.addEventListener('message', (event) => {
749
+ try {
750
+ const message = parseOverlayMessage(String(event.data));
751
+ if (message.type === 'overlay.ok' && message.generation === 1) {
752
+ clearTimeout(timer);
753
+ resolve();
754
+ }
755
+ else if (message.type === 'auth.error') {
756
+ clearTimeout(timer);
757
+ reject(new Error(`serve: ${message.error}`));
758
+ }
759
+ }
760
+ catch {
761
+ // not a message of ours
762
+ }
763
+ });
764
+ ws.addEventListener('close', () => {
765
+ clearTimeout(timer);
766
+ reject(new Error('the environment closed the connection before it took the code'));
767
+ });
768
+ });
769
+ pushOverlay(ws, company, sequences, 1);
770
+ try {
771
+ await served;
772
+ }
773
+ catch (error) {
774
+ ws.close();
775
+ throw error;
776
+ }
777
+ return {
778
+ sequences: [...sequences.keys()],
779
+ close: async () => {
780
+ if (ws.readyState === WebSocket.CLOSED)
781
+ return;
782
+ await new Promise((resolve) => {
783
+ ws.addEventListener('close', () => resolve(), { once: true });
784
+ ws.close();
785
+ });
786
+ },
787
+ };
788
+ }
650
789
  //# sourceMappingURL=index.js.map
@@ -0,0 +1,105 @@
1
+ import type { PageChannel } from '../../core/page/define.js';
2
+ export type TurnSummary = {
3
+ id: string;
4
+ seq: number;
5
+ runId: string;
6
+ callId: string | null;
7
+ instanceId: string;
8
+ /** customer | assistant | silence | wrap_up | run */
9
+ kind: string;
10
+ channel: string;
11
+ heard: string | null;
12
+ said: string[];
13
+ /** The model's thought at each step: what it heard, what it does, what comes next. */
14
+ thoughts: string[];
15
+ actions: Array<{
16
+ key: string;
17
+ name: string;
18
+ kind: string;
19
+ status: string;
20
+ args: unknown;
21
+ result: unknown;
22
+ error: string | null;
23
+ }>;
24
+ writes: Array<{
25
+ rowId: string;
26
+ table: string;
27
+ field: string;
28
+ value: unknown;
29
+ state: string | null;
30
+ }>;
31
+ outcome: string | null;
32
+ error: string | null;
33
+ startedAt: string;
34
+ endedAt: string | null;
35
+ /** Whether its snapshot is still kept (30 days). */
36
+ snapshot: boolean;
37
+ };
38
+ export type TurnRecord = TurnSummary & {
39
+ format: 1;
40
+ sequence: string;
41
+ steps: Array<{
42
+ thought: string | null;
43
+ say: string;
44
+ actions: unknown[];
45
+ outcomes: Array<{
46
+ action: string;
47
+ status: string;
48
+ detail?: string;
49
+ }>;
50
+ }>;
51
+ platformRules: string;
52
+ };
53
+ export type TurnSnapshot = {
54
+ pages: Array<{
55
+ seq: number;
56
+ step: number;
57
+ auto: boolean;
58
+ text: string;
59
+ }>;
60
+ data: Record<string, Array<Record<string, unknown>>>;
61
+ context: Record<string, unknown> | null;
62
+ history: unknown;
63
+ start: unknown;
64
+ };
65
+ export type TurnRead = {
66
+ turn: TurnRecord;
67
+ snapshot: TurnSnapshot | null;
68
+ };
69
+ export type TurnsQuery = {
70
+ instanceId?: string;
71
+ runId?: string;
72
+ callId?: string;
73
+ since?: string;
74
+ limit?: number;
75
+ };
76
+ /** POST to an exec route with the run token; the backend's error as the message. */
77
+ export declare function postExec<T>(backendUrl: string, runToken: string, what: string, body: unknown): Promise<T>;
78
+ /** The turn's directory: `turn.json`, `page/<n>.txt`, `data.json`, `context.json`, `history.json`, `start.json`. */
79
+ export declare function writeTurnExport(dir: string, read: TurnRead): string;
80
+ /** A turn's directory read back. Its snapshot is null when it was exported after the snapshot was dropped. */
81
+ export declare function readTurnExport(dir: string): TurnRead;
82
+ /** One line: where it is in the run, who spoke, what was said, what it did. */
83
+ export declare function formatTurnLine(turn: TurnSummary): string;
84
+ /** The channel of a page a turn's context names. */
85
+ export declare function channelOf(turn: TurnRecord): PageChannel;
86
+ type TurnsOptions = {
87
+ env?: string;
88
+ instance?: string;
89
+ call?: string;
90
+ run?: string;
91
+ since?: string;
92
+ limit?: number;
93
+ json?: boolean;
94
+ url?: string;
95
+ };
96
+ /** `turns`: the turns, one line each (or one JSON each with `--json`). */
97
+ export declare function runTurns(repoRoot: string, options: TurnsOptions): Promise<void>;
98
+ /** `turns export <turnId>`: the turn written to `.superpanel/turns/<turnId>/`; prints the directory. */
99
+ export declare function runTurnExport(repoRoot: string, turnId: string, options: {
100
+ env?: string;
101
+ url?: string;
102
+ out?: string;
103
+ }): Promise<void>;
104
+ export {};
105
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,128 @@
1
+ /**
2
+ * `turns`: the turns of a case, a run or a call in a non-prod environment (68 §7.4), one line each, and
3
+ * `turns export <turnId>`: one turn written to `.superpanel/turns/<turnId>/` (its record and snapshot),
4
+ * which `fromTurn` reads back for `renderPage`. The same files an integration test's `exportTurn` writes.
5
+ */
6
+ import { existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync } from 'node:fs';
7
+ import path from 'node:path';
8
+ import { loadCachedServeAuthorization, resolveBackendUrl } from '../auth/auth.js';
9
+ import { apiUrl, requestJson } from '../auth/http.js';
10
+ import { requestExecRun } from '../exec/session.js';
11
+ import { timeOf } from '../logs/index.js';
12
+ /** POST to an exec route with the run token; the backend's error as the message. */
13
+ export async function postExec(backendUrl, runToken, what, body) {
14
+ const { response, payload } = await requestJson(apiUrl(backendUrl, `cli-auth/exec/${what}`), {
15
+ method: 'POST',
16
+ headers: { Authorization: `Bearer ${runToken}`, 'Content-Type': 'application/json' },
17
+ body: JSON.stringify(body),
18
+ });
19
+ if (!response.ok)
20
+ throw new Error(`${what}: ${payload.error ?? `the backend answered ${response.status}`}`);
21
+ return payload;
22
+ }
23
+ /** The turn's directory: `turn.json`, `page/<n>.txt`, `data.json`, `context.json`, `history.json`, `start.json`. */
24
+ export function writeTurnExport(dir, read) {
25
+ mkdirSync(dir, { recursive: true });
26
+ const json = (name, value) => writeFileSync(path.join(dir, name), `${JSON.stringify(value, null, 2)}\n`);
27
+ json('turn.json', read.turn);
28
+ if (read.snapshot) {
29
+ mkdirSync(path.join(dir, 'page'), { recursive: true });
30
+ for (const page of read.snapshot.pages)
31
+ if (!page.auto)
32
+ writeFileSync(path.join(dir, 'page', `${page.seq}.txt`), page.text);
33
+ json('data.json', read.snapshot.data);
34
+ json('context.json', read.snapshot.context);
35
+ json('history.json', read.snapshot.history);
36
+ json('start.json', read.snapshot.start);
37
+ }
38
+ return dir;
39
+ }
40
+ /** A turn's directory read back. Its snapshot is null when it was exported after the snapshot was dropped. */
41
+ export function readTurnExport(dir) {
42
+ const file = (name) => path.join(dir, name);
43
+ if (!existsSync(file('turn.json')))
44
+ throw new Error(`${dir}: no turn.json (export a turn with \`turns export <turnId>\`)`);
45
+ const turn = JSON.parse(readFileSync(file('turn.json'), 'utf8'));
46
+ if (turn.format !== 1)
47
+ throw new Error(`${dir}: turn.json format ${String(turn.format)}; this runtime reads format 1`);
48
+ if (!existsSync(file('data.json')))
49
+ return { turn, snapshot: null };
50
+ const json = (name) => JSON.parse(readFileSync(file(name), 'utf8'));
51
+ const pages = existsSync(file('page'))
52
+ ? readdirSync(file('page'))
53
+ .filter((name) => /^\d+\.txt$/.test(name))
54
+ .map((name) => ({ seq: Number(name.slice(0, -4)), step: 0, auto: false, text: readFileSync(path.join(file('page'), name), 'utf8') }))
55
+ .sort((a, b) => a.seq - b.seq)
56
+ : [];
57
+ return {
58
+ turn,
59
+ snapshot: {
60
+ pages,
61
+ data: json('data.json'),
62
+ context: json('context.json'),
63
+ history: json('history.json'),
64
+ start: json('start.json'),
65
+ },
66
+ };
67
+ }
68
+ const clip = (text, max = 80) => {
69
+ const one = (text ?? '').replace(/\s+/g, ' ').trim();
70
+ return one.length > max ? `${one.slice(0, max - 1)}…` : one;
71
+ };
72
+ /** One line: where it is in the run, who spoke, what was said, what it did. */
73
+ export function formatTurnLine(turn) {
74
+ const parts = [`#${turn.seq} ${turn.kind}`, turn.id];
75
+ if (turn.heard)
76
+ parts.push(`heard "${clip(turn.heard)}"`);
77
+ if (turn.said.length)
78
+ parts.push(`said "${clip(turn.said.join(' '))}"`);
79
+ if (turn.actions.length)
80
+ parts.push(`did ${turn.actions.map((a) => `${a.key}:${a.status}`).join(', ')}`);
81
+ if (turn.writes.length)
82
+ parts.push(`wrote ${turn.writes.map((w) => `${w.table}.${w.field}`).join(', ')}`);
83
+ if (turn.outcome && turn.outcome !== 'done')
84
+ parts.push(turn.outcome);
85
+ if (turn.error)
86
+ parts.push(`error: ${clip(turn.error)}`);
87
+ return parts.join(' ');
88
+ }
89
+ /** The channel of a page a turn's context names. */
90
+ export function channelOf(turn) {
91
+ return turn.channel === 'call' || turn.channel === 'sms' || turn.channel === 'email' ? turn.channel : 'call';
92
+ }
93
+ async function execToken(repoRoot, options) {
94
+ const backendUrl = resolveBackendUrl(repoRoot, options.url);
95
+ const authorization = await loadCachedServeAuthorization(backendUrl);
96
+ if (!authorization)
97
+ throw new Error('not logged in: run `superpanel-sdk-runtime login` first');
98
+ const run = await requestExecRun(backendUrl, authorization.token, options.env ?? 'sandbox', undefined);
99
+ return { backendUrl, runToken: run.runToken };
100
+ }
101
+ /** `turns`: the turns, one line each (or one JSON each with `--json`). */
102
+ export async function runTurns(repoRoot, options) {
103
+ if (!options.instance && !options.call && !options.run)
104
+ throw new Error('turns: name the case (--instance), the call (--call) or the run (--run)');
105
+ const { backendUrl, runToken } = await execToken(repoRoot, options);
106
+ const since = timeOf(options.since);
107
+ const { turns } = await postExec(backendUrl, runToken, 'turns', {
108
+ ...(options.instance ? { instanceId: options.instance } : {}),
109
+ ...(options.call ? { callId: options.call } : {}),
110
+ ...(options.run ? { runId: options.run } : {}),
111
+ ...(since ? { since } : {}),
112
+ ...(options.limit ? { limit: options.limit } : {}),
113
+ });
114
+ if (!turns.length)
115
+ console.error('no turns');
116
+ for (const turn of turns)
117
+ console.log(options.json ? JSON.stringify(turn) : formatTurnLine(turn));
118
+ }
119
+ /** `turns export <turnId>`: the turn written to `.superpanel/turns/<turnId>/`; prints the directory. */
120
+ export async function runTurnExport(repoRoot, turnId, options) {
121
+ const { backendUrl, runToken } = await execToken(repoRoot, options);
122
+ const read = await postExec(backendUrl, runToken, 'turns/read', { turnId });
123
+ const dir = writeTurnExport(path.resolve(repoRoot, options.out ?? path.join('.superpanel', 'turns', turnId)), read);
124
+ if (!read.snapshot)
125
+ console.error(`turn ${turnId}: its snapshot is no longer kept (30 days); only turn.json was written`);
126
+ console.log(dir);
127
+ }
128
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,13 @@
1
+ export type TurnCliOptions = {
2
+ env?: string;
3
+ url?: string;
4
+ from?: string;
5
+ instance?: string;
6
+ channel?: 'sms' | 'email' | 'call';
7
+ say?: string;
8
+ caller?: string;
9
+ repeat?: number;
10
+ json?: boolean;
11
+ };
12
+ export declare function runTurnCli(repoRoot: string, options: TurnCliOptions): Promise<void>;
13
+ //# sourceMappingURL=run.d.ts.map
@@ -0,0 +1,57 @@
1
+ /**
2
+ * `superpanel-sdk-runtime turn` (68 §8.1): one turn as a shadow, from the command line — on a case now, or
3
+ * from a turn that ran (its id or its exported directory), with this repository's code serving the
4
+ * environment while it runs. Each run's words, buttons, writes and functions, one block per run.
5
+ */
6
+ import { connect } from '../../testing.js';
7
+ function describe(run, n) {
8
+ const lines = [`── run ${n}`];
9
+ for (const thought of run.turns.flatMap((t) => t.thoughts ?? []))
10
+ lines.push(` (thought) ${thought}`);
11
+ for (const said of run.said)
12
+ lines.push(`AI: ${said}`);
13
+ if (run.outcome && run.outcome.kind !== 'reply')
14
+ lines.push(`ended: ${run.outcome.kind}${'reason' in run.outcome ? ` (${run.outcome.reason})` : ''}`);
15
+ for (const action of run.actions)
16
+ lines.push(` ${action.name}: ${action.status}${action.error ? ` (${action.error})` : ''}`);
17
+ for (const write of run.writes)
18
+ lines.push(` wrote ${write.table}.${write.field} = ${JSON.stringify(write.value)}${write.state && write.state !== 'filled' ? ` (${write.state})` : ''}`);
19
+ for (const event of run.events)
20
+ lines.push(` on the line: ${JSON.stringify(event)}`);
21
+ for (const call of run.functions)
22
+ lines.push(` function ${call.name}: ${call.how}${call.error ? ` (${call.error})` : ''}`);
23
+ for (const warning of run.warnings)
24
+ lines.push(` ! ${warning}`);
25
+ return lines.join('\n');
26
+ }
27
+ export async function runTurnCli(repoRoot, options) {
28
+ if (!options.from && !options.instance)
29
+ throw new Error('turn: --from <turnId|dir>, or --instance <case id> with --say');
30
+ const sandbox = await connect({ env: options.env ?? 'sandbox', repoRoot, ...(options.url ? { url: options.url } : {}) });
31
+ let result;
32
+ try {
33
+ result = await sandbox.turn({
34
+ ...(options.from ? { from: options.from } : {}),
35
+ ...(options.instance ? { instanceId: options.instance } : {}),
36
+ ...(options.channel ? { channel: options.channel } : {}),
37
+ ...(options.say ? { say: options.say } : {}),
38
+ ...(options.caller ? { caller: options.caller } : {}),
39
+ ...(options.repeat ? { repeat: options.repeat } : {}),
40
+ });
41
+ }
42
+ finally {
43
+ await sandbox.close();
44
+ }
45
+ if (options.json) {
46
+ for (const run of result.runs)
47
+ console.log(JSON.stringify(run));
48
+ }
49
+ else {
50
+ if (result.from)
51
+ console.log(`from turn ${result.from.turnId} (${result.from.kind})${result.from.heard ? `: "${result.from.heard}"` : ''}`);
52
+ result.runs.forEach((run, i) => console.log(describe(run, i + 1)));
53
+ console.log('(a shadow: nothing was kept, sent or booked)');
54
+ }
55
+ process.exit(0);
56
+ }
57
+ //# sourceMappingURL=run.js.map