@superpanel-sdk/runtime 0.2.2 → 0.5.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 (274) hide show
  1. package/README.md +28 -8
  2. package/dist/builder/codegen/emit.d.ts +29 -11
  3. package/dist/builder/codegen/emit.js +328 -61
  4. package/dist/builder/codegen/generate.d.ts +21 -0
  5. package/dist/builder/codegen/generate.js +152 -20
  6. package/dist/builder/company-import-graph.d.ts +2 -0
  7. package/dist/builder/company-import-graph.js +40 -8
  8. package/dist/builder/company-imports.d.ts +24 -0
  9. package/dist/builder/company-imports.js +114 -0
  10. package/dist/builder/compile/compile-sequence.d.ts +59 -8
  11. package/dist/builder/compile/compile-sequence.js +279 -77
  12. package/dist/builder/compile/function-code.d.ts +12 -2
  13. package/dist/builder/compile/function-code.js +87 -39
  14. package/dist/builder/compile/registry.d.ts +7 -3
  15. package/dist/builder/compile/registry.js +24 -23
  16. package/dist/builder/load.d.ts +70 -10
  17. package/dist/builder/load.js +250 -61
  18. package/dist/builder/paths.d.ts +3 -0
  19. package/dist/builder/paths.js +16 -0
  20. package/dist/builder/schedules.d.ts +23 -0
  21. package/dist/builder/schedules.js +102 -0
  22. package/dist/builder/slot-schema-validation.d.ts +4 -0
  23. package/dist/builder/slot-schema-validation.js +10 -0
  24. package/dist/builder/typecheck.d.ts +8 -0
  25. package/dist/builder/typecheck.js +105 -0
  26. package/dist/builder/types.d.ts +91 -19
  27. package/dist/builder/types.js +2 -1
  28. package/dist/cli/auth/auth.d.ts +37 -0
  29. package/dist/cli/auth/auth.js +211 -0
  30. package/dist/cli/auth/backend-url.d.ts +5 -0
  31. package/dist/cli/auth/backend-url.js +25 -0
  32. package/dist/cli/auth/clipboard.d.ts +9 -0
  33. package/dist/cli/auth/clipboard.js +38 -0
  34. package/dist/cli/auth/http.d.ts +20 -0
  35. package/dist/cli/auth/http.js +27 -0
  36. package/dist/cli/auth/index.d.ts +10 -0
  37. package/dist/cli/auth/index.js +27 -0
  38. package/dist/cli/auth/progress.d.ts +12 -0
  39. package/dist/cli/auth/progress.js +15 -0
  40. package/dist/cli/auth/serve-login.d.ts +8 -0
  41. package/dist/cli/auth/serve-login.js +24 -0
  42. package/dist/cli/compile/index.js +3 -0
  43. package/dist/cli/config.d.ts +10 -0
  44. package/dist/cli/config.js +52 -0
  45. package/dist/cli/index.js +28 -8
  46. package/dist/cli/serve/index.d.ts +73 -3
  47. package/dist/cli/serve/index.js +410 -113
  48. package/dist/cli/type-gen/index.js +9 -8
  49. package/dist/compiler.d.ts +7 -0
  50. package/dist/compiler.js +7 -0
  51. package/dist/compute-host/compute-host.d.ts +15 -23
  52. package/dist/compute-host/compute-host.js +25 -28
  53. package/dist/compute-host/index.d.ts +2 -2
  54. package/dist/compute-host/index.js +1 -1
  55. package/dist/compute-host/source-compute-host.d.ts +11 -9
  56. package/dist/compute-host/source-compute-host.js +50 -31
  57. package/dist/core/case-orchestrator/contact-hours.d.ts +50 -0
  58. package/dist/core/case-orchestrator/contact-hours.js +108 -0
  59. package/dist/core/case-orchestrator/follow-ups.d.ts +31 -0
  60. package/dist/core/case-orchestrator/follow-ups.js +103 -0
  61. package/dist/core/case-orchestrator/index.d.ts +6 -0
  62. package/dist/core/case-orchestrator/index.js +6 -0
  63. package/dist/core/case-orchestrator/render.d.ts +75 -0
  64. package/dist/core/case-orchestrator/render.js +239 -0
  65. package/dist/core/case-orchestrator/time.d.ts +11 -0
  66. package/dist/core/case-orchestrator/time.js +29 -0
  67. package/dist/core/case-orchestrator/tools.d.ts +29 -0
  68. package/dist/core/case-orchestrator/tools.js +31 -0
  69. package/dist/core/case-orchestrator/types.d.ts +753 -0
  70. package/dist/core/case-orchestrator/types.js +46 -0
  71. package/dist/core/case-orchestrator/validate.d.ts +11 -0
  72. package/dist/core/case-orchestrator/validate.js +63 -0
  73. package/dist/core/context/call.d.ts +291 -0
  74. package/dist/core/context/call.js +94 -0
  75. package/dist/core/context/channels.d.ts +60 -0
  76. package/dist/core/context/channels.js +35 -0
  77. package/dist/core/context/contact.d.ts +96 -6
  78. package/dist/core/context/contact.js +48 -2
  79. package/dist/core/context/email-message.d.ts +159 -0
  80. package/dist/core/context/email-message.js +59 -0
  81. package/dist/core/context/email.d.ts +97 -0
  82. package/dist/core/context/email.js +91 -0
  83. package/dist/core/context/exchanges.d.ts +142 -0
  84. package/dist/core/context/exchanges.js +63 -0
  85. package/dist/core/context/instances.d.ts +278 -0
  86. package/dist/core/context/instances.js +123 -0
  87. package/dist/core/context/kv.d.ts +113 -0
  88. package/dist/core/context/kv.js +60 -0
  89. package/dist/core/context/platform-registry.d.ts +509 -4
  90. package/dist/core/context/platform-registry.js +14 -2
  91. package/dist/core/context/platform.d.ts +2 -1
  92. package/dist/core/context/sms.d.ts +147 -0
  93. package/dist/core/context/sms.js +57 -0
  94. package/dist/core/context/types.d.ts +51 -30
  95. package/dist/core/context/types.js +1 -7
  96. package/dist/core/conversation/index.d.ts +31 -0
  97. package/dist/core/conversation/index.js +14 -0
  98. package/dist/core/functions/define/bind.d.ts +25 -0
  99. package/dist/core/functions/define/bind.js +14 -0
  100. package/dist/core/functions/define/case.d.ts +9 -0
  101. package/dist/core/functions/define/case.js +13 -0
  102. package/dist/core/functions/define/events.d.ts +20 -0
  103. package/dist/core/functions/define/events.js +21 -0
  104. package/dist/core/functions/define/no-case.d.ts +4 -0
  105. package/dist/core/functions/define/no-case.js +9 -0
  106. package/dist/core/functions/events.d.ts +247 -0
  107. package/dist/core/functions/events.js +32 -0
  108. package/dist/core/functions/func-ref.d.ts +11 -2
  109. package/dist/core/functions/schedules.d.ts +51 -0
  110. package/dist/core/functions/schedules.js +20 -0
  111. package/dist/core/functions/types.d.ts +62 -51
  112. package/dist/core/functions/types.js +6 -1
  113. package/dist/core/office-hours/index.d.ts +39 -0
  114. package/dist/core/office-hours/index.js +187 -0
  115. package/dist/core/page/define.d.ts +834 -0
  116. package/dist/core/page/define.js +477 -0
  117. package/dist/core/page/ops.d.ts +58 -0
  118. package/dist/core/page/ops.js +220 -0
  119. package/dist/core/page/render.d.ts +253 -0
  120. package/dist/core/page/render.js +840 -0
  121. package/dist/core/page/time-format.d.ts +19 -0
  122. package/dist/core/page/time-format.js +41 -0
  123. package/dist/core/page/timeline.d.ts +40 -0
  124. package/dist/core/page/timeline.js +2 -0
  125. package/dist/core/page/typed.d.ts +642 -0
  126. package/dist/core/page/typed.js +2 -0
  127. package/dist/core/page/workspace.d.ts +33 -0
  128. package/dist/core/page/workspace.js +231 -0
  129. package/dist/core/party/index.d.ts +3 -0
  130. package/dist/core/party/index.js +3 -0
  131. package/dist/core/party/schema.d.ts +137 -0
  132. package/dist/core/party/schema.js +80 -0
  133. package/dist/core/party/types.d.ts +40 -0
  134. package/dist/core/party/types.js +15 -0
  135. package/dist/core/sequence/define.d.ts +57 -0
  136. package/dist/core/sequence/define.js +10 -0
  137. package/dist/core/sequence/index.d.ts +63 -0
  138. package/dist/core/sequence/index.js +231 -0
  139. package/dist/core/slot/binding.d.ts +40 -0
  140. package/dist/core/slot/binding.js +2 -0
  141. package/dist/core/slot/company-metadata.d.ts +113 -0
  142. package/dist/core/slot/company-metadata.js +563 -0
  143. package/dist/core/slot/compile-schema.d.ts +19 -13
  144. package/dist/core/slot/compile-schema.js +51 -248
  145. package/dist/core/slot/contact-timezone.d.ts +110 -0
  146. package/dist/core/slot/contact-timezone.js +186 -0
  147. package/dist/core/slot/field.d.ts +2 -24
  148. package/dist/core/slot/field.js +0 -69
  149. package/dist/core/slot/id-ref.d.ts +17 -2
  150. package/dist/core/slot/id-ref.js +16 -8
  151. package/dist/core/slot/index.d.ts +12 -13
  152. package/dist/core/slot/index.js +11 -12
  153. package/dist/core/slot/naming.d.ts +15 -0
  154. package/dist/core/slot/naming.js +96 -0
  155. package/dist/core/slot/platform-fields.d.ts +119 -0
  156. package/dist/core/slot/platform-fields.js +121 -0
  157. package/dist/core/slot/ref.d.ts +16 -0
  158. package/dist/core/slot/ref.js +14 -0
  159. package/dist/core/slot/row.d.ts +2 -9
  160. package/dist/core/slot/row.js +13 -23
  161. package/dist/core/slot/schema-compat.d.ts +16 -0
  162. package/dist/core/slot/schema-compat.js +112 -0
  163. package/dist/core/slot/schema.d.ts +409 -76
  164. package/dist/core/slot/schema.js +178 -10
  165. package/dist/core/slot/types.d.ts +27 -92
  166. package/dist/core/slot/value.d.ts +26 -3
  167. package/dist/core/slot/value.js +89 -57
  168. package/dist/dev-overlay/protocol.d.ts +41 -8
  169. package/dist/fn-context/build-context.d.ts +3 -1
  170. package/dist/fn-context/build-context.js +20 -10
  171. package/dist/fn-context/fake/empty-api.js +9 -28
  172. package/dist/fn-context/fake/index.d.ts +11 -4
  173. package/dist/fn-context/fake/index.js +27 -2
  174. package/dist/fn-context/fn-params.d.ts +6 -0
  175. package/dist/fn-context/fn-params.js +13 -0
  176. package/dist/fn-context/index.d.ts +5 -5
  177. package/dist/fn-context/index.js +4 -4
  178. package/dist/fn-context/log.js +2 -2
  179. package/dist/fn-context/make-ctx.d.ts +11 -0
  180. package/dist/fn-context/make-ctx.js +18 -8
  181. package/dist/fn-context/rpc/case-orchestrator-tools.d.ts +24 -0
  182. package/dist/fn-context/rpc/case-orchestrator-tools.js +13 -0
  183. package/dist/fn-context/rpc/index.d.ts +3 -1
  184. package/dist/fn-context/rpc/index.js +3 -1
  185. package/dist/fn-context/rpc/integration/index.d.ts +4 -4
  186. package/dist/fn-context/rpc/integration/index.js +1 -1
  187. package/dist/fn-context/rpc/integration/list-surfaces.d.ts +21 -0
  188. package/dist/fn-context/rpc/integration/{list-bindings.js → list-surfaces.js} +4 -4
  189. package/dist/fn-context/rpc/platform.d.ts +2 -2
  190. package/dist/fn-context/rpc/platform.js +1 -1
  191. package/dist/fn-context/rpc/records.d.ts +78 -0
  192. package/dist/fn-context/rpc/records.js +26 -0
  193. package/dist/fn-context/rpc/trpc-client.d.ts +2 -2
  194. package/dist/fn-context/rpc/trpc-client.js +89 -5
  195. package/dist/fn-context/rpc/trpc-init.d.ts +2 -2
  196. package/dist/fn-context/rpc/trpc-router-factory.d.ts +49 -3
  197. package/dist/fn-context/rpc/trpc-router-factory.js +2 -0
  198. package/dist/fn-context/run-scope.d.ts +4 -0
  199. package/dist/fn-context/run-scope.js +9 -0
  200. package/dist/fn-context/types.d.ts +36 -3
  201. package/dist/index.d.ts +37 -46
  202. package/dist/index.js +33 -33
  203. package/dist/logger.d.ts +7 -0
  204. package/dist/logger.js +41 -0
  205. package/dist/platform.d.ts +76 -0
  206. package/dist/platform.js +56 -0
  207. package/dist/voice-service/actions.d.ts +71 -4
  208. package/dist/voice-service/types.d.ts +42 -2
  209. package/dist/worker.d.ts +10 -0
  210. package/dist/worker.js +10 -0
  211. package/package.json +38 -19
  212. package/dist/builder/compile/compile-agenda.d.ts +0 -15
  213. package/dist/builder/compile/compile-agenda.js +0 -59
  214. package/dist/builder/merge.d.ts +0 -4
  215. package/dist/builder/merge.js +0 -19
  216. package/dist/core/context/db.d.ts +0 -69
  217. package/dist/core/context/db.js +0 -44
  218. package/dist/core/context/docusign.d.ts +0 -134
  219. package/dist/core/context/docusign.js +0 -108
  220. package/dist/core/context/filevine.d.ts +0 -323
  221. package/dist/core/context/filevine.js +0 -254
  222. package/dist/core/context/http.d.ts +0 -9
  223. package/dist/core/context/http.js +0 -4
  224. package/dist/core/context/hubspot.d.ts +0 -250
  225. package/dist/core/context/hubspot.js +0 -191
  226. package/dist/core/context/twilio.d.ts +0 -50
  227. package/dist/core/context/twilio.js +0 -42
  228. package/dist/core/functions/define/case-context.d.ts +0 -14
  229. package/dist/core/functions/define/case-context.js +0 -15
  230. package/dist/core/functions/define/compute.d.ts +0 -7
  231. package/dist/core/functions/define/compute.js +0 -17
  232. package/dist/core/functions/define/custom.d.ts +0 -4
  233. package/dist/core/functions/define/custom.js +0 -8
  234. package/dist/core/functions/define/system.d.ts +0 -7
  235. package/dist/core/functions/define/system.js +0 -12
  236. package/dist/core/slot/deps.d.ts +0 -15
  237. package/dist/core/slot/deps.js +0 -88
  238. package/dist/core/slot/derived.d.ts +0 -32
  239. package/dist/core/slot/derived.js +0 -115
  240. package/dist/core/slot/platform-session-fields.d.ts +0 -38
  241. package/dist/core/slot/platform-session-fields.js +0 -48
  242. package/dist/core/slot/sf-schema.d.ts +0 -23
  243. package/dist/core/slot/sf-schema.js +0 -125
  244. package/dist/core/slot/sf-validate.d.ts +0 -14
  245. package/dist/core/slot/sf-validate.js +0 -132
  246. package/dist/core/slot/snapshot.d.ts +0 -18
  247. package/dist/core/slot/snapshot.js +0 -138
  248. package/dist/core/slot/write.d.ts +0 -41
  249. package/dist/core/slot/write.js +0 -319
  250. package/dist/core/stage/builders.d.ts +0 -36
  251. package/dist/core/stage/builders.js +0 -28
  252. package/dist/core/stage/condition.d.ts +0 -17
  253. package/dist/core/stage/condition.js +0 -198
  254. package/dist/core/stage/drive.d.ts +0 -43
  255. package/dist/core/stage/drive.js +0 -77
  256. package/dist/core/stage/finish-when.d.ts +0 -20
  257. package/dist/core/stage/finish-when.js +0 -82
  258. package/dist/core/stage/flatten.d.ts +0 -27
  259. package/dist/core/stage/flatten.js +0 -464
  260. package/dist/core/stage/index.d.ts +0 -11
  261. package/dist/core/stage/index.js +0 -10
  262. package/dist/core/stage/simulate.d.ts +0 -24
  263. package/dist/core/stage/simulate.js +0 -33
  264. package/dist/core/stage/transfer-fields.d.ts +0 -14
  265. package/dist/core/stage/transfer-fields.js +0 -88
  266. package/dist/core/stage/types.d.ts +0 -177
  267. package/dist/core/stage/types.js +0 -2
  268. package/dist/core/stage/validate.d.ts +0 -11
  269. package/dist/core/stage/validate.js +0 -226
  270. package/dist/core/stage/walk.d.ts +0 -34
  271. package/dist/core/stage/walk.js +0 -96
  272. package/dist/fn-context/rpc/integration/list-bindings.d.ts +0 -21
  273. package/dist/fn-context/system-args.d.ts +0 -6
  274. package/dist/fn-context/system-args.js +0 -14
@@ -1,25 +1,26 @@
1
1
  import path from 'node:path';
2
+ import { cancel, isCancel, select } from '@clack/prompts';
2
3
  import chokidar from 'chokidar';
3
- import { compileAll } from '../../builder/compile/compile-sequence.js';
4
+ import { compileAll, compileCompanyMetadataFromRepo, compileCompanyPart, runtimePackageSource, runtimePackageVersion, } from '../../builder/compile/compile-sequence.js';
4
5
  import { withCompanyImportGraph } from '../../builder/company-import-graph.js';
5
- import { findTsModule } from '../../builder/paths.js';
6
- import { listSequenceIds, loadSequenceCaseContextView, loadSequenceFunctions } from '../../builder/load.js';
6
+ import { findTsModule, requireCompanyResolveModule, requireResolveModule } from '../../builder/paths.js';
7
+ import { listSequenceIds, loadCompanyWebhook, loadSequenceCaseOrchestrator, WEBHOOK_MODULE, loadCompanyResolver, loadSequenceResolver, loadSequenceEvents, loadSequenceFunctions, } from '../../builder/load.js';
8
+ import { loadSchedules } from '../../builder/schedules.js';
9
+ import { ORCHESTRATOR_CONTACTS_FUNCTION, ORCHESTRATOR_FUNCTION } from '../../core/case-orchestrator/types.js';
10
+ import { WEBHOOK_RECEIVED_FUNCTION } from '../../core/functions/events.js';
11
+ import { COMPANY_RESOLVE_FUNCTION, RESOLVE_FUNCTION } from '../../core/party/types.js';
12
+ import { COMPANY_SCOPE } from '../../builder/types.js';
7
13
  import { createSourceComputeHost } from '../../compute-host/source-compute-host.js';
14
+ import { formatRuntimeVersion } from '../../fn-context/rpc/request-id.js';
8
15
  import { encodeOverlayMessage, OVERLAY_REPLACED_CLOSE_CODE, parseOverlayMessage, } from '../../dev-overlay/protocol.js';
9
- const DEFAULT_BACKEND_URL = 'http://127.0.0.1:8100';
10
- function env(name) {
11
- const value = process.env[name];
12
- return value && value.length > 0 ? value : undefined;
13
- }
16
+ import { getLogger } from '../../logger.js';
17
+ import { listServeEnvironments, loadCachedServeAuthorization, resolveBackendUrl, ServeAuthorizationRejectedError, } from '../auth/auth.js';
18
+ import { forgetServeAuthorization, loginServe } from '../auth/serve-login.js';
19
+ import { toHttpUrl, toWsUrl } from '../auth/backend-url.js';
20
+ import { progressIndicator } from '../auth/progress.js';
21
+ const logger = getLogger(['runtime', 'overlay']);
14
22
  export function overlayWebSocketUrl(raw) {
15
- const url = new URL(raw);
16
- if (url.protocol === 'http:')
17
- url.protocol = 'ws:';
18
- else if (url.protocol === 'https:')
19
- url.protocol = 'wss:';
20
- else if (url.protocol !== 'ws:' && url.protocol !== 'wss:') {
21
- throw new Error(`unsupported backend url: ${raw}`);
22
- }
23
+ const url = toWsUrl(raw);
23
24
  const path = url.pathname.replace(/\/$/, '');
24
25
  if (path === '')
25
26
  url.pathname = '/overlay';
@@ -27,15 +28,33 @@ export function overlayWebSocketUrl(raw) {
27
28
  url.hash = '';
28
29
  return url.toString();
29
30
  }
31
+ function overlayRpcUrl(raw) {
32
+ const url = toHttpUrl(raw);
33
+ url.pathname = `${url.pathname.replace(/\/$/, '')}/trpc`;
34
+ url.search = '';
35
+ url.hash = '';
36
+ return url.toString();
37
+ }
30
38
  /** `sequences/<id>/…` → sequence id */
31
39
  function sequenceIdFromPath(repoRoot, file) {
32
40
  const rel = path.relative(repoRoot, file);
33
41
  const match = /^sequences[/\\]([^/\\]+)(?:[/\\]|$)/.exec(rel);
34
42
  return match?.[1] ?? null;
35
43
  }
36
- function isSchemaPath(repoRoot, file) {
37
- const rel = path.relative(repoRoot, file);
38
- return rel === 'schema.ts' || rel.startsWith(`schema${path.sep}`) || rel.startsWith('schema/');
44
+ /** Not the repository's code: dependencies, git, and what compiling writes. */
45
+ export function isIgnoredServePath(repoRoot, file) {
46
+ const parts = path.relative(repoRoot, file).split(/[/\\]/);
47
+ return parts.some((part) => part === 'node_modules' || part === '__gen' || part === 'build' || part === 'dist' || part.startsWith('.'));
48
+ }
49
+ /**
50
+ * What a changed file recompiles (22-sequence-as-business-unit §6): a file of one sequence, that
51
+ * sequence; anything else (`schema/`, `resolve.ts`, shared code) can reach every sequence, so all.
52
+ */
53
+ export function reloadScopeForPath(repoRoot, file) {
54
+ if (isIgnoredServePath(repoRoot, file))
55
+ return null;
56
+ const sequenceId = sequenceIdFromPath(repoRoot, file);
57
+ return sequenceId ? { sequenceId } : 'all';
39
58
  }
40
59
  async function loadOne(repoRoot, sequenceId, generation) {
41
60
  return withCompanyImportGraph(repoRoot, async () => {
@@ -52,54 +71,148 @@ async function loadOne(repoRoot, sequenceId, generation) {
52
71
  if (!functionsPath) {
53
72
  throw new Error(`No functions module for sequence ${sequenceId}`);
54
73
  }
55
- const functions = await loadSequenceFunctions(functionsPath);
56
- const viewPath = findTsModule(sequenceDir, 'view');
57
- const view = viewPath ? await loadSequenceCaseContextView(viewPath) : undefined;
58
- return { build: compiled.build, functions, view, generation };
74
+ const caseOrchestratorPath = findTsModule(sequenceDir, 'orchestrator');
75
+ const authored = await loadSequenceFunctions(functionsPath);
76
+ const functions = {
77
+ ...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,
82
+ };
83
+ const eventsPath = findTsModule(sequenceDir, 'events');
84
+ const events = eventsPath ? await loadSequenceEvents(eventsPath) : {};
85
+ return { build: compiled.build, functions, events, generation };
59
86
  });
60
87
  }
88
+ export async function loadCompany(repoRoot) {
89
+ return withCompanyImportGraph(repoRoot, async () => {
90
+ const webhookPath = findTsModule(repoRoot, WEBHOOK_MODULE);
91
+ return {
92
+ part: await compileCompanyPart(repoRoot, { includeCode: false }),
93
+ companyMetadata: await compileCompanyMetadataFromRepo(repoRoot),
94
+ functions: {
95
+ [COMPANY_RESOLVE_FUNCTION]: await loadCompanyResolver(requireCompanyResolveModule(repoRoot)),
96
+ ...(webhookPath ? { [WEBHOOK_RECEIVED_FUNCTION]: await loadCompanyWebhook(webhookPath) } : {}),
97
+ ...(await loadSchedules(repoRoot, 'schedules.ts'))?.functions,
98
+ },
99
+ };
100
+ });
101
+ }
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
+ /** The repository's sequences, compiled; a listed sequence missing from the repository is left out. */
61
111
  async function loadSequences(repoRoot, sequenceIds, generation) {
62
112
  return withCompanyImportGraph(repoRoot, async () => {
113
+ const present = new Set(listSequenceIds(repoRoot));
63
114
  const out = new Map();
64
115
  for (const sequenceId of sequenceIds) {
65
- out.set(sequenceId, await loadOne(repoRoot, sequenceId, generation));
116
+ if (present.has(sequenceId))
117
+ out.set(sequenceId, await loadOne(repoRoot, sequenceId, generation));
66
118
  }
67
119
  return out;
68
120
  });
69
121
  }
122
+ /**
123
+ * The changes waiting to be reloaded. A reload that fails keeps its scope (`fail`) for the next one,
124
+ * which a new file change starts (no retry on its own: the failure is in the code until it is
125
+ * edited); it is cleared only once a reload of it loads.
126
+ */
127
+ export class ReloadQueue {
128
+ pending = { all: false, ids: new Set() };
129
+ failed = { all: false, ids: new Set() };
130
+ add(scope) {
131
+ if (scope === 'all')
132
+ this.pending.all = true;
133
+ else
134
+ this.pending.ids.add(scope.sequenceId);
135
+ }
136
+ /** Something changed since the last reload. */
137
+ hasChanges() {
138
+ return this.pending.all || this.pending.ids.size > 0;
139
+ }
140
+ /** The next reload: the new changes plus what failed before. */
141
+ take() {
142
+ const all = this.pending.all || this.failed.all;
143
+ const job = { all, ids: all ? new Set() : new Set([...this.failed.ids, ...this.pending.ids]) };
144
+ this.pending = { all: false, ids: new Set() };
145
+ this.failed = { all: false, ids: new Set() };
146
+ return job;
147
+ }
148
+ fail(job) {
149
+ this.failed = { all: this.failed.all || job.all, ids: new Set([...this.failed.ids, ...job.ids]) };
150
+ }
151
+ }
152
+ /**
153
+ * The state after a reload (`all`: the whole repository; else the sequences `ids` names), built
154
+ * aside: if any part fails to load it throws and the current state stays whole, never new company
155
+ * code with old sequences.
156
+ */
157
+ export async function reloadServeState(repoRoot, current, job, generation) {
158
+ // Recompiling everything also picks up sequence directories added or removed since.
159
+ const ids = job.all ? listSequenceIds(repoRoot) : [...job.ids];
160
+ // The company's code is outside `sequences/`, so only a recompile of everything reaches it.
161
+ const company = job.all ? await loadCompany(repoRoot) : current.company;
162
+ const loaded = await loadSequences(repoRoot, ids, generation);
163
+ const sequences = job.all ? new Map() : new Map(current.sequences);
164
+ for (const id of ids) {
165
+ const next = loaded.get(id);
166
+ if (next)
167
+ sequences.set(id, next);
168
+ else
169
+ sequences.delete(id);
170
+ }
171
+ return { company, sequences };
172
+ }
70
173
  function send(ws, message) {
71
174
  ws.send(encodeOverlayMessage(message));
72
175
  }
73
176
  function logField(label, value) {
74
- console.log(` ${label.padEnd(10)} ${value}`);
177
+ logger.info(label, value);
75
178
  }
76
- async function handleExecute(loaded, message) {
179
+ async function handleExecute(loaded, message, rpcUrl) {
180
+ const data = message.data
181
+ ? {
182
+ ...message.data,
183
+ config: { ...message.data.config, backendUrl: rpcUrl },
184
+ }
185
+ : undefined;
77
186
  const host = createSourceComputeHost({
78
187
  functions: loaded.functions,
79
- caseContextView: loaded.view,
80
- data: message.data,
188
+ events: loaded.events,
189
+ data: data,
81
190
  runToken: message.runToken,
82
- runtimeVersion: loaded.build.runtimeVersion ?? loaded.build.engineVersion,
83
- runtimeSource: loaded.build.runtimeSource ?? 'npm',
191
+ runtimeVersion: loaded.runtimeVersion,
192
+ runtimeSource: loaded.runtimeSource,
84
193
  getRequestId: () => message.logRequestId,
85
194
  getTags: () => message.logTags,
86
195
  });
87
196
  try {
88
- return await host.execute(message.name, message.args);
197
+ return await host.execute(message.name, message.invocation);
89
198
  }
90
199
  finally {
91
200
  host.dispose();
92
201
  }
93
202
  }
94
- function pushOverlays(ws, sequences) {
95
- for (const [sequenceId, loaded] of sequences) {
96
- send(ws, {
97
- type: 'overlay',
98
- sequenceId,
99
- generation: loaded.generation,
100
- build: loaded.build,
101
- });
102
- }
203
+ /**
204
+ * Push the local company build: the company's functions and every loaded sequence, as one overlay.
205
+ * With no sequence left it is still pushed, so the environment drops the ones it had (22 §11).
206
+ */
207
+ export function pushOverlay(ws, company, sequences, generation) {
208
+ const build = {
209
+ company: company.part,
210
+ sequences: Object.fromEntries([...sequences].map(([sequenceId, sequence]) => [sequenceId, sequence.build])),
211
+ };
212
+ send(ws, { type: 'overlay', generation, build, companyMetadata: company.companyMetadata });
213
+ }
214
+ function closeDetail(event) {
215
+ return event.reason ? `${event.code} ${event.reason}` : String(event.code);
103
216
  }
104
217
  function waitOpen(ws) {
105
218
  return new Promise((resolve, reject) => {
@@ -107,8 +220,27 @@ function waitOpen(ws) {
107
220
  resolve();
108
221
  return;
109
222
  }
110
- ws.addEventListener('open', () => resolve(), { once: true });
111
- ws.addEventListener('error', () => reject(new Error('websocket error')), { once: true });
223
+ let settled = false;
224
+ const finish = (fn) => {
225
+ if (settled)
226
+ return;
227
+ settled = true;
228
+ cleanup();
229
+ fn();
230
+ };
231
+ const onOpen = () => finish(() => resolve());
232
+ const onClose = (event) => finish(() => reject(new Error(`websocket closed ${closeDetail(event)}`)));
233
+ const onError = () => {
234
+ setTimeout(() => finish(() => reject(new Error('websocket error'))), 250);
235
+ };
236
+ const cleanup = () => {
237
+ ws.removeEventListener('open', onOpen);
238
+ ws.removeEventListener('close', onClose);
239
+ ws.removeEventListener('error', onError);
240
+ };
241
+ ws.addEventListener('open', onOpen);
242
+ ws.addEventListener('close', onClose);
243
+ ws.addEventListener('error', onError);
112
244
  });
113
245
  }
114
246
  class OverlayReplacedError extends Error {
@@ -117,6 +249,36 @@ class OverlayReplacedError extends Error {
117
249
  this.name = 'OverlayReplacedError';
118
250
  }
119
251
  }
252
+ /** Transient: an infra hiccup, or a lease that was just replaced. Retry with backoff. */
253
+ class OverlayUnavailableError extends Error {
254
+ constructor(message) {
255
+ super(message);
256
+ this.name = 'OverlayUnavailableError';
257
+ }
258
+ }
259
+ /** Permanent: the request itself is invalid (missing or unknown environment, prod). Fatal. */
260
+ class OverlayRequestError extends Error {
261
+ constructor(message) {
262
+ super(message);
263
+ this.name = 'OverlayRequestError';
264
+ }
265
+ }
266
+ export function toOverlayError(message) {
267
+ switch (message.code) {
268
+ case 'authorization':
269
+ return new ServeAuthorizationRejectedError(message.error);
270
+ case 'request':
271
+ return new OverlayRequestError(message.error);
272
+ case 'unavailable':
273
+ return new OverlayUnavailableError(message.error);
274
+ default: {
275
+ // The wire format is not type-checked: a code this CLI doesn't know is a protocol bug, so
276
+ // stop and say so instead of reconnecting forever on an `undefined` error.
277
+ const unknown = message.code;
278
+ return new OverlayRequestError(`unknown auth.error code ${JSON.stringify(unknown)}: ${message.error}`);
279
+ }
280
+ }
281
+ }
120
282
  function waitAuthOk(ws) {
121
283
  return new Promise((resolve, reject) => {
122
284
  const onMessage = (event) => {
@@ -134,7 +296,7 @@ function waitAuthOk(ws) {
134
296
  }
135
297
  if (message.type === 'auth.error') {
136
298
  cleanup();
137
- reject(new Error(message.error));
299
+ reject(toOverlayError(message));
138
300
  }
139
301
  }
140
302
  catch (error) {
@@ -146,7 +308,7 @@ function waitAuthOk(ws) {
146
308
  cleanup();
147
309
  reject(event.code === OVERLAY_REPLACED_CLOSE_CODE
148
310
  ? new OverlayReplacedError()
149
- : new Error('websocket closed before auth.ok'));
311
+ : new Error(`websocket closed before auth.ok ${closeDetail(event)}`));
150
312
  };
151
313
  const cleanup = () => {
152
314
  ws.removeEventListener('message', onMessage);
@@ -156,7 +318,7 @@ function waitAuthOk(ws) {
156
318
  ws.addEventListener('close', onClose);
157
319
  });
158
320
  }
159
- function attachExecuteHandler(ws, sequences) {
321
+ function attachExecuteHandler(ws, find, rpcUrl) {
160
322
  ws.addEventListener('message', (event) => {
161
323
  void (async () => {
162
324
  let message;
@@ -164,57 +326,88 @@ function attachExecuteHandler(ws, sequences) {
164
326
  message = parseOverlayMessage(String(event.data));
165
327
  }
166
328
  catch (error) {
167
- console.error('bad server message:', error instanceof Error ? error.message : error);
329
+ logger.error('bad server message', error);
168
330
  return;
169
331
  }
170
332
  if (message.type !== 'execute')
171
333
  return;
172
- const call = `${message.sequenceId} ${message.name}`;
173
- const loaded = sequences.get(message.sequenceId);
334
+ const loaded = find(message.sequenceId);
174
335
  if (!loaded) {
175
336
  const error = `unknown sequence ${message.sequenceId}`;
176
- console.error(`execute ${call} failed: ${error}`);
177
337
  send(ws, {
178
338
  type: 'execute.result',
179
339
  requestId: message.requestId,
180
340
  ok: false,
181
341
  error,
182
342
  });
343
+ logger.error('execute failed', { sequenceId: message.sequenceId, name: message.name, error });
183
344
  return;
184
345
  }
185
346
  const started = Date.now();
186
- console.log(`execute ${call}`);
347
+ let result;
187
348
  try {
188
- const result = await handleExecute(loaded, message);
189
- console.log(`execute ${call} ok ${Date.now() - started}ms`);
190
- send(ws, { type: 'execute.result', requestId: message.requestId, ok: true, result });
349
+ result = await handleExecute(loaded, message, rpcUrl);
191
350
  }
192
351
  catch (error) {
193
352
  const text = error instanceof Error ? error.message : String(error);
194
- console.error(`execute ${call} failed: ${text}`);
195
353
  send(ws, {
196
354
  type: 'execute.result',
197
355
  requestId: message.requestId,
198
356
  ok: false,
199
357
  error: text,
200
358
  });
359
+ logger.error('execute failed', {
360
+ sequenceId: message.sequenceId,
361
+ name: message.name,
362
+ durationMs: Date.now() - started,
363
+ error: text,
364
+ });
365
+ return;
201
366
  }
367
+ send(ws, { type: 'execute.result', requestId: message.requestId, ok: true, result });
368
+ logger.info('execute ok', {
369
+ sequenceId: message.sequenceId,
370
+ name: message.name,
371
+ durationMs: Date.now() - started,
372
+ });
202
373
  })();
203
374
  });
204
375
  }
376
+ function attachNoticeHandler(ws) {
377
+ ws.addEventListener('message', (event) => {
378
+ let message;
379
+ try {
380
+ message = parseOverlayMessage(String(event.data));
381
+ }
382
+ catch {
383
+ return;
384
+ }
385
+ if (message.type !== 'notice')
386
+ return;
387
+ if (message.details.length > 0)
388
+ logger.warn(message.message, message.details);
389
+ else
390
+ logger.warn(message.message);
391
+ });
392
+ }
205
393
  function waitSessionEnd(ws) {
206
- return new Promise((resolve) => {
394
+ return new Promise((resolve, reject) => {
207
395
  if (ws.readyState === WebSocket.CLOSED) {
208
- resolve('closed');
396
+ resolve({ closed: 'already closed' });
209
397
  return;
210
398
  }
211
399
  const onMessage = (event) => {
212
400
  try {
213
401
  const message = parseOverlayMessage(String(event.data));
214
402
  if (message.type === 'replaced') {
403
+ logger.warn('another serve took the environment');
215
404
  cleanup();
216
405
  resolve('replaced');
217
406
  }
407
+ else if (message.type === 'auth.error') {
408
+ cleanup();
409
+ reject(toOverlayError(message));
410
+ }
218
411
  }
219
412
  catch {
220
413
  return;
@@ -222,7 +415,7 @@ function waitSessionEnd(ws) {
222
415
  };
223
416
  const onClose = (event) => {
224
417
  cleanup();
225
- resolve(event.code === OVERLAY_REPLACED_CLOSE_CODE ? 'replaced' : 'closed');
418
+ resolve(event.code === OVERLAY_REPLACED_CLOSE_CODE ? 'replaced' : { closed: closeDetail(event) });
226
419
  };
227
420
  const cleanup = () => {
228
421
  ws.removeEventListener('message', onMessage);
@@ -232,30 +425,128 @@ function waitSessionEnd(ws) {
232
425
  ws.addEventListener('close', onClose);
233
426
  });
234
427
  }
428
+ function exitOnCancel() {
429
+ cancel('Cancelled.');
430
+ process.exit(1);
431
+ }
432
+ /**
433
+ * Only prompt when there's an actual person at a terminal to answer — a CI runner, a piped/
434
+ * redirected stdin, or any other non-interactive invocation must keep failing fast on a missing
435
+ * flag instead of hanging on a readline prompt with undefined behavior off a real TTY.
436
+ */
437
+ function requireInteractiveOrThrow(flag) {
438
+ if (!process.stdin.isTTY || !process.stdout.isTTY) {
439
+ throw new Error(`${flag} is required (not running in an interactive terminal to prompt for it)`);
440
+ }
441
+ }
442
+ /** The token's non-prod environments. A rejected token surfaces as `ServeAuthorizationRejectedError`. */
443
+ async function fetchEnvironments(backendUrl, authorization) {
444
+ const progress = progressIndicator();
445
+ const company = authorization.companyName ? ` in ${authorization.companyName}` : '';
446
+ progress.start(`Fetching environments${company}…`);
447
+ let environments;
448
+ try {
449
+ environments = await listServeEnvironments(backendUrl, authorization.token);
450
+ }
451
+ catch (error) {
452
+ if (error instanceof ServeAuthorizationRejectedError) {
453
+ progress.error('Login was rejected by the backend.');
454
+ throw error;
455
+ }
456
+ progress.error('Could not fetch environments.');
457
+ throw new Error(`Could not list environments: ${error instanceof Error ? error.message : String(error)}`);
458
+ }
459
+ progress.stop(`Found ${environments.length} environment${environments.length === 1 ? '' : 's'}${company}.`);
460
+ return environments;
461
+ }
462
+ /**
463
+ * Login plus the company's environments. Listing them doubles as checking the cached token against
464
+ * the backend: it can be unexpired locally yet rejected (revoked, re-keyed, access removed), and
465
+ * then it is dropped for a fresh browser login. Rejections after startup stop serve instead.
466
+ */
467
+ async function authorizeWithEnvironments(backendUrl, openBrowser) {
468
+ const cached = await loadCachedServeAuthorization(backendUrl);
469
+ if (cached) {
470
+ logger.info('using cached serve authorization');
471
+ try {
472
+ return { authorization: cached, environments: await fetchEnvironments(backendUrl, cached) };
473
+ }
474
+ catch (error) {
475
+ if (!(error instanceof ServeAuthorizationRejectedError))
476
+ throw error;
477
+ logger.warn('cached serve authorization is no longer valid; authorizing again', { error: error.message });
478
+ await forgetServeAuthorization(backendUrl, cached);
479
+ }
480
+ }
481
+ const authorization = await loginServe(backendUrl, { openBrowser });
482
+ return { authorization, environments: await fetchEnvironments(backendUrl, authorization) };
483
+ }
484
+ /** Prompts for one non-prod environment when `--environment` was not given. Shows id and name. */
485
+ async function promptForEnvironment(environments) {
486
+ if (environments.length === 0) {
487
+ throw new Error('No non-prod environments available for this company.');
488
+ }
489
+ const picked = await select({
490
+ message: 'Which environment should Local Serve overlay?',
491
+ options: environments.map((environment) => ({
492
+ value: environment.id,
493
+ label: environment.name,
494
+ hint: environment.id,
495
+ })),
496
+ });
497
+ if (isCancel(picked))
498
+ exitOnCancel();
499
+ return picked;
500
+ }
235
501
  export async function runServe(repoRoot, options = {}) {
236
- const companyId = options.companyId ?? env('SUPERPANEL_SDK_COMPANY_ID');
237
- const apiKey = options.apiKey ?? env('SUPERPANEL_SDK_API_KEY');
238
- if (!companyId)
239
- throw new Error('SUPERPANEL_SDK_COMPANY_ID or --company is required');
240
- if (!apiKey)
241
- throw new Error('SUPERPANEL_SDK_API_KEY or --api-key is required');
242
- const backendUrl = options.url ?? env('SUPERPANEL_SDK_BACKEND_URL') ?? DEFAULT_BACKEND_URL;
243
- const url = overlayWebSocketUrl(backendUrl);
244
- const sequenceIds = options.sequenceIds ?? listSequenceIds(repoRoot);
245
- if (sequenceIds.length === 0) {
246
- throw new Error(`No sequences with slots found under ${path.join(repoRoot, 'sequences')}`);
502
+ const backendUrl = resolveBackendUrl(repoRoot, options.url);
503
+ // A non-interactive caller cannot answer the prompt. Check this before authorization so a CI job
504
+ // with a missing flag fails instead of waiting on a browser-based device-auth flow.
505
+ let environment = options.environment?.trim();
506
+ if (!environment)
507
+ requireInteractiveOrThrow('--environment <id>');
508
+ if (listSequenceIds(repoRoot).length === 0) {
509
+ throw new Error(`No sequences found under ${path.join(repoRoot, 'sequences')}`);
247
510
  }
511
+ // Login is the first interactive step: the environment prompt needs the company-scoped token.
512
+ const { authorization, environments } = await authorizeWithEnvironments(backendUrl, options.openBrowser !== false);
513
+ if (!environment)
514
+ environment = await promptForEnvironment(environments);
515
+ // The whole repository serves the environment (22 §6): every sequence, and only these.
248
516
  let generation = 1;
249
- const sequences = await loadSequences(repoRoot, sequenceIds, generation);
517
+ let company = await loadCompany(repoRoot);
518
+ let sequences = await loadSequences(repoRoot, listSequenceIds(repoRoot), generation);
519
+ const runtimeSource = runtimePackageSource(repoRoot);
520
+ const find = (scope) => {
521
+ if (scope === COMPANY_SCOPE) {
522
+ return { functions: company.functions, events: {}, runtimeVersion: runtimePackageVersion(), runtimeSource };
523
+ }
524
+ const loaded = sequences.get(scope);
525
+ return loaded
526
+ ? {
527
+ functions: loaded.functions,
528
+ events: loaded.events,
529
+ runtimeVersion: loaded.build.engineVersion,
530
+ runtimeSource: loaded.build.runtimeSource,
531
+ }
532
+ : undefined;
533
+ };
534
+ const url = overlayWebSocketUrl(backendUrl);
535
+ const rpcUrl = overlayRpcUrl(backendUrl);
250
536
  let socket = null;
251
- const names = [...sequences.keys()];
252
- console.log('Local overlay');
253
- logField('company', companyId);
254
- logField('sequences', names.join(', '));
537
+ const firstBuild = sequences.values().next().value?.build;
538
+ logger.info('Local overlay');
539
+ logField('user', authorization.user.email);
540
+ logField('company', authorization.companyId);
541
+ logField('environment', environment);
542
+ logField('sequences', [...sequences.keys()].join(', '));
543
+ if (firstBuild) {
544
+ logField('runtime', formatRuntimeVersion(firstBuild.engineVersion, firstBuild.runtimeSource));
545
+ }
255
546
  logField('backend', backendUrl);
256
547
  logField('websocket', url);
257
548
  let timer;
258
- let pending = { all: false, ids: new Set() };
549
+ const queue = new ReloadQueue();
259
550
  let flushing = false;
260
551
  const flush = () => {
261
552
  if (flushing)
@@ -263,54 +554,51 @@ export async function runServe(repoRoot, options = {}) {
263
554
  flushing = true;
264
555
  void (async () => {
265
556
  try {
266
- while (pending.all || pending.ids.size > 0) {
267
- const job = pending;
268
- pending = { all: false, ids: new Set() };
557
+ while (queue.hasChanges()) {
558
+ const job = queue.take();
269
559
  generation += 1;
270
- const ids = job.all ? sequenceIds : [...job.ids];
271
- const next = await loadSequences(repoRoot, ids, generation);
272
- if (job.all)
273
- sequences.clear();
274
- for (const [id, loaded] of next)
275
- sequences.set(id, loaded);
560
+ // Swapped in only whole: a failed reload leaves the code that runs (and is pushed) as it was.
561
+ let next;
562
+ try {
563
+ next = await reloadServeState(repoRoot, { company, sequences }, job, generation);
564
+ }
565
+ catch (error) {
566
+ // Kept for the next change: fixing one sequence then also reloads what this one covered.
567
+ queue.fail(job);
568
+ logger.error('reload failed', error);
569
+ continue;
570
+ }
571
+ company = next.company;
572
+ sequences = next.sequences;
276
573
  if (socket && socket.readyState === WebSocket.OPEN) {
277
- pushOverlays(socket, sequences);
278
- console.log(`reloaded sequences ${ids.join(', ')}`);
574
+ pushOverlay(socket, company, sequences, generation);
575
+ logger.info('reloaded sequences', job.all ? 'all' : [...job.ids]);
279
576
  }
280
577
  }
281
578
  }
282
579
  catch (error) {
283
- console.error('reload failed:', error instanceof Error ? error.message : error);
580
+ logger.error('reload failed', error);
284
581
  }
285
582
  finally {
286
583
  flushing = false;
287
- if (pending.all || pending.ids.size > 0)
584
+ if (queue.hasChanges())
288
585
  flush();
289
586
  }
290
587
  })();
291
588
  };
292
589
  chokidar
293
- .watch([path.join(repoRoot, 'schema'), path.join(repoRoot, 'schema.ts'), path.join(repoRoot, 'sequences')], {
590
+ .watch(repoRoot, {
294
591
  ignoreInitial: true,
295
- ignored: (p) => p.includes(`${path.sep}__gen${path.sep}`) || p.includes(`${path.sep}build${path.sep}`),
592
+ ignored: (p) => isIgnoredServePath(repoRoot, p),
296
593
  })
297
594
  .on('all', (event, file) => {
298
595
  if (!file)
299
596
  return;
300
- if (file.includes(`${path.sep}__gen${path.sep}`))
301
- return;
302
- if (file.includes(`${path.sep}build${path.sep}`))
303
- return;
304
- const rel = path.relative(repoRoot, file);
305
- const schemaHit = isSchemaPath(repoRoot, file);
306
- const sequenceId = sequenceIdFromPath(repoRoot, file);
307
- if (!schemaHit && !sequenceId)
597
+ const scope = reloadScopeForPath(repoRoot, file);
598
+ if (!scope)
308
599
  return;
309
- console.log(`changed ${rel}`);
310
- if (schemaHit)
311
- pending.all = true;
312
- else if (sequenceId)
313
- pending.ids.add(sequenceId);
600
+ logger.info('changed', path.relative(repoRoot, file));
601
+ queue.add(scope);
314
602
  clearTimeout(timer);
315
603
  timer = setTimeout(flush, 100);
316
604
  });
@@ -321,26 +609,35 @@ export async function runServe(repoRoot, options = {}) {
321
609
  try {
322
610
  await waitOpen(ws);
323
611
  const authOk = waitAuthOk(ws);
324
- send(ws, {
325
- type: 'auth',
326
- apiKey,
327
- companyId,
328
- names: [...sequences.keys()],
329
- });
612
+ send(ws, { type: 'auth', token: authorization.token, environment });
330
613
  await authOk;
331
- attachExecuteHandler(ws, sequences);
332
- pushOverlays(ws, sequences);
614
+ attachExecuteHandler(ws, find, rpcUrl);
615
+ attachNoticeHandler(ws);
616
+ pushOverlay(ws, company, sequences, generation);
333
617
  delayMs = 500;
334
- console.log(`connected overlay ${url}`);
618
+ logger.info('connected', { url });
335
619
  const end = await waitSessionEnd(ws);
336
620
  if (end === 'replaced')
337
621
  throw new OverlayReplacedError();
338
- console.log('disconnected retrying');
622
+ logger.warn('disconnected; retrying', { detail: end.closed });
339
623
  }
340
624
  catch (error) {
341
625
  if (error instanceof OverlayReplacedError)
342
626
  throw error;
343
- console.error('connection failed:', error instanceof Error ? error.message : error);
627
+ if (error instanceof OverlayRequestError)
628
+ throw error;
629
+ if (error instanceof ServeAuthorizationRejectedError) {
630
+ // Mid-session the identity is fixed: stop rather than log in again, and drop the cached
631
+ // token so the user's next `serve` starts with a fresh login.
632
+ await forgetServeAuthorization(backendUrl, authorization);
633
+ throw new Error(`Serve authorization was rejected (${error.message}). Run serve again to log in.`);
634
+ }
635
+ if (error instanceof OverlayUnavailableError) {
636
+ logger.warn('serve is temporarily unavailable; retrying', { error: error.message });
637
+ }
638
+ else {
639
+ logger.error('connection failed', error);
640
+ }
344
641
  if (ws.readyState === WebSocket.OPEN || ws.readyState === WebSocket.CONNECTING) {
345
642
  ws.close();
346
643
  }